Remove *PasswordWithAuthentication and replace with accessControl option

This commit is contained in:
Joel Arvidsson 2018-02-26 12:31:06 +01:00
parent 37ea15ae5e
commit add90c5769
8 changed files with 105 additions and 299 deletions

View File

@ -2,6 +2,7 @@ import React, { Component } from 'react';
import {
KeyboardAvoidingView,
Platform,
SegmentedControlIOS,
StyleSheet,
Text,
TextInput,
@ -11,12 +12,16 @@ import {
import * as Keychain from 'react-native-keychain';
const ACCESS_CONTROL_OPTIONS = ['None', 'Passcode', 'Password'];
const ACCESS_CONTROL_MAP = [null, Keychain.ACCESS_CONTROL.DEVICE_PASSCODE, Keychain.ACCESS_CONTROL.APPLICATION_PASSWORD, Keychain.ACCESS_CONTROL.BIOMETRY_CURRENT_SET]
export default class KeychainExample extends Component {
state = {
username: '',
password: '',
status: '',
biometryType: null,
accessControl: null,
};
componentDidMount() {
@ -25,24 +30,13 @@ export default class KeychainExample extends Component {
});
}
async save() {
async save(accessControl) {
try {
if (this.state.biometryType) {
await Keychain.setPasswordWithAuthentication(
this.state.username,
this.state.password,
{
accessControl:
Keychain.ACCESS_CONTROL.TOUCH_ID_ANY_OR_DEVICE_PASSCODE,
authenticationType: Keychain.AUTHENTICATION_TYPE.BIOMETRICS,
}
);
} else {
await Keychain.setGenericPassword(
this.state.username,
this.state.password
);
}
await Keychain.setGenericPassword(
this.state.username,
this.state.password,
{ accessControl: this.state.accessControl }
);
this.setState({ status: 'Credentials saved!' });
} catch (err) {
this.setState({ status: 'Could not save credentials, ' + err });
@ -51,13 +45,7 @@ export default class KeychainExample extends Component {
async load() {
try {
const credentials = await (this.state.biometryType
? Keychain.getPasswordWithAuthentication({
accessControl:
Keychain.ACCESS_CONTROL.TOUCH_ID_ANY_OR_DEVICE_PASSCODE,
authenticationType: Keychain.AUTHENTICATION_TYPE.BIOMETRICS,
})
: Keychain.getGenericPassword());
const credentials = await Keychain.getGenericPassword();
if (credentials) {
this.setState({ ...credentials, status: 'Credentials loaded!' });
} else {
@ -113,14 +101,24 @@ export default class KeychainExample extends Component {
underlineColorAndroid="transparent"
/>
</View>
{Platform.OS === 'ios' && (
<View style={styles.field}>
<Text style={styles.label}>Access Control</Text>
<SegmentedControlIOS
selectedIndex={0}
values={this.state.biometryType ? [...ACCESS_CONTROL_OPTIONS, this.state.biometryType] : ACCESS_CONTROL_OPTIONS}
onChange={({ nativeEvent }) => {
this.setState({
accessControl: ACCESS_CONTROL_MAP[nativeEvent.selectedSegmentIndex],
});
}}
/>
</View>
)}
{!!this.state.status && (
<Text style={styles.status}>{this.state.status}</Text>
)}
{!!this.state.biometryType && (
<Text style={styles.biometryType}>
Supported biometry: {this.state.biometryType}
</Text>
)}
<View style={styles.buttons}>
<TouchableHighlight
onPress={() => this.save()}
@ -156,12 +154,11 @@ export default class KeychainExample extends Component {
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#F5FCFF',
},
content: {
width: 250,
marginHorizontal: 20,
},
title: {
fontSize: 28,

View File

@ -11,26 +11,9 @@
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <RNKeychain/RNKeychainAuthenticationListener.h>
@interface AppDelegate() <RNKeychainAuthenticationListener>
@end
@implementation AppDelegate
@synthesize willPromptForAuthentication = _willPromptForAuthentication;
- (void)setWillPromptForAuthentication:(BOOL)willPromptForAuthentication {
_willPromptForAuthentication = willPromptForAuthentication;
if (willPromptForAuthentication) {
NSLog(@"APPDELEGATE::: will prompt TouchId");
} else {
NSLog(@"APPDELEGATE::: ended prompt TouchId");
}
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSURL *jsCodeLocation;

View File

@ -127,12 +127,12 @@ Keychain
| Key | Applies to | Description | Default |
|---|---|---|---|
|**`accessControl`**|`PasswordWithAuthentication`|This dictates how a keychain item may be used, see possible values in `Keychain.ACCESS_CONTROL`. |*`Keychain.ACCESS_CONTROL.TOUCH_ID_CURRENT_SET_OR_DEVICE_PASSCODE`*|
|**`accessible`**|`GenericPassword`, `InternetCredentials`|This dictates when a keychain item is accessible, see possible values in `Keychain.ACCESSIBLE`. |*`Keychain.ACCESSIBLE.WHEN_UNLOCKED`*|
|**`accessGroup`**|`GenericPassword`, `InternetCredentials`, `PasswordWithAuthentication`|In which App Group to share the keychain. Requires additional setup with entitlements. |*None*|
|**`authenticationPrompt`**|`PasswordWithAuthentication`|What to prompt the user when unlocking the keychain with biometry or device password. |`Authenticate to retrieve secret!`|
|**`accessControl`**|`setGenericPassword`, `setInternetCredentials`|This dictates how a keychain item may be used, see possible values in `Keychain.ACCESS_CONTROL`. |*None*|
|**`accessible`**|`setGenericPassword`, `setInternetCredentials`|This dictates when a keychain item is accessible, see possible values in `Keychain.ACCESSIBLE`. |*`Keychain.ACCESSIBLE.WHEN_UNLOCKED`*|
|**`accessGroup`**|`setGenericPassword`, `setInternetCredentials`|In which App Group to share the keychain. Requires additional setup with entitlements. |*None*|
|**`authenticationPrompt`**|`getGenericPassword`, `getInternetCredentials`|What to prompt the user when unlocking the keychain with biometry or device password. |`Authenticate to retrieve secret`|
|**`authenticationType`**|`canImplyAuthentication`|Policies specifying which forms of authentication are acceptable. |`Keychain.AUTHENTICATION_TYPE.DEVICE_PASSCODE_OR_BIOMETRICS`|
|**`service`**|`GenericPassword`, `PasswordWithAuthentication`|Reverse domain name qualifier for the service associated with password. |*App bundle ID*|
|**`service`**|`setGenericPassword`, `getGenericPassword`|Reverse domain name qualifier for the service associated with password. |*App bundle ID*|
#### `Keychain.ACCESS_CONTROL` enum

View File

@ -10,11 +10,7 @@
5D82368F1B0CE3CB005A9EF3 /* RNKeychainManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D82368C1B0CE2A6005A9EF3 /* RNKeychainManager.m */; };
5D8236911B0CE3D6005A9EF3 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D8236901B0CE3D6005A9EF3 /* Security.framework */; };
5DE632D52043423E004F9598 /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DE632D42043423E004F9598 /* LocalAuthentication.framework */; };
5DE632D720434276004F9598 /* RNKeychainAuthenticationListener.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5DE632D62043426A004F9598 /* RNKeychainAuthenticationListener.h */; };
5DE632D920434291004F9598 /* RNKeychainAuthenticationListener.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DE632D62043426A004F9598 /* RNKeychainAuthenticationListener.h */; };
5DE632DB204342AE004F9598 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DE632DA204342AE004F9598 /* Security.framework */; };
5DE632DC204342B5004F9598 /* RNKeychainAuthenticationListener.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5DE632D62043426A004F9598 /* RNKeychainAuthenticationListener.h */; };
5DE632DE204342C3004F9598 /* RNKeychainAuthenticationListener.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DE632D62043426A004F9598 /* RNKeychainAuthenticationListener.h */; };
6478986B1F38BFA100DA1C12 /* RNKeychainManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D82368C1B0CE2A6005A9EF3 /* RNKeychainManager.m */; };
/* End PBXBuildFile section */
@ -25,7 +21,6 @@
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
5DE632D720434276004F9598 /* RNKeychainAuthenticationListener.h in Copy Headers */,
);
name = "Copy Headers";
runOnlyForDeploymentPostprocessing = 0;
@ -36,7 +31,6 @@
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
5DE632DC204342B5004F9598 /* RNKeychainAuthenticationListener.h in Copy Headers */,
);
name = "Copy Headers";
runOnlyForDeploymentPostprocessing = 0;
@ -46,10 +40,9 @@
/* Begin PBXFileReference section */
5D82366F1B0CE05B005A9EF3 /* libRNKeychain.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNKeychain.a; sourceTree = BUILT_PRODUCTS_DIR; };
5D82368B1B0CE2A6005A9EF3 /* RNKeychainManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNKeychainManager.h; sourceTree = "<group>"; };
5D82368C1B0CE2A6005A9EF3 /* RNKeychainManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNKeychainManager.m; sourceTree = "<group>"; };
5D82368C1B0CE2A6005A9EF3 /* RNKeychainManager.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = RNKeychainManager.m; sourceTree = "<group>"; tabWidth = 2; };
5D8236901B0CE3D6005A9EF3 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
5DE632D42043423E004F9598 /* LocalAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LocalAuthentication.framework; path = System/Library/Frameworks/LocalAuthentication.framework; sourceTree = SDKROOT; };
5DE632D62043426A004F9598 /* RNKeychainAuthenticationListener.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNKeychainAuthenticationListener.h; sourceTree = "<group>"; };
5DE632DA204342AE004F9598 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS11.2.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; };
6478985F1F38BF9100DA1C12 /* libRNKeychain.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNKeychain.a; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
@ -97,7 +90,6 @@
5D82368A1B0CE2A6005A9EF3 /* RNKeychainManager */ = {
isa = PBXGroup;
children = (
5DE632D62043426A004F9598 /* RNKeychainAuthenticationListener.h */,
5D82368B1B0CE2A6005A9EF3 /* RNKeychainManager.h */,
5D82368C1B0CE2A6005A9EF3 /* RNKeychainManager.m */,
);
@ -121,7 +113,6 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
5DE632D920434291004F9598 /* RNKeychainAuthenticationListener.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -129,7 +120,6 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
5DE632DE204342C3004F9598 /* RNKeychainAuthenticationListener.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -1,22 +0,0 @@
//
// TouchIdPromptListener.h
// RNKeychain
//
// Created by Steffen Blümm on 05/04/17.
// Copyright © 2017 Joel Arvidsson. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
This is a protocol to be implemented by the AppDelegate in case
the AppDelegate takes precautions to obfuscate the screen when
the app resigns active state.
Thus the AppDelegate can avoid to obfuscating the screen when
the TouchId-prompt is brought up by the OS
*/
@protocol RNKeychainAuthenticationListener <NSObject>
@property (nonatomic, assign) BOOL willPromptForAuthentication;
@end

View File

@ -15,8 +15,6 @@
#import <LocalAuthentication/LAContext.h>
#import <UIKit/UIKit.h>
#import "RNKeychainAuthenticationListener.h"
@implementation RNKeychainManager
@synthesize bridge = _bridge;
@ -148,43 +146,69 @@ LAPolicy authPolicy(NSDictionary *options)
return LAPolicyDeviceOwnerAuthentication;
}
SecAccessControlCreateFlags secureAccessControl(NSDictionary *options)
SecAccessControlCreateFlags accessControlValue(NSDictionary *options)
{
if (options && options[kAccessControlType]) {
if ([ options[kAccessControlType] isEqualToString: kAccessControlUserPresence ]) {
if (options && options[kAccessControlType] && [options[kAccessControlType] isKindOfClass:[NSString class]]) {
if ([options[kAccessControlType] isEqualToString: kAccessControlUserPresence]) {
return kSecAccessControlUserPresence;
}
else if ([ options[kAccessControlType] isEqualToString: kAccessControlBiometryAny ]) {
else if ([options[kAccessControlType] isEqualToString: kAccessControlBiometryAny]) {
return kSecAccessControlTouchIDAny;
}
else if ([ options[kAccessControlType] isEqualToString: kAccessControlBiometryCurrentSet ]) {
else if ([options[kAccessControlType] isEqualToString: kAccessControlBiometryCurrentSet]) {
return kSecAccessControlTouchIDCurrentSet;
}
else if ([ options[kAccessControlType] isEqualToString: kAccessControlDevicePasscode ]) {
else if ([options[kAccessControlType] isEqualToString: kAccessControlDevicePasscode]) {
return kSecAccessControlDevicePasscode;
}
else if ([ options[kAccessControlType] isEqualToString: kAccessControlBiometryAnyOrDevicePasscode ]) {
else if ([options[kAccessControlType] isEqualToString: kAccessControlBiometryAnyOrDevicePasscode]) {
return kSecAccessControlTouchIDAny|kSecAccessControlOr|kSecAccessControlDevicePasscode;
}
else if ([ options[kAccessControlType] isEqualToString: kAccessControlBiometryCurrentSetOrDevicePasscode ]) {
else if ([options[kAccessControlType] isEqualToString: kAccessControlBiometryCurrentSetOrDevicePasscode]) {
return kSecAccessControlTouchIDCurrentSet|kSecAccessControlOr|kSecAccessControlDevicePasscode;
}
else if ([ options[kAccessControlType] isEqualToString: kAccessControlApplicationPassword ]) {
else if ([options[kAccessControlType] isEqualToString: kAccessControlApplicationPassword]) {
return kSecAccessControlApplicationPassword;
}
}
return kSecAccessControlTouchIDCurrentSet|kSecAccessControlOr|kSecAccessControlDevicePasscode;
return 0;
}
//LAPolicyDeviceOwnerAuthenticationWithBiometrics | LAPolicyDeviceOwnerAuthentication
- (void)insertKeychainEntry:(NSDictionary *)attributes withAccessGroup:(NSString * __nullable)accessGroup resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject
- (void)insertKeychainEntry:(NSDictionary *)attributes withOptions:(NSDictionary * __nullable)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject
{
if (accessGroup != nil) {
NSMutableDictionary *mAttributes = attributes.mutableCopy;
mAttributes[(__bridge NSString *)kSecAttrAccessGroup] = accessGroup;
attributes = [NSDictionary dictionaryWithDictionary:mAttributes];
NSString *accessGroup = accessGroupValue(options);
CFStringRef accessible = accessibleValue(options);
SecAccessControlCreateFlags accessControl = accessControlValue(options);
NSMutableDictionary *mAttributes = attributes.mutableCopy;
if (accessControl) {
NSError *aerr = nil;
BOOL canAuthenticate = [[LAContext new] canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&aerr];
if (aerr || !canAuthenticate) {
return rejectWithError(reject, aerr);
}
CFErrorRef error = NULL;
SecAccessControlRef sacRef = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
accessible,
accessControl,
&error);
if (error) {
return rejectWithError(reject, aerr);
}
mAttributes[(__bridge NSString *)kSecAttrAccessControl] = (__bridge id)sacRef;
} else {
mAttributes[(__bridge NSString *)kSecAttrAccessible] = (__bridge id)accessible;
}
if (accessGroup != nil) {
mAttributes[(__bridge NSString *)kSecAttrAccessGroup] = accessGroup;
}
attributes = [NSDictionary dictionaryWithDictionary:mAttributes];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
OSStatus osStatus = SecItemAdd((__bridge CFDictionaryRef) attributes, NULL);
@ -223,7 +247,7 @@ SecAccessControlCreateFlags secureAccessControl(NSDictionary *options)
return SecItemDelete((__bridge CFDictionaryRef) query);
}
#pragma mark - Proposed functionality - RCT_EXPORT_METHOD
#pragma mark - RNKeychain
#if TARGET_OS_IOS
RCT_EXPORT_METHOD(canCheckAuthentication:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
@ -261,129 +285,28 @@ RCT_EXPORT_METHOD(getSupportedBiometryType:(RCTPromiseResolveBlock)resolve rejec
}
#endif
RCT_EXPORT_METHOD(setPasswordWithAuthentication:(NSDictionary *)options withUsername:(NSString *)username withPassword:(NSString *)password resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
NSString *service = serviceValue(options);
NSError *aerr = nil;
BOOL canAuthenticate = [[LAContext new] canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&aerr];
if (aerr || !canAuthenticate) {
return rejectWithError(reject, aerr);
}
[self deletePasswordsForService:service];
CFErrorRef error = NULL;
SecAccessControlRef sacRef = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, //kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
secureAccessControl(options),
&error);
if (error) {
// ok: failed
return rejectWithError(reject, aerr);
}
NSDictionary *attributes = @{
(__bridge NSString *)kSecClass: (__bridge id)(kSecClassGenericPassword),
(__bridge NSString *)kSecAttrService: service,
(__bridge NSString *)kSecAttrAccount: username,
(__bridge NSString *)kSecValueData: [password dataUsingEncoding:NSUTF8StringEncoding],
(__bridge NSString *)kSecAttrAccessControl: (__bridge id)sacRef
};
[self insertKeychainEntry:attributes withAccessGroup:accessGroupValue(options) resolver:resolve rejecter:reject];
}
RCT_EXPORT_METHOD(getPasswordWithAuthentication:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
NSString *service = serviceValue(options);
NSString *promptMessage = @"Authenticate to retrieve secret!";
if (options && options[kAuthenticationPromptMessage]) {
promptMessage = options[kAuthenticationPromptMessage];
}
NSDictionary *query = @{
(__bridge NSString *)kSecClass: (__bridge id)(kSecClassGenericPassword),
(__bridge NSString *)kSecAttrService: service,
(__bridge NSString *)kSecReturnAttributes: (__bridge id)kCFBooleanTrue,
(__bridge NSString *)kSecReturnData: (__bridge id)kCFBooleanTrue,
(__bridge NSString *)kSecMatchLimit: (__bridge NSString *)kSecMatchLimitOne,
(__bridge NSString *)kSecUseOperationPrompt: promptMessage
};
// Notify AppDelegate
dispatch_async(dispatch_get_main_queue(), ^{
[self notifyAuthenticationListener: YES];
});
// Look up password for service in the keychain
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__block NSDictionary* found = nil;
CFTypeRef foundTypeRef = NULL;
OSStatus osStatus = SecItemCopyMatching((__bridge CFDictionaryRef) query, (CFTypeRef*)&foundTypeRef);
dispatch_async(dispatch_get_main_queue(), ^{
[self notifyAuthenticationListener: NO];
if (osStatus != noErr && osStatus != errSecItemNotFound) {
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil];
return rejectWithError(reject, error);
}
found = (__bridge NSDictionary*)(foundTypeRef);
if (!found) {
return resolve(@(NO));
}
// Found
NSString *username = (NSString *) [found objectForKey:(__bridge id)(kSecAttrAccount)];
NSString *password = [[NSString alloc] initWithData:[found objectForKey:(__bridge id)(kSecValueData)] encoding:NSUTF8StringEncoding];
return resolve(@{
@"service": service,
@"username": username,
@"password": password
});
});
});
}
- (void) notifyAuthenticationListener:(BOOL)willPresent {
id<UIApplicationDelegate> appDelegate = [ UIApplication sharedApplication ].delegate;
if ([ appDelegate conformsToProtocol:@protocol(RNKeychainAuthenticationListener) ]) {
((id<RNKeychainAuthenticationListener>)appDelegate).willPromptForAuthentication = willPresent;
}
}
#pragma mark - RNKeychain
RCT_EXPORT_METHOD(setGenericPasswordForOptions:(NSDictionary *)options withUsername:(NSString *)username withPassword:(NSString *)password resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
NSString *service = serviceValue(options);
[self deletePasswordsForService:service];
// Create dictionary of parameters to add
NSDictionary *attributes = @{
NSDictionary *attributes = attributes = @{
(__bridge NSString *)kSecClass: (__bridge id)(kSecClassGenericPassword),
(__bridge NSString *)kSecAttrService: service,
(__bridge NSString *)kSecAttrAccount: username,
(__bridge NSString *)kSecValueData: [password dataUsingEncoding:NSUTF8StringEncoding],
(__bridge NSString *)kSecAttrAccessible: (__bridge id)accessibleValue(options)
(__bridge NSString *)kSecValueData: [password dataUsingEncoding:NSUTF8StringEncoding]
};
[self insertKeychainEntry:attributes withAccessGroup:accessGroupValue(options) resolver:resolve rejecter:reject];
[self deletePasswordsForService:service];
[self insertKeychainEntry:attributes withOptions:options resolver:resolve rejecter:reject];
}
RCT_EXPORT_METHOD(getGenericPasswordForOptions:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
NSString *service = serviceValue(options);
// secure compatibility with TouchId / Passcode secured stored items
// http://stackoverflow.com/questions/42339000/ksecuseauthenticationuiskip-how-to-use-it
// Silently skip any items that require user authentication. Only use this value with the SecItemCopyMatching function.
NSString *authenticationPrompt = @"Authenticate to retrieve secret";
if (options && options[kAuthenticationPromptMessage]) {
authenticationPrompt = options[kAuthenticationPromptMessage];
}
NSDictionary *query = @{
(__bridge NSString *)kSecClass: (__bridge id)(kSecClassGenericPassword),
@ -391,7 +314,7 @@ RCT_EXPORT_METHOD(getGenericPasswordForOptions:(NSDictionary *)options resolver:
(__bridge NSString *)kSecReturnAttributes: (__bridge id)kCFBooleanTrue,
(__bridge NSString *)kSecReturnData: (__bridge id)kCFBooleanTrue,
(__bridge NSString *)kSecMatchLimit: (__bridge NSString *)kSecMatchLimitOne,
(__bridge NSString *)kSecUseAuthenticationUI: (__bridge id)kSecUseAuthenticationUISkip
(__bridge NSString *)kSecUseOperationPrompt: authenticationPrompt
};
// Look up service in the keychain
@ -443,26 +366,20 @@ RCT_EXPORT_METHOD(setInternetCredentialsForServer:(NSString *)server withUsernam
(__bridge NSString *)kSecClass: (__bridge id)(kSecClassInternetPassword),
(__bridge NSString *)kSecAttrServer: server,
(__bridge NSString *)kSecAttrAccount: username,
(__bridge NSString *)kSecValueData: [password dataUsingEncoding:NSUTF8StringEncoding],
(__bridge NSString *)kSecAttrAccessible: (__bridge id)accessibleValue(options)
(__bridge NSString *)kSecValueData: [password dataUsingEncoding:NSUTF8StringEncoding]
};
[self insertKeychainEntry:attributes withAccessGroup:accessGroupValue(options) resolver:resolve rejecter:reject];
[self insertKeychainEntry:attributes withOptions:options resolver:resolve rejecter:reject];
}
RCT_EXPORT_METHOD(getInternetCredentialsForServer:(NSString *)server withOptions:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
// secure compatibility with TouchId / Passcode secured stored items
// http://stackoverflow.com/questions/42339000/ksecuseauthenticationuiskip-how-to-use-it
// Silently skip any items that require user authentication. Only use this value with the SecItemCopyMatching function.
NSDictionary *query = @{
(__bridge NSString *)kSecClass: (__bridge id)(kSecClassInternetPassword),
(__bridge NSString *)kSecAttrServer: server,
(__bridge NSString *)kSecReturnAttributes: (__bridge id)kCFBooleanTrue,
(__bridge NSString *)kSecReturnData: (__bridge id)kCFBooleanTrue,
(__bridge NSString *)kSecMatchLimit: (__bridge NSString *)kSecMatchLimitOne,
(__bridge NSString *)kSecUseAuthenticationUI: (__bridge id)kSecUseAuthenticationUISkip
(__bridge NSString *)kSecMatchLimit: (__bridge NSString *)kSecMatchLimitOne
};
// Look up server in the keychain
@ -502,7 +419,6 @@ RCT_EXPORT_METHOD(resetInternetCredentialsForServer:(NSString *)server withOptio
}
return resolve(@(YES));
}
#if TARGET_OS_IOS

View File

@ -41,12 +41,6 @@ type SecAccessible =
| 'AccessibleAfterFirstUnlockThisDeviceOnly'
| 'AccessibleAlwaysThisDeviceOnly';
type Options = {
accessGroup?: string,
accessible?: SecAccessible,
service?: string,
};
type SecAccessControl =
| 'UserPresence'
| 'BiometryAny'
@ -58,9 +52,10 @@ type SecAccessControl =
type LAPolicy = 'Authentication' | 'AuthenticationWithBiometrics';
type SecureOptions = {
type Options = {
accessControl?: SecAccessControl,
accessGroup?: string,
accessible?: SecAccessible,
authenticationPrompt?: string,
authenticationType?: LAPolicy,
service?: string,
@ -72,7 +67,7 @@ type SecureOptions = {
* @param {object} options LAPolicy option, iOS only
* @return {Promise} Resolves to `true` when supported, otherwise `false`
*/
export function canImplyAuthentication(options?: SecureOptions): Promise {
export function canImplyAuthentication(options?: Options): Promise {
if (RNKeychainManager.canCheckAuthentication) {
return Promise.reject(
new Error(`canImplyAuthentication() is not supported on this platform`)
@ -93,50 +88,6 @@ export function getSupportedBiometryType(): Promise {
return RNKeychainManager.getSupportedBiometryType();
}
/**
* Saves the `username` and `password` combination securely - needs authentication to retrieve it.
* @param {string} username Associated username or e-mail to be saved.
* @param {string} password Associated password to be saved.
* @param {object} options Keychain options, iOS only
* @return {Promise} Resolves to `true` when successful
*/
export function setPasswordWithAuthentication(
username: string,
password: string,
options?: SecureOptions
): Promise {
if (Platform.OS !== 'ios') {
return Promise.reject(
new Error(
`setPasswordWithAuthentication() is not supported on ${Platform.OS} yet`
)
);
}
return RNKeychainManager.setPasswordWithAuthentication(
options,
username,
password
);
}
/**
* Fetches login combination for `service` - demands for authentication if necessary.
* @param {string|object} serviceOrOptions Reverse domain name qualifier for the service, defaults to `bundleId` or an options object.
* @return {Promise} Resolves to `{ service, username, password }` when successful
*/
export function getPasswordWithAuthentication(
options?: SecureOptions
): Promise {
if (Platform.OS !== 'ios') {
return Promise.reject(
new Error(
`getPasswordWithAuthentication() is not supported on ${Platform.OS} yet`
)
);
}
return RNKeychainManager.getPasswordWithAuthentication(options);
}
/**
* Saves the `username` and `password` combination for `server`.
* @param {string} server URL to server.
@ -185,7 +136,7 @@ export function resetInternetCredentials(
return RNKeychainManager.resetInternetCredentialsForServer(server, options);
}
function getOptionsArgument(serviceOrOptions?: string | KeychainOptions) {
function getOptionsArgument(serviceOrOptions?: string | Options) {
if (Platform.OS !== 'ios') {
return typeof serviceOrOptions === 'object'
? serviceOrOptions.service
@ -206,7 +157,7 @@ function getOptionsArgument(serviceOrOptions?: string | KeychainOptions) {
export function setGenericPassword(
username: string,
password: string,
serviceOrOptions?: string | KeychainOptions
serviceOrOptions?: string | Options
): Promise {
return RNKeychainManager.setGenericPasswordForOptions(
getOptionsArgument(serviceOrOptions),
@ -221,7 +172,7 @@ export function setGenericPassword(
* @return {Promise} Resolves to `{ service, username, password }` when successful
*/
export function getGenericPassword(
serviceOrOptions?: string | KeychainOptions
serviceOrOptions?: string | Options
): Promise {
return RNKeychainManager.getGenericPasswordForOptions(
getOptionsArgument(serviceOrOptions)
@ -234,7 +185,7 @@ export function getGenericPassword(
* @return {Promise} Resolves to `true` when successful
*/
export function resetGenericPassword(
serviceOrOptions?: string | KeychainOptions
serviceOrOptions?: string | Options
): Promise {
return RNKeychainManager.resetGenericPasswordForOptions(
getOptionsArgument(serviceOrOptions)

View File

@ -11,7 +11,7 @@ declare module 'react-native-keychain' {
password: string;
}
export interface SecureOptions {
export interface Options {
accessControl?: string;
accessGroup?: string;
authenticationPrompt?: string;
@ -20,26 +20,17 @@ declare module 'react-native-keychain' {
}
function canImplyAuthentication(
options?: SecureOptions
options?: Options
): Promise<boolean>;
function getSupportedBiometryType(
): Promise<string>;
function setSecurePassword(
username: string,
password: string,
options?: SecureOptions
): Promise<boolean>;
function getSecurePassword(
options?: SecureOptions
): Promise<boolean | {service: string, username: string, password: string}>;
function setInternetCredentials(
server: string,
username: string,
password: string
password: string,
options?: Options
): Promise<void>;
function getInternetCredentials(
@ -53,15 +44,15 @@ declare module 'react-native-keychain' {
function setGenericPassword(
username: string,
password: string,
service?: string
options?: Options
): Promise<boolean>;
function getGenericPassword(
service?: string
options?: Options
): Promise<boolean | {service: string, username: string, password: string}>;
function resetGenericPassword(
service?: string
options?: Options
): Promise<boolean>
function requestSharedWebCredentials (