Implement security level guarantees for Android.
Supported security levels: - ANY - SECURE_SOFTWARE - SECURE_HARDWARE (TEE or SE guarantees). (1) Add `getSecurityLevel()` API that returns which security level is supported on this Android version and the specific device. (2) For APIs that store credentials, an additional optional parameter was added that fails storing the credentials if the security level is not what is expected. ``` // Store the credentials. // Will fail if Keychain can't guarantee at least SECURE_HARDWARE level of encryption key. await Keychain.setGenericPassword(username, password, Keychain.SECURITY_LEVEL.SECURE_HARDWARE); ``` (3) StongBox support on Android 9+ (and supported devices [Pixel 3]).
This commit is contained in:
parent
1c35579a36
commit
50090a1fd3
|
@ -285,7 +285,7 @@ RCT_EXPORT_METHOD(getSupportedBiometryType:(RCTPromiseResolveBlock)resolve rejec
|
|||
}
|
||||
#endif
|
||||
|
||||
RCT_EXPORT_METHOD(setGenericPasswordForOptions:(NSDictionary *)options withUsername:(NSString *)username withPassword:(NSString *)password resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
RCT_EXPORT_METHOD(setGenericPasswordForOptions:(NSDictionary *)options withUsername:(NSString *)username withPassword:(NSString *)password withSecurityLevel:(__unused NSString *)level resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
NSString *service = serviceValue(options);
|
||||
NSDictionary *attributes = attributes = @{
|
||||
|
@ -358,7 +358,7 @@ RCT_EXPORT_METHOD(resetGenericPasswordForOptions:(NSDictionary *)options resolve
|
|||
return resolve(@(YES));
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(setInternetCredentialsForServer:(NSString *)server withUsername:(NSString*)username withPassword:(NSString*)password withOptions:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
RCT_EXPORT_METHOD(setInternetCredentialsForServer:(NSString *)server withUsername:(NSString*)username withPassword:(NSString*)password withSecurityLevel:(__unused NSString *)level withOptions:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
[self deleteCredentialsForServer:server];
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@ import com.oblador.keychain.cipherStorage.CipherStorageKeystoreAESCBC;
|
|||
import com.oblador.keychain.exceptions.CryptoFailedException;
|
||||
import com.oblador.keychain.exceptions.EmptyParameterException;
|
||||
import com.oblador.keychain.exceptions.KeyStoreAccessException;
|
||||
import com.oblador.keychain.DeviceAvailability;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
@ -56,16 +55,35 @@ public class KeychainModule extends ReactContextBaseJavaModule {
|
|||
}
|
||||
|
||||
@ReactMethod
|
||||
public void setGenericPasswordForOptions(String service, String username, String password, Promise promise) {
|
||||
public void getSecurityLevel(Promise promise) {
|
||||
try {
|
||||
CipherStorage storage = getCipherStorageForCurrentAPILevel(SecurityLevel.ANY);
|
||||
if (!storage.securityLevel().satisfiesSafetyThreshold(SecurityLevel.SECURE_SOFTWARE)) {
|
||||
promise.resolve(SecurityLevel.ANY.name());
|
||||
}
|
||||
|
||||
if (isSecureHardwareAvailable()) {
|
||||
promise.resolve(SecurityLevel.SECURE_HARDWARE.name());
|
||||
} else {
|
||||
promise.resolve(SecurityLevel.SECURE_SOFTWARE.name());
|
||||
}
|
||||
} catch (CryptoFailedException e) {
|
||||
promise.resolve(SecurityLevel.ANY.name());
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void setGenericPasswordForOptions(String service, String username, String password, String minimumSecurityLevel, Promise promise) {
|
||||
try {
|
||||
SecurityLevel level = SecurityLevel.valueOf(minimumSecurityLevel);
|
||||
if (username == null || username.isEmpty() || password == null || password.isEmpty()) {
|
||||
throw new EmptyParameterException("you passed empty or null username/password");
|
||||
}
|
||||
service = getDefaultServiceIfNull(service);
|
||||
|
||||
CipherStorage currentCipherStorage = getCipherStorageForCurrentAPILevel();
|
||||
CipherStorage currentCipherStorage = getCipherStorageForCurrentAPILevel(level);
|
||||
|
||||
EncryptionResult result = currentCipherStorage.encrypt(service, username, password);
|
||||
EncryptionResult result = currentCipherStorage.encrypt(service, username, password, level);
|
||||
prefsStorage.storeEncryptedEntry(service, result);
|
||||
|
||||
promise.resolve(true);
|
||||
|
@ -83,7 +101,7 @@ public class KeychainModule extends ReactContextBaseJavaModule {
|
|||
try {
|
||||
service = getDefaultServiceIfNull(service);
|
||||
|
||||
CipherStorage currentCipherStorage = getCipherStorageForCurrentAPILevel();
|
||||
CipherStorage currentCipherStorage = getCipherStorageForCurrentAPILevel(SecurityLevel.ANY);
|
||||
|
||||
final DecryptionResult decryptionResult;
|
||||
ResultSet resultSet = prefsStorage.getEncryptedEntry(service);
|
||||
|
@ -103,7 +121,8 @@ public class KeychainModule extends ReactContextBaseJavaModule {
|
|||
// decrypt using the older cipher storage
|
||||
decryptionResult = oldCipherStorage.decrypt(service, resultSet.usernameBytes, resultSet.passwordBytes);
|
||||
// encrypt using the current cipher storage
|
||||
EncryptionResult encryptionResult = currentCipherStorage.encrypt(service, decryptionResult.username, decryptionResult.password);
|
||||
// TODO: IGORM: fix this thing
|
||||
EncryptionResult encryptionResult = currentCipherStorage.encrypt(service, decryptionResult.username, decryptionResult.password, SecurityLevel.ANY);
|
||||
// store the encryption result
|
||||
prefsStorage.storeEncryptedEntry(service, encryptionResult);
|
||||
// clean up the old cipher storage
|
||||
|
@ -165,7 +184,7 @@ public class KeychainModule extends ReactContextBaseJavaModule {
|
|||
|
||||
@ReactMethod
|
||||
public void setInternetCredentialsForServer(@NonNull String server, String username, String password, ReadableMap unusedOptions, Promise promise) {
|
||||
setGenericPasswordForOptions(server, username, password, promise);
|
||||
setGenericPasswordForOptions(server, username, password, minimumSecurityLevel, promise);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
|
@ -194,20 +213,24 @@ public class KeychainModule extends ReactContextBaseJavaModule {
|
|||
}
|
||||
|
||||
// The "Current" CipherStorage is the cipherStorage with the highest API level that is lower than or equal to the current API level
|
||||
private CipherStorage getCipherStorageForCurrentAPILevel() throws CryptoFailedException {
|
||||
private CipherStorage getCipherStorageForCurrentAPILevel(SecurityLevel level) throws CryptoFailedException {
|
||||
int currentAPILevel = Build.VERSION.SDK_INT;
|
||||
CipherStorage currentCipherStorage = null;
|
||||
for (CipherStorage cipherStorage : cipherStorageMap.values()) {
|
||||
int cipherStorageAPILevel = cipherStorage.getMinSupportedApiLevel();
|
||||
// Is the cipherStorage supported on the current API level?
|
||||
boolean isSupported = (cipherStorageAPILevel <= currentAPILevel);
|
||||
boolean guaranteesSecurityLevel = cipherStorage.securityLevel().satisfiesSafetyThreshold(level);
|
||||
if (!isSupported || !guaranteesSecurityLevel) {
|
||||
continue;
|
||||
}
|
||||
// Is the API level better than the one we previously selected (if any)?
|
||||
if (isSupported && (currentCipherStorage == null || cipherStorageAPILevel > currentCipherStorage.getMinSupportedApiLevel())) {
|
||||
if (currentCipherStorage == null || cipherStorageAPILevel > currentCipherStorage.getMinSupportedApiLevel()) {
|
||||
currentCipherStorage = cipherStorage;
|
||||
}
|
||||
}
|
||||
if (currentCipherStorage == null) {
|
||||
throw new CryptoFailedException("Unsupported Android SDK " + Build.VERSION.SDK_INT);
|
||||
throw new CryptoFailedException("Unsupported Android SDK or no storage providing enough guarantees" + Build.VERSION.SDK_INT);
|
||||
}
|
||||
return currentCipherStorage;
|
||||
}
|
||||
|
@ -220,6 +243,15 @@ public class KeychainModule extends ReactContextBaseJavaModule {
|
|||
return DeviceAvailability.isFingerprintAuthAvailable(getReactApplicationContext());
|
||||
}
|
||||
|
||||
private boolean isSecureHardwareAvailable() {
|
||||
try {
|
||||
return getCipherStorageForCurrentAPILevel(SecurityLevel.ANY).supportsSecureHardware();
|
||||
} catch (CryptoFailedException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
private String getDefaultServiceIfNull(String service) {
|
||||
return service == null ? EMPTY_STRING : service;
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package com.oblador.keychain;
|
||||
|
||||
public enum SecurityLevel {
|
||||
ANY,
|
||||
SECURE_SOFTWARE,
|
||||
SECURE_HARDWARE; // Trusted Execution Environment or Secure Environment guarantees
|
||||
|
||||
public boolean satisfiesSafetyThreshold(SecurityLevel threshold) {
|
||||
return this.compareTo(threshold) >= 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.oblador.keychain.cipherStorage;
|
|||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.oblador.keychain.SecurityLevel;
|
||||
import com.oblador.keychain.exceptions.CryptoFailedException;
|
||||
import com.oblador.keychain.exceptions.KeyStoreAccessException;
|
||||
|
||||
|
@ -31,7 +32,7 @@ public interface CipherStorage {
|
|||
}
|
||||
}
|
||||
|
||||
EncryptionResult encrypt(@NonNull String service, @NonNull String username, @NonNull String password) throws CryptoFailedException;
|
||||
EncryptionResult encrypt(@NonNull String service, @NonNull String username, @NonNull String password, SecurityLevel level) throws CryptoFailedException;
|
||||
|
||||
DecryptionResult decrypt(@NonNull String service, @NonNull byte[] username, @NonNull byte[] password) throws CryptoFailedException;
|
||||
|
||||
|
@ -40,4 +41,8 @@ public interface CipherStorage {
|
|||
String getCipherStorageName();
|
||||
|
||||
int getMinSupportedApiLevel();
|
||||
|
||||
SecurityLevel securityLevel();
|
||||
|
||||
boolean supportsSecureHardware();
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import com.facebook.crypto.CryptoConfig;
|
|||
import com.facebook.crypto.Entity;
|
||||
import com.facebook.crypto.keychain.KeyChain;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.oblador.keychain.SecurityLevel;
|
||||
import com.oblador.keychain.exceptions.CryptoFailedException;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
@ -35,7 +36,23 @@ public class CipherStorageFacebookConceal implements CipherStorage {
|
|||
}
|
||||
|
||||
@Override
|
||||
public EncryptionResult encrypt(@NonNull String service, @NonNull String username, @NonNull String password) throws CryptoFailedException {
|
||||
public SecurityLevel securityLevel() {
|
||||
return SecurityLevel.ANY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSecureHardware() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EncryptionResult encrypt(@NonNull String service, @NonNull String username, @NonNull String password, SecurityLevel level) throws CryptoFailedException {
|
||||
|
||||
if (!this.securityLevel().satisfiesSafetyThreshold(level)) {
|
||||
// TODO: IGORM: refactor to another exception type!
|
||||
throw new CryptoFailedException(String.format("Insufficient security level (wants %s; got %s)", level, this.securityLevel()));
|
||||
}
|
||||
|
||||
if (!crypto.isAvailable()) {
|
||||
throw new CryptoFailedException("Crypto is missing");
|
||||
}
|
||||
|
|
|
@ -3,9 +3,12 @@ package com.oblador.keychain.cipherStorage;
|
|||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.security.keystore.KeyGenParameterSpec;
|
||||
import android.security.keystore.KeyInfo;
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import com.oblador.keychain.SecurityLevel;
|
||||
import com.oblador.keychain.exceptions.CryptoFailedException;
|
||||
import com.oblador.keychain.exceptions.KeyStoreAccessException;
|
||||
|
||||
|
@ -21,16 +24,20 @@ import java.security.NoSuchAlgorithmException;
|
|||
import java.security.NoSuchProviderException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import android.security.keystore.StrongBoxUnavailableException;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
import javax.crypto.CipherOutputStream;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
public class CipherStorageKeystoreAESCBC implements CipherStorage {
|
||||
public static final String TAG = "KeystoreAESCBC";
|
||||
public static final String CIPHER_STORAGE_NAME = "KeystoreAESCBC";
|
||||
public static final String DEFAULT_SERVICE = "RN_KEYCHAIN_DEFAULT_ALIAS";
|
||||
public static final String KEYSTORE_TYPE = "AndroidKeyStore";
|
||||
|
@ -54,7 +61,32 @@ public class CipherStorageKeystoreAESCBC implements CipherStorage {
|
|||
}
|
||||
|
||||
@Override
|
||||
public EncryptionResult encrypt(@NonNull String service, @NonNull String username, @NonNull String password) throws CryptoFailedException {
|
||||
public SecurityLevel securityLevel() {
|
||||
// it can guarantee security levels up to SECURE_HARDWARE/SE/StrongBox
|
||||
return SecurityLevel.SECURE_HARDWARE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSecureHardware() {
|
||||
final String testKeyAlias = "AndroidKeyStore#supportsSecureHardware";
|
||||
|
||||
try {
|
||||
SecretKey key = tryGenerateRegularSecurityKey(testKeyAlias);
|
||||
return validateKeySecurityLevel(SecurityLevel.SECURE_HARDWARE, key);
|
||||
} catch (NoSuchAlgorithmException|InvalidAlgorithmParameterException|NoSuchProviderException|InvalidKeySpecException e) {
|
||||
return false;
|
||||
} finally {
|
||||
try {
|
||||
removeKey(testKeyAlias);
|
||||
} catch (KeyStoreAccessException e) {
|
||||
Log.e(TAG, "Unable to remove temp key from keychain", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
@Override
|
||||
public EncryptionResult encrypt(@NonNull String service, @NonNull String username, @NonNull String password, SecurityLevel level) throws CryptoFailedException {
|
||||
service = getDefaultServiceIfEmpty(service);
|
||||
|
||||
try {
|
||||
|
@ -79,21 +111,34 @@ public class CipherStorageKeystoreAESCBC implements CipherStorage {
|
|||
}
|
||||
}
|
||||
|
||||
private void generateKeyAndStoreUnderAlias(@NonNull String service) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
|
||||
AlgorithmParameterSpec spec = new KeyGenParameterSpec.Builder(
|
||||
service,
|
||||
KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT)
|
||||
.setBlockModes(ENCRYPTION_BLOCK_MODE)
|
||||
.setEncryptionPaddings(ENCRYPTION_PADDING)
|
||||
.setRandomizedEncryptionRequired(true)
|
||||
//.setUserAuthenticationRequired(true) // Will throw InvalidAlgorithmParameterException if there is no fingerprint enrolled on the device
|
||||
.setKeySize(ENCRYPTION_KEY_SIZE)
|
||||
.build();
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
private boolean validateKeySecurityLevel(SecurityLevel level, SecretKey generatedKey) throws NoSuchAlgorithmException,
|
||||
NoSuchProviderException, InvalidKeySpecException {
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance(generatedKey.getAlgorithm(), KEYSTORE_TYPE);
|
||||
KeyInfo keyInfo;
|
||||
keyInfo = (KeyInfo) factory.getKeySpec(generatedKey, KeyInfo.class);
|
||||
|
||||
KeyGenerator generator = KeyGenerator.getInstance(ENCRYPTION_ALGORITHM, KEYSTORE_TYPE);
|
||||
generator.init(spec);
|
||||
if (level == SecurityLevel.SECURE_HARDWARE && !keyInfo.isInsideSecureHardware()) {
|
||||
Log.w(TAG, "Could not create a key inside secure hardware (SECURE_HARDWARE), even though it is required");
|
||||
return false;
|
||||
}
|
||||
|
||||
generator.generateKey();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void generateKeyAndStoreUnderAlias(@NonNull String service) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, CryptoFailedException {
|
||||
// Firstly, try to generate the key as safe as possible (strongbox).
|
||||
// see https://developer.android.com/training/articles/keystore#HardwareSecurityModule
|
||||
SecretKey secretKey = tryGenerateStrongBoxSecurityKey(service);
|
||||
if (secretKey == null) {
|
||||
// If that is not possible, we generate the key in a regular way
|
||||
// (it still might be generated in hardware, but not in StrongBox)
|
||||
secretKey = tryGenerateRegularSecurityKey(service);
|
||||
}
|
||||
|
||||
if(!validateKeySecurityLevel(level, secretKey)) {
|
||||
throw new CryptoFailedException("Cannot generate keys with required security guarantees");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -198,4 +243,46 @@ public class CipherStorageKeystoreAESCBC implements CipherStorage {
|
|||
private String getDefaultServiceIfEmpty(@NonNull String service) {
|
||||
return service.isEmpty() ? DEFAULT_SERVICE : service;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.P)
|
||||
private SecretKey tryGenerateStrongBoxSecurityKey(String service) throws NoSuchAlgorithmException,
|
||||
InvalidAlgorithmParameterException, NoSuchProviderException {
|
||||
// StrongBox is only supported on Android P and higher
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return generateKey(getKeyGenSpecBuilder(service).setIsStrongBoxBacked(true).build());
|
||||
} catch (StrongBoxUnavailableException e) {
|
||||
Log.i(TAG, "StrongBox is unavailable on this device");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
private SecretKey tryGenerateRegularSecurityKey(String service) throws NoSuchAlgorithmException,
|
||||
InvalidAlgorithmParameterException, NoSuchProviderException {
|
||||
return generateKey(getKeyGenSpecBuilder(service).build());
|
||||
}
|
||||
|
||||
// returns true if the key was generated successfully
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
private SecretKey generateKey(KeyGenParameterSpec spec) throws NoSuchProviderException,
|
||||
NoSuchAlgorithmException, InvalidAlgorithmParameterException {
|
||||
KeyGenerator generator = KeyGenerator.getInstance(ENCRYPTION_ALGORITHM, KEYSTORE_TYPE);
|
||||
generator.init(spec);
|
||||
return generator.generateKey();
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
private KeyGenParameterSpec.Builder getKeyGenSpecBuilder(String service) {
|
||||
return new KeyGenParameterSpec.Builder(
|
||||
service,
|
||||
KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT)
|
||||
.setBlockModes(ENCRYPTION_BLOCK_MODE)
|
||||
.setEncryptionPaddings(ENCRYPTION_PADDING)
|
||||
.setRandomizedEncryptionRequired(true)
|
||||
//.setUserAuthenticationRequired(true) // Will throw InvalidAlgorithmParameterException if there is no fingerprint enrolled on the device
|
||||
.setKeySize(ENCRYPTION_KEY_SIZE);
|
||||
}
|
||||
}
|
||||
|
|
54
index.js
54
index.js
|
@ -1,6 +1,12 @@
|
|||
import { NativeModules, Platform } from 'react-native';
|
||||
const { RNKeychainManager } = NativeModules;
|
||||
|
||||
export const SECURITY_LEVEL = {
|
||||
ANY: 'ANY',
|
||||
SECURE_SOFTWARE: 'SECURE_SOFTWARE',
|
||||
SECURE_HARDWARE: 'SECURE_HARDWARE',
|
||||
};
|
||||
|
||||
export const ACCESSIBLE = {
|
||||
WHEN_UNLOCKED: 'AccessibleWhenUnlocked',
|
||||
AFTER_FIRST_UNLOCK: 'AccessibleAfterFirstUnlock',
|
||||
|
@ -33,6 +39,11 @@ export const BIOMETRY_TYPE = {
|
|||
FINGERPRINT: 'Fingerprint',
|
||||
};
|
||||
|
||||
type SecMinimumLevel =
|
||||
| 'ANY'
|
||||
| 'SECURE_SOFTWARE'
|
||||
| 'TEE' ;
|
||||
|
||||
type SecAccessible =
|
||||
| 'AccessibleWhenUnlocked'
|
||||
| 'AccessibleAfterFirstUnlock'
|
||||
|
@ -62,6 +73,18 @@ type Options = {
|
|||
service?: string,
|
||||
};
|
||||
|
||||
/**
|
||||
* (Android only) Returns guaranteed security level supported by this library
|
||||
* on the current device.
|
||||
* @return {Promise} Resolves to `SECURITY_LEVEL` when supported, otherwise `null`.
|
||||
*/
|
||||
export function getSecurityLevel(): Promise {
|
||||
if (!RNKeychainManager.getSecurityLevel){
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return RNKeychainManager.getSecurityLevel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inquire if the type of local authentication policy (LAPolicy) is supported
|
||||
* on this device with the device settings the user chose.
|
||||
|
@ -91,6 +114,8 @@ export function getSupportedBiometryType(): Promise {
|
|||
* @param {string} server URL to server.
|
||||
* @param {string} username Associated username or e-mail to be saved.
|
||||
* @param {string} password Associated password to be saved.
|
||||
* @param {string} minimumSecurityLevel `SECURITY_LEVEL` defines which security
|
||||
* level is minimally acceptable for this password.
|
||||
* @param {object} options Keychain options, iOS only
|
||||
* @return {Promise} Resolves to `true` when successful
|
||||
*/
|
||||
|
@ -98,12 +123,14 @@ export function setInternetCredentials(
|
|||
server: string,
|
||||
username: string,
|
||||
password: string,
|
||||
minimumSecurityLevel?: SecMinimumLevel,
|
||||
options?: Options
|
||||
): Promise {
|
||||
return RNKeychainManager.setInternetCredentialsForServer(
|
||||
server,
|
||||
username,
|
||||
password,
|
||||
getMinimumSecurityLevel(minimumSecurityLevel),
|
||||
options
|
||||
);
|
||||
}
|
||||
|
@ -156,35 +183,34 @@ function getOptionsArgument(serviceOrOptions?: string | Options) {
|
|||
: serviceOrOptions;
|
||||
}
|
||||
|
||||
function getMinimumSecurityLevel(minimumSecurityLevel?: SecMinimumLevel) {
|
||||
if (minimumSecurityLevel === undefined) {
|
||||
return SECURITY_LEVEL.ANY;
|
||||
} else {
|
||||
return minimumSecurityLevel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the `username` and `password` combination for `service`.
|
||||
* @param {string} username Associated username or e-mail to be saved.
|
||||
* @param {string} password Associated password to be saved.
|
||||
* @param {string} minimumSecurityLevel `SECURITY_LEVEL` defines which security
|
||||
* level is minimally acceptable for this password.
|
||||
* @param {string|object} serviceOrOptions Reverse domain name qualifier for the service, defaults to `bundleId` or an options object.
|
||||
* @return {Promise} Resolves to `true` when successful
|
||||
*/
|
||||
export function setGenericPassword(
|
||||
username: string,
|
||||
password: string,
|
||||
minimumSecurityLevel?: SecMinimumLevel,
|
||||
serviceOrOptions?: string | Options
|
||||
): Promise {
|
||||
return RNKeychainManager.setGenericPasswordForOptions(
|
||||
getOptionsArgument(serviceOrOptions),
|
||||
username,
|
||||
password
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the `username` for further use on get requests.
|
||||
* @param {string} username Associated username or e-mail to be saved.
|
||||
* @return {Promise} Resolves to `true` when successful
|
||||
*/
|
||||
export function setUsername(
|
||||
username: string
|
||||
): Promise {
|
||||
return RNKeychainManager.setUsername(
|
||||
username
|
||||
password,
|
||||
getMinimumSecurityLevel(minimumSecurityLevel)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue