4 Replies
      Latest reply on Aug 6, 2018 10:05 AM by megajess
      craigaps Level 1 Level 1 (0 points)

        Hi

         

        I have created a private key in the Keychain with kSecAttrAccessibleWhenUnlockedThisDeviceOnly.  When I attempt to access the key to perform a signing operation, the Touch ID dialog will sometimes appear,  but in most cases I have to touch the biometry sensor, then the dialog is displayed.  Here's the code I'm using to create the key and access the key in a sign operation.

         

        public static func create(with name: String, authenticationRequired: SecAccessControlCreateFlags? = nil) -> Bool
        {
            guard !name.isEmpty else
            {
                return false
            }
            
            var error: Unmanaged<CFError>?
            
            // Private key parameters
            var privateKeyParams: [String: Any] = [
                kSecAttrIsPermanent as String: true,
                kSecAttrApplicationTag as String: name
            ]
            
            // If we are using a biometric sensor to access the key, we need to create an SecAccessControl instance.
            if authenticationRequired != nil
            {
                guard let access = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, authenticationRequired!, &error) else
                {
                    return false
                }
                
                privateKeyParams[kSecAttrAccessControl as String] = access
            }
            
            // Global parameters for our key generation
            let parameters: [String: Any] = [
                kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
                kSecAttrKeySizeInBits as String: 2048,
                kSecPrivateKeyAttrs as String: privateKeyParams
            ]
            
            // Generate the keys.
            guard let privateKey = SecKeyCreateRandomKey(parameters as CFDictionary, &error) else
            {
                return false
            }
            
            // Private key created!
            return true
        }
        

        This is the code to sign the data that should prompt for the biometry sensor (Touch ID or Face ID).

         

        public static func sign(using name: String, value: String, localizedReason: String? = nil, base64EncodingOptions: Data.Base64EncodingOptions = []) -> String?
        {
            guard !name.isEmpty else
            {
                return nil
            }
            
            guard !value.isEmpty else
            {
                return nil
            }
            
            // Check if the private key exists in the chain, otherwise return
            guard let privateKey: SecKey = getPrivateKey(name, localizedReason: localizedReason ?? "") else
            {
                return nil
            }
            
            let data = value.data(using: .utf8)!
            var error: Unmanaged<CFError>?
            guard let signedData = SecKeyCreateSignature(privateKey,
                                                         rsaSignatureMessagePKCS1v15SHA512,
                                                         data as CFData,
                                                         &error) as Data? else
            {
                return nil
            }
            
            return signedData.base64EncodedString(options: base64EncodingOptions)
        }
        
        fileprivate static func getPrivateKey(_ name: String, localizedReason: String) -> SecKey?
        {
            let query: [String: Any] = [
                kSecClass as String: kSecClassKey,
                kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
                kSecAttrApplicationTag as String: name,
                kSecReturnRef as String: true,
                kSecUseOperationPrompt as String : localizedReason
            ]
            
            var item: CFTypeRef? = nil
            let status = SecItemCopyMatching(query as CFDictionary, &item)
            
            guard status == errSecSuccess else
            {
                if status == errSecUserCanceled
                {
                    print("\tError: Accessing private key failed: The user cancelled (%@).", "\(status)")
                }
                else if status == errSecDuplicateItem
                {
                    print("\tError: The specified item already exists in the keychain (%@).", "\(status)")
                }
                else if status == errSecItemNotFound
                {
                    print("\tError: The specified item could not be found in the keychain (%@).", "\(status)")
                }
                else if status == errSecInvalidItemRef
                {
                    print("\tError: The specified item is no longer valid. It may have been deleted from the keychain (%@).", "\(status)")
                }
                else
                {
                    print("\tError: Accessing private key failed (%@).", "\(status)")
                }
                return nil
            }
            
            return (item as! SecKey)
        }
        

        Then in my app, I would simply call

        guard let result = sign("mykey", "helloworld") else
        {
           print("failed to sign")
           return
        }
        
        print(result)
        
        

        So the getPrivateKey function is the one that calls SecKeyCopyingMatching, the 3 methods are in a helper class; what's the best approach to reliabably display the biometry dialog?

         

        Thanks

        • Re: SecItemCopyMatching Touch ID Dialog
          eskimo Apple Staff Apple Staff (10,295 points)

          You posted a lot of code (thanks!) but you missed one key point.  What value do you pass to the authenticationRequired parameter of create(with:authenticationRequired:)?

          Share and Enjoy

          Quinn “The Eskimo!”
          Apple Developer Relations, Developer Technical Support, Core OS/Hardware
          let myEmail = "eskimo" + "1" + "@apple.com"

          WWDC runs Mon, 4 Jun through to Fri, 8 Jun.  During that time all of DTS will be at the conference, helping folks out face-to-face.

            • Re: SecItemCopyMatching Touch ID Dialog
              craigaps Level 1 Level 1 (0 points)

              Typically this would be how I would call the function:

               

              create(with "myKey", authenticationRequired: SecAccessControlCreateFlags.biometryAny)

               

              Thanks for having a look at this

                • Re: SecItemCopyMatching Touch ID Dialog
                  eskimo Apple Staff Apple Staff (10,295 points)

                  So, to confirm: You’re running this app on a Touch ID device (that is, not Face ID) and the specific problem is that, most of the time, the Touch ID UI does not show up until you actually touch the Home button.  Right?

                  If so, that’s quite weird.  I have a couple of suggestions for you here:

                  • Try pulling this code out into a separate test app to see if it also has the problem.  It’s possible that there’s something else in your main app that’s triggering the issue.

                  • Grab a copy of the KeychainTouchID sample code to see if it exhibits the same behaviour.

                    Note The sample code does not do the exact operation you’re doing, but you can start with something like that (the Add Item (TouchID Only) test), see if that has the same issue, and then tweak things from there.

                  Share and Enjoy

                  Quinn “The Eskimo!”
                  Apple Developer Relations, Developer Technical Support, Core OS/Hardware
                  let myEmail = "eskimo" + "1" + "@apple.com"

                  WWDC runs Mon, 4 Jun through to Fri, 8 Jun.  During that time all of DTS will be at the conference, helping folks out face-to-face.

              • Re: SecItemCopyMatching Touch ID Dialog
                megajess Level 1 Level 1 (0 points)

                I too am having this issue. Have you managed to get a solution?