Protect the data stored in keychain by TouchId or Passcode (#65)
* First draft of implementing secured storage support (TouchId or Passcode) * minor improvements * improving the implementation; Support for AppDelegate-notification * minor changes and improvements * provding requested constant as usability feature; added documentation to canImplyAuthentication * updating .d.ts-file * when fetching stored items using the traditional modality (not TouchId or Passcode protected) ignore any items that need authentication.
This commit is contained in:
parent
55681fa8e8
commit
172368f2fd
|
@ -19,7 +19,7 @@ export default class KeychainExample extends Component {
|
|||
|
||||
save() {
|
||||
Keychain
|
||||
.setGenericPassword(this.state.username, this.state.password)
|
||||
.setSecurePassword('myService', this.state.username, this.state.password)
|
||||
.then(() => {
|
||||
this.setState({ status: 'Credentials saved!' });
|
||||
})
|
||||
|
@ -30,7 +30,7 @@ export default class KeychainExample extends Component {
|
|||
|
||||
load() {
|
||||
Keychain
|
||||
.getGenericPassword()
|
||||
.getSecurePassword('myService')
|
||||
.then((credentials) => {
|
||||
if (credentials) {
|
||||
this.setState({ ...credentials, status: 'Credentials loaded!' });
|
||||
|
|
|
@ -1123,7 +1123,9 @@
|
|||
"$(SRCROOT)/../node_modules/react-native-keychain/RNKeychainManager",
|
||||
);
|
||||
INFOPLIST_FILE = KeychainExample/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = "";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
|
@ -1144,7 +1146,9 @@
|
|||
"$(SRCROOT)/../node_modules/react-native-keychain/RNKeychainManager",
|
||||
);
|
||||
INFOPLIST_FILE = KeychainExample/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = "";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
|
|
|
@ -11,9 +11,26 @@
|
|||
|
||||
#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;
|
||||
|
|
|
@ -9,19 +9,21 @@
|
|||
/* Begin PBXBuildFile section */
|
||||
5D82368F1B0CE3CB005A9EF3 /* RNKeychainManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D82368C1B0CE2A6005A9EF3 /* RNKeychainManager.m */; };
|
||||
5D8236911B0CE3D6005A9EF3 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D8236901B0CE3D6005A9EF3 /* Security.framework */; };
|
||||
6478986A1F38BF9D00DA1C12 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 647898691F38BF9D00DA1C12 /* Security.framework */; };
|
||||
6478986B1F38BFA100DA1C12 /* RNKeychainManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D82368C1B0CE2A6005A9EF3 /* RNKeychainManager.m */; };
|
||||
B2AF06371E97BF10006435CD /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2AF06361E97BF10006435CD /* LocalAuthentication.framework */; };
|
||||
B2AF063D1E97DE25006435CD /* RNKeychainAuthenticationListener.h in Headers */ = {isa = PBXBuildFile; fileRef = B22856161E950BB300CCF753 /* RNKeychainAuthenticationListener.h */; };
|
||||
B2AF063F1E97DF85006435CD /* RNKeychainAuthenticationListener.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = B22856161E950BB300CCF753 /* RNKeychainAuthenticationListener.h */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
5D82366D1B0CE05B005A9EF3 /* Copy Files */ = {
|
||||
5D82366D1B0CE05B005A9EF3 /* Copy Headers */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "include/$(PRODUCT_NAME)";
|
||||
dstPath = include/RNKeychain;
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
B2AF063F1E97DF85006435CD /* RNKeychainAuthenticationListener.h in Copy Headers */,
|
||||
);
|
||||
name = "Copy Files";
|
||||
name = "Copy Headers";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
6478985D1F38BF9100DA1C12 /* CopyFiles */ = {
|
||||
|
@ -40,8 +42,8 @@
|
|||
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>"; };
|
||||
5D8236901B0CE3D6005A9EF3 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
|
||||
6478985F1F38BF9100DA1C12 /* libRNKeychain.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNKeychain.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
647898691F38BF9D00DA1C12 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.2.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; };
|
||||
B22856161E950BB300CCF753 /* RNKeychainAuthenticationListener.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNKeychainAuthenticationListener.h; sourceTree = "<group>"; };
|
||||
B2AF06361E97BF10006435CD /* LocalAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LocalAuthentication.framework; path = System/Library/Frameworks/LocalAuthentication.framework; sourceTree = SDKROOT; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -49,6 +51,7 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B2AF06371E97BF10006435CD /* LocalAuthentication.framework in Frameworks */,
|
||||
5D8236911B0CE3D6005A9EF3 /* Security.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -69,7 +72,7 @@
|
|||
children = (
|
||||
5D82368A1B0CE2A6005A9EF3 /* RNKeychainManager */,
|
||||
5D8236701B0CE05B005A9EF3 /* Products */,
|
||||
647898681F38BF9C00DA1C12 /* Frameworks */,
|
||||
B2AF06351E97BF10006435CD /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
wrapsLines = 0;
|
||||
|
@ -88,21 +91,32 @@
|
|||
children = (
|
||||
5D82368B1B0CE2A6005A9EF3 /* RNKeychainManager.h */,
|
||||
5D82368C1B0CE2A6005A9EF3 /* RNKeychainManager.m */,
|
||||
B22856161E950BB300CCF753 /* RNKeychainAuthenticationListener.h */,
|
||||
);
|
||||
path = RNKeychainManager;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
647898681F38BF9C00DA1C12 /* Frameworks */ = {
|
||||
B2AF06351E97BF10006435CD /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5D8236901B0CE3D6005A9EF3 /* Security.framework */,
|
||||
647898691F38BF9D00DA1C12 /* Security.framework */,
|
||||
B2AF06361E97BF10006435CD /* LocalAuthentication.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
B2AF063C1E97DE1E006435CD /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B2AF063D1E97DE25006435CD /* RNKeychainAuthenticationListener.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
5D82366E1B0CE05B005A9EF3 /* RNKeychain */ = {
|
||||
isa = PBXNativeTarget;
|
||||
|
@ -110,7 +124,8 @@
|
|||
buildPhases = (
|
||||
5D82366B1B0CE05B005A9EF3 /* Sources */,
|
||||
5D82366C1B0CE05B005A9EF3 /* Frameworks */,
|
||||
5D82366D1B0CE05B005A9EF3 /* Copy Files */,
|
||||
B2AF063C1E97DE1E006435CD /* Headers */,
|
||||
5D82366D1B0CE05B005A9EF3 /* Copy Headers */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// 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
|
|
@ -12,6 +12,11 @@
|
|||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTUtils.h>
|
||||
|
||||
#import <LocalAuthentication/LAContext.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RNKeychainAuthenticationListener.h"
|
||||
|
||||
@implementation RNKeychainManager
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
@ -104,6 +109,196 @@ NSString *serviceValue(NSDictionary *options)
|
|||
return [[NSBundle mainBundle] bundleIdentifier];
|
||||
}
|
||||
|
||||
#pragma mark - Proposed functionality - Helpers
|
||||
|
||||
#define kAuthenticationType @"authenticationType"
|
||||
#define kBiometrics @"AuthenticationWithBiometrics"
|
||||
|
||||
#define kAccessControlType @"accessControl"
|
||||
#define kAccessControlUserPresence @"UserPresence"
|
||||
#define kAccessControlTouchIDAny @"TouchIDAny"
|
||||
#define kAccessControlTouchIDCurrentSet @"TouchIDCurrentSet"
|
||||
#define kAccessControlDevicePasscode @"DevicePasscode"
|
||||
#define kAccessControlTouchIDAnyOrDevicePasscode @"TouchIDAnyOrDevicePasscode"
|
||||
#define kAccessControlTouchIDCurrentSetOrDevicePasscode @"TouchIDCurrentSetOrDevicePasscode"
|
||||
|
||||
|
||||
#define kCustomPromptMessage @"customPrompt"
|
||||
|
||||
LAPolicy authPolicy(NSDictionary *options)
|
||||
{
|
||||
if (options && options[kAuthenticationType]) {
|
||||
if ([ options[kAuthenticationType] isEqualToString:kBiometrics ]) {
|
||||
return LAPolicyDeviceOwnerAuthenticationWithBiometrics;
|
||||
}
|
||||
}
|
||||
return LAPolicyDeviceOwnerAuthentication;
|
||||
}
|
||||
|
||||
SecAccessControlCreateFlags secureAccessControl(NSDictionary *options)
|
||||
{
|
||||
if (options && options[kAccessControlType]) {
|
||||
if ([ options[kAccessControlType] isEqualToString: kAccessControlUserPresence ]) {
|
||||
return kSecAccessControlUserPresence;
|
||||
}
|
||||
else if ([ options[kAccessControlType] isEqualToString: kAccessControlTouchIDAny ]) {
|
||||
return kSecAccessControlTouchIDAny;
|
||||
}
|
||||
else if ([ options[kAccessControlType] isEqualToString: kAccessControlTouchIDCurrentSet ]) {
|
||||
return kSecAccessControlTouchIDCurrentSet;
|
||||
}
|
||||
else if ([ options[kAccessControlType] isEqualToString: kAccessControlDevicePasscode ]) {
|
||||
return kSecAccessControlDevicePasscode;
|
||||
}
|
||||
else if ([ options[kAccessControlType] isEqualToString: kAccessControlTouchIDAnyOrDevicePasscode ]) {
|
||||
return kSecAccessControlTouchIDAny|kSecAccessControlOr|kSecAccessControlDevicePasscode;
|
||||
}
|
||||
else if ([ options[kAccessControlType] isEqualToString: kAccessControlTouchIDCurrentSetOrDevicePasscode ]) {
|
||||
return kSecAccessControlTouchIDCurrentSet|kSecAccessControlOr|kSecAccessControlDevicePasscode;
|
||||
}
|
||||
}
|
||||
return kSecAccessControlTouchIDCurrentSet|kSecAccessControlOr|kSecAccessControlDevicePasscode;
|
||||
}
|
||||
|
||||
//LAPolicyDeviceOwnerAuthenticationWithBiometrics | LAPolicyDeviceOwnerAuthentication
|
||||
|
||||
#pragma mark - Proposed functionality - RCT_EXPORT_METHOD
|
||||
|
||||
RCT_EXPORT_METHOD(canCheckAuthentication:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
LAPolicy policyToEvaluate = authPolicy(options);
|
||||
|
||||
NSError *aerr = nil;
|
||||
BOOL canBeProtected = [self canCheckAuthentication:policyToEvaluate error:&aerr ];
|
||||
|
||||
if (aerr || !canBeProtected) {
|
||||
return rejectWithError(reject, aerr);
|
||||
} else {
|
||||
return resolve(@(YES));
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL) canCheckAuthentication:(LAPolicy)policyToEvaluate error:(NSError **)err {
|
||||
return [[[ LAContext alloc] init ] canEvaluatePolicy:policyToEvaluate error:err ];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(setSecurePasswordForService:(NSString *)service withUsername:(NSString *)username withPassword:(NSString *)password withOptions:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
// Delete old entry for that key if Available
|
||||
NSError *aerr = nil;
|
||||
BOOL canAuthenticate = [ self canCheckAuthentication:LAPolicyDeviceOwnerAuthentication error:&aerr ];
|
||||
if (aerr || !canAuthenticate) {
|
||||
return rejectWithError(reject, aerr);
|
||||
}
|
||||
|
||||
NSMutableDictionary *dict = @{ (__bridge NSString *)kSecClass : (__bridge id)(kSecClassGenericPassword),
|
||||
(__bridge NSString *)kSecAttrService: service,
|
||||
(__bridge NSString *)kSecReturnAttributes: (__bridge id)kCFBooleanTrue
|
||||
}.mutableCopy;
|
||||
|
||||
OSStatus osStatus = SecItemDelete((__bridge CFDictionaryRef) dict);
|
||||
|
||||
// make new entry
|
||||
dict = @{ (__bridge NSString *)kSecClass : (__bridge id)(kSecClassGenericPassword),
|
||||
(__bridge NSString *)kSecAttrService : service,
|
||||
(__bridge NSString *)kSecAttrAccount : username
|
||||
}.mutableCopy;
|
||||
|
||||
CFErrorRef error = NULL;
|
||||
SecAccessControlRef sacRef = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
|
||||
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, //kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
|
||||
secureAccessControl(options),
|
||||
&error);
|
||||
|
||||
if (error) {
|
||||
// ok: failed
|
||||
return rejectWithError(reject, aerr);
|
||||
}
|
||||
|
||||
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
|
||||
[ dict setObject:(__bridge id)sacRef forKey:kSecAttrAccessControl ];
|
||||
|
||||
[ dict setObject:passwordData forKey:kSecValueData ];
|
||||
|
||||
// Try to save to keychain
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
OSStatus osStatus = SecItemAdd((__bridge CFDictionaryRef) dict, NULL);
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (osStatus != noErr && osStatus != errSecItemNotFound) {
|
||||
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil];
|
||||
return rejectWithError(reject, error);
|
||||
} else {
|
||||
return resolve(@(YES));
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(getSecurePasswordForService:(NSString *)service withOptions:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
NSString *promptMessage = @"Authenticate to retrieve secret!";
|
||||
if (options && options[kCustomPromptMessage]) {
|
||||
promptMessage = options[kCustomPromptMessage];
|
||||
}
|
||||
|
||||
NSMutableDictionary *dict = @{ (__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
|
||||
}.mutableCopy;
|
||||
|
||||
// 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) dict, (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);
|
||||
|
@ -149,6 +344,11 @@ RCT_EXPORT_METHOD(getGenericPasswordForOptions:(NSDictionary *)options resolver:
|
|||
[dict setObject:options[@"accessGroup"] forKey:kSecAttrAccessGroup];
|
||||
}
|
||||
|
||||
// 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.
|
||||
[ dict setObject:kSecUseAuthenticationUISkip forKey:kSecUseAuthenticationUI ];
|
||||
|
||||
// Look up server in the keychain
|
||||
NSDictionary* found = nil;
|
||||
CFTypeRef foundTypeRef = NULL;
|
||||
|
@ -238,6 +438,11 @@ RCT_EXPORT_METHOD(getInternetCredentialsForServer:(NSString *)server withOptions
|
|||
[dict setObject:options[@"accessGroup"] forKey:kSecAttrAccessGroup];
|
||||
}
|
||||
|
||||
// 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.
|
||||
[ dict setObject:kSecUseAuthenticationUISkip forKey:kSecUseAuthenticationUI ];
|
||||
|
||||
// Look up server in the keychain
|
||||
NSDictionary *found = nil;
|
||||
CFTypeRef foundTypeRef = NULL;
|
||||
|
|
59
index.js
59
index.js
|
@ -16,6 +16,65 @@ type Options = {
|
|||
service?: string;
|
||||
};
|
||||
|
||||
type SecAccessControl =
|
||||
| 'UserPresence'
|
||||
| 'TouchIDAny'
|
||||
| 'TouchIDCurrentSet'
|
||||
| 'DevicePasscode'
|
||||
| 'TouchIDAnyOrDevicePasscode'
|
||||
| 'TouchIDCurrentSetOrDevicePasscode'
|
||||
|
||||
type LAPolicy =
|
||||
| 'Authentication'
|
||||
| 'AuthenticationWithBiometrics'
|
||||
|
||||
type SecureOptions = {
|
||||
customPrompt?: string;
|
||||
authenticationType?: LAPolicy;
|
||||
accessControl?: SecAccessControl;
|
||||
};
|
||||
|
||||
/**
|
||||
* Inquire if the type of local authentication policy (LAPolicy) is supported
|
||||
* on this device with the device settings the user chose.
|
||||
* @param {object} options LAPolicy option, iOS only
|
||||
* @return {Promise} Resolves to `true` when successful
|
||||
*/
|
||||
export function canImplyAuthentication(
|
||||
options?: SecureOptions
|
||||
): Promise {
|
||||
return RNKeychainManager.canCheckAuthentication(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the `username` and `password` combination for `service` securely - needs authentication to retrieve it.
|
||||
* @param {string} service Associated service.
|
||||
* @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 setSecurePassword(
|
||||
service: string,
|
||||
username: string,
|
||||
password: string,
|
||||
options?: SecureOptions
|
||||
): Promise {
|
||||
return RNKeychainManager.setSecurePasswordForService(service, username, password, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 getSecurePassword(
|
||||
service: string,
|
||||
options?: SecureOptions
|
||||
): Promise {
|
||||
return RNKeychainManager.getSecurePasswordForService(service, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the `username` and `password` combination for `server`.
|
||||
* @param {string} server URL to server.
|
||||
|
|
|
@ -11,6 +11,28 @@ declare module 'react-native-keychain' {
|
|||
password: string;
|
||||
}
|
||||
|
||||
export interface SecureOptions {
|
||||
customPrompt?: string;
|
||||
authenticationType?: string;
|
||||
accessControl?: string;
|
||||
}
|
||||
|
||||
function canImplyAuthentication(
|
||||
options?: SecureOptions
|
||||
): Promise<boolean>;
|
||||
|
||||
function setSecurePassword(
|
||||
service: string,
|
||||
username: string,
|
||||
password: string,
|
||||
options?: SecureOptions
|
||||
): Promise<boolean>;
|
||||
|
||||
function getSecurePassword(
|
||||
service: string,
|
||||
options?: SecureOptions
|
||||
): Promise<boolean | {service: string, username: string, password: string}>;
|
||||
|
||||
function setInternetCredentials(
|
||||
server: string,
|
||||
username: string,
|
||||
|
|
Loading…
Reference in New Issue