agens-no / EllipticCurveKeyPair

Sign, verify, encrypt and decrypt using the Secure Enclave
Other
708 stars 114 forks source link

No prompts for Touch ID when accessing private key #20

Closed ma-pe closed 6 years ago

ma-pe commented 6 years ago

It's probably me, but with master-code I experience the following problems: I won't be prompted for touch-id, when accessing the private key. In addition to that the signature I am creating is different all the time (for the same digest).

So it seems that the private key isn't accessed at all - and the keypair is regenerated every time (therefore no prompt). Though the logs say: SecItemCopyMatching: ["kcls": 1, "class": keys, "labl": "my.key", "u_OpPrompt": "Auth", "u_AuthCtx": <LAContext: 0x1c5861900>, "r_Ref": true]

I followed your great tutorial, but didn't get it to work :( https://github.com/agens-no/EllipticCurveKeyPair/blob/master/Demo-iOS/SignatureViewController.swift

Here is my code:

  struct KeyPair {
    static let manager: EllipticCurveKeyPair.Manager = {
      EllipticCurveKeyPair.logger = { print($0) }
      let publicAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAlwaysThisDeviceOnly, flags: [])
      let privateAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleWhenUnlockedThisDeviceOnly, flags: {
        return EllipticCurveKeyPair.Device.hasSecureEnclave ? [.userPresence, .privateKeyUsage] : [.userPresence]
      }())
      let config = EllipticCurveKeyPair.Config(
        publicLabel: "my.key.public",
        privateLabel: "my.key",
        operationPrompt: "Auth",
        publicKeyAccessControl: publicAccessControl,
        privateKeyAccessControl: privateAccessControl,
        token: .secureEnclaveIfAvailable)
      return EllipticCurveKeyPair.Manager(config: config)
    }()
  }

  var context: LAContext! = LAContext()

  DispatchQueue.global(qos: .background).async {
    do {
      let digest = givenDigest.data(using: .utf8)!
      signature = try KeyPair.manager.sign(digest, hash: .sha256, context: self.context)

      signature.base64EncodedString() // different every time
    } else {
      // ...
    }
  }

What am I doing wrong?

hfossli commented 6 years ago

Signature is by design by ecdsa different every time. Can you try the demo examples xcode project on master? Do you have the same problems there?

hfossli commented 6 years ago

Can you paste the full log?

ma-pe commented 6 years ago

Wow. Yep, you are right!

Pointed me straight to a conceptional mistake of mine.

Still wondering about the issue that I wasn't prompted for TouchID. Will check on that after correcting my concept. Stay tuned.

hfossli commented 6 years ago

If that context already has been authenticated it will not show any dialogue. Try to create a new LAContext each time.

ma-pe commented 6 years ago

Sounds plausible, I was not once prompted for TouchID though.

hfossli commented 6 years ago

On device? Which hardware and setup? Same as last time?

ma-pe commented 6 years ago

Yes. Same as last time.

I switched to encryption and decryption now. I use the same ECManager-Definition from above and the following code. I won't be prompted for Touch-ID, the decryption is successful though. Same for Simulator and Device.

  DispatchQueue.global(qos: .background).async {
      do {
        let decryptedMessage: Data;

        let encrypted = Data(base64Encoded: encryptedMessage)!
        decryptedMessage = try KeyPair.manager.decrypt(encrypted, hash: .sha256, context: self.context)

        print("------ try_to_decrypt end ------");
        print(decryptedMessage.base64EncodedString())
      } catch {
        ...
      }
    }
ma-pe commented 6 years ago

EncryptionViewController.swift:

var privateKeyParams: [String: Any] = [
  kSecAttrLabel as String: config.privateLabel,
  kSecAttrIsPermanent as String: true,
  kSecUseAuthenticationUI as String: kSecUseAuthenticationUIAllow,
]
...

// On iOS 11 and lower: access control with empty flags doesn't work
if config.privateKeyAccessControl.flags.isEmpty {
  privateKeyParams[kSecAttrAccessControl as String] = try config.privateKeyAccessControl.underlying()
} else {
  privateKeyParams[kSecAttrAccessible as String] = config.privateKeyAccessControl.protection
}

Seems to me like the flags aren't considered when creating the KeyPair (for both private- and public-key).

hfossli commented 6 years ago

Yes, you are correct. I was just about to write to you that it seems like there's a typo with that if statement. It should be reversed.

// On iOS 11 and lower: access control with empty flags doesn't work
if !config.privateKeyAccessControl.flags.isEmpty {
  privateKeyParams[kSecAttrAccessControl as String] = try config.privateKeyAccessControl.underlying()
} else {
  privateKeyParams[kSecAttrAccessible as String] = config.privateKeyAccessControl.protection
}
hfossli commented 6 years ago

Thanks for submitting this issue! I will push this fix to master. I didn't finalise the latest changes on master properly. That's why I haven't released this a public version. It shouldn't be on master though, should have been on a development branch.

ma-pe commented 6 years ago

No worries. You are welcome.

You want me to submit a pull request? I'd be happy to help.

hfossli commented 6 years ago

When testing this you need to recreate the keys (your old keys will have wrong access flags).

ma-pe commented 6 years ago

Works like a charm now. Thanks a lot for your amazingly fast support!

Greetings to Oslo. Enjoy your sunday :-)

hfossli commented 6 years ago

Happy to help. Sorry for the bug – though happy it wasn't released in a tag yet through dependency managers. Let me know if there's anything else you'd like to discuss.