├── .gitignore ├── LICENSE ├── README.md ├── example ├── index.html └── index.js ├── package.json ├── plugin.xml ├── src └── ios │ ├── A0SimpleKeychain+KeyPair.h │ ├── A0SimpleKeychain+KeyPair.m │ ├── A0SimpleKeychain.h │ ├── A0SimpleKeychain.m │ ├── CDVKeychain.h │ ├── CDVKeychain.m │ └── SimpleKeychain.h └── www └── keychain.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright [2012] [Shazron Abdullah] 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Keychain Plugin for Apache Cordova 2 | ===================================== 3 | created by Shazron Abdullah 4 | 5 | Updated by Max Lynch 6 | 7 | _Maintenance Status_: 8 | 9 | Looking for maintainer. PRs and community contributions welcome. 10 | 11 | ### Alternative: Enterprise Keychain and Keystore 12 | 13 | For enterprise use cases requiring secret storage with biometric authentication for iOS (keychain) and Android (Keystore), see [Identity Vault](https://ionicframework.com/enterprise/identity-vault). 14 | 15 | ### Installation 16 | 17 | ```shell 18 | cordova plugin add https://github.com/ionic-team/cordova-plugin-ios-keychain 19 | ``` 20 | 21 | ### iCloud keychain enabled 22 | 23 | iCloud keychain synchonizing is enabled, so the keychain will be mirrored across all devices *if* the user is signed in to iCloud (Settings > iCloud) and has iCloud keychain turned on (Settings > iCloud > Keychain) 24 | 25 | ### Usage 26 | 27 | See the **example** folder for example usage. 28 | 29 | ```js 30 | /* 31 | Retrieves a value for a key 32 | 33 | @param successCallback returns the value as the argument to the callback when successful 34 | @param failureCallback returns the error string as the argument to the callback, for a failure 35 | @param key the key to retrieve 36 | @param TouchIDMessage the message to show underneath the TouchID prompt (if any) 37 | */ 38 | Keychain.get(successCallback, failureCallback, 'key', 'TouchID Message'); 39 | 40 | /* 41 | Sets a value for a key 42 | 43 | @param successCallback returns when successful 44 | @param failureCallback returns the error string as the argument to the callback, for a failure 45 | @param key the key to set 46 | @param value the value to set 47 | @param useTouchID whether to store the value with security such that TouchID will be needed to grab it 48 | */ 49 | Keychain.set(successCallback, failureCallback, 'key', 'value', useTouchID); 50 | 51 | /* 52 | Removes a value for a key 53 | 54 | @param successCallback returns when successful 55 | @param failureCallback returns the error string as the argument to the callback 56 | @param key the key to remove 57 | */ 58 | Keychain.remove(successCallback, failureCallback, 'key'); 59 | 60 | /* 61 | Sets a JSON value for a key 62 | 63 | @param successCallback returns when successful 64 | @param failureCallback returns the error string as the argument to the callback, for a failure 65 | @param key the key to set 66 | @param value the value to set 67 | @param useTouchID whether to store the value with security such that TouchID will be needed to grab it 68 | */ 69 | Keychain.setJson(successCallback, failureCallback, 'key', 'value', useTouchID); 70 | 71 | /* 72 | Gets a JSON value for a key 73 | 74 | @param successCallback returns when successful 75 | @param failureCallback returns the error string as the argument to the callback, for a failure 76 | @param key the key to set 77 | @param value the value to set 78 | @param useTouchID whether to store the value with security such that TouchID will be needed to grab it 79 | */ 80 | Keychain.getJson(successCallback, failureCallback, 'key', useTouchID); 81 | ``` 82 | 83 | ### License 84 | 85 | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0.html) except for the Auth0 SimpelKeychain code that is under MIT 86 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

15 |
16 |

17 |
18 | GET FROM KEYCHAIN 19 |
20 |

21 |
22 |
23 |

24 |
25 |

26 |
27 |

28 |
29 | SET TO KEYCHAIN 30 |
31 |

32 |
33 |
34 |
35 |

36 |
37 |

38 |
39 |

40 |
41 | REMOVE FROM KEYCHAIN 42 |
43 |

44 |
45 | 46 |

47 | 48 | 49 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | PRE-REQUISITE: Install the Keychain plugin using the Cordova cli or plugman 3 | */ 4 | 5 | function onBodyLoad() { 6 | document.addEventListener("deviceready", onDeviceReady, false); 7 | } 8 | 9 | /* When this function is called, PhoneGap has been initialized and is ready to roll */ 10 | 11 | function onDeviceReady() { 12 | try { 13 | 14 | // do your thing! 15 | } catch (e) { 16 | debug.error(e); 17 | } 18 | } 19 | 20 | function onGet() { 21 | var key = document.getElementById("keytoget").value; 22 | var touchIdMessage = 'TouchID Message' 23 | 24 | var win = function(value) { 25 | alert("GET SUCCESS - Key: " + key + " Value: " + value); 26 | }; 27 | var fail = function(error) { 28 | alert("GET FAIL - Key: " + key + " Error: " + error); 29 | }; 30 | 31 | Keychain.get(win, fail, key, touchIdMessage); 32 | } 33 | 34 | function onSet() { 35 | var key = document.getElementById("keytoset").value; 36 | var value = document.getElementById("valuetoset").value; 37 | 38 | var useTouchID = false; 39 | 40 | var win = function() { 41 | alert("SET SUCCESS - Key: " + key); 42 | }; 43 | var fail = function(error) { 44 | alert("SET FAIL - Key: " + key + " Error: " + error); 45 | }; 46 | 47 | Keychain.set(win, fail, key, value, useTouchID); 48 | } 49 | 50 | function onRemove() { 51 | var key = document.getElementById("keytoremove").value; 52 | 53 | var win = function() { 54 | alert("REMOVE SUCCESS - Key: " + key); 55 | }; 56 | var fail = function(error) { 57 | alert("REMOVE FAIL - Key: " + key + " Error: " + error); 58 | }; 59 | 60 | Keychain.remove(win, fail, key); 61 | } 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cordova-plugin-ios-keychain", 3 | "version": "2.0.0", 4 | "description": "Keychain Plugin for Apache Cordova ===================================== created by Shazron Abdullah", 5 | "main": "index.js", 6 | "directories": { 7 | "example": "example" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/driftyco/cordova-plugin-ios-keychain.git" 15 | }, 16 | "keywords": [], 17 | "author": "", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/driftyco/cordova-plugin-ios-keychain/issues" 21 | }, 22 | "homepage": "https://github.com/driftyco/cordova-plugin-ios-keychain#readme" 23 | } 24 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | KeyChain Plugin for Cordova iOS 7 | 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 8 | Apache 2.0 9 | keychain 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/ios/A0SimpleKeychain+KeyPair.h: -------------------------------------------------------------------------------- 1 | // A0SimpleKeychain+KeyPair.h 2 | // 3 | // Copyright (c) 2014 Auth0 (http://auth0.com) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | #import "A0SimpleKeychain.h" 24 | 25 | typedef NS_ENUM(NSUInteger, A0SimpleKeychainRSAKeySize) { 26 | A0SimpleKeychainRSAKeySize512Bits = 512, 27 | A0SimpleKeychainRSAKeySize1024Bits = 1024, 28 | A0SimpleKeychainRSAKeySize2048Bits = 2048 29 | }; 30 | 31 | NS_ASSUME_NONNULL_BEGIN 32 | 33 | /** 34 | * Category of `A0SimpleKeychain` to handle RSA pairs keys in the Keychain 35 | */ 36 | @interface A0SimpleKeychain (KeyPair) 37 | 38 | /** 39 | * Generates a RSA key pair with a specific length and tags. 40 | * Each key is marked as permanent in the Keychain 41 | * 42 | * @param keyLength number of bits of the keys. 43 | * @param publicKeyTag tag of the public key 44 | * @param privateKeyTag tag of the private key 45 | * 46 | * @return if the key par is created it will return YES, otherwise NO. 47 | */ 48 | - (BOOL)generateRSAKeyPairWithLength:(A0SimpleKeychainRSAKeySize)keyLength 49 | publicKeyTag:(NSString *)publicKeyTag 50 | privateKeyTag:(NSString *)privateKeyTag; 51 | 52 | /** 53 | * Returns a RSA key as NSData. 54 | * 55 | * @param keyTag tag of the key 56 | * 57 | * @return the key as NSData or nil if not found 58 | */ 59 | - (nullable NSData *)dataForRSAKeyWithTag:(NSString *)keyTag; 60 | 61 | /** 62 | * Removes a key using its tag. 63 | * 64 | * @param keyTag tag of the key to remove 65 | * 66 | * @return if the key was removed successfuly. 67 | */ 68 | - (BOOL)deleteRSAKeyWithTag:(NSString *)keyTag; 69 | 70 | /** 71 | * Returns a RSA key as `SecKeyRef`. You must release it when you're done with it 72 | * 73 | * @param keyTag tag of the RSA Key 74 | * 75 | * @return SecKeyRef of RSA Key 76 | */ 77 | - (SecKeyRef)keyRefOfRSAKeyWithTag:(NSString *)keyTag; 78 | 79 | /** 80 | * Checks if a RSA key exists with a given tag. 81 | * 82 | * @param keyTag tag of RSA Key 83 | * 84 | * @return if the key exists or not. 85 | */ 86 | - (BOOL)hasRSAKeyWithTag:(NSString *)keyTag; 87 | 88 | @end 89 | 90 | @interface A0SimpleKeychain (Deprecated) 91 | 92 | /** 93 | * Returns the public key as NSData. 94 | * 95 | * @param keyTag tag of the public key 96 | * 97 | * @return the public key as NSData or nil if not found 98 | * 99 | * @deprecated 0.2.0 100 | */ 101 | - (NSData *)publicRSAKeyDataForTag:(NSString *)keyTag; 102 | 103 | @end 104 | 105 | NS_ASSUME_NONNULL_END 106 | -------------------------------------------------------------------------------- /src/ios/A0SimpleKeychain+KeyPair.m: -------------------------------------------------------------------------------- 1 | // A0SimpleKeychain+KeyPair.m 2 | // 3 | // Copyright (c) 2014 Auth0 (http://auth0.com) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | #import "A0SimpleKeychain+KeyPair.h" 24 | 25 | @implementation A0SimpleKeychain (KeyPair) 26 | 27 | - (BOOL)generateRSAKeyPairWithLength:(A0SimpleKeychainRSAKeySize)keyLength 28 | publicKeyTag:(NSString *)publicKeyTag 29 | privateKeyTag:(NSString *)privateKeyTag { 30 | NSAssert(publicKeyTag.length > 0 && privateKeyTag.length > 0, @"Both key tags should be non-empty!"); 31 | 32 | NSMutableDictionary *pairAttr = [@{ 33 | (__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeRSA, 34 | (__bridge id)kSecAttrKeySizeInBits: @(keyLength), 35 | } mutableCopy]; 36 | NSDictionary *privateAttr = @{ 37 | (__bridge id)kSecAttrIsPermanent: @YES, 38 | (__bridge id)kSecAttrApplicationTag: [privateKeyTag dataUsingEncoding:NSUTF8StringEncoding], 39 | }; 40 | NSDictionary *publicAttr = @{ 41 | (__bridge id)kSecAttrIsPermanent: @YES, 42 | (__bridge id)kSecAttrApplicationTag: [publicKeyTag dataUsingEncoding:NSUTF8StringEncoding], 43 | }; 44 | pairAttr[(__bridge id)kSecPrivateKeyAttrs] = privateAttr; 45 | pairAttr[(__bridge id)kSecPublicKeyAttrs] = publicAttr; 46 | 47 | SecKeyRef publicKeyRef; 48 | SecKeyRef privateKeyRef; 49 | 50 | OSStatus status = SecKeyGeneratePair((__bridge CFDictionaryRef)pairAttr, &publicKeyRef, &privateKeyRef); 51 | 52 | CFRelease(publicKeyRef); 53 | CFRelease(privateKeyRef); 54 | 55 | return status == errSecSuccess; 56 | } 57 | 58 | - (NSData *)dataForRSAKeyWithTag:(NSString *)keyTag { 59 | NSAssert(keyTag.length > 0, @"key tag should be non-empty!"); 60 | 61 | NSDictionary *publicKeyQuery = @{ 62 | (__bridge id)kSecClass: (__bridge id)kSecClassKey, 63 | (__bridge id)kSecAttrApplicationTag: [keyTag dataUsingEncoding:NSUTF8StringEncoding], 64 | (__bridge id)kSecAttrType: (__bridge id)kSecAttrKeyTypeRSA, 65 | (__bridge id)kSecReturnData: @YES, 66 | }; 67 | 68 | CFTypeRef dataRef; 69 | OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)publicKeyQuery, &dataRef); 70 | 71 | if (status != errSecSuccess) { 72 | return nil; 73 | } 74 | 75 | NSData *data = [NSData dataWithData:(__bridge NSData *)dataRef]; 76 | if (dataRef) { 77 | CFRelease(dataRef); 78 | } 79 | return data; 80 | } 81 | 82 | - (BOOL)hasRSAKeyWithTag:(NSString *)keyTag { 83 | NSAssert(keyTag.length > 0, @"key tag should be non-empty!"); 84 | 85 | NSDictionary *publicKeyQuery = @{ 86 | (__bridge id)kSecClass: (__bridge id)kSecClassKey, 87 | (__bridge id)kSecAttrApplicationTag: [keyTag dataUsingEncoding:NSUTF8StringEncoding], 88 | (__bridge id)kSecAttrType: (__bridge id)kSecAttrKeyTypeRSA, 89 | (__bridge id)kSecReturnData: @NO, 90 | }; 91 | 92 | OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)publicKeyQuery, NULL); 93 | return status == errSecSuccess; 94 | } 95 | 96 | 97 | - (BOOL)deleteRSAKeyWithTag:(NSString *)keyTag { 98 | NSAssert(keyTag.length > 0, @"key tag should be non-empty!"); 99 | NSDictionary *deleteKeyQuery = @{ 100 | (__bridge id)kSecClass: (__bridge id)kSecClassKey, 101 | (__bridge id)kSecAttrApplicationTag: [keyTag dataUsingEncoding:NSUTF8StringEncoding], 102 | (__bridge id)kSecAttrType: (__bridge id)kSecAttrKeyTypeRSA, 103 | }; 104 | 105 | OSStatus status = SecItemDelete((__bridge CFDictionaryRef)deleteKeyQuery); 106 | return status == errSecSuccess; 107 | } 108 | 109 | - (SecKeyRef)keyRefOfRSAKeyWithTag:(NSString *)keyTag { 110 | NSAssert(keyTag.length > 0, @"key tag should be non-empty!"); 111 | NSDictionary *query = @{ 112 | (__bridge id)kSecClass: (__bridge id)kSecClassKey, 113 | (__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeRSA, 114 | (__bridge id)kSecReturnRef: @YES, 115 | (__bridge id)kSecAttrApplicationTag: keyTag, 116 | }; 117 | SecKeyRef privateKeyRef = NULL; 118 | OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&privateKeyRef); 119 | if (status != errSecSuccess) { 120 | return NULL; 121 | } 122 | return privateKeyRef; 123 | } 124 | 125 | @end 126 | 127 | @implementation A0SimpleKeychain (Deprecated) 128 | 129 | - (NSData *)publicRSAKeyDataForTag:(NSString *)keyTag { 130 | return [self dataForRSAKeyWithTag:keyTag]; 131 | } 132 | 133 | @end 134 | -------------------------------------------------------------------------------- /src/ios/A0SimpleKeychain.h: -------------------------------------------------------------------------------- 1 | // A0SimpleKeychain.h 2 | // 3 | // Copyright (c) 2014 Auth0 (http://auth0.com) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | #import 24 | 25 | ///--------------------------------------------------- 26 | /// @name Keychain Items Accessibility Values 27 | ///--------------------------------------------------- 28 | 29 | /** 30 | * Enum with Kechain items accessibility types. It's a mirror of `kSecAttrAccessible` values. 31 | */ 32 | typedef NS_ENUM(NSInteger, A0SimpleKeychainItemAccessible) { 33 | /** 34 | * @see kSecAttrAccessibleWhenUnlocked 35 | */ 36 | A0SimpleKeychainItemAccessibleWhenUnlocked = 0, 37 | /** 38 | * @see kSecAttrAccessibleAfterFirstUnlock 39 | */ 40 | A0SimpleKeychainItemAccessibleAfterFirstUnlock, 41 | /** 42 | * @see kSecAttrAccessibleAlways 43 | */ 44 | A0SimpleKeychainItemAccessibleAlways, 45 | /** 46 | * @see kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly 47 | */ 48 | A0SimpleKeychainItemAccessibleWhenPasscodeSetThisDeviceOnly, 49 | /** 50 | * @see kSecAttrAccessibleWhenUnlockedThisDeviceOnly 51 | */ 52 | A0SimpleKeychainItemAccessibleWhenUnlockedThisDeviceOnly, 53 | /** 54 | * kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly 55 | */ 56 | A0SimpleKeychainItemAccessibleAfterFirstUnlockThisDeviceOnly, 57 | /** 58 | * @see kSecAttrAccessibleAlwaysThisDeviceOnly 59 | */ 60 | A0SimpleKeychainItemAccessibleAlwaysThisDeviceOnly 61 | }; 62 | 63 | #define A0ErrorDomain @"com.auth0.simplekeychain" 64 | 65 | /** 66 | * Enum with keychain error codes. It's a mirror of the keychain error codes. 67 | */ 68 | typedef NS_ENUM(NSInteger, A0SimpleKeychainError) { 69 | /** 70 | * @see errSecSuccess 71 | */ 72 | A0SimpleKeychainErrorNoError = 0, 73 | /** 74 | * @see errSecUnimplemented 75 | */ 76 | A0SimpleKeychainErrorUnimplemented = -4, 77 | /** 78 | * @see errSecParam 79 | */ 80 | A0SimpleKeychainErrorWrongParameter = -50, 81 | /** 82 | * @see errSecAllocate 83 | */ 84 | A0SimpleKeychainErrorAllocation = -108, 85 | /** 86 | * @see errSecNotAvailable 87 | */ 88 | A0SimpleKeychainErrorNotAvailable = -25291, 89 | /** 90 | * @see errSecAuthFailed 91 | */ 92 | A0SimpleKeychainErrorAuthFailed = -25293, 93 | /** 94 | * @see errSecDuplicateItem 95 | */ 96 | A0SimpleKeychainErrorDuplicateItem = -25299, 97 | /** 98 | * @see errSecItemNotFound 99 | */ 100 | A0SimpleKeychainErrorItemNotFound = -25300, 101 | /** 102 | * @see errSecInteractionNotAllowed 103 | */ 104 | A0SimpleKeychainErrorInteractionNotAllowed = -25308, 105 | /** 106 | * @see errSecDecode 107 | */ 108 | A0SimpleKeychainErrorDecode = -26275 109 | }; 110 | 111 | NS_ASSUME_NONNULL_BEGIN 112 | 113 | /** 114 | * A simple helper class to deal with storing and retrieving values from iOS Keychain. 115 | * 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). 116 | * The support is only available for iOS 8+, otherwise it will default using the coarse grained accesibility field. 117 | * 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). 118 | */ 119 | @interface A0SimpleKeychain : NSObject 120 | 121 | /** 122 | * Service name under all items are saved. Default value is Bundle Identifier. 123 | */ 124 | @property (readonly, nonatomic) NSString *service; 125 | 126 | /** 127 | * Access Group for Keychain item sharing. If it's nil no keychain sharing is possible. Default value is nil. 128 | */ 129 | @property (readonly, nullable, nonatomic) NSString *accessGroup; 130 | 131 | /** 132 | * What type of accessibility the items stored will have. All values are translated to `kSecAttrAccessible` constants. 133 | * Default value is A0SimpleKeychainItemAccessibleAfterFirstUnlock. 134 | * @see kSecAttrAccessible 135 | */ 136 | @property (assign, nonatomic) A0SimpleKeychainItemAccessible defaultAccessiblity; 137 | 138 | /** 139 | * Tells A0SimpleKeychain to use `kSecAttrAccessControl` instead of `kSecAttrAccessible`. It will work only in iOS 8+, defaulting to `kSecAttrAccessible` on lower version. 140 | * Default value is NO. 141 | */ 142 | @property (assign, nonatomic) BOOL useAccessControl; 143 | 144 | ///--------------------------------------------------- 145 | /// @name Initialization 146 | ///--------------------------------------------------- 147 | 148 | /** 149 | * Initialise a `A0SimpleKeychain` with default values. 150 | * 151 | * @return an initialised instance 152 | */ 153 | - (instancetype)init; 154 | 155 | /** 156 | * Initialise a `A0SimpleKeychain` with a given service. 157 | * 158 | * @param service name of the service to use to save items. 159 | * 160 | * @return an initialised instance. 161 | */ 162 | - (instancetype)initWithService:(NSString *)service; 163 | 164 | /** 165 | * Initialise a `A0SimpleKeychain` with a given service and access group. 166 | * 167 | * @param service name of the service to use to save items. 168 | * @param accessGroup name of the access group to share items. 169 | * 170 | * @return an initialised instance. 171 | */ 172 | - (instancetype)initWithService:(NSString *)service accessGroup:(nullable NSString *)accessGroup; 173 | 174 | ///--------------------------------------------------- 175 | /// @name Store values 176 | ///--------------------------------------------------- 177 | 178 | /** 179 | * Saves the NSString with the type `kSecClassGenericPassword` in the keychain. 180 | * 181 | * @param string value to save in the keychain 182 | * @param key key for the keychain entry. 183 | * 184 | * @return if the value was saved it will return YES. Otherwise it'll return NO. 185 | */ 186 | - (BOOL)setString:(NSString *)string forKey:(NSString *)key; 187 | 188 | /** 189 | * Saves the NSData with the type `kSecClassGenericPassword` in the keychain. 190 | * 191 | * @param data value to save in the keychain 192 | * @param key key for the keychain entry. 193 | * 194 | * @return if the value was saved it will return YES. Otherwise it'll return NO. 195 | */ 196 | - (BOOL)setData:(NSData *)data forKey:(NSString *)key; 197 | 198 | /** 199 | * Saves the NSString with the type `kSecClassGenericPassword` in the keychain. 200 | * 201 | * @param string value to save in the keychain 202 | * @param key key for the keychain entry. 203 | * @param message prompt message to display for TouchID/passcode prompt if neccesary 204 | * 205 | * @return if the value was saved it will return YES. Otherwise it'll return NO. 206 | */ 207 | - (BOOL)setString:(NSString *)string forKey:(NSString *)key promptMessage:(nullable NSString *)message; 208 | 209 | /** 210 | * Saves the NSData with the type `kSecClassGenericPassword` in the keychain. 211 | * 212 | * @param string value to save in the keychain 213 | * @param key key for the keychain entry. 214 | * @param message prompt message to display for TouchID/passcode prompt if neccesary 215 | * 216 | * @return if the value was saved it will return YES. Otherwise it'll return NO. 217 | */ 218 | - (BOOL)setData:(NSData *)data forKey:(NSString *)key promptMessage:(nullable NSString *)message; 219 | 220 | ///--------------------------------------------------- 221 | /// @name Remove values 222 | ///--------------------------------------------------- 223 | 224 | /** 225 | * Removes an entry from the Keychain using its key 226 | * 227 | * @param key the key of the entry to delete. 228 | * 229 | * @return If the entry was removed it will return YES. Otherwise it will return NO. 230 | */ 231 | - (BOOL)deleteEntryForKey:(NSString *)key; 232 | 233 | /** 234 | * Remove all entries from the kechain with the service and access group values. 235 | */ 236 | - (void)clearAll; 237 | 238 | ///--------------------------------------------------- 239 | /// @name Obtain values 240 | ///--------------------------------------------------- 241 | 242 | /** 243 | * Fetches a NSString from the keychain 244 | * 245 | * @param key the key of the value to fetch 246 | * 247 | * @return the value or nil if an error occurs. 248 | */ 249 | - (nullable NSString *)stringForKey:(NSString *)key; 250 | 251 | /** 252 | * Fetches a NSData from the keychain 253 | * 254 | * @param key the key of the value to fetch 255 | * 256 | * @return the value or nil if an error occurs. 257 | */ 258 | - (nullable NSData *)dataForKey:(NSString *)key; 259 | 260 | /** 261 | * Fetches a NSString from the keychain 262 | * 263 | * @param key the key of the value to fetch 264 | * @param message prompt message to display for TouchID/passcode prompt if neccesary 265 | * 266 | * @return the value or nil if an error occurs. 267 | */ 268 | - (nullable NSString *)stringForKey:(NSString *)key promptMessage:(nullable NSString *)message; 269 | 270 | /** 271 | * Fetches a NSData from the keychain 272 | * 273 | * @param key the key of the value to fetch 274 | * @param message prompt message to display for TouchID/passcode prompt if neccesary 275 | * 276 | * @return the value or nil if an error occurs. 277 | */ 278 | - (nullable NSData *)dataForKey:(NSString *)key promptMessage:(nullable NSString *)message; 279 | 280 | /** 281 | * Fetches a NSData from the keychain 282 | * 283 | * @param key the key of the value to fetch 284 | * @param message prompt message to display for TouchID/passcode prompt if neccesary 285 | * @param err Returns an error, if the item cannot be retrieved. F.e. item not found 286 | * or user authentication failed in TouchId case. 287 | * 288 | * @return the value or nil if an error occurs. 289 | */ 290 | - (nullable NSData *)dataForKey:(NSString *)key promptMessage:(nullable NSString *)message error:(NSError **)err; 291 | 292 | /** 293 | * Checks if a key has a value in the Keychain 294 | * 295 | * @param key the key to check if it has a value 296 | * 297 | * @return if the key has an associated value in the Keychain or not. 298 | */ 299 | - (BOOL)hasValueForKey:(NSString *)key; 300 | 301 | ///--------------------------------------------------- 302 | /// @name Create helper methods 303 | ///--------------------------------------------------- 304 | 305 | /** 306 | * Creates a new instance of `A0SimpleKeychain` 307 | * 308 | * @return a new instance 309 | */ 310 | + (A0SimpleKeychain *)keychain; 311 | 312 | /** 313 | * Creates a new instance of `A0SimpleKeychain` with a service name. 314 | * 315 | * @param service name of the service under all items will be stored. 316 | * 317 | * @return a new instance 318 | */ 319 | + (A0SimpleKeychain *)keychainWithService:(NSString *)service; 320 | 321 | /** 322 | * Creates a new instance of `A0SimpleKeychain` with a service name and access group 323 | * 324 | * @param service name of the service under all items will be stored. 325 | * @param accessGroup name of the access group to share keychain items. 326 | * 327 | * @return a new instance 328 | */ 329 | + (A0SimpleKeychain *)keychainWithService:(NSString *)service accessGroup:(NSString *)accessGroup; 330 | 331 | @end 332 | 333 | NS_ASSUME_NONNULL_END -------------------------------------------------------------------------------- /src/ios/A0SimpleKeychain.m: -------------------------------------------------------------------------------- 1 | // A0SimpleKeychain.h 2 | // 3 | // Copyright (c) 2014 Auth0 (http://auth0.com) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | #import "A0SimpleKeychain.h" 24 | 25 | @interface A0SimpleKeychain () 26 | 27 | @end 28 | 29 | @implementation A0SimpleKeychain 30 | 31 | - (instancetype)init { 32 | NSString *service = [[NSBundle mainBundle] bundleIdentifier]; 33 | return [self initWithService:service accessGroup:nil]; 34 | } 35 | 36 | - (instancetype)initWithService:(NSString *)service { 37 | return [self initWithService:service accessGroup:nil]; 38 | } 39 | 40 | - (instancetype)initWithService:(NSString *)service accessGroup:(NSString *)accessGroup { 41 | self = [super init]; 42 | if (self) { 43 | _service = service; 44 | _accessGroup = accessGroup; 45 | _defaultAccessiblity = A0SimpleKeychainItemAccessibleAfterFirstUnlock; 46 | _useAccessControl = NO; 47 | } 48 | return self; 49 | } 50 | 51 | - (NSString *)stringForKey:(NSString *)key { 52 | return [self stringForKey:key promptMessage:nil]; 53 | } 54 | 55 | - (NSData *)dataForKey:(NSString *)key { 56 | return [self dataForKey:key promptMessage:nil]; 57 | } 58 | 59 | - (NSString *)stringForKey:(NSString *)key promptMessage:(NSString *)message { 60 | NSData *data = [self dataForKey:key promptMessage:message]; 61 | NSString *string = nil; 62 | if (data) { 63 | string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 64 | } 65 | return string; 66 | } 67 | 68 | - (NSData *)dataForKey:(NSString *)key promptMessage:(NSString *)message { 69 | return [self dataForKey:key promptMessage:message error:nil]; 70 | } 71 | 72 | - (NSData *)dataForKey:(NSString *)key promptMessage:(NSString *)message error:(NSError**)err { 73 | if (!key) { 74 | return nil; 75 | } 76 | 77 | NSDictionary *query = [self queryFetchOneByKey:key message:message]; 78 | CFTypeRef data = nil; 79 | OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &data); 80 | if (status != errSecSuccess) { 81 | if(err != nil) { 82 | *err = [NSError errorWithDomain:A0ErrorDomain code:status userInfo:@{NSLocalizedDescriptionKey : [self stringForSecStatus:status]}]; 83 | } 84 | return nil; 85 | } 86 | 87 | NSData *dataFound = [NSData dataWithData:(__bridge NSData *)data]; 88 | if (data) { 89 | CFRelease(data); 90 | } 91 | 92 | return dataFound; 93 | } 94 | 95 | - (BOOL)hasValueForKey:(NSString *)key { 96 | if (!key) { 97 | return NO; 98 | } 99 | NSDictionary *query = [self queryFindByKey:key message:nil]; 100 | OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL); 101 | return status == errSecSuccess; 102 | } 103 | 104 | - (BOOL)setString:(NSString *)string forKey:(NSString *)key { 105 | return [self setString:string forKey:key promptMessage:nil]; 106 | } 107 | 108 | - (BOOL)setData:(NSData *)data forKey:(NSString *)key { 109 | return [self setData:data forKey:key promptMessage:nil]; 110 | } 111 | 112 | - (BOOL)setString:(NSString *)string forKey:(NSString *)key promptMessage:(NSString *)message { 113 | NSData *data = key ? [string dataUsingEncoding:NSUTF8StringEncoding] : nil; 114 | return [self setData:data forKey:key promptMessage:message]; 115 | } 116 | 117 | 118 | - (BOOL)setData:(NSData *)data forKey:(NSString *)key promptMessage:(NSString *)message { 119 | if (!key) { 120 | return NO; 121 | } 122 | 123 | NSDictionary *query = [self queryFindByKey:key message:message]; 124 | 125 | // Touch ID case 126 | if (self.useAccessControl && self.defaultAccessiblity == A0SimpleKeychainItemAccessibleWhenPasscodeSetThisDeviceOnly) { 127 | // TouchId case. Doesn't support updating keychain items 128 | // see Known Issues: https://developer.apple.com/library/ios/releasenotes/General/RN-iOSSDK-8.0/ 129 | // We need to delete old and add a new item. This can fail 130 | OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query); 131 | if (status == errSecSuccess || status == errSecItemNotFound) { 132 | NSDictionary *newQuery = [self queryNewKey:key value:data]; 133 | OSStatus status = SecItemAdd((__bridge CFDictionaryRef)newQuery, NULL); 134 | return status == errSecSuccess; 135 | } 136 | } 137 | 138 | // Normal case 139 | OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL); 140 | if (status == errSecSuccess) { 141 | if (data) { 142 | NSDictionary *updateQuery = [self queryUpdateValue:data message:message]; 143 | status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)updateQuery); 144 | return status == errSecSuccess; 145 | } else { 146 | OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query); 147 | return status == errSecSuccess; 148 | } 149 | } else { 150 | NSDictionary *newQuery = [self queryNewKey:key value:data]; 151 | OSStatus status = SecItemAdd((__bridge CFDictionaryRef)newQuery, NULL); 152 | return status == errSecSuccess; 153 | } 154 | } 155 | 156 | - (BOOL)deleteEntryForKey:(NSString *)key { 157 | if (!key) { 158 | return NO; 159 | } 160 | NSDictionary *deleteQuery = [self queryFindByKey:key message:nil]; 161 | OSStatus status = SecItemDelete((__bridge CFDictionaryRef)deleteQuery); 162 | return status == errSecSuccess; 163 | } 164 | 165 | - (void)clearAll { 166 | #if TARGET_OS_IPHONE 167 | NSDictionary *query = [self queryFindAll]; 168 | CFArrayRef result = nil; 169 | OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result); 170 | if (status == errSecSuccess || status == errSecItemNotFound) { 171 | NSArray *items = [NSArray arrayWithArray:(__bridge NSArray *)result]; 172 | CFBridgingRelease(result); 173 | for (NSDictionary *item in items) { 174 | NSMutableDictionary *queryDelete = [[NSMutableDictionary alloc] initWithDictionary:item]; 175 | queryDelete[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword; 176 | 177 | OSStatus status = SecItemDelete((__bridge CFDictionaryRef)queryDelete); 178 | if (status != errSecSuccess) { 179 | break; 180 | } 181 | } 182 | } 183 | #else 184 | NSMutableDictionary *queryDelete = [self baseQuery]; 185 | queryDelete[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword; 186 | queryDelete[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll; 187 | OSStatus status = SecItemDelete((__bridge CFDictionaryRef)queryDelete); 188 | if (status != errSecSuccess) { 189 | return; 190 | } 191 | #endif 192 | } 193 | 194 | + (A0SimpleKeychain *)keychain { 195 | return [[A0SimpleKeychain alloc] init]; 196 | } 197 | 198 | + (A0SimpleKeychain *)keychainWithService:(NSString *)service { 199 | return [[A0SimpleKeychain alloc] initWithService:service]; 200 | } 201 | 202 | + (A0SimpleKeychain *)keychainWithService:(NSString *)service accessGroup:(NSString *)accessGroup { 203 | return [[A0SimpleKeychain alloc] initWithService:service accessGroup:accessGroup]; 204 | } 205 | 206 | #pragma mark - Utility methods 207 | 208 | - (CFTypeRef)accessibility { 209 | CFTypeRef accessibility; 210 | switch (self.defaultAccessiblity) { 211 | case A0SimpleKeychainItemAccessibleAfterFirstUnlock: 212 | accessibility = kSecAttrAccessibleAfterFirstUnlock; 213 | break; 214 | case A0SimpleKeychainItemAccessibleAlways: 215 | accessibility = kSecAttrAccessibleAlways; 216 | break; 217 | case A0SimpleKeychainItemAccessibleAfterFirstUnlockThisDeviceOnly: 218 | accessibility = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly; 219 | break; 220 | case A0SimpleKeychainItemAccessibleAlwaysThisDeviceOnly: 221 | accessibility = kSecAttrAccessibleAlwaysThisDeviceOnly; 222 | break; 223 | #if TARGET_OS_IPHONE 224 | case A0SimpleKeychainItemAccessibleWhenPasscodeSetThisDeviceOnly: 225 | #ifdef __IPHONE_8_0 226 | if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_7_1) { //iOS 8 227 | accessibility = kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly; 228 | } else { //iOS <= 7.1 229 | accessibility = kSecAttrAccessibleWhenUnlockedThisDeviceOnly; 230 | } 231 | #else 232 | accessibility = kSecAttrAccessibleWhenUnlockedThisDeviceOnly; 233 | #endif 234 | #endif 235 | break; 236 | case A0SimpleKeychainItemAccessibleWhenUnlocked: 237 | accessibility = kSecAttrAccessibleWhenUnlocked; 238 | break; 239 | case A0SimpleKeychainItemAccessibleWhenUnlockedThisDeviceOnly: 240 | accessibility = kSecAttrAccessibleWhenUnlockedThisDeviceOnly; 241 | break; 242 | default: 243 | accessibility = kSecAttrAccessibleWhenUnlockedThisDeviceOnly; 244 | } 245 | return accessibility; 246 | } 247 | 248 | - (NSString*)stringForSecStatus:(OSStatus)status { 249 | 250 | switch(status) { 251 | case errSecSuccess: 252 | return NSLocalizedStringFromTable(@"errSecSuccess: No error", @"SimpleKeychain", @"Possible error from keychain. "); 253 | case errSecUnimplemented: 254 | return NSLocalizedStringFromTable(@"errSecUnimplemented: Function or operation not implemented", @"SimpleKeychain", @"Possible error from keychain. "); 255 | case errSecParam: 256 | return NSLocalizedStringFromTable(@"errSecParam: One or more parameters passed to the function were not valid", @"SimpleKeychain", @"Possible error from keychain. "); 257 | case errSecAllocate: 258 | return NSLocalizedStringFromTable(@"errSecAllocate: Failed to allocate memory", @"SimpleKeychain", @"Possible error from keychain. "); 259 | case errSecNotAvailable: 260 | return NSLocalizedStringFromTable(@"errSecNotAvailable: No trust results are available", @"SimpleKeychain", @"Possible error from keychain. "); 261 | case errSecAuthFailed: 262 | return NSLocalizedStringFromTable(@"errSecAuthFailed: Authorization/Authentication failed", @"SimpleKeychain", @"Possible error from keychain. "); 263 | case errSecDuplicateItem: 264 | return NSLocalizedStringFromTable(@"errSecDuplicateItem: The item already exists", @"SimpleKeychain", @"Possible error from keychain. "); 265 | case errSecItemNotFound: 266 | return NSLocalizedStringFromTable(@"errSecItemNotFound: The item cannot be found", @"SimpleKeychain", @"Possible error from keychain. "); 267 | case errSecInteractionNotAllowed: 268 | return NSLocalizedStringFromTable(@"errSecInteractionNotAllowed: Interaction with the Security Server is not allowed", @"SimpleKeychain", @"Possible error from keychain. "); 269 | case errSecDecode: 270 | return NSLocalizedStringFromTable(@"errSecDecode: Unable to decode the provided data", @"SimpleKeychain", @"Possible error from keychain. "); 271 | default: 272 | return [NSString stringWithFormat:NSLocalizedStringFromTable(@"Unknown error code %d", @"SimpleKeychain", @"Possible error from keychain. "), status]; 273 | } 274 | } 275 | 276 | #pragma mark - Query Dictionary Builder methods 277 | 278 | - (NSMutableDictionary *)baseQuery { 279 | NSMutableDictionary *attributes = [@{ 280 | (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, 281 | (__bridge id)kSecAttrService: self.service, 282 | } mutableCopy]; 283 | #if !TARGET_IPHONE_SIMULATOR 284 | if (self.accessGroup) { 285 | attributes[(__bridge id)kSecAttrAccessGroup] = self.accessGroup; 286 | } 287 | #endif 288 | 289 | return attributes; 290 | } 291 | 292 | - (NSDictionary *)queryFindAll { 293 | NSMutableDictionary *query = [self baseQuery]; 294 | [query addEntriesFromDictionary:@{ 295 | (__bridge id)kSecReturnAttributes: @YES, 296 | (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitAll, 297 | }]; 298 | return query; 299 | } 300 | 301 | - (NSDictionary *)queryFindByKey:(NSString *)key message:(NSString *)message { 302 | NSAssert(key != nil, @"Must have a valid non-nil key"); 303 | NSMutableDictionary *query = [self baseQuery]; 304 | query[(__bridge id)kSecAttrAccount] = key; 305 | #if TARGET_OS_IPHONE 306 | if (message) { 307 | query[(__bridge id)kSecUseOperationPrompt] = message; 308 | } 309 | #endif 310 | return query; 311 | } 312 | 313 | - (NSDictionary *)queryUpdateValue:(NSData *)data message:(NSString *)message { 314 | if (message) { 315 | return @{ 316 | #if TARGET_OS_IPHONE 317 | (__bridge id)kSecUseOperationPrompt: message, 318 | #endif 319 | (__bridge id)kSecValueData: data, 320 | }; 321 | } else { 322 | return @{ 323 | (__bridge id)kSecValueData: data, 324 | }; 325 | } 326 | } 327 | 328 | - (NSDictionary *)queryNewKey:(NSString *)key value:(NSData *)value { 329 | NSMutableDictionary *query = [self baseQuery]; 330 | query[(__bridge id)kSecAttrAccount] = key; 331 | query[(__bridge id)kSecValueData] = value; 332 | #if TARGET_OS_IPHONE 333 | #ifdef __IPHONE_8_0 334 | if (self.useAccessControl && floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_7_1) { 335 | CFErrorRef error = NULL; 336 | SecAccessControlRef accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, [self accessibility], kSecAccessControlUserPresence, &error); 337 | if (error == NULL || accessControl != NULL) { 338 | query[(__bridge id)kSecAttrAccessControl] = (__bridge_transfer id)accessControl; 339 | query[(__bridge id)kSecUseNoAuthenticationUI] = @YES; 340 | } 341 | } else { 342 | query[(__bridge id)kSecAttrAccessible] = (__bridge id)[self accessibility]; 343 | } 344 | #else 345 | query[(__bridge id)kSecAttrAccessible] = (__bridge id)[self accessibility]; 346 | #endif 347 | #endif 348 | return query; 349 | } 350 | 351 | - (NSDictionary *)queryFetchOneByKey:(NSString *)key message:(NSString *)message { 352 | NSMutableDictionary *query = [self baseQuery]; 353 | [query addEntriesFromDictionary:@{ 354 | (__bridge id)kSecReturnData: @YES, 355 | (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne, 356 | (__bridge id)kSecAttrAccount: key, 357 | }]; 358 | #if TARGET_OS_IPHONE 359 | if (self.useAccessControl) { 360 | if (message && floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_7_1) { 361 | query[(__bridge id)kSecUseOperationPrompt] = message; 362 | } 363 | } 364 | #endif 365 | 366 | return query; 367 | } 368 | @end 369 | -------------------------------------------------------------------------------- /src/ios/CDVKeychain.h: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | #import 21 | #import 22 | #import 23 | 24 | @interface CDVKeychain : CDVPlugin { 25 | } 26 | 27 | - (void) get:(CDVInvokedUrlCommand*)command; 28 | - (void) set:(CDVInvokedUrlCommand*)command; 29 | - (void) remove:(CDVInvokedUrlCommand*)command; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /src/ios/CDVKeychain.m: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | #import "CDVKeychain.h" 21 | #import "A0SimpleKeychain.h" 22 | 23 | @implementation CDVKeychain 24 | 25 | - (void) get:(CDVInvokedUrlCommand*)command { 26 | [self.commandDelegate runInBackground:^{ 27 | NSArray* arguments = command.arguments; 28 | CDVPluginResult* pluginResult = nil; 29 | 30 | if([arguments count] < 2) { 31 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR 32 | messageAsString:@"incorrect number of arguments for getWithTouchID"]; 33 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 34 | return; 35 | } 36 | 37 | NSString *key = [arguments objectAtIndex:0]; 38 | NSString *touchIDMessage = [arguments objectAtIndex:1]; 39 | 40 | NSString *message = NSLocalizedString(@"Please Authenticate", nil); 41 | if(![touchIDMessage isEqual:[NSNull null]]) { 42 | message = NSLocalizedString(touchIDMessage, @"Prompt TouchID message"); 43 | } 44 | 45 | A0SimpleKeychain *keychain = [A0SimpleKeychain keychain]; 46 | 47 | keychain.useAccessControl = YES; 48 | keychain.defaultAccessiblity = A0SimpleKeychainItemAccessibleWhenPasscodeSetThisDeviceOnly; 49 | 50 | NSString *value = [keychain stringForKey:key promptMessage:message]; 51 | 52 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:value]; 53 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 54 | }]; 55 | } 56 | 57 | - (void) set:(CDVInvokedUrlCommand*)command { 58 | [self.commandDelegate runInBackground:^{ 59 | NSArray* arguments = command.arguments; 60 | CDVPluginResult* pluginResult = nil; 61 | 62 | if([arguments count] < 3) { 63 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR 64 | messageAsString:@"incorrect number of arguments for setWithTouchID"]; 65 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 66 | return; 67 | } 68 | 69 | NSString* key = [arguments objectAtIndex:0]; 70 | NSString* value = [arguments objectAtIndex:1]; 71 | BOOL useTouchID = [[arguments objectAtIndex:2] boolValue]; 72 | 73 | A0SimpleKeychain *keychain = [A0SimpleKeychain keychain]; 74 | 75 | if(useTouchID) { 76 | keychain.useAccessControl = YES; 77 | keychain.defaultAccessiblity = A0SimpleKeychainItemAccessibleWhenPasscodeSetThisDeviceOnly; 78 | } 79 | 80 | [keychain setString:value forKey:key]; 81 | 82 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 83 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 84 | }]; 85 | } 86 | 87 | - (void) remove:(CDVInvokedUrlCommand*)command { 88 | [self.commandDelegate runInBackground:^{ 89 | NSArray* arguments = command.arguments; 90 | CDVPluginResult* pluginResult = nil; 91 | 92 | if([arguments count] < 1) { 93 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR 94 | messageAsString:@"incorrect number of arguments for remove"]; 95 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 96 | return; 97 | } 98 | 99 | NSString *key = [arguments objectAtIndex:0]; 100 | 101 | A0SimpleKeychain *keychain = [A0SimpleKeychain keychain]; 102 | [keychain deleteEntryForKey:key]; 103 | 104 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 105 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 106 | }]; 107 | } 108 | 109 | @end 110 | -------------------------------------------------------------------------------- /src/ios/SimpleKeychain.h: -------------------------------------------------------------------------------- 1 | // SimpleKeychain.h 2 | // 3 | // Copyright (c) 2014 Auth0 (http://auth0.com) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | #import 24 | 25 | //! Project version number for SimpleKeychain. 26 | FOUNDATION_EXPORT double SimpleKeychainVersionNumber; 27 | 28 | //! Project version string for SimpleKeychain. 29 | FOUNDATION_EXPORT const unsigned char SimpleKeychainVersionString[]; 30 | 31 | // In this header, you should import all the public headers of your framework using statements like #import 32 | 33 | #import 34 | #import 35 | -------------------------------------------------------------------------------- /www/keychain.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | // This is installed as a so it doesn't have a cordova.define wrapper 21 | 22 | var exec = require('cordova/exec'); 23 | 24 | 25 | var Keychain = { 26 | serviceName: "Keychain", 27 | 28 | get: function(success, error, key, touchIDMessage) { 29 | exec(success, error, this.serviceName, "get", [key, touchIDMessage]); 30 | }, 31 | set: function(success, error, key, value, useTouchID) { 32 | exec(success, error, this.serviceName, "set", [key, value, !!useTouchID]); 33 | }, 34 | 35 | setJson: function(success, error, key, obj, useTouchID) { 36 | var value = JSON.stringify(obj); 37 | value = value 38 | .replace(/[\\]/g, '\\\\') 39 | .replace(/[\"]/g, '\\\"') 40 | .replace(/[\/]/g, '\\/') 41 | .replace(/[\b]/g, '\\b') 42 | .replace(/[\f]/g, '\\f') 43 | .replace(/[\n]/g, '\\n') 44 | .replace(/[\r]/g, '\\r') 45 | .replace(/[\t]/g, '\\t'); 46 | 47 | exec(success, error, this.serviceName, "set", [key, value, !!useTouchID]); 48 | }, 49 | 50 | getJson: function(success, error, key, touchIDMessage) { 51 | var cb = function(v) { 52 | if(!v) { 53 | success(null); 54 | return; 55 | } 56 | v = v.replace(/\\\"/g, '"'); 57 | 58 | try { 59 | var obj = JSON.parse(v); 60 | success(obj); 61 | } catch(e) { 62 | error(e); 63 | } 64 | }; 65 | exec(cb, error, this.serviceName, "get", [key, touchIDMessage]); 66 | }, 67 | 68 | remove: function(successCallback, failureCallback, key) { 69 | exec(successCallback, failureCallback, this.serviceName, "remove", [key]); 70 | } 71 | }; 72 | 73 | module.exports = Keychain; 74 | --------------------------------------------------------------------------------