Max Lynch преди 9 години
родител
ревизия
093fcb2962

+ 10 - 10
plugin.xml

@@ -6,18 +6,18 @@
     <name>KeyChain Plugin for Cordova iOS</name>
     <description>This plugin allows your app access to the iOS KeyChain from Cordova. See: https://developer.apple.com/library/mac/documentation/security/conceptual/keychainServConcepts/iPhoneTasks/iPhoneTasks.html</description>
     <license>Apache 2.0</license>
-    <keywords>keychain</keywords>	
-    
+    <keywords>keychain</keywords>
+
     <engines>
         <engine name="cordova" version=">=2.2.0" />
     </engines>
-	
+
     <asset src="www/keychain.js" target="plugins/keychain.js" />
 
 	<js-module src="www/keychain.js" name="Keychain">
     	<clobbers target="window.Keychain" />
 	</js-module>
-	
+
     <!-- ios -->
     <platform name="ios">
         <!-- feature tag in config.xml -->
@@ -26,14 +26,14 @@
             <param name="ios-package" value="CDVKeychain"/>
           </feature>
         </config-file>
-        
+
         <header-file src="src/ios/CDVKeychain.h" />
-        <header-file src="src/ios/SFHFKeychainUtils/SFHFKeychainUtils.h"/>
-        
+        <header-file src="src/ios/A0KeyChain/A0SimpleKeychain.h"/>
+
         <source-file src="src/ios/CDVKeychain.m"/>
-        <source-file src="src/ios/SFHFKeychainUtils/SFHFKeychainUtils.m" compiler-flags="-fobjc-arc" />
-        
+        <source-file src="src/ios/A0KeyChain/A0SimpleKeychain.m"/>
+
         <framework src="Security.framework" />
-        
+
     </platform>
 </plugin>

+ 105 - 0
src/ios/A0KeyChain/A0SimpleKeychain+KeyPair.h

@@ -0,0 +1,105 @@
+//  A0SimpleKeychain+KeyPair.h
+//
+// Copyright (c) 2014 Auth0 (http://auth0.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "A0SimpleKeychain.h"
+
+typedef NS_ENUM(NSUInteger, A0SimpleKeychainRSAKeySize) {
+    A0SimpleKeychainRSAKeySize512Bits = 512,
+    A0SimpleKeychainRSAKeySize1024Bits = 1024,
+    A0SimpleKeychainRSAKeySize2048Bits = 2048
+};
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ *  Category of `A0SimpleKeychain` to handle RSA pairs keys in the Keychain
+ */
+@interface A0SimpleKeychain (KeyPair)
+
+/**
+ *  Generates a RSA key pair with a specific length and tags. 
+ *  Each key is marked as permanent in the Keychain
+ *
+ *  @param keyLength     number of bits of the keys.
+ *  @param publicKeyTag  tag of the public key
+ *  @param privateKeyTag tag of the private key
+ *
+ *  @return if the key par is created it will return YES, otherwise NO.
+ */
+- (BOOL)generateRSAKeyPairWithLength:(A0SimpleKeychainRSAKeySize)keyLength
+                        publicKeyTag:(NSString *)publicKeyTag
+                       privateKeyTag:(NSString *)privateKeyTag;
+
+/**
+ *  Returns a RSA key as NSData.
+ *
+ *  @param keyTag tag of the key
+ *
+ *  @return the key as NSData or nil if not found
+ */
+- (nullable NSData *)dataForRSAKeyWithTag:(NSString *)keyTag;
+
+/**
+ *  Removes a key using its tag.
+ *
+ *  @param keyTag tag of the key to remove
+ *
+ *  @return if the key was removed successfuly.
+ */
+- (BOOL)deleteRSAKeyWithTag:(NSString *)keyTag;
+
+/**
+ *  Returns a RSA key as `SecKeyRef`. You must release it when you're done with it
+ *
+ *  @param keyTag tag of the RSA Key
+ *
+ *  @return SecKeyRef of RSA Key
+ */
+- (SecKeyRef)keyRefOfRSAKeyWithTag:(NSString *)keyTag;
+
+/**
+ *  Checks if a RSA key exists with a given tag.
+ *
+ *  @param keyTag tag of RSA Key
+ *
+ *  @return if the key exists or not.
+ */
+- (BOOL)hasRSAKeyWithTag:(NSString *)keyTag;
+
+@end
+
+@interface A0SimpleKeychain (Deprecated)
+
+/**
+ *  Returns the public key as NSData.
+ *
+ *  @param keyTag tag of the public key
+ *
+ *  @return the public key as NSData or nil if not found
+ *  
+ *  @deprecated 0.2.0
+ */
+- (NSData *)publicRSAKeyDataForTag:(NSString *)keyTag;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 133 - 0
src/ios/A0KeyChain/A0SimpleKeychain+KeyPair.m

@@ -0,0 +1,133 @@
+//  A0SimpleKeychain+KeyPair.m
+//
+// Copyright (c) 2014 Auth0 (http://auth0.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "A0SimpleKeychain+KeyPair.h"
+
+@implementation A0SimpleKeychain (KeyPair)
+
+- (BOOL)generateRSAKeyPairWithLength:(A0SimpleKeychainRSAKeySize)keyLength
+                        publicKeyTag:(NSString *)publicKeyTag
+                       privateKeyTag:(NSString *)privateKeyTag {
+    NSAssert(publicKeyTag.length > 0 && privateKeyTag.length > 0, @"Both key tags should be non-empty!");
+
+    NSMutableDictionary *pairAttr = [@{
+                                       (__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeRSA,
+                                       (__bridge id)kSecAttrKeySizeInBits: @(keyLength),
+                                       } mutableCopy];
+    NSDictionary *privateAttr = @{
+                                  (__bridge id)kSecAttrIsPermanent: @YES,
+                                  (__bridge id)kSecAttrApplicationTag: [privateKeyTag dataUsingEncoding:NSUTF8StringEncoding],
+                                  };
+    NSDictionary *publicAttr = @{
+                                 (__bridge id)kSecAttrIsPermanent: @YES,
+                                 (__bridge id)kSecAttrApplicationTag: [publicKeyTag dataUsingEncoding:NSUTF8StringEncoding],
+                                 };
+    pairAttr[(__bridge id)kSecPrivateKeyAttrs] = privateAttr;
+    pairAttr[(__bridge id)kSecPublicKeyAttrs] = publicAttr;
+
+    SecKeyRef publicKeyRef;
+    SecKeyRef privateKeyRef;
+
+    OSStatus status = SecKeyGeneratePair((__bridge CFDictionaryRef)pairAttr, &publicKeyRef, &privateKeyRef);
+
+    CFRelease(publicKeyRef);
+    CFRelease(privateKeyRef);
+
+    return status == errSecSuccess;
+}
+
+- (NSData *)dataForRSAKeyWithTag:(NSString *)keyTag {
+    NSAssert(keyTag.length > 0, @"key tag should be non-empty!");
+
+    NSDictionary *publicKeyQuery = @{
+                                     (__bridge id)kSecClass: (__bridge id)kSecClassKey,
+                                     (__bridge id)kSecAttrApplicationTag: [keyTag dataUsingEncoding:NSUTF8StringEncoding],
+                                     (__bridge id)kSecAttrType: (__bridge id)kSecAttrKeyTypeRSA,
+                                     (__bridge id)kSecReturnData: @YES,
+                                     };
+
+    CFTypeRef dataRef;
+    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)publicKeyQuery, &dataRef);
+
+    if (status != errSecSuccess) {
+        return nil;
+    }
+
+    NSData *data = [NSData dataWithData:(__bridge NSData *)dataRef];
+    if (dataRef) {
+        CFRelease(dataRef);
+    }
+    return data;
+}
+
+- (BOOL)hasRSAKeyWithTag:(NSString *)keyTag {
+    NSAssert(keyTag.length > 0, @"key tag should be non-empty!");
+
+    NSDictionary *publicKeyQuery = @{
+                                     (__bridge id)kSecClass: (__bridge id)kSecClassKey,
+                                     (__bridge id)kSecAttrApplicationTag: [keyTag dataUsingEncoding:NSUTF8StringEncoding],
+                                     (__bridge id)kSecAttrType: (__bridge id)kSecAttrKeyTypeRSA,
+                                     (__bridge id)kSecReturnData: @NO,
+                                     };
+
+    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)publicKeyQuery, NULL);
+    return status == errSecSuccess;
+}
+
+
+- (BOOL)deleteRSAKeyWithTag:(NSString *)keyTag {
+    NSAssert(keyTag.length > 0, @"key tag should be non-empty!");
+    NSDictionary *deleteKeyQuery = @{
+                                     (__bridge id)kSecClass: (__bridge id)kSecClassKey,
+                                     (__bridge id)kSecAttrApplicationTag: [keyTag dataUsingEncoding:NSUTF8StringEncoding],
+                                     (__bridge id)kSecAttrType: (__bridge id)kSecAttrKeyTypeRSA,
+                                     };
+
+    OSStatus status = SecItemDelete((__bridge CFDictionaryRef)deleteKeyQuery);
+    return status == errSecSuccess;
+}
+
+- (SecKeyRef)keyRefOfRSAKeyWithTag:(NSString *)keyTag {
+    NSAssert(keyTag.length > 0, @"key tag should be non-empty!");
+    NSDictionary *query = @{
+                            (__bridge id)kSecClass: (__bridge id)kSecClassKey,
+                            (__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeRSA,
+                            (__bridge id)kSecReturnRef: @YES,
+                            (__bridge id)kSecAttrApplicationTag: keyTag,
+                            };
+    SecKeyRef privateKeyRef = NULL;
+    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&privateKeyRef);
+    if (status != errSecSuccess) {
+        return NULL;
+    }
+    return privateKeyRef;
+}
+
+@end
+
+@implementation A0SimpleKeychain (Deprecated)
+
+- (NSData *)publicRSAKeyDataForTag:(NSString *)keyTag {
+    return [self dataForRSAKeyWithTag:keyTag];
+}
+
+@end

+ 333 - 0
src/ios/A0KeyChain/A0SimpleKeychain.h

@@ -0,0 +1,333 @@
+//  A0SimpleKeychain.h
+//
+// Copyright (c) 2014 Auth0 (http://auth0.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+
+///---------------------------------------------------
+/// @name Keychain Items Accessibility Values
+///---------------------------------------------------
+
+/**
+ *  Enum with Kechain items accessibility types. It's a mirror of `kSecAttrAccessible` values.
+ */
+typedef NS_ENUM(NSInteger, A0SimpleKeychainItemAccessible) {
+    /**
+     *  @see kSecAttrAccessibleWhenUnlocked
+     */
+    A0SimpleKeychainItemAccessibleWhenUnlocked = 0,
+    /**
+     *  @see kSecAttrAccessibleAfterFirstUnlock
+     */
+    A0SimpleKeychainItemAccessibleAfterFirstUnlock,
+    /**
+     *  @see kSecAttrAccessibleAlways
+     */
+    A0SimpleKeychainItemAccessibleAlways,
+    /**
+     *  @see kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
+     */
+    A0SimpleKeychainItemAccessibleWhenPasscodeSetThisDeviceOnly,
+    /**
+     *  @see kSecAttrAccessibleWhenUnlockedThisDeviceOnly
+     */
+    A0SimpleKeychainItemAccessibleWhenUnlockedThisDeviceOnly,
+    /**
+     *  kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
+     */
+    A0SimpleKeychainItemAccessibleAfterFirstUnlockThisDeviceOnly,
+    /**
+     *  @see kSecAttrAccessibleAlwaysThisDeviceOnly
+     */
+    A0SimpleKeychainItemAccessibleAlwaysThisDeviceOnly
+};
+
+#define A0ErrorDomain @"com.auth0.simplekeychain"
+
+/**
+ * Enum with keychain error codes. It's a mirror of the keychain error codes. 
+ */
+typedef NS_ENUM(NSInteger, A0SimpleKeychainError) {
+    /**
+     * @see errSecSuccess
+     */
+    A0SimpleKeychainErrorNoError = 0,
+    /**
+     * @see errSecUnimplemented
+     */
+    A0SimpleKeychainErrorUnimplemented = -4,
+    /**
+     * @see errSecParam
+     */
+    A0SimpleKeychainErrorWrongParameter = -50,
+    /**
+     * @see errSecAllocate
+     */
+    A0SimpleKeychainErrorAllocation = -108,
+    /**
+     * @see errSecNotAvailable
+     */
+    A0SimpleKeychainErrorNotAvailable = -25291,
+    /**
+     * @see errSecAuthFailed
+     */
+    A0SimpleKeychainErrorAuthFailed = -25293,
+    /**
+     * @see errSecDuplicateItem
+     */
+    A0SimpleKeychainErrorDuplicateItem = -25299,
+    /**
+     * @see errSecItemNotFound
+     */
+    A0SimpleKeychainErrorItemNotFound = -25300,
+    /**
+     * @see errSecInteractionNotAllowed
+     */
+    A0SimpleKeychainErrorInteractionNotAllowed = -25308,
+    /**
+     * @see errSecDecode
+     */
+    A0SimpleKeychainErrorDecode = -26275
+};
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ *  A simple helper class to deal with storing and retrieving values from iOS Keychain.
+ *  It has support for sharing keychain items using Access Group and also for iOS 8 fine grained accesibility over a specific Kyechain Item (Using Access Control).
+ *  The support is only available for iOS 8+, otherwise it will default using the coarse grained accesibility field.
+ *  When a `NSString` or `NSData` is stored using Access Control and the accesibility flag `A0SimpleKeychainItemAccessibleWhenPasscodeSetThisDeviceOnly`, iOS will prompt the user for it's passcode or pass a TouchID challenge (if available).
+ */
+@interface A0SimpleKeychain : NSObject
+
+/**
+ *  Service name under all items are saved. Default value is Bundle Identifier.
+ */
+@property (readonly, nonatomic) NSString *service;
+
+/**
+ *  Access Group for Keychain item sharing. If it's nil no keychain sharing is possible. Default value is nil.
+ */
+@property (readonly, nullable, nonatomic) NSString *accessGroup;
+
+/**
+ *  What type of accessibility the items stored will have. All values are translated to `kSecAttrAccessible` constants.
+ *  Default value is A0SimpleKeychainItemAccessibleAfterFirstUnlock.
+ *  @see kSecAttrAccessible
+ */
+@property (assign, nonatomic) A0SimpleKeychainItemAccessible defaultAccessiblity;
+
+/**
+ *  Tells A0SimpleKeychain to use `kSecAttrAccessControl` instead of `kSecAttrAccessible`. It will work only in iOS 8+, defaulting to `kSecAttrAccessible` on lower version.
+ *  Default value is NO.
+ */
+@property (assign, nonatomic) BOOL useAccessControl;
+
+///---------------------------------------------------
+/// @name Initialization
+///---------------------------------------------------
+
+/**
+ *  Initialise a `A0SimpleKeychain` with default values.
+ *
+ *  @return an initialised instance
+ */
+- (instancetype)init;
+
+/**
+ *  Initialise a `A0SimpleKeychain` with a given service.
+ *
+ *  @param service name of the service to use to save items.
+ *
+ *  @return an initialised instance.
+ */
+- (instancetype)initWithService:(NSString *)service;
+
+/**
+ *  Initialise a `A0SimpleKeychain` with a given service and access group.
+ *
+ *  @param service name of the service to use to save items.
+ *  @param accessGroup name of the access group to share items.
+ *
+ *  @return an initialised instance.
+ */
+- (instancetype)initWithService:(NSString *)service accessGroup:(nullable NSString *)accessGroup;
+
+///---------------------------------------------------
+/// @name Store values
+///---------------------------------------------------
+
+/**
+ *  Saves the NSString with the type `kSecClassGenericPassword` in the keychain.
+ *
+ *  @param string value to save in the keychain
+ *  @param key    key for the keychain entry.
+ *
+ *  @return if the value was saved it will return YES. Otherwise it'll return NO.
+ */
+- (BOOL)setString:(NSString *)string forKey:(NSString *)key;
+
+/**
+ *  Saves the NSData with the type `kSecClassGenericPassword` in the keychain.
+ *
+ *  @param data value to save in the keychain
+ *  @param key    key for the keychain entry.
+ *
+ *  @return if the value was saved it will return YES. Otherwise it'll return NO.
+ */
+- (BOOL)setData:(NSData *)data forKey:(NSString *)key;
+
+/**
+ *  Saves the NSString with the type `kSecClassGenericPassword` in the keychain.
+ *
+ *  @param string   value to save in the keychain
+ *  @param key      key for the keychain entry.
+ *  @param message  prompt message to display for TouchID/passcode prompt if neccesary
+ *
+ *  @return if the value was saved it will return YES. Otherwise it'll return NO.
+ */
+- (BOOL)setString:(NSString *)string forKey:(NSString *)key promptMessage:(nullable NSString *)message;
+
+/**
+ *  Saves the NSData with the type `kSecClassGenericPassword` in the keychain.
+ *
+ *  @param string   value to save in the keychain
+ *  @param key      key for the keychain entry.
+ *  @param message  prompt message to display for TouchID/passcode prompt if neccesary
+ *
+ *  @return if the value was saved it will return YES. Otherwise it'll return NO.
+ */
+- (BOOL)setData:(NSData *)data forKey:(NSString *)key promptMessage:(nullable NSString *)message;
+
+///---------------------------------------------------
+/// @name Remove values
+///---------------------------------------------------
+
+/**
+ *  Removes an entry from the Keychain using its key
+ *
+ *  @param key the key of the entry to delete.
+ *
+ *  @return If the entry was removed it will return YES. Otherwise it will return NO.
+ */
+- (BOOL)deleteEntryForKey:(NSString *)key;
+
+/**
+ *  Remove all entries from the kechain with the service and access group values.
+ */
+- (void)clearAll;
+
+///---------------------------------------------------
+/// @name Obtain values
+///---------------------------------------------------
+
+/**
+ *  Fetches a NSString from the keychain
+ *
+ *  @param key the key of the value to fetch
+ *
+ *  @return the value or nil if an error occurs.
+ */
+- (nullable NSString *)stringForKey:(NSString *)key;
+
+/**
+ *  Fetches a NSData from the keychain
+ *
+ *  @param key the key of the value to fetch
+ *
+ *  @return the value or nil if an error occurs.
+ */
+- (nullable NSData *)dataForKey:(NSString *)key;
+
+/**
+ *  Fetches a NSString from the keychain
+ *
+ *  @param key     the key of the value to fetch
+ *  @param message prompt message to display for TouchID/passcode prompt if neccesary
+ *
+ *  @return the value or nil if an error occurs.
+ */
+- (nullable NSString *)stringForKey:(NSString *)key promptMessage:(nullable NSString *)message;
+
+/**
+ *  Fetches a NSData from the keychain
+ *
+ *  @param key     the key of the value to fetch
+ *  @param message prompt message to display for TouchID/passcode prompt if neccesary
+ *
+ *  @return the value or nil if an error occurs.
+ */
+- (nullable NSData *)dataForKey:(NSString *)key promptMessage:(nullable NSString *)message;
+
+/**
+ *  Fetches a NSData from the keychain
+ *
+ *  @param key     the key of the value to fetch
+ *  @param message prompt message to display for TouchID/passcode prompt if neccesary
+ *  @param err     Returns an error, if the item cannot be retrieved. F.e. item not found 
+ *                 or user authentication failed in TouchId case.
+ *
+ *  @return the value or nil if an error occurs.
+ */
+- (nullable NSData *)dataForKey:(NSString *)key promptMessage:(nullable NSString *)message error:(NSError **)err;
+
+/**
+ *  Checks if a key has a value in the Keychain
+ *
+ *  @param key the key to check if it has a value
+ *
+ *  @return if the key has an associated value in the Keychain or not.
+ */
+- (BOOL)hasValueForKey:(NSString *)key;
+
+///---------------------------------------------------
+/// @name Create helper methods
+///---------------------------------------------------
+
+/**
+ *  Creates a new instance of `A0SimpleKeychain`
+ *
+ *  @return a new instance
+ */
++ (A0SimpleKeychain *)keychain;
+
+/**
+ *  Creates a new instance of `A0SimpleKeychain` with a service name.
+ *
+ *  @param service name of the service under all items will be stored.
+ *
+ *  @return a new instance
+ */
++ (A0SimpleKeychain *)keychainWithService:(NSString *)service;
+
+/**
+ *  Creates a new instance of `A0SimpleKeychain` with a service name and access group
+ *
+ *  @param service     name of the service under all items will be stored.
+ *  @param accessGroup name of the access group to share keychain items.
+ *
+ *  @return a new instance
+ */
++ (A0SimpleKeychain *)keychainWithService:(NSString *)service accessGroup:(NSString *)accessGroup;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 368 - 0
src/ios/A0KeyChain/A0SimpleKeychain.m

@@ -0,0 +1,368 @@
+//  A0SimpleKeychain.h
+//
+// Copyright (c) 2014 Auth0 (http://auth0.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "A0SimpleKeychain.h"
+
+@interface A0SimpleKeychain ()
+
+@end
+
+@implementation A0SimpleKeychain
+
+- (instancetype)init {
+    NSString *service = [[NSBundle mainBundle] bundleIdentifier];
+    return [self initWithService:service accessGroup:nil];
+}
+
+- (instancetype)initWithService:(NSString *)service {
+    return [self initWithService:service accessGroup:nil];
+}
+
+- (instancetype)initWithService:(NSString *)service accessGroup:(NSString *)accessGroup {
+    self = [super init];
+    if (self) {
+        _service = service;
+        _accessGroup = accessGroup;
+        _defaultAccessiblity = A0SimpleKeychainItemAccessibleAfterFirstUnlock;
+        _useAccessControl = NO;
+    }
+    return self;
+}
+
+- (NSString *)stringForKey:(NSString *)key {
+    return [self stringForKey:key promptMessage:nil];
+}
+
+- (NSData *)dataForKey:(NSString *)key {
+    return [self dataForKey:key promptMessage:nil];
+}
+
+- (NSString *)stringForKey:(NSString *)key promptMessage:(NSString *)message {
+    NSData *data = [self dataForKey:key promptMessage:message];
+    NSString *string = nil;
+    if (data) {
+        string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+    }
+    return string;
+}
+
+- (NSData *)dataForKey:(NSString *)key promptMessage:(NSString *)message {
+    return [self dataForKey:key promptMessage:message error:nil];
+}
+
+- (NSData *)dataForKey:(NSString *)key promptMessage:(NSString *)message error:(NSError**)err {
+    if (!key) {
+        return nil;
+    }
+    
+    NSDictionary *query = [self queryFetchOneByKey:key message:message];
+    CFTypeRef data = nil;
+    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &data);
+    if (status != errSecSuccess) {
+        if(err != nil) {
+            *err = [NSError errorWithDomain:A0ErrorDomain code:status userInfo:@{NSLocalizedDescriptionKey : [self stringForSecStatus:status]}];
+        }
+        return nil;
+    }
+    
+    NSData *dataFound = [NSData dataWithData:(__bridge NSData *)data];
+    if (data) {
+        CFRelease(data);
+    }
+    
+    return dataFound;
+}
+
+- (BOOL)hasValueForKey:(NSString *)key {
+    if (!key) {
+        return NO;
+    }
+    NSDictionary *query = [self queryFindByKey:key message:nil];
+    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL);
+    return status == errSecSuccess;
+}
+
+- (BOOL)setString:(NSString *)string forKey:(NSString *)key {
+    return [self setString:string forKey:key promptMessage:nil];
+}
+
+- (BOOL)setData:(NSData *)data forKey:(NSString *)key {
+    return [self setData:data forKey:key promptMessage:nil];
+}
+
+- (BOOL)setString:(NSString *)string forKey:(NSString *)key promptMessage:(NSString *)message {
+    NSData *data = key ? [string dataUsingEncoding:NSUTF8StringEncoding] : nil;
+    return [self setData:data forKey:key promptMessage:message];
+}
+
+
+- (BOOL)setData:(NSData *)data forKey:(NSString *)key promptMessage:(NSString *)message {
+    if (!key) {
+        return NO;
+    }
+    
+    NSDictionary *query = [self queryFindByKey:key message:message];
+    
+    // Touch ID case
+    if (self.useAccessControl && self.defaultAccessiblity == A0SimpleKeychainItemAccessibleWhenPasscodeSetThisDeviceOnly) {
+        // TouchId case. Doesn't support updating keychain items
+        // see Known Issues: https://developer.apple.com/library/ios/releasenotes/General/RN-iOSSDK-8.0/
+        // We need to delete old and add a new item. This can fail
+        OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
+        if (status == errSecSuccess || status == errSecItemNotFound) {
+            NSDictionary *newQuery = [self queryNewKey:key value:data];
+            OSStatus status = SecItemAdd((__bridge CFDictionaryRef)newQuery, NULL);
+            return status == errSecSuccess;
+        }
+    }
+    
+    // Normal case
+    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL);
+    if (status == errSecSuccess) {
+        if (data) {
+            NSDictionary *updateQuery = [self queryUpdateValue:data message:message];
+            status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)updateQuery);
+            return status == errSecSuccess;
+        } else {
+            OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
+            return status == errSecSuccess;
+        }
+    } else {
+        NSDictionary *newQuery = [self queryNewKey:key value:data];
+        OSStatus status = SecItemAdd((__bridge CFDictionaryRef)newQuery, NULL);
+        return status == errSecSuccess;
+    }
+}
+
+- (BOOL)deleteEntryForKey:(NSString *)key {
+    if (!key) {
+        return NO;
+    }
+    NSDictionary *deleteQuery = [self queryFindByKey:key message:nil];
+    OSStatus status = SecItemDelete((__bridge CFDictionaryRef)deleteQuery);
+    return status == errSecSuccess;
+}
+
+- (void)clearAll {
+#if TARGET_OS_IPHONE
+  NSDictionary *query = [self queryFindAll];
+  CFArrayRef result = nil;
+  OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
+  if (status == errSecSuccess || status == errSecItemNotFound) {
+    NSArray *items = [NSArray arrayWithArray:(__bridge NSArray *)result];
+    CFBridgingRelease(result);
+    for (NSDictionary *item in items) {
+      NSMutableDictionary *queryDelete = [[NSMutableDictionary alloc] initWithDictionary:item];
+      queryDelete[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword;
+
+      OSStatus status = SecItemDelete((__bridge CFDictionaryRef)queryDelete);
+      if (status != errSecSuccess) {
+        break;
+      }
+    }
+  }
+#else
+  NSMutableDictionary *queryDelete = [self baseQuery];
+  queryDelete[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword;
+  queryDelete[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;
+  OSStatus status = SecItemDelete((__bridge CFDictionaryRef)queryDelete);
+  if (status != errSecSuccess) {
+    return;
+  }
+#endif
+}
+
++ (A0SimpleKeychain *)keychain {
+    return [[A0SimpleKeychain alloc] init];
+}
+
++ (A0SimpleKeychain *)keychainWithService:(NSString *)service {
+    return [[A0SimpleKeychain alloc] initWithService:service];
+}
+
++ (A0SimpleKeychain *)keychainWithService:(NSString *)service accessGroup:(NSString *)accessGroup {
+    return [[A0SimpleKeychain alloc] initWithService:service accessGroup:accessGroup];
+}
+
+#pragma mark - Utility methods
+
+- (CFTypeRef)accessibility {
+    CFTypeRef accessibility;
+    switch (self.defaultAccessiblity) {
+        case A0SimpleKeychainItemAccessibleAfterFirstUnlock:
+            accessibility = kSecAttrAccessibleAfterFirstUnlock;
+            break;
+        case A0SimpleKeychainItemAccessibleAlways:
+            accessibility = kSecAttrAccessibleAlways;
+            break;
+        case A0SimpleKeychainItemAccessibleAfterFirstUnlockThisDeviceOnly:
+            accessibility = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
+            break;
+        case A0SimpleKeychainItemAccessibleAlwaysThisDeviceOnly:
+            accessibility = kSecAttrAccessibleAlwaysThisDeviceOnly;
+            break;
+#if TARGET_OS_IPHONE
+        case A0SimpleKeychainItemAccessibleWhenPasscodeSetThisDeviceOnly:
+#ifdef __IPHONE_8_0
+            if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_7_1) { //iOS 8
+                accessibility = kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly;
+            } else { //iOS <= 7.1
+                accessibility = kSecAttrAccessibleWhenUnlockedThisDeviceOnly;
+            }
+#else
+            accessibility = kSecAttrAccessibleWhenUnlockedThisDeviceOnly;
+#endif
+#endif
+            break;
+        case A0SimpleKeychainItemAccessibleWhenUnlocked:
+            accessibility = kSecAttrAccessibleWhenUnlocked;
+            break;
+        case A0SimpleKeychainItemAccessibleWhenUnlockedThisDeviceOnly:
+            accessibility = kSecAttrAccessibleWhenUnlockedThisDeviceOnly;
+            break;
+        default:
+            accessibility = kSecAttrAccessibleWhenUnlockedThisDeviceOnly;
+    }
+    return accessibility;
+}
+
+- (NSString*)stringForSecStatus:(OSStatus)status {
+    
+    switch(status) {
+        case errSecSuccess:
+            return NSLocalizedStringFromTable(@"errSecSuccess: No error", @"SimpleKeychain", @"Possible error from keychain. ");
+        case errSecUnimplemented:
+            return NSLocalizedStringFromTable(@"errSecUnimplemented: Function or operation not implemented", @"SimpleKeychain", @"Possible error from keychain. ");
+        case errSecParam:
+            return NSLocalizedStringFromTable(@"errSecParam: One or more parameters passed to the function were not valid", @"SimpleKeychain", @"Possible error from keychain. ");
+        case errSecAllocate:
+            return NSLocalizedStringFromTable(@"errSecAllocate: Failed to allocate memory", @"SimpleKeychain", @"Possible error from keychain. ");
+        case errSecNotAvailable:
+            return NSLocalizedStringFromTable(@"errSecNotAvailable: No trust results are available", @"SimpleKeychain", @"Possible error from keychain. ");
+        case errSecAuthFailed:
+            return NSLocalizedStringFromTable(@"errSecAuthFailed: Authorization/Authentication failed", @"SimpleKeychain", @"Possible error from keychain. ");
+        case errSecDuplicateItem:
+            return NSLocalizedStringFromTable(@"errSecDuplicateItem: The item already exists", @"SimpleKeychain", @"Possible error from keychain. ");
+        case errSecItemNotFound:
+            return NSLocalizedStringFromTable(@"errSecItemNotFound: The item cannot be found", @"SimpleKeychain", @"Possible error from keychain. ");
+        case errSecInteractionNotAllowed:
+            return NSLocalizedStringFromTable(@"errSecInteractionNotAllowed: Interaction with the Security Server is not allowed", @"SimpleKeychain", @"Possible error from keychain. ");
+        case errSecDecode:
+            return NSLocalizedStringFromTable(@"errSecDecode: Unable to decode the provided data", @"SimpleKeychain", @"Possible error from keychain. ");
+        default:
+            return [NSString stringWithFormat:NSLocalizedStringFromTable(@"Unknown error code %d", @"SimpleKeychain", @"Possible error from keychain. "), status];
+    }
+}
+
+#pragma mark - Query Dictionary Builder methods
+
+- (NSMutableDictionary *)baseQuery {
+    NSMutableDictionary *attributes = [@{
+                                         (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
+                                         (__bridge id)kSecAttrService: self.service,
+                                         } mutableCopy];
+#if !TARGET_IPHONE_SIMULATOR
+    if (self.accessGroup) {
+        attributes[(__bridge id)kSecAttrAccessGroup] = self.accessGroup;
+    }
+#endif
+
+    return attributes;
+}
+
+- (NSDictionary *)queryFindAll {
+    NSMutableDictionary *query = [self baseQuery];
+    [query addEntriesFromDictionary:@{
+                                     (__bridge id)kSecReturnAttributes: @YES,
+                                     (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitAll,
+                                     }];
+    return query;
+}
+
+- (NSDictionary *)queryFindByKey:(NSString *)key message:(NSString *)message {
+    NSAssert(key != nil, @"Must have a valid non-nil key");
+    NSMutableDictionary *query = [self baseQuery];
+    query[(__bridge id)kSecAttrAccount] = key;
+#if TARGET_OS_IPHONE
+    if (message) {
+        query[(__bridge id)kSecUseOperationPrompt] = message;
+    }
+#endif
+    return query;
+}
+
+- (NSDictionary *)queryUpdateValue:(NSData *)data message:(NSString *)message {
+    if (message) {
+        return @{
+#if TARGET_OS_IPHONE
+                 (__bridge id)kSecUseOperationPrompt: message,
+#endif
+                 (__bridge id)kSecValueData: data,
+                 };
+    } else {
+        return @{
+                 (__bridge id)kSecValueData: data,
+                 };
+    }
+}
+
+- (NSDictionary *)queryNewKey:(NSString *)key value:(NSData *)value {
+    NSMutableDictionary *query = [self baseQuery];
+    query[(__bridge id)kSecAttrAccount] = key;
+    query[(__bridge id)kSecValueData] = value;
+#if TARGET_OS_IPHONE
+#ifdef __IPHONE_8_0
+    if (self.useAccessControl && floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_7_1) {
+        CFErrorRef error = NULL;
+        SecAccessControlRef accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, [self accessibility], kSecAccessControlUserPresence, &error);
+        if (error == NULL || accessControl != NULL) {
+            query[(__bridge id)kSecAttrAccessControl] = (__bridge_transfer id)accessControl;
+            query[(__bridge id)kSecUseNoAuthenticationUI] = @YES;
+        }
+    } else {
+        query[(__bridge id)kSecAttrAccessible] = (__bridge id)[self accessibility];
+    }
+#else
+    query[(__bridge id)kSecAttrAccessible] = (__bridge id)[self accessibility];
+#endif
+#endif
+    return query;
+}
+
+- (NSDictionary *)queryFetchOneByKey:(NSString *)key message:(NSString *)message {
+    NSMutableDictionary *query = [self baseQuery];
+    [query addEntriesFromDictionary:@{
+                                      (__bridge id)kSecReturnData: @YES,
+                                      (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne,
+                                      (__bridge id)kSecAttrAccount: key,
+                                      }];
+#if TARGET_OS_IPHONE
+    if (self.useAccessControl) {
+        if (message && floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_7_1) {
+            query[(__bridge id)kSecUseOperationPrompt] = message;
+        }
+    }
+#endif
+
+    return query;
+}
+@end

+ 34 - 0
src/ios/A0KeyChain/SimpleKeychain.h

@@ -0,0 +1,34 @@
+// SimpleKeychain.h
+//
+// Copyright (c) 2014 Auth0 (http://auth0.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+
+//! Project version number for SimpleKeychain.
+FOUNDATION_EXPORT double SimpleKeychainVersionNumber;
+
+//! Project version string for SimpleKeychain.
+FOUNDATION_EXPORT const unsigned char SimpleKeychainVersionString[];
+
+// In this header, you should import all the public headers of your framework using statements like #import <SimpleKeychain/PublicHeader.h>
+
+#import <A0SimpleKeychain.h>
+#import <A0SimpleKeychain+KeyPair.h>

+ 4 - 4
src/ios/CDVKeychain.h

@@ -16,7 +16,7 @@
  specific language governing permissions and limitations
  under the License.
  */
- 
+
 #import <UIKit/UIKit.h>
 #import <Foundation/Foundation.h>
 #import <Cordova/CDVPlugin.h>
@@ -24,8 +24,8 @@
 @interface CDVKeychain : CDVPlugin {
 }
 
-- (void) getForKey:(CDVInvokedUrlCommand*)command;
-- (void) setForKey:(CDVInvokedUrlCommand*)command;
-- (void) removeForKey:(CDVInvokedUrlCommand*)command;
+- (void) get:(CDVInvokedUrlCommand*)command;
+- (void) set:(CDVInvokedUrlCommand*)command;
+- (void) remove:(CDVInvokedUrlCommand*)command;
 
 @end

+ 72 - 87
src/ios/CDVKeychain.m

@@ -18,100 +18,85 @@
  */
 
 #import "CDVKeychain.h"
-#import "SFHFKeychainUtils.h"
+#import "A0KeyChain/A0SimpleKeychain.h"
 
 @implementation CDVKeychain
 
-- (void) getForKey:(CDVInvokedUrlCommand*)command
-{
-    [self.commandDelegate runInBackground:^{
-        NSArray* arguments = command.arguments;
-        CDVPluginResult* pluginResult = nil;
-        
-        if ([arguments count] >= 2)
-        {
-            NSString* key = [arguments objectAtIndex:0];
-            NSString* serviceName = [arguments objectAtIndex:1];
-            NSError* error = nil;
-            
-            NSString* value = [SFHFKeychainUtils getPasswordForUsername:key andServiceName:serviceName error:&error];
-            if (error == nil && value != nil) {
-                pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:value];
-            } else {
-                pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
-                                                 messageAsString:[NSString stringWithFormat:@"error retrieving value for key '%@' : %@", key, [error localizedDescription]]];
-            }
-        }
-        else
-        {
-            pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
-                                             messageAsString:@"incorrect number of arguments for getForkey"];
-        }
-        
-        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
-    }];
-}
+- (void) get:(CDVInvokedUrlCommand*)command {
+  [self.commandDelegate runInBackground:^{
+    NSArray* arguments = command.arguments;
+    CDVPluginResult* pluginResult = nil;
+
+    if([arguments count] < 2) {
+      pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
+      messageAsString:@"incorrect number of arguments for getWithTouchID"];
+      [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
+      return;
+    }
+
+    NSString *key = [arguments objectAtIndex:0];
+    NSString *touchIDMessage = [arguments objectAtIndex:1];
+
+    NSString *message = NSLocalizedString(touchIDMessage, @"Prompt TouchID message");
+
+    A0SimpleKeychain *keychain = [A0SimpleKeychain keychain];
+    NSString *value = [keychain stringForKey:key promptMessage:message];
 
-- (void) setForKey:(CDVInvokedUrlCommand*)command
-{
-    [self.commandDelegate runInBackground:^{
-        NSArray* arguments = command.arguments;
-        CDVPluginResult* pluginResult = nil;
-        
-        if ([arguments count] >= 3)
-        {
-            NSString* key = [arguments objectAtIndex:0];
-            NSString* serviceName = [arguments objectAtIndex:1];
-            NSString* value = [arguments objectAtIndex:2];
-            NSError* error = nil;
-            
-            BOOL stored = [SFHFKeychainUtils storeUsername:key andPassword:value forServiceName:serviceName updateExisting:YES error:&error];
-            if (stored && error == nil) {
-                pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
-            } else {
-                pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]];
-            }
-        }
-        else
-        {
-            pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
-                                             messageAsString:@"incorrect number of arguments for setForKey"];
-        }
-
-        
-        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
-    }];
+    pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:value];
+    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
+  }];
 }
 
-- (void) removeForKey:(CDVInvokedUrlCommand*)command
-{
-    [self.commandDelegate runInBackground:^{
-        NSArray* arguments = command.arguments;
-        CDVPluginResult* pluginResult = nil;
-        
-        if ([arguments count] >= 2)
-        {
-            NSString* key = [arguments objectAtIndex:0];
-            NSString* serviceName = [arguments objectAtIndex:1];
-            NSError* error = nil;
-            
-            BOOL deleted = [SFHFKeychainUtils deleteItemForUsername:key andServiceName:serviceName error:&error];
-            if (deleted && error == nil) {
-                pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
-            } else {
-                pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]];
-            }
-        }
-        else
-        {
-            pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
-                                             messageAsString:@"incorrect number of arguments for removeForKey"];
-        }
-        
-        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
-    }];
+- (void) set:(CDVInvokedUrlCommand*)command {
+  [self.commandDelegate runInBackground:^{
+    NSArray* arguments = command.arguments;
+    CDVPluginResult* pluginResult = nil;
+
+    if([arguments count] < 3) {
+      pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
+      messageAsString:@"incorrect number of arguments for setWithTouchID"];
+      [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
+      return;
+    }
+
+    NSString* key = [arguments objectAtIndex:0];
+    NSString* value = [arguments objectAtIndex:1];
+    BOOL useTouchID = [arguments objectAtIndex:2];
+
+    A0SimpleKeychain *keychain = [A0SimpleKeychain keychain];
+
+    if(useTouchID) {
+      keychain.useAccessControl = YES;
+      keychain.defaultAccessiblity = A0SimpleKeychainItemAccessibleWhenPasscodeSetThisDeviceOnly;
+    }
+
+    [keychain setString:value forKey:key];
+
+    pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
+    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
+  }];
 }
 
+- (void) remove:(CDVInvokedUrlCommand*)command {
+  [self.commandDelegate runInBackground:^{
+    NSArray* arguments = command.arguments;
+    CDVPluginResult* pluginResult = nil;
 
-@end
+    if([arguments count] < 1) {
+      pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
+      messageAsString:@"incorrect number of arguments for remove"];
+      [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
+      return;
+    }
+
+    NSString *key = [arguments objectAtIndex:0];
 
+    A0SimpleKeychain *keychain = [A0SimpleKeychain keychain];
+    [keychain deleteEntryForKey:key];
+
+    pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
+    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
+  }];
+}
+
+@end

+ 0 - 41
src/ios/SFHFKeychainUtils/SFHFKeychainUtils.h

@@ -1,41 +0,0 @@
-//
-//  SFHFKeychainUtils.h
-//
-//  Created by Buzz Andersen on 10/20/08.
-//  Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone.
-//  Copyright 2008 Sci-Fi Hi-Fi. All rights reserved.
-//
-//  Permission is hereby granted, free of charge, to any person
-//  obtaining a copy of this software and associated documentation
-//  files (the "Software"), to deal in the Software without
-//  restriction, including without limitation the rights to use,
-//  copy, modify, merge, publish, distribute, sublicense, and/or sell
-//  copies of the Software, and to permit persons to whom the
-//  Software is furnished to do so, subject to the following
-//  conditions:
-//
-//  The above copyright notice and this permission notice shall be
-//  included in all copies or substantial portions of the Software.
-//
-//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-//  OTHER DEALINGS IN THE SOFTWARE.
-//
-
-#import <UIKit/UIKit.h>
-
-
-@interface SFHFKeychainUtils : NSObject {
-  
-}
-
-+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
-+ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error;
-+ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
-
-@end

+ 0 - 311
src/ios/SFHFKeychainUtils/SFHFKeychainUtils.m

@@ -1,311 +0,0 @@
-//
-//  SFHFKeychainUtils.m
-//
-//  Created by Buzz Andersen on 10/20/08.
-//  Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone.
-//  Copyright 2008 Sci-Fi Hi-Fi. All rights reserved.
-//
-//  Permission is hereby granted, free of charge, to any person
-//  obtaining a copy of this software and associated documentation
-//  files (the "Software"), to deal in the Software without
-//  restriction, including without limitation the rights to use,
-//  copy, modify, merge, publish, distribute, sublicense, and/or sell
-//  copies of the Software, and to permit persons to whom the
-//  Software is furnished to do so, subject to the following
-//  conditions:
-//
-//  The above copyright notice and this permission notice shall be
-//  included in all copies or substantial portions of the Software.
-//
-//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-//  OTHER DEALINGS IN THE SOFTWARE.
-//
-
-/* 
-
- Modified to take advantage of iCloud Keychain by Jamie Perkins
- 09/24/2014
- 
- Error codes:
- 
- 0 - No error.
- -4 - Function or operation not implemented.
- -50 - One or more parameters passed to the function were not valid.
- -108 - Failed to allocate memory.
- -2000 - username or servicename nil
- –25291 - No trust results are available.
- –25293 - Authorization/Authentication failed.
- –25299 - The item already exists.
- –25300 - The item cannot be found.
- –25308 - Interaction with the Security Server is not allowed.
- -26275 - Unable to decode the provided data.
- */
-
-#import "SFHFKeychainUtils.h"
-#import <Security/Security.h>
-
-static NSString *SFHFKeychainUtilsErrorDomain = @"SFHFKeychainUtilsErrorDomain";
-
-@implementation SFHFKeychainUtils
-
-
-+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
-    if (!username || !serviceName) {
-        if (error != nil) {
-            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
-        }
-        return nil;
-    }
-    
-    if (error != nil) {
-        *error = nil;
-    }
-    
-    // Set up a query dictionary with the base query attributes: item type (generic), username, and service
-    NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge NSString *)
-                     kSecClass,
-                     kSecAttrAccount,
-                     kSecAttrService,
-                     kSecAttrSynchronizable,
-                     nil];
-    
-    NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge NSString *)
-                        kSecClassGenericPassword,
-                        username,
-                        serviceName,
-                        kCFBooleanTrue,
-                        nil];
-    
-    NSMutableDictionary *query = [[NSMutableDictionary alloc] initWithObjects: objects forKeys: keys];
-    
-    // First do a query for attributes, in case we already have a Keychain item with no password data set.
-    // One likely way such an incorrect item could have come about is due to the previous (incorrect)
-    // version of this code (which set the password as a generic attribute instead of password data).
-    
-    NSDictionary *attributeResult = NULL;
-    NSMutableDictionary *attributeQuery = [query mutableCopy];
-    [attributeQuery setObject: (id) kCFBooleanTrue forKey:(__bridge id) kSecReturnAttributes];
-    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) attributeQuery, (void*) &attributeResult);
-    
-    
-    if (status != noErr) {
-        // No existing item found--simply return nil for the password
-        if (error != nil && status != errSecItemNotFound) {
-            //Only return an error if a real exception happened--not simply for "not found."
-            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
-        }
-        
-        return nil;
-    }
-    
-    // We have an existing item, now query for the password data associated with it.
-    
-    NSData *resultData = nil;
-    NSMutableDictionary *passwordQuery = [query mutableCopy];
-    [passwordQuery setObject: (id) kCFBooleanTrue forKey: (__bridge id) kSecReturnData];
-    
-    status = SecItemCopyMatching((__bridge CFDictionaryRef) passwordQuery, (void*) &resultData);
-    
-    
-    if (status != noErr) {
-        if (status == errSecItemNotFound) {
-            // We found attributes for the item previously, but no password now, so return a special error.
-            // Users of this API will probably want to detect this error and prompt the user to
-            // re-enter their credentials.  When you attempt to store the re-entered credentials
-            // using storeUsername:andPassword:forServiceName:updateExisting:error
-            // the old, incorrect entry will be deleted and a new one with a properly encrypted
-            // password will be added.
-            if (error != nil) {
-                *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];
-            }
-        }
-        else {
-            // Something else went wrong. Simply return the normal Keychain API error code.
-            if (error != nil) {
-                *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
-            }
-        }
-        
-        return nil;
-    }
-    
-    NSString *password = nil;
-    
-    if (resultData) {
-        password = [[NSString alloc] initWithData: resultData encoding: NSUTF8StringEncoding];
-    }
-    else {
-        // There is an existing item, but we weren't able to get password data for it for some reason,
-        // Possibly as a result of an item being incorrectly entered by the previous code.
-        // Set the -1999 error so the code above us can prompt the user again.
-        if (error != nil) {
-            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];
-        }
-    }
-    
-    return password;
-}
-
-+ (BOOL) storeUsername: (NSString *)username andPassword: (NSString *)password forServiceName: (NSString *)serviceName updateExisting: (BOOL)updateExisting error: (NSError **)error {
-    
-    if (!username || !password || !serviceName) {
-        if (error != nil) {
-            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
-        }
-        return NO;
-    }
-    
-    // See if we already have a password entered for these credentials.
-    NSError *getError = nil;
-    NSString *existingPassword = [SFHFKeychainUtils getPasswordForUsername: username andServiceName: serviceName error:&getError];
-    
-    if ([getError code] == -1999)  {
-        // There is an existing entry without a password properly stored (possibly as a result of the previous incorrect version of this code.
-        // Delete the existing item before moving on entering a correct one.
-        
-        getError = nil;
-        
-        [self deleteItemForUsername: username andServiceName: serviceName error: &getError];
-        
-        if ([getError code] != noErr) {
-            if (error != nil) {
-                *error = getError;
-            }
-            return NO;
-        }
-    }
-    else if ([getError code] != noErr) {
-        if (error != nil) {
-            *error = getError;
-        }
-        return NO;
-    }
-    
-    if (error != nil) {
-        *error = nil;
-    }
-    
-    OSStatus status = noErr;
-    
-    if (existingPassword) {
-        // We have an existing, properly entered item with a password.
-        // Update the existing item.
-        
-        if (![existingPassword isEqualToString:password] && updateExisting) {
-            //Only update if we're allowed to update existing.  If not, simply do nothing.
-            
-            NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge NSString *) kSecClass,
-                             kSecAttrService,
-                             kSecAttrLabel,
-                             kSecAttrAccount,
-                             kSecAttrSynchronizable,
-                             nil];
-            
-            NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge NSString *) kSecClassGenericPassword,
-                                serviceName,
-                                serviceName,
-                                username,
-                                kCFBooleanTrue,
-                                nil];
-            
-            NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
-            
-            status = SecItemUpdate((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef) [NSDictionary dictionaryWithObject: [password dataUsingEncoding: NSUTF8StringEncoding] forKey: (__bridge NSString *) kSecValueData]);
-        }
-    }
-    else {
-        // No existing entry (or an existing, improperly entered, and therefore now
-        // deleted, entry).  Create a new entry.
-        
-        NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge NSString *)
-                         kSecClass,
-                         kSecAttrService,
-                         kSecAttrLabel,
-                         kSecAttrAccount,
-                         kSecValueData,
-                         kSecAttrSynchronizable,
-                         nil];
-        
-        NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge NSString *)
-                            kSecClassGenericPassword,
-                            serviceName,
-                            serviceName,
-                            username,
-                            [password dataUsingEncoding: NSUTF8StringEncoding],
-                            kCFBooleanTrue,
-                            nil];
-        
-        NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
-        
-        status = SecItemAdd((__bridge CFDictionaryRef) query, NULL);
-    }
-    NSLog(@"save keychain status: %d", (int)status);
-    
-    if (status != noErr) {
-        // Something went wrong with adding the new item. Return the Keychain error code.
-        if (error != nil) {
-            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
-        }
-        
-        return NO;
-    }
-    
-    return YES;
-}
-
-+ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error 
-{
-    if (!username || !serviceName) 
-    {
-        if (error != nil) 
-        {
-            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -
-                      000 userInfo: nil];
-        }
-        return NO;
-    }
-    
-    if (error != nil) 
-    {
-        *error = nil;
-    }
-    
-    NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge NSString *)
-                     kSecClass,
-                     kSecAttrAccount,
-                     kSecAttrService,
-                     kSecReturnAttributes,
-                     kSecAttrSynchronizable,
-                     nil];
-    
-    NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge NSString *)
-                        kSecClassGenericPassword,
-                        username,
-                        serviceName,
-                        kCFBooleanTrue,
-                        kCFBooleanTrue,
-                        nil];
-    
-    NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
-    
-    OSStatus status = SecItemDelete((__bridge CFDictionaryRef) query);
-    
-    if (status != noErr) 
-    {
-        if (error != nil) {
-            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
-        }
-        
-        return NO;
-    }
-    
-    return YES;
-}
-
-@end

+ 39 - 15
www/keychain.js

@@ -21,23 +21,47 @@
 
 var exec = require('cordova/exec');
 
-var Keychain = function() {
-	this.serviceName = "Keychain";
-};
 
-Keychain.prototype.getForKey = function(successCallback, failureCallback, key, servicename)
-{
-	exec(successCallback, failureCallback, this.serviceName, "getForKey", [key, servicename]);
-}
+var Keychain = {
+	serviceName: "Keychain",
+
+	get: function(success, error, key, touchIDMessage) {
+		exec(success, error, this.serviceName, "get", [key, touchIDMessage]);
+	},
+	set: function(success, error, key, value, useTouchID) {
+		exec(success, error, this.serviceName, "set", [key, value, useTouchID]);
+	},
+
+	setJson: function(success, error, key, obj) {
+		var value = JSON.stringify(obj);
+		value = value
+			.replace(/[\\]/g, '\\\\')
+			.replace(/[\"]/g, '\\\"')
+			.replace(/[\/]/g, '\\/')
+			.replace(/[\b]/g, '\\b')
+			.replace(/[\f]/g, '\\f')
+			.replace(/[\n]/g, '\\n')
+			.replace(/[\r]/g, '\\r')
+			.replace(/[\t]/g, '\\t');
 
-Keychain.prototype.setForKey = function(successCallback, failureCallback, key, servicename, value)
-{
-	exec(successCallback, failureCallback, this.serviceName, "setForKey", [key, servicename, value]);
-}
+		exec(success, error, this.serviceName, "set", [key, value]);
+	},
 
-Keychain.prototype.removeForKey = function(successCallback, failureCallback, key, servicename)
-{
-	exec(successCallback, failureCallback, this.serviceName, "removeForKey", [key, servicename]);
-}
+	getJson: function(success, error, key) {
+		var cb = function(v) {
+			try {
+				var obj = JSON.parse(v);
+				success(obj);
+			} catch(e) {
+				error(e);
+			}
+		};
+		exec(cb, error, this.serviceName, "get", [key]);
+	},
+
+	remove: function(successCallback, failureCallback, key) {
+		exec(successCallback, failureCallback, this.serviceName, "remove", [key]);
+	}
+};
 
 module.exports = Keychain;