Minimal security guarantees for react-native-keychain (#6)

* 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]).

Co-Authored-By: mandrigin <mandrigin@users.noreply.github.com>
This commit is contained in:
Igor Mandrigin 2018-12-19 09:11:10 +01:00 committed by GitHub
parent ea1bfe6b80
commit 43e5512cab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 285 additions and 54 deletions

View File

@ -45,7 +45,7 @@ See `KeychainExample` for fully working project example.
Both `setGenericPassword` and `setInternetCredentials` are limited to strings only, so if you need to store objects etc, please use `JSON.stringify`/`JSON.parse` when you store/access it.
### `setGenericPassword(username, password, [{ accessControl, accessible, accessGroup, service }])`
### `setGenericPassword(username, password, securityLevel, [{ accessControl, accessible, accessGroup, service }])`
Will store the username/password combination in the secure storage. Resolves to `true` or rejects in case of an error.
@ -57,7 +57,7 @@ Will retreive the username/password combination from the secure storage. Resolve
Will remove the username/password combination from the secure storage.
### `setInternetCredentials(server, username, password, [{ accessControl, accessible, accessGroup }])`
### `setInternetCredentials(server, username, password, securityLevel, [{ accessControl, accessible, accessGroup }])`
Will store the server/username/password combination in the secure storage.
@ -85,6 +85,18 @@ Inquire if the type of local authentication policy is supported on this device w
Get what type of hardware biometry support the device has. Resolves to a `Keychain.BIOMETRY_TYPE` value when supported, otherwise `null`.
### `getSecurityLevel()`
Get security level that is supported on the current device with the current OS.
### Security Levels (Android only)
If set, `securityLevel` parameter specifies minimum security level that the encryption key storage should guarantee for storing credentials to succeed.
* `ANY` - no security guarantees needed (default value); Credentials can be stored in FB Secure Storage;
* `SECURE_SOFTWARE` - requires for the key to be stored in the Android Keystore, separate from the encrypted data;
* `SECURE_HARDWARE` - requires for the key to be stored on a secure hardware (Trusted Execution Environment or Secure Environment). Read [this article](https://developer.android.com/training/articles/keystore#ExtractionPrevention) for more information.
### Options
| Key | Platform | Description | Default |

View File

@ -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];

View File

@ -10,13 +10,17 @@ buildscript {
apply plugin: 'com.android.library'
def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
compileSdkVersion safeExtGet('compileSdkVersion', 28)
buildToolsVersion safeExtGet('buildToolsVersion', '26.0.3')
defaultConfig {
minSdkVersion 16
targetSdkVersion 23
minSdkVersion safeExtGet('minSdkVersion', 16)
targetSdkVersion safeExtGet('targetSdkVersion', 26)
versionCode 1
versionName "1.0"
}

View File

@ -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,23 @@ public class KeychainModule extends ReactContextBaseJavaModule {
}
@ReactMethod
public void setGenericPasswordForOptions(String service, String username, String password, Promise promise) {
public void getSecurityLevel(Promise promise) {
promise.resolve(getSecurityLevel().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();
validateCipherStorageSecurityLevel(currentCipherStorage, level);
EncryptionResult result = currentCipherStorage.encrypt(service, username, password);
EncryptionResult result = currentCipherStorage.encrypt(service, username, password, level);
prefsStorage.storeEncryptedEntry(service, result);
promise.resolve(true);
@ -103,11 +109,17 @@ 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);
// store the encryption result
prefsStorage.storeEncryptedEntry(service, encryptionResult);
// clean up the old cipher storage
oldCipherStorage.removeKey(service);
try {
// don't allow to degrade security level when transferring, the new storage should be as safe as the old one.
EncryptionResult encryptionResult = currentCipherStorage.encrypt(service, decryptionResult.username, decryptionResult.password, decryptionResult.getSecurityLevel());
// store the encryption result
prefsStorage.storeEncryptedEntry(service, encryptionResult);
// clean up the old cipher storage
oldCipherStorage.removeKey(service);
} catch (CryptoFailedException e) {
Log.e(KEYCHAIN_MODULE, "Migrating to a less safe storage is not allowed. Keeping the old one");
}
}
WritableMap credentials = Arguments.createMap();
@ -150,8 +162,8 @@ 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);
public void setInternetCredentialsForServer(@NonNull String server, String username, String password, String minimumSecurityLevel, ReadableMap unusedOptions, Promise promise) {
setGenericPasswordForOptions(server, username, password, minimumSecurityLevel, promise);
}
@ReactMethod
@ -187,8 +199,11 @@ public class KeychainModule extends ReactContextBaseJavaModule {
int cipherStorageAPILevel = cipherStorage.getMinSupportedApiLevel();
// Is the cipherStorage supported on the current API level?
boolean isSupported = (cipherStorageAPILevel <= currentAPILevel);
if (!isSupported) {
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;
}
}
@ -198,6 +213,19 @@ public class KeychainModule extends ReactContextBaseJavaModule {
return currentCipherStorage;
}
private void validateCipherStorageSecurityLevel(CipherStorage cipherStorage, SecurityLevel requiredLevel) throws CryptoFailedException {
if (cipherStorage.securityLevel().satisfiesSafetyThreshold(requiredLevel)) {
return;
}
throw new CryptoFailedException(
String.format(
"Cipher Storage is too weak. Required security level is: %s, but only %s is provided",
requiredLevel.name(),
cipherStorage.securityLevel().name()));
}
private CipherStorage getCipherStorageByName(String cipherStorageName) {
return cipherStorageMap.get(cipherStorageName);
}
@ -206,6 +234,33 @@ public class KeychainModule extends ReactContextBaseJavaModule {
return DeviceAvailability.isFingerprintAuthAvailable(getCurrentActivity());
}
private boolean isSecureHardwareAvailable() {
try {
return getCipherStorageForCurrentAPILevel().supportsSecureHardware();
} catch (CryptoFailedException e) {
return false;
}
}
private SecurityLevel getSecurityLevel() {
try {
CipherStorage storage = getCipherStorageForCurrentAPILevel();
if (!storage.securityLevel().satisfiesSafetyThreshold(SecurityLevel.SECURE_SOFTWARE)) {
return SecurityLevel.ANY;
}
if (isSecureHardwareAvailable()) {
return SecurityLevel.SECURE_HARDWARE;
} else {
return SecurityLevel.SECURE_SOFTWARE;
}
} catch (CryptoFailedException e) {
return SecurityLevel.ANY;
}
}
@NonNull
private String getDefaultServiceIfNull(String service) {
return service == null ? EMPTY_STRING : service;

View File

@ -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;
}
}

View File

@ -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;
@ -26,12 +27,19 @@ public interface CipherStorage {
}
class DecryptionResult extends CipherResult<String> {
public DecryptionResult(String username, String password) {
private SecurityLevel securityLevel;
public DecryptionResult(String username, String password, SecurityLevel level) {
super(username, password);
securityLevel = level;
}
public SecurityLevel getSecurityLevel() {
return securityLevel;
}
}
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 +48,8 @@ public interface CipherStorage {
String getCipherStorageName();
int getMinSupportedApiLevel();
SecurityLevel securityLevel();
boolean supportsSecureHardware();
}

View File

@ -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,22 @@ 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)) {
throw new CryptoFailedException(String.format("Insufficient security level (wants %s; got %s)", level, this.securityLevel()));
}
if (!crypto.isAvailable()) {
throw new CryptoFailedException("Crypto is missing");
}
@ -66,7 +82,8 @@ public class CipherStorageFacebookConceal implements CipherStorage {
return new DecryptionResult(
new String(decryptedUsername, Charset.forName("UTF-8")),
new String(decryptedPassword, Charset.forName("UTF-8")));
new String(decryptedPassword, Charset.forName("UTF-8")),
SecurityLevel.ANY);
} catch (Exception e) {
throw new CryptoFailedException("Decryption failed for service " + service, e);
}

View File

@ -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,15 +24,19 @@ 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;
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";
@ -52,30 +59,51 @@ public class CipherStorageKeystoreAESCBC implements CipherStorage {
return Build.VERSION_CODES.M;
}
@Override
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 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) throws CryptoFailedException {
public EncryptionResult encrypt(@NonNull String service, @NonNull String username, @NonNull String password, SecurityLevel level) throws CryptoFailedException {
service = getDefaultServiceIfEmpty(service);
try {
KeyStore keyStore = getKeyStoreAndLoad();
if (!keyStore.containsAlias(service)) {
AlgorithmParameterSpec spec;
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();
// 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);
}
KeyGenerator generator = KeyGenerator.getInstance(ENCRYPTION_ALGORITHM, KEYSTORE_TYPE);
generator.init(spec);
generator.generateKey();
if (!validateKeySecurityLevel(level, secretKey)) {
throw new CryptoFailedException("Cannot generate keys with required security guarantees");
}
}
Key key = keyStore.getKey(service, null);
@ -88,6 +116,25 @@ public class CipherStorageKeystoreAESCBC implements CipherStorage {
throw new CryptoFailedException("Could not encrypt data for service " + service, e);
} catch (KeyStoreException | KeyStoreAccessException e) {
throw new CryptoFailedException("Could not access Keystore for service " + service, e);
} catch (Exception e) {
throw new CryptoFailedException("Unknown error: " + e.getMessage(), e);
}
}
@TargetApi(Build.VERSION_CODES.M)
private boolean validateKeySecurityLevel(SecurityLevel level, SecretKey generatedKey) {
return getSecurityLevel(generatedKey).satisfiesSafetyThreshold(level);
}
@TargetApi(Build.VERSION_CODES.M)
private SecurityLevel getSecurityLevel(SecretKey key) {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance(key.getAlgorithm(), KEYSTORE_TYPE);
KeyInfo keyInfo;
keyInfo = (KeyInfo) factory.getKeySpec(key, KeyInfo.class);
return keyInfo.isInsideSecureHardware() ? SecurityLevel.SECURE_HARDWARE : SecurityLevel.SECURE_SOFTWARE;
} catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeySpecException e) {
return SecurityLevel.ANY;
}
}
@ -103,11 +150,13 @@ public class CipherStorageKeystoreAESCBC implements CipherStorage {
String decryptedUsername = decryptBytes(key, username);
String decryptedPassword = decryptBytes(key, password);
return new DecryptionResult(decryptedUsername, decryptedPassword);
return new DecryptionResult(decryptedUsername, decryptedPassword, getSecurityLevel((SecretKey) key));
} catch (KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException e) {
throw new CryptoFailedException("Could not get key from Keystore", e);
} catch (KeyStoreAccessException e) {
throw new CryptoFailedException("Could not access Keystore", e);
} catch (Exception e) {
throw new CryptoFailedException("Unknown error: " + e.getMessage(), e);
}
}
@ -123,6 +172,8 @@ public class CipherStorageKeystoreAESCBC implements CipherStorage {
}
} catch (KeyStoreException e) {
throw new KeyStoreAccessException("Failed to access Keystore", e);
} catch (Exception e) {
throw new KeyStoreAccessException("Unknown error " + e.getMessage(), e);
}
}
@ -189,4 +240,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);
}
}

View File

@ -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'
| 'SECURE_HARDWARE' ;
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
);
}
@ -145,35 +172,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)
);
}