[TIMOB-20547] iOS: Add Keychain-access to Ti.TouchID, add passcode-fallback mode, expose new iOS 10 TouchID API's
GitHub Issue | n/a |
---|---|
Type | New Feature |
Priority | Medium |
Status | Closed |
Resolution | Fixed |
Resolution Date | 2017-01-11T04:25:30.000+0000 |
Affected Version/s | n/a |
Fix Version/s | Release 6.1.0 |
Components | iOS |
Labels | n/a |
Reporter | Ricardo Ramirez |
Assignee | Hans Knöchel |
Created | 2016-03-10T19:22:46.000+0000 |
Updated | 2017-02-02T15:23:45.000+0000 |
Description
Customers are unable to secure a key-value pair in the Keychain and then unlock it using Touch Id or passcode.
Here is a talk by Apple at WWDC 2014 that describes what users are looking to do.
https://developer.apple.com/videos/play/wwdc2014/711/
We have [ti.touchid](https://github.com/appcelerator-modules/ti.touchid) for fingerprint-secured access and there is a [native module](https://github.com/pegli/ti_keychain) that supports keychain access.
[~cng] [~hansknoechel] Any update here ? The Appcelerator ti.touchid module only uses LocalAuthentication and does not involve the KeyChain so its basically useless if you want to use it to replace a password. The https://github.com/pegli/ti_keychain native module wraps the KeyChain but does not utilize TouchId.
[~rramirez] iOS does not expose the keychain together with the touchID-dialog (same natively). So Apple does not see that use-case, yet. Similar discussion here: https://github.com/appcelerator-modules/ti.touchid/issues/15
Hans, this functionality was added in iOS 9. Apple created a sample project to demonstrate it. https://developer.apple.com/library/ios/samplecode/KeychainTouchID/Introduction/Intro.html
Also, issue 15 on ti.touchid is completely unrelated. Replacing a password was just an example I brought up. What we are looking for is to secure a key-value pair in the keychain that is only accessible through TouchId.
Hey Collin, thanks for the feedback! So the Github reference dealt with the ability to enter the native PIN, so thats why I called it "similar". I checked the example. So it is possible to save a password and in the keychain and fetch it using TouchID. Is that what you want? And how would you like to access it from the JS? As more infos the better. Thanks!
Ultimately our ask is for iOS/Android KeyChain access. Both platforms allow data to be encrypted within a keychain at a variety of levels. *Basic*: Securely store key-value pairs in the Keychain. These values are only released once a device has been unlocked. *Enchanced*: These values are only released once a device has been unlocked with a passcode/pattern/fingerprint and the key-values are removed automatically if the passcode/pattern/fingerprint are ever removed. *Biometric*: same as Enhanced but only applies to fingerprints. If you're looking at TouchId only an API might look like this: isTouchIdSupported() : boolean isTouchIdEnabled() : boolean saveWithTouchId(key, value) : boolean readWithTouchId(key, callback) : void // where callback returns a success or error Android M (SDK 23) has introduced similar support for fingerprint access and keychain integration so a cross-platform module could be created.
[~hansknoechel] Can we use Hyperloop here?
Any update ?
Ok, so the primary request is to enable the key-value storage and protect it with a fingerprint. Since all keychain-operations need to be done on a C-level, Apple provides an (official) [Objective-C wrapper](https://developer.apple.com/library/ios/samplecode/GenericKeychain/Listings/Classes_KeychainItemWrapper_h.html) we could use to wrap it. (*EDIT*: See the full demo below) [~CollinPrice] Is that something you could work with? Of course the PR is not ready for production, yet and is awaiting feedback on the public interface-proposal. :-)
After thinking it a bit more, I would not add the
authorizationRequired
to that API, since it would no the same as we already expose with theauthenticate
method. So chaining that one before calling the above method to save / access data would make more sense to me. Also, looking into the sample app that Apple provides for keychain access, there is a special dialog to save passwords to the keychain by typing and confirming it in a system-dialog. We could also expose that one toaddPasswordToKeychain
if needed. Please let me know what exactly is required in the first step as well as future steps for additional implementations. Thanks!PR: https://github.com/appcelerator-modules/ti.touchid/pull/23 New version (1.3.0): [Download](https://www.dropbox.com/s/ro3lknh5v7j2rqy/ti.touchid-iphone-1.3.0.zip?dl=0) The above PR adds support for writing, reading and deleting values from the native iOS-keychain. As discussed about, I removed the
authorizationRequired
from the params list to keep the keychain clean. Please let me know if you need additional fields to be covered. *EDIT*: I will add docs and put this in review as soon as the public interface is approved. Demo:The PR is ready to review now! I'll update the exmaple above and add another example that covers the new TouchID features that I also added.
Ok, so I kind of escalated here (in a good way :-). We now support the following new features: - Save, read and delete values with the native iOS keychain - Expose the properties
maxBiometryFailures
,allowableReuseDuration
,fallbackTitle
andcancelTitle
to customize the Touch ID experience - Expose theinvalidate()
method to hide the Touch ID dialog programatically - Major refactoring in both code-base and documentation - Align module versions to match 2.1.0 Packaged module: https://github.com/appcelerator-modules/ti.touchid/releases/tag/2.1.0-beta.1 Test-Case 1: Write, read and delete from the keychainTest-Case 2: Expose new Touch ID properties, allow the user to invalidate the dialog manually (cc [~emerriman]):
Is there documentation for the accessControlMode and accessibilityMode iOS arguments?
Could you please provide reference for accessControlMode possible values?
Chillll :-) It's [here](https://github.com/hansemannn/ti.touchid/blob/fa99e6cdf935acabd51f06baa61a939b3966485a/apidoc/TouchId.yml#L541-L556) with some constants to use there like advised natively.
Hi Hans, I've done a bit of testing using your examples above and here are my results: Case 1. 1) There is an error in the docs stating that for iOS 10 and up I need to add entitlements file in order to access keychain. I had to do that on my test device that is running iOS 8.2, otherwise callback was successfull, but I could see native iOS error being thrown. I was also not able to retrieve saved keychain item in that case. 2) Saving to keychain without using
accessibilityMode
andaccessControlMode
works as expected 3) Retrieving from keychain works as expected 4) Deleting from keychain works as expected 5) Saving to keychain without using withaccessControlMode
flag being set crashes the app without any errors being throw. Tested with all possible values. 6) Saving to keychain usingaccessibilityMode
does not seem to be working as expected for values ending with*THIS_DEVICE_ONLY
. I've saved to keychain with this flag, backed up device, restored backup on another device and I was still able to retrieve item from keychain. Case 2. 1)TiTouchId.isSupported
does not work as intended. In case neither password/fingerprint are set up,TiTouchId.isSupported
returns false on devices that actually support the feature (Tested on devices with/without the fingerprint reader). 2) UsingTouchID.authenticate
without checkingTiTouchId.isSupported
: * Passcode turned off, no fingerprints added -> recievedTiTouchId.ERROR_TOUCH_ID_NOT_AVAILABLE
instead ofTiTouchId.ERROR_PASSCODE_NOT_SET
orTiTouchId.ERROR_TOUCH_ID_NOT_ENROLLED
* Passcode turned on, no fingerprints added -> recievedTiTouchId.ERROR_TOUCH_ID_NOT_AVAILABLE
insteadTiTouchId.ERROR_TOUCH_ID_NOT_ENROLLED
3) When touchId is being locked out due to user failing to provide correct fingerprint *maxBiometryFailures
value does not work, I've set it to values from 1 to 5, I've always been asked 5 times before lockout. * after being locked out, on iPhone 6 OS v8.2 further attempt to authenticate launch passcode unlock screen. On iPhone7 OS 10.0.2 further attempts to authenticate only yieldTiTouchId.ERROR_TOUCH_ID_NOT_AVAILABLE
code being thrown and no unlock modal is presented. * On iPhone7, after 5th incorrect authentication attemptTiTouchId.ERROR_TOUCH_ID_LOCKOUT
is being thrown, which is correct, but any consecutive tries only causeTiTouchId.ERROR_TOUCH_ID_NOT_AVAILABLE
. I believe either unlock modal should be presented orTiTouchId.ERROR_TOUCH_ID_LOCKOUT
code should be shown. 4)allowableReuseDuration
,fallbackTitle
andcancelTitle
do not seem to work at all. 5) All other cases i.e. user cancel, user fallback, authentication failed, authentication passed, system cancel etc work as expected. ---- Tested with: iPhone6 OS v8.2, iPhone7 OS7 v10.0.2, iPad Mini (without fingerprint scanner), SDK v5.5.1GA, TiTouchId Module v 2.1.0 Sorry for the long post. Please let me know if you have any questions.Hey Nikita, addressing your comments line by line: {quote} 1) There is an error in the docs stating that for iOS 10 and up I need to add entitlements file in order to access keychain. I had to do that on my test device that is running iOS 8.2, otherwise callback was successfull, but I could see native iOS error being thrown. I was also not able to retrieve saved keychain item in that case. {quote} So the keychain access entitlement is also required for iOS 8? Then I'll update the docs. And you meant "otherwise callback was unsuccessful", right? Otherwise, I don't get the sentence structure {quote} 2) Saving to keychain without using accessibilityMode and accessControlMode works as expected {quote} OK {quote} 3) Retrieving from keychain works as expected {quote} OK {quote} 4) Deleting from keychain works as expected {quote} OK {quote} 5) Saving to keychain without using with accessControlMode flag being set crashes the app without any errors being throw. Tested with all possible values. {quote} Example please {quote} 6) Saving to keychain using accessibilityMode does not seem to be working as expected for values ending with *THIS_DEVICE_ONLY. I've saved to keychain with this flag, backed up device, restored backup on another device and I was still able to retrieve item from keychain. Case 2. {quote} Example please {quote} 1) TiTouchId.isSupported does not work as intended. In case neither password/fingerprint are set up, TiTouchId.isSupported returns false on devices that actually support the feature (Tested on devices with/without the fingerprint reader). 2) Using TouchID.authenticate without checking TiTouchId.isSupported: Passcode turned off, no fingerprints added -> recieved TiTouchId.ERROR_TOUCH_ID_NOT_AVAILABLE instead of TiTouchId.ERROR_PASSCODE_NOT_SET or TiTouchId.ERROR_TOUCH_ID_NOT_ENROLLED Passcode turned on, no fingerprints added -> recieved TiTouchId.ERROR_TOUCH_ID_NOT_AVAILABLE instead TiTouchId.ERROR_TOUCH_ID_NOT_ENROLLED 3) When touchId is being locked out due to user failing to provide correct fingerprint maxBiometryFailures value does not work, I've set it to values from 1 to 5, I've always been asked 5 times before lockout. after being locked out, on iPhone 6 OS v8.2 further attempt to authenticate launch passcode unlock screen. On iPhone7 OS 10.0.2 further attempts to authenticate only yield TiTouchId.ERROR_TOUCH_ID_NOT_AVAILABLE code being thrown and no unlock modal is presented. On iPhone7, after 5th incorrect authentication attempt TiTouchId.ERROR_TOUCH_ID_LOCKOUT is being thrown, which is correct, but any consecutive tries only cause TiTouchId.ERROR_TOUCH_ID_NOT_AVAILABLE. I believe either unlock modal should be presented or TiTouchId.ERROR_TOUCH_ID_LOCKOUT code should be shown. {quote} 1) We don't use custom business logic to check this, we just use the system API
canEvaluatePolicy
. If your device can use Touch ID but has not been set up to use it, it will return false. And the method didn't change. 2) Same here. We use theisSupported
method internally and will throwERROR_TOUCH_ID_NOT_AVAILABLE
wheneverisSupported
returnsfalse
. But I can see that we can improve this by usingdeviceCanAuthenticate
instead that also returns the exact error code which we can bubble up. I will add that to the PR. *EDIT*: Changed in the latest commit-sets. 3) I will checkmaxBiometryFailures
but it did work in my tests-cases before. And what do you mean with "modal unlock"? *EDIT*: I removed the property again. It's deprecated since 9.0 (introduced in 8.3) so Apple probably disabled the usage of the property in iOS 10+ already. {quote} 4) allowableReuseDuration, fallbackTitle and cancelTitle do not seem to work at all. {quote} They all did work for me (see the attached image in the Github release page). Ensure to call it as the params of theauthenticate
method. *EDIT*: After some checks, I found out that this property is only used for authentications from the lock-screen, not in-app: "This property is meant only for reusing Touch ID matches from the device lock screen. "It does not allow reusing previous Touch ID matches in application or between applications." {quote} 5) All other cases i.e. user cancel, user fallback, authentication failed, authentication passed, system cancel etc work as expected. {quote} OK And thank you very much for the detailed review, appreciate that! *EDIT*: 2.1.0 Beta 2 is out, having all fixed listed in https://github.com/appcelerator-modules/ti.touchid/releases/tag/2.1.0-beta.2Oh, and the more I think about this, the more I would like to remove
writeToKeychain
,readFromKeychain
anddeleteFromKeychain
and use an own namespace for managing them. Like this:And maybe some additional utilities:
So we either put this interface changes in this PR or merge it and do an additional ticket where we deprecate the old one again.
HI Hans, Thank you for timely and descriptive response. Here are my test notes for updated module: Case 1. -
accessControlMode
no longer crashes the app when set. However, I've tried different values and they do not seem to have any effect. I'll post my exact test code below. -accessibilityMode
also appears to do nothing. I've tried all possible values and they do not seem to alter app behaviour. Based off that, I suspect I might not be using those flags correctly. Please refer to test code below. Case 2. - I was able to confirm thatERROR_TOUCH_ID_NOT_ENROLLED
andERROR_PASSCODE_NOT_SET
messages are now working correctly. - When user chooses to enter passcode after unsuccessful fingerprint authentication, correct message (ERROR_USER_FALLBACK
) is being thrown. However, no dialogue to enter passcode is presented. This worked correctly in the previous iteration of the module. When user is being locked out due to maximum unsuccessful authentication attempts * On iPhone 6 OS v8.2 incorrect message is being thrownERROR_AUTHENTICATION_FAILED.
Subsequent authentication attempt launches passcode modal. When user enters correct passcode he is able to try to authenticate with fingerprint again * On iPhone 7 OS v10.0.2 correct message is being thrownERROR_TOUCH_ID_LOCKOUT
. Subsequent authentication attempt throws same message again. Enter passcode modal dialogue is not being presented. I believe both cases should yield same result:ERROR_TOUCH_ID_LOCKOUT
message when lockout happens, passcode modal dialogue on subsequent attempts to authenticate. Lastly, I appreciate your comment regardingisSupported
functionality. Its weird that it works that way, especially given the fact thatTouchID.authenticate
is actually able to make a distinction between not having fingerprint scanner, password not being set and not having any fingerprints in the system. Is there any way you could leverageTouchID.authenticate
functionality to enhanceisSupported
. ---- My code: index.xml:index.tss:
index.js:
---- I feel like we are moving in the right direction. Lets keep momentum going! Nikita.
{quote} However, no dialogue to enter passcode is presented. This worked correctly in the previous iteration of the module. {quote} Not sure what you mean with pass code. A video demonstrating that old behavior would be more than useful Regarding the accessControl, use an array, since you (could) combine multiple access control modes. *EDIT*: It's because some constants are iOS9+ (ERROR_TOUCH_ID_LOCKOUT, ERROR_APP_CANCELLED and ERROR_INVALID_CONTEXT) And for the error-codes, it now uses the internal error codes that are bubbled up, so it might be an OS behavior change causing the differences. I can not think of other ways this could happen.
Tried accessControl with array of control modes. It did not do anything. Could you please post a sample test case? It is possible that my test cases are incorrect. {noformat} However, no dialogue to enter passcode is presented. This worked correctly in the previous iteration of the module.{noformat} When user calls TouchID.authenticate, tries to authenticate with fingerprint and fails, he is authentication window has the button that says "Enter Password", which allows user to enter passcode the he set in "Touch ID & Passcode" section of Settings. In previous version of the module, when user pressed that button he was able to enter passcode and authenticate that way (which corresponds to ERROR_USER_FALLBACK message). In the new version of the module correct message is thrown, but the screen where user would enter his password is missing.
This is not how it worked in the past. The fallback button only triggers the fallback event key so the developer can present an alternative UI to authorize (e.g. a login-form). This has also been discussed natively several times. But if I missed something here, a video demonstrating the feature with previous ti.touchid versions would be interesting. For the
accessibilityMode
, it seems to receive the correct values down to the keychain creation (SecAdd()
. ForaccessControlMode
, it seems like the underlaying type (SecAccessControlCreateFlags = CFOptionFlags = long) seems to make problems. I need to check what's the best way to cast that value correctly.Hey [~nradaev], just solved the casting, and the constants for
accessControlMode
can be either be supplied as a single arg or in logical-or order. New version: https://www.dropbox.com/s/291lj6t0bebhep4/ti.touchid-iphone-2.1.0.zip?dl=0 Demo:Hi Hans, Retested with the provided code - access group seems to be causing issue now:
After that I commented out accessGroup value. Afterwards I tried using single value for accessControlMode as well as multiple values - no effect.
I was able to save/read values after adding/removing fingerprints to current set, 2nd value never caused app to ask for passcode/fingerprint, 3rd value did not affect app functionality whether there was passcode or not. Furthermore, I tested accessibilityMode and had similar result - it did not work for me, i was able to save/read keychain values with/without password set:
I've also tried different values for this property and none of them seem to work.
Updated the constants-casting, give it a try: https://www.dropbox.com/s/291lj6t0bebhep4/ti.touchid-iphone-2.1.0.zip?dl=1 Tried with the following code:
Hi Hans, setting
accessGroup
property still throws this:After commenting it out and testing further, the only change I noticed is in reading deleted or not set keychain item. Once item is deleted from keychain and user tries to access it, app throws this:
Previous version of the module handled this differently.
accessibilityMode
andaccessControlMode
+still dont work as expected+[~nradaev] I contacted you via email to not spam this thread with our discussion. I guess that's the quickest way to resolve the remaining issues asap. Thx!
It looks like this module is in stable and functioning condition. Thank you Hans.
Added one additional feature to complete the Touch ID API: Being able to change the authentication policy from biometrics to passcode, so people can fallback to entering their passcode instead of a custom password mask (MOD-2211). Example:
Any input [~vijaysingh] ?
[~lmorris] Please follow steps mentioned below - 1. Include App Groups in App ID at apple developer account and create a provisioning profile using the same app id. Download it and use for running on device. 2. Get team id as required in test cases. You can get it from apple developer account or open provisioning profile using textedit and search for “ApplicationIdentifierPrefix”. Get corresponding value. It will be something like - “WP4952RUHJ” 3. This feature should be test on device. 4. Use test samples given in the TouchId code base - app.js, keychain-basic.js, keychain-touchid.js Please let me know, if you need anything else.
Can finally confirm that this fix works and has been verified. Tested with; iPhone 7 (10) & (10.2) MacOS 10.11.6 (15G31) Studio 4.8.1.201612050850 Ti SDK 6.1.0.v20170131132207 Appc NPM 4.2.8 Appc CLI 6.2.0-12 Ti CLI 5.0.11 Alloy 1.9.6 Arrow 1.10.2 Xcode 8.2 (8C38) Node v4.6.0 Java 1.7.0_80
Guys, I believe I just found some irregularities with this module. I've tried using it this way:
When I try to read the value back I am consistently being asked for password. Same goes for ACCESSIBLE_ALWAYS, ACCESSIBLE_WHEN_UNLOCKED, ACCESSIBLE_WHEN_UNLOCKED_THIS_DEVICE_ONLY, ACCESSIBLE_ALWAYS_THIS_DEVICE_ONLY. According to https://developer.apple.com/reference/security/keychain_services/keychain_item_accessibility_constants I should not be asked the password... Tested with TouchID module v 2.1.0 Ti.SDK 6.0.1 GA on iPhone 7 os 10.2. Please reopen the ticket.
[~nradaev] We'd rather create a new ticket for this specific use-case. Can you file one, then I'll move it accordingly. Thx!