react-native-keychain/RNKeychainManager/RNKeychainManager.m

333 lines
12 KiB
Objective-C

//
// RNKeychainManager.m
// RNKeychainManager
//
// Created by Joel Arvidsson on 2015-05-20.
// Copyright (c) 2015 Joel Arvidsson. All rights reserved.
//
#import <Security/Security.h>
#import "RNKeychainManager.h"
#import <React/RCTConvert.h>
#import <React/RCTBridge.h>
#import <React/RCTUtils.h>
@implementation RNKeychainManager
@synthesize bridge = _bridge;
RCT_EXPORT_MODULE();
// Messages from the comments in <Security/SecBase.h>
NSString *messageForError(NSError *error)
{
switch (error.code) {
case errSecUnimplemented:
return @"Function or operation not implemented.";
case errSecIO:
return @"I/O error.";
case errSecOpWr:
return @"File already open with with write permission.";
case errSecParam:
return @"One or more parameters passed to a function where not valid.";
case errSecAllocate:
return @"Failed to allocate memory.";
case errSecUserCanceled:
return @"User canceled the operation.";
case errSecBadReq:
return @"Bad parameter or invalid state for operation.";
case errSecNotAvailable:
return @"No keychain is available. You may need to restart your computer.";
case errSecDuplicateItem:
return @"The specified item already exists in the keychain.";
case errSecItemNotFound:
return @"The specified item could not be found in the keychain.";
case errSecInteractionNotAllowed:
return @"User interaction is not allowed.";
case errSecDecode:
return @"Unable to decode the provided data.";
case errSecAuthFailed:
return @"The user name or passphrase you entered is not correct.";
default:
return error.localizedDescription;
}
}
NSString *codeForError(NSError *error)
{
return [NSString stringWithFormat:@"%li", (long)error.code];
}
void rejectWithError(RCTPromiseRejectBlock reject, NSError *error)
{
return reject(codeForError(error), messageForError(error), nil);
}
CFStringRef accessibleValue(NSDictionary *options)
{
if (options && options[@"accessible"] != nil) {
NSDictionary *keyMap = @{
@"AccessibleWhenUnlocked": (__bridge NSString *)kSecAttrAccessibleWhenUnlocked,
@"AccessibleAfterFirstUnlock": (__bridge NSString *)kSecAttrAccessibleAfterFirstUnlock,
@"AccessibleAlways": (__bridge NSString *)kSecAttrAccessibleAlways,
@"AccessibleWhenPasscodeSetThisDeviceOnly": (__bridge NSString *)kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
@"AccessibleWhenUnlockedThisDeviceOnly": (__bridge NSString *)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
@"AccessibleAfterFirstUnlockThisDeviceOnly": (__bridge NSString *)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
@"AccessibleAlwaysThisDeviceOnly": (__bridge NSString *)kSecAttrAccessibleAlwaysThisDeviceOnly
};
NSString *result = keyMap[options[@"accessible"]];
if (result) {
return (__bridge CFStringRef)result;
}
}
return kSecAttrAccessibleAfterFirstUnlock;
}
NSString *serviceValue(NSDictionary *options)
{
if (options && options[@"service"] != nil) {
return options[@"service"];
}
return [[NSBundle mainBundle] bundleIdentifier];
}
RCT_EXPORT_METHOD(setGenericPasswordForOptions:(NSDictionary *)options withUsername:(NSString *)username withPassword:(NSString *)password resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
NSString *service = serviceValue(options);
// Create dictionary of search parameters
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassGenericPassword), kSecClass, service, kSecAttrService, kCFBooleanTrue, kSecReturnAttributes, nil];
if (options && options[@"accessGroup"]) {
[dict setObject:options[@"accessGroup"] forKey:kSecAttrAccessGroup];
}
// Remove any old values from the keychain
OSStatus osStatus = SecItemDelete((__bridge CFDictionaryRef) dict);
// Create dictionary of parameters to add
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassGenericPassword), kSecClass, accessibleValue(options), kSecAttrAccessible, service, kSecAttrService, passwordData, kSecValueData, username, kSecAttrAccount, nil];
if (options && options[@"accessGroup"]) {
[dict setObject:options[@"accessGroup"] forKey:kSecAttrAccessGroup];
}
// Try to save to keychain
osStatus = SecItemAdd((__bridge CFDictionaryRef) dict, NULL);
if (osStatus != noErr && osStatus != errSecItemNotFound) {
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil];
return rejectWithError(reject, error);
}
return resolve(@(YES));
}
RCT_EXPORT_METHOD(getGenericPasswordForOptions:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
NSString *service = serviceValue(options);
// Create dictionary of search parameters
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassGenericPassword), kSecClass, service, kSecAttrService, kCFBooleanTrue, kSecReturnAttributes, kCFBooleanTrue, kSecReturnData, nil];
if (options && options[@"accessGroup"]) {
[dict setObject:options[@"accessGroup"] forKey:kSecAttrAccessGroup];
}
// Look up server in the keychain
NSDictionary* found = nil;
CFTypeRef foundTypeRef = NULL;
OSStatus osStatus = SecItemCopyMatching((__bridge CFDictionaryRef) dict, (CFTypeRef*)&foundTypeRef);
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
});
}
RCT_EXPORT_METHOD(resetGenericPasswordForOptions:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
NSString *service = serviceValue(options);
// Create dictionary of search parameters
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassGenericPassword), kSecClass, service, kSecAttrService, kCFBooleanTrue, kSecReturnAttributes, kCFBooleanTrue, kSecReturnData, nil];
if (options[@"accessGroup"]) {
[dict setObject:options[@"accessGroup"] forKey:kSecAttrAccessGroup];
}
// Remove any old values from the keychain
OSStatus osStatus = SecItemDelete((__bridge CFDictionaryRef) dict);
if (osStatus != noErr && osStatus != errSecItemNotFound) {
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil];
return rejectWithError(reject, error);
}
return resolve(@(YES));
}
RCT_EXPORT_METHOD(setInternetCredentialsForServer:(NSString *)server withUsername:(NSString*)username withPassword:(NSString*)password withOptions:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
// Create dictionary of search parameters
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassInternetPassword), kSecClass, server, kSecAttrServer, kCFBooleanTrue, kSecReturnAttributes, nil];
if (options && options[@"accessGroup"]) {
[dict setObject:options[@"accessGroup"] forKey:kSecAttrAccessGroup];
}
// Remove any old values from the keychain
OSStatus osStatus = SecItemDelete((__bridge CFDictionaryRef) dict);
// Create dictionary of parameters to add
NSData* passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassInternetPassword), kSecClass, accessibleValue(options), kSecAttrAccessible, server, kSecAttrServer, passwordData, kSecValueData, username, kSecAttrAccount, nil];
if (options && options[@"accessGroup"]) {
[dict setObject:options[@"accessGroup"] forKey:kSecAttrAccessGroup];
}
// Try to save to keychain
osStatus = SecItemAdd((__bridge CFDictionaryRef) dict, NULL);
if (osStatus != noErr && osStatus != errSecItemNotFound) {
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil];
return rejectWithError(reject, error);
}
return resolve(@(YES));
}
RCT_EXPORT_METHOD(getInternetCredentialsForServer:(NSString *)server withOptions:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
// Create dictionary of search parameters
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassInternetPassword), kSecClass, server, kSecAttrServer, kCFBooleanTrue, kSecReturnAttributes, kCFBooleanTrue, kSecReturnData, nil];
if (options && options[@"accessGroup"]) {
[dict setObject:options[@"accessGroup"] forKey:kSecAttrAccessGroup];
}
// Look up server in the keychain
NSDictionary *found = nil;
CFTypeRef foundTypeRef = NULL;
OSStatus osStatus = SecItemCopyMatching((__bridge CFDictionaryRef) dict, (CFTypeRef*)&foundTypeRef);
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(@{
@"server": server,
@"username": username,
@"password": password
});
}
RCT_EXPORT_METHOD(resetInternetCredentialsForServer:(NSString *)server withOptions:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
// Create dictionary of search parameters
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassInternetPassword), kSecClass, server, kSecAttrServer, kCFBooleanTrue, kSecReturnAttributes, kCFBooleanTrue, kSecReturnData, nil];
if (options && options[@"accessGroup"]) {
[dict setObject:options[@"accessGroup"] forKey:kSecAttrAccessGroup];
}
// Remove any old values from the keychain
OSStatus osStatus = SecItemDelete((__bridge CFDictionaryRef) dict);
if (osStatus != noErr && osStatus != errSecItemNotFound) {
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil];
return rejectWithError(reject, error);
}
return resolve(@(YES));
}
RCT_EXPORT_METHOD(requestSharedWebCredentials:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
SecRequestSharedWebCredential(NULL, NULL, ^(CFArrayRef credentials, CFErrorRef error) {
if (error != NULL) {
NSError *nsError = (__bridge NSError *)error;
return reject([NSString stringWithFormat:@"%li", (long)nsError.code], nsError.description, nil);
}
if (CFArrayGetCount(credentials) > 0) {
CFDictionaryRef credentialDict = CFArrayGetValueAtIndex(credentials, 0);
NSString *server = (__bridge NSString *)CFDictionaryGetValue(credentialDict, kSecAttrServer);
NSString *username = (__bridge NSString *)CFDictionaryGetValue(credentialDict, kSecAttrAccount);
NSString *password = (__bridge NSString *)CFDictionaryGetValue(credentialDict, kSecSharedPassword);
return resolve(@{
@"server": server,
@"username": username,
@"password": password
});
}
return resolve(@(NO));
});
}
RCT_EXPORT_METHOD(setSharedWebCredentialsForServer:(NSString *)server withUsername:(NSString *)username withPassword:(NSString *)password resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
SecAddSharedWebCredential(
(__bridge CFStringRef)server,
(__bridge CFStringRef)username,
(__bridge CFStringRef)password,
^(CFErrorRef error)
{
if (error != NULL) {
NSError *nsError = (__bridge NSError *)error;
return reject([NSString stringWithFormat:@"%li", (long)nsError.code], nsError.description, nil);
}
resolve(@(YES));
});
}
@end