0

I'm attempting to do the following in my iOS application:

  1. Generate a key pair using SecKeyGeneratePair, storing the private key in the Secure Enclave
  2. Sign some data using the private key

It works if I hang on to the private key reference when keys are first generated, but does not work if I attempt to retrieve the reference from the Keychain after discarding the initial pointer.

The keys are generated like this:

func generateKeyPair() -> Bool {

    if let access = SecAccessControlCreateWithFlags(nil,
                                                    kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
                                                    [.userPresence, .privateKeyUsage],
                                                    nil) {

        let privateKeyAttr = [kSecAttrIsPermanent : 1,
                              kSecAttrApplicationTag : privateTag,
                              kSecAttrAccessControl as String: access
            ] as NSDictionary

        let publicKeyAttr = [kSecAttrIsPermanent : 0,
                             kSecAttrApplicationTag : publicTag
            ] as NSDictionary

        let keyPairAttr = [kSecAttrKeySizeInBits : 256,
                           kSecAttrKeyType : kSecAttrKeyTypeEC,
                           kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
                           kSecPrivateKeyAttrs : privateKeyAttr,
                           kSecPublicKeyAttrs : publicKeyAttr] as NSDictionary

        let err = SecKeyGeneratePair(keyPairAttr, &publicKey, &privateKey)
        return err == noErr
}

The signing method is below:

func signData(plainText: Data) -> NSData? {                
    guard privateKey != nil else {
        print("Private key unavailable")
        return nil
    }

    let digestToSign = self.sha1DigestForData(data: plainText as NSData) as Data

    let signature = UnsafeMutablePointer<UInt8>.allocate(capacity: 128)
    var signatureLength = 128
    let err = SecKeyRawSign(privateKey!,
                            .PKCS1SHA1,
                            [UInt8](digestToSign),
                            Int(CC_SHA1_DIGEST_LENGTH),
                            signature,
                            &signatureLength)

    print("Signature status: \(err)")

    let sigData = NSData(bytes: signature, length: Int(signatureLength))

    return sigData
}

func sha1DigestForData(data: NSData) -> NSData {
    let len = Int(CC_SHA1_DIGEST_LENGTH)
    let digest = UnsafeMutablePointer<UInt8>.allocate(capacity: len)
    CC_SHA1(data.bytes, CC_LONG(data.length), digest)
    return NSData(bytesNoCopy: UnsafeMutableRawPointer(digest), length: len)
}

This asks for my fingerprint and works flawlessly. I then use another method to get the key reference from the Keychain:

func getPrivateKeyRef() -> SecKey? {

    let parameters = [
        kSecClass as String: kSecClassKey,
        kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
        kSecAttrApplicationTag as String: privateTag,
        kSecReturnRef as String: true,
        ] as [String : Any]
    var ref: AnyObject?
    let status = SecItemCopyMatching(parameters as CFDictionary, &ref)
    print("Get key status: \(status)")

    if status == errSecSuccess { return ref as! SecKey? } else { return nil }
}

The SecItemCopyMatching returns a success status, but attempting to use the resulting SecKey item as a private key in SecKeyRawSign results in error -25293 Authorization/Authentication failed. This status only appears after I provide my fingerprint, so the actual fingerprint verification succeeds, but the key somehow remains unusable.

What is the correct way to use a key stored in Secure Enclave to sign data?

SaltyNuts
  • 5,068
  • 8
  • 48
  • 80
  • 1
    Were you able to get it work? I got the same issue, SecItemCopyMatch is successful, but no fingerprint dialog is prompt and SecKeyRawSign fails. – Krypton Aug 28 '17 at 07:20
  • @Krypton yes, see my self-answer below for how I got it to work. – SaltyNuts Sep 07 '17 at 21:45

1 Answers1

0

Looks like using kSecAttrLabel attribute is necessary for correct fetching of the key. It must be specified when generating the keys, and when fetching them through SecItemCopyMatching.

My working solution for fetching the private key:

func getPrivateKey() -> SecKey? {
    let parameters = [
        kSecClass as String: kSecClassKey,
        kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
        kSecAttrApplicationTag as String : "privateTag",
        kSecAttrLabel as String : "privateTag",
        kSecReturnRef as String: true,
        ] as [String : Any]
    var ref: AnyObject?
    let status = SecItemCopyMatching(parameters as CFDictionary, &ref)
    if status == errSecSuccess {
        return (ref as! SecKey)
    }
    return nil
}

Where the key pair was generated like this:

    var publicKey:SecKey?
    var privateKey:SecKey?
    if let access = SecAccessControlCreateWithFlags(nil,
                                                    kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
                                                    [.userPresence, .privateKeyUsage],
                                                    nil) {

        let privateKeyAttr = [kSecAttrIsPermanent : 1,
                              kSecAttrApplicationTag as String : "privateTag",
                              kSecAttrLabel as String : "privateTag",
                              kSecAttrAccessControl as String: access
            ] as NSDictionary

        let publicKeyAttr = [kSecAttrIsPermanent : 0,
                             kSecAttrApplicationTag as String : "publicTag",
                             kSecAttrLabel as String : "publicTag",
                             ] as NSDictionary

        // only 256 bit EC keys are supported in the Secure Enclave
        let keyPairAttr = [kSecAttrKeySizeInBits : 256,
                           kSecAttrKeyType : kSecAttrKeyTypeEC,
                           kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
                           kSecPrivateKeyAttrs : privateKeyAttr,
                           kSecPublicKeyAttrs : publicKeyAttr] as NSDictionary

        let err = SecKeyGeneratePair(keyPairAttr, &publicKey, &privateKey)
SaltyNuts
  • 5,068
  • 8
  • 48
  • 80