From add90c57695586c0d5b7e903fbc1acbea918a9c9 Mon Sep 17 00:00:00 2001 From: Joel Arvidsson Date: Mon, 26 Feb 2018 12:31:06 +0100 Subject: [PATCH] Remove *PasswordWithAuthentication and replace with accessControl option --- KeychainExample/App.js | 59 +++--- .../ios/KeychainExample/AppDelegate.m | 17 -- README.md | 10 +- RNKeychain.xcodeproj/project.pbxproj | 12 +- .../RNKeychainAuthenticationListener.h | 22 -- RNKeychainManager/RNKeychainManager.m | 198 +++++------------- index.js | 63 +----- typings/react-native-keychain.d.ts | 23 +- 8 files changed, 105 insertions(+), 299 deletions(-) delete mode 100644 RNKeychainManager/RNKeychainAuthenticationListener.h diff --git a/KeychainExample/App.js b/KeychainExample/App.js index dc5cf01..d5fd757 100644 --- a/KeychainExample/App.js +++ b/KeychainExample/App.js @@ -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" /> + {Platform.OS === 'ios' && ( + + Access Control + { + this.setState({ + accessControl: ACCESS_CONTROL_MAP[nativeEvent.selectedSegmentIndex], + }); + }} + /> + + )} {!!this.state.status && ( {this.state.status} )} - {!!this.state.biometryType && ( - - Supported biometry: {this.state.biometryType} - - )} + 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, diff --git a/KeychainExample/ios/KeychainExample/AppDelegate.m b/KeychainExample/ios/KeychainExample/AppDelegate.m index 3ba5994..2112a27 100644 --- a/KeychainExample/ios/KeychainExample/AppDelegate.m +++ b/KeychainExample/ios/KeychainExample/AppDelegate.m @@ -11,26 +11,9 @@ #import #import -#import - -@interface AppDelegate() - -@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; diff --git a/README.md b/README.md index 0471de5..9911738 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/RNKeychain.xcodeproj/project.pbxproj b/RNKeychain.xcodeproj/project.pbxproj index 3a2f04a..b2c4692 100644 --- a/RNKeychain.xcodeproj/project.pbxproj +++ b/RNKeychain.xcodeproj/project.pbxproj @@ -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 = ""; }; - 5D82368C1B0CE2A6005A9EF3 /* RNKeychainManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNKeychainManager.m; sourceTree = ""; }; + 5D82368C1B0CE2A6005A9EF3 /* RNKeychainManager.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = RNKeychainManager.m; sourceTree = ""; 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 = ""; }; 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; }; diff --git a/RNKeychainManager/RNKeychainAuthenticationListener.h b/RNKeychainManager/RNKeychainAuthenticationListener.h deleted file mode 100644 index 214f07d..0000000 --- a/RNKeychainManager/RNKeychainAuthenticationListener.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// TouchIdPromptListener.h -// RNKeychain -// -// Created by Steffen Blümm on 05/04/17. -// Copyright © 2017 Joel Arvidsson. All rights reserved. -// - -#import - -/** - 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 - -@property (nonatomic, assign) BOOL willPromptForAuthentication; - -@end diff --git a/RNKeychainManager/RNKeychainManager.m b/RNKeychainManager/RNKeychainManager.m index 8fbedb6..381981b 100644 --- a/RNKeychainManager/RNKeychainManager.m +++ b/RNKeychainManager/RNKeychainManager.m @@ -15,8 +15,6 @@ #import #import -#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 appDelegate = [ UIApplication sharedApplication ].delegate; - - if ([ appDelegate conformsToProtocol:@protocol(RNKeychainAuthenticationListener) ]) { - ((id)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 diff --git a/index.js b/index.js index 4cca283..90af9a7 100644 --- a/index.js +++ b/index.js @@ -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) diff --git a/typings/react-native-keychain.d.ts b/typings/react-native-keychain.d.ts index c4b65a0..c3373b6 100644 --- a/typings/react-native-keychain.d.ts +++ b/typings/react-native-keychain.d.ts @@ -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; function getSupportedBiometryType( ): Promise; - function setSecurePassword( - username: string, - password: string, - options?: SecureOptions - ): Promise; - - function getSecurePassword( - options?: SecureOptions - ): Promise; - function setInternetCredentials( server: string, username: string, - password: string + password: string, + options?: Options ): Promise; function getInternetCredentials( @@ -53,15 +44,15 @@ declare module 'react-native-keychain' { function setGenericPassword( username: string, password: string, - service?: string + options?: Options ): Promise; function getGenericPassword( - service?: string + options?: Options ): Promise; function resetGenericPassword( - service?: string + options?: Options ): Promise function requestSharedWebCredentials (