Can't export EC kSecAttrTokenIDSecureEnclave public key

Hi all,


Using iOS 9 beta 2, I'm trying to export an elliptic curve public key that was generated with kSecAttrTokenIDSecureEnclave and kSecAccessControlPrivateKeyUsage but I am having a few issues.


First, I can't specify kSecAttrIsPermanent for kSecPublicKeyAttrs or SecKeyGeneratePair() fails. I guess that makes sense because kSecAttrTokenIDSecureEnclave is specified for the entire SecKeyGeneratePair() operation (it fails if I put it under kSecPrivateKeyAttrs?) and there is no reason to save an elliptic curve public key with Secure Enclave protection. But this means that later looking up the elliptic curve public key with SecItemCopyMatching and kSecReturnData fail, so there doesn't seem to be a way to get the public key material in order to export the elliptic curve public key using the KeyChain API calls.


Second, of course I have the SecKeyRef for the elliptic curve public key returned by SecKeyGeneratePair(), but on iOS there is no way to export the elliptic curve public key from this opaque handle.


Third, SecKeyRef will print out diagnostic info for the elliptic curve public key though! This is the output for a typical elliptic curve public key as returned by the OS:


<SecKeyRef curve type: kSecECCurveSecp256r1, algorithm id: 3, key type: ECPublicKey, version: 3, block size: 256 bits, y: 0620A1AE78F7EA7D79F1CA6F63F5954BD710BDBCEA9F03838A5F939F60140A7E01, x: 120DE3D293CF8B6F8A6049942ABD2C206BC7050B2330C348FDBA2999A8CB1AD90620A1AE78F7EA7D79F1CA6F63F5954BD710BDBCEA9F03838A5F939F60140A7E01, addr: 0x134672110>


x and y are specified, so for the time being I thought I could export the elliptic curve public key from the x and y dump. But x is 130 hexadecimal digits and y is 66 hexadecimal digits? Shouldn't these values be 32 bytes each?


The Apple KeyChainTouchID sample from iOS 9 beta 2 does not show how to export elliptic curve public keys, only how to generate, sign, and delete.


Things work properly with RSA, but then kSecAttrTokenIDSecureEnclave and kSecAccessControlPrivateKeyUsage can't be specified.


Confused. Any help appreciated!

Unfortunately, it's not working on my side.

Bummer. I couldn’t see anything obviously wrong with your technique. Please drop me a line at my individual email address (in my signature, below).

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi Eskimo.

I am having the same problem her. I have done

  • Created the key in the secure element.
  • Made a signature with kSecPaddingPKCS1 of a SHA1 hashed plain text.
  • Verified it on the Iphone sucessfully.
  • Exported the public key sucessfully with the header.(Looks OK using a ASN.1 dump)


And I am completely unable to verify this signature using either openssl nor a java program....


Would you mind sharing the data you are doing your experiment on ? You are not ecplictly stading that you do make a SHA-1 hash before signing and that you place the plaintext into the file for openssl validation, but I assume you are ?

I.e. what do you sign ?

And do you make any special provisions when putting this on a file for openSSL verification (Like to compensate for end of file marker ?)

In addition what version of open SSL are you using ?


Thaks for your time...


Ronny

You are not ecplictly stading that you do make a SHA-1 hash before signing and that you place the plaintext into the file for openssl validation, but I assume you are ?

Correct. In the example I posted:

  • EC key.pem
    is the concatenation of the ‘standard’ EC header,
    EC key header.dat
    , and the EC key from iOS,
    EC key raw.dat
  • signature.dat
    was the output from
    SecKeyRawSign
    on iOS
  • dataToSign.dat
    was a bunch of random bytes that’s my test ‘message’

The paste dumps are pasted in below.

And do you make any special provisions when putting this on a file for openSSL verification (Like to compensate for end of file marker ?)

No.

In addition what version of open SSL are you using ?

The one built in to OS X 10.11.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
$ hexdump -v EC\ key\ header.dat
0000000 30 59 30 13 06 07 2a 86 48 ce 3d 02 01 06 08 2a
0000010 86 48 ce 3d 03 01 07 03 42 00
$ hexdump -v EC\ key\ raw.dat
0000000 04 5a e5 3a da 6e b3 21 53 a4 0e 6b de bc 54 bb
0000010 6d 89 c2 6c 22 8a 6a 82 47 96 57 5a e3 a0 a4 dc
0000020 17 0c fb ce 4a f2 ed 34 a5 62 77 04 f7 b9 2e 5b
0000030 38 61 74 81 c0 a3 62 63 79 74 0e 67 af dd df 74
0000040 ab
$ hexdump -v signature.dat
0000000 30 44 02 20 09 57 a8 25 a4 60 79 3b 00 24 06 81
0000010 81 7a c6 f8 81 29 96 65 ca dc da 7d f6 15 ef c2
0000020 68 d2 b9 d9 02 20 22 05 cd d8 a8 28 91 85 9c 31
0000030 1f c8 a4 57 cc 94 42 68 73 94 05 fd 54 b3 ba 39
0000040 16 a4 eb c0 8a 9c
$ hexdump -v dataToSign.dat
0000000 2f 8d e6 68 42 19 d4 5b e2 e3 30 c8 c6 2c e1 0b
0000010 69 4c 9c af c4 82 ee d5 67 e0 15 49 72 c8 60 4a
0000020 4b 31 2d 9f 94 89 c5 49 f3 6e b9 ab b7 ae 76 52
0000030 5a 56 d8 c4 c6 15 17 50 db 72 83 f4 c3 76 f8 fe
0000040 8e db 8a b5 6c 62 7b 5f 33 87 57 47 53 1c 69 b5
0000050 f5 e9 40 fc c8 fb d0 62 76 51 13 c5 8e fd b9 42
0000060 dc a9 c7 68 47 ea dc 4f 7f b1 f5 04 fb 53 a3 96
0000070 f4 58 88 9d 2d fd 13 ff 0a b4 d7 2b 5f f4 f2 ce
0000080 9f 31 25 d9 fa 86 27 2e 71 36 ce 99 8a c3 0e b8
0000090 9b 1c d6 be 63 3c a7 6d db 6e 7c da 53 40 83 03
00000a0 ec f5 d3 fb e9 1c ad a9 b1 5f 9b dd 79 d7 45 d2
00000b0 16 0c c9 2f 62 a6 78 99 8d ea fb 50 21 42 90 81
00000c0 2b 7f 3e d7 56 52 43 9c 81 21 40 39 50 7d d6 a6
00000d0 b9 d9 1c a7 b6 0c 05 95 5c f5 1e 20 84 ce f9 5e
00000e0 89 00 83 e9 08 7f 3c 96 f0 f6 90 e3 d4 e8 7c 4e
00000f0 02 28 8a bf ba 2d ba 91 dc 67 f8 6d bd 50 ef fe
0000100 db 31 5e 05 6f cc b8 26 b7 2c 3e aa f2 32 56 5b
0000110 18 ae 98 d0 a5 27 85 62 78 49 51 79 88 05 5d e8
0000120 10 dc 79 fe 1f f2 a9 37 3f e1 c3 4c fb cb 18 26
0000130 2e 35 cb 55 d6 e6 06 db 5c 98

Thanks Eskimo.

I did not verify this using openssl but I did verify it using a java program so I kind of doubth that it will not work using openssl. In my case I must have messed up with the public key by running this program to often at not deleting the public key placed in keystore or maybe one other suspect which is the way you do hasing for small data. I started out using.


CC_SHA1_Init(&SHA1);

CC_SHA1_Update(&SHA1, data , usedLen);

unsigned char digest[CC_SHA1_DIGEST_LENGTH];

memset(digest, 0, CC_SHA1_DIGEST_LENGTH);

CC_SHA1_Final(digest, &SHA1);


And substituded this to

CC_SHA1(data,usedLen,digest);


I kind of doubth if this was the problem, but since I am not that convinced that I actually messed up with the public key (since I do belive to have verified the signature with both the generated ans stored key) either I tougth to mention it for some body else on the verge of a deep depression.


So given that you follow Eskimo´s procedure it does work and as you can see I am using Objective C while Eskimo has done this in Swift.

One other strage thing I experienced was that the private key was only good for one go..If you intend to use it again you must retrive it again. Failing to do so will produce a strange delay and a strage error message about credentials.

So given that you follow Eskimo´s procedure it does work …

Yay!

One other strage thing I experienced was that the private key was only good for one go..If you intend to use it again you must retrive it again. Failing to do so will produce a strange delay and a strage error message about credentials.

Well, that’s weird. I suspect it’s related to the Touch ID integration. Please file a bug about this, then post your bug number, just for the record.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi Eskimo and all,

thank you for contributing to this thread. Lot of good information. I have read through this discussion multiple times to understand if I'm doing anything wrong.
I'm basically running into the same problem that users in this thread ran into, i.e. verifying the signed content outside iOS.
Verification is failing in both java program (BouncyCastle libs and JDK7) and openssl.

The keypair was generated using with keytype as kSecAttrKeyTypeEC. See the code below.

I'm getting dataToSign, signature and public key from a iOS device.
On Java side or OpenSSL (after adding the header) I'm able to parse the public key with no issues.

Here is the section that is used to generate the key pair.

var publicKey: SecKey? = nil
let publicKeyAttrs: [String: AnyObject] = [
kSecAttrKeyClass as String : kSecAttrKeyClassPublic,
kSecAttrApplicationTag as String : uuidStr
]
var privateKey: SecKey? = nil
let err = SecKeyGeneratePair([
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecAttrKeyType as String: kSecAttrKeyTypeEC,
kSecAttrKeySizeInBits as String: 256,
kSecAttrApplicationTag as String: uuidStr,
kSecPrivateKeyAttrs as String: [
kSecAttrIsPermanent as String: true,
kSecAttrAccessControl as String:access,
],
kSecPublicKeyAttrs as String: publicKeyAttrs
] as NSDictionary, &publicKey, &privateKey)

and code to sign the content...
...
let stringToSign: String = "SignThisString"
let dataToSign: NSMutableData = stringToSign.dataUsingEncoding(NSUTF8StringEncoding)! as! NSMutableData
NSLog("dataToSign %@", dataToSign)

let signature = NSMutableData(length: 128)!
var signatureLength = signature.length
/* Tried various padding options i.e. .PKCSSHA1, .NONE*/
let signErr = SecKeyRawSign(privateKey, .PKCS1, UnsafePointer(dataToSign.bytes), dataToSign.length, UnsafeMutablePointer(signature.mutableBytes), &signatureLength)
guard signErr == errSecSuccess else
NSLog("verify sign error %d", signErr)


NSLog("signature %@", signature)
NSLog("signatureLength %d", signatureLength)


Here is the data I'm trying to verify using a java program. The below data was output from iOs program.

String dataToSignInHex="22f3e5d2d0f6031340f987a979dba76b0f0c1a7c";
String publicKeyInHex="046052c59c1cc8aa3a48b33ec9908f8332af82402ace8754eabf087918a94a18446ddb01f4fef4936784200e6f4b66b0079f7fa363a42b5828e15c1c265e260e04";
String sigInHex="3044022021250762bbb601867165181ba1b8462cc4c52dac7bb895764068b7fa38d46014022054ee80cead07b1f022242e158a17cb097a9ee7520ed7f3bf8c4695f1a5ccfb8f";

Passed public key info

Algorithm=ECDSA
getFormat=X.509
getEncoded=[B@5b4ec310
sigBytes Length=70
parsed pk_in_hex=308201333081ec06072a8648ce3d02013081e0020101302c06072a8648ce3d0101022100ffffffff00000001000000000000000000000000ffffffffffffffffffffffff30440420ffffffff00000001000000000000000000000000fffffffffffffffffffffffc04205ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b0441046b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551020101034200046052c59c1cc8aa3a48b33ec9908f8332af82402ace8754eabf087918a94a18446ddb01f4fef4936784200e6f4b66b0079f7fa363a42b5828e15c1c265e260e04

Verification keeps failing in both Java and OpenSSL. Not sure what we are missing. Greatly appreciate your help.

Hi Ronny,


I see that you figured out how to validate the signature using Java. Can you please shed some light what I could be doing wrong.

I'm using BouncyCastle libraries. Here is the code to parse the public key. Does this look right?


public PublicKey decodePublicKey(byte[] encodedPublicKey) throws U2FException {
try {
X9ECParameters curve = SECNamedCurves.getByName("secp256r1");
ECPoint point;
try {
point = curve.getCurve().decodePoint(encodedPublicKey);
} catch (RuntimeException e) {
throw new U2FException("Couldn't parse user public key", e);
}

return KeyFactory.getInstance("ECDSA").generatePublic(
new ECPublicKeySpec(point,
new ECParameterSpec(
curve.getCurve(),
curve.getG(),
curve.getN(),
curve.getH())));
} catch (InvalidKeySpecException e) {
throw new U2FException("Error when decoding public key", e);
} catch (NoSuchAlgorithmException e) {
throw new U2FException("Error when decoding public key", e);
}
}

Verification is failing in both java program (BouncyCastle libs and JDK7) and openssl.

I had cause to look at this again and modified my code to make it easier to test things. To start, here’s the code I used to generate the key.

func generate() {
    let uuidStr = NSUUID().UUIDString

    let access = SecAccessControlCreateWithFlags(
        nil,
        kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
        [.TouchIDAny, .PrivateKeyUsage],
        nil
    )!

    var publicKey: SecKey? = nil
    var privateKey: SecKey? = nil
    let err = SecKeyGeneratePair([
        kSecAttrTokenID as String:          kSecAttrTokenIDSecureEnclave,
        kSecAttrKeyType as String:          kSecAttrKeyTypeEC,
        kSecAttrKeySizeInBits as String:    256,
        kSecAttrApplicationTag as String:  uuidStr,
        kSecPrivateKeyAttrs as String: [
            kSecAttrIsPermanent as String:  true,
            kSecAttrAccessControl as String:access,
        ]
    ] as NSDictionary, &publicKey, &privateKey)
    if err != errSecSuccess {
        NSLog("generate error %d", err)
    } else {
        NSLog("generate success")

        let addErr = SecItemAdd([
            kSecClass as String:                kSecClassKey,
            kSecValueRef as String:            publicKey!,
            kSecAttrApplicationTag as String:  uuidStr
        ] as NSDictionary, nil)
        if addErr != errSecSuccess {
            NSLog("add error %d", addErr)
        } else {
            NSLog("add success")
        }
    }
}

Here’s the top-level signing code:

func sign() {
    // Create some unique data to sign.

    let stringToSign = NSString(format: "This string was signed after %@.", NSDate())
    let dataToSign = stringToSign.dataUsingEncoding(NSUTF8StringEncoding)!

    // Start off our shell script with some lines that put that data in "dataToSign.dat".

    var shellScriptLines = [ "#! /bin/sh" ]
    shellScriptLines.append(String(format: "echo %@ | xxd -r -p > dataToSign.dat", QHex.hexStringWithData(dataToSign)))

    // Now add lines for each key.

    for (_, (publicKey: publicKey, privateKey: privateKey)) in self.keyPairsByUUIDStr() {
        _ = try? self.signData(dataToSign, withPrivateKey: privateKey, publicKey: publicKey, shellScriptLines: &shellScriptLines)
    }

    // Finally print the script.

    for l in shellScriptLines {
        print(l)
    }
}

This uses two helpers:

  • keyPairsByUUIDStr()
    iterates through all the keys in the keychain returning public and private keys matched by their UUID. If you’re only using one key then this level of complexity is unnecessary, so I haven’t included this code here.
  • signData(xxx)
    , which actually does the signing. It shown below.

-

func signData(dataToSign: NSData, withPrivateKey privateKey: SecKey, publicKey: SecKey, inout shellScriptLines: [String]) throws {
    let digestToSign = CommonCryptoAccess.sha1DigestForData(dataToSign)
    // let digestToSign = CommonCryptoAccess.sha256DigestForData(dataToSign)

    // There's no reliable API to get the correct signature buffer length to use
    // <rdar://problem/23128926> so we hard code 128 as the theoretical maximum.

    let signature = NSMutableData(length: 128)!
    var signatureLength = signature.length
    let signErr = SecKeyRawSign(privateKey, .PKCS1, UnsafePointer(digestToSign.bytes), digestToSign.length, UnsafeMutablePointer(signature.mutableBytes), &signatureLength)
    guard signErr == errSecSuccess else {
        NSLog("verify sign error %d", signErr)
        throw NSError(domain: NSOSStatusErrorDomain, code: Int(signErr), userInfo: nil)
    }
    signature.length = signatureLength

    // Add a command to create "signature.dat".

    shellScriptLines.append(String(format: "echo %@ | xxd -r -p > signature.dat", QHex.hexStringWithData(signature)))

    var matchResult: AnyObject? = nil
    let err = SecItemCopyMatching([
        kSecClass as String:                kSecClassKey,
        kSecValueRef as String:            publicKey,
        kSecReturnData as String:          true
    ] as NSDictionary, &matchResult)
    if err != errSecSuccess {
        NSLog("match error %d", err)
    } else if let keyRaw = matchResult as? NSData {

        // We take the raw key and prepend an ASN.1 prefix to it.  The end result is an
        // ASN.1 SubjectPublicKeyInfo structure, which is what OpenSSL is looking for.
        //
        // See the following DevForums post for more details on this.
        //
        // <https://forums.developer.apple.com/message/84684#84684>.

        let keyHeader = QHex.dataWithValidHexString("3059301306072a8648ce3d020106082a8648ce3d030107034200")
        let keyASN1 = NSMutableData(data: keyHeader)
        keyASN1.appendData(keyRaw)

        // Convert the key to Base64 then wrap it up as "key.pem".

        let keyBase64 = keyASN1.base64EncodedStringWithOptions([.Encoding64CharacterLineLength])
        shellScriptLines.append("cat > key.pem <<EOF")
        shellScriptLines.append("-----BEGIN PUBLIC KEY-----")
        shellScriptLines.appendContentsOf( keyBase64.componentsSeparatedByString("\r\n") )
        shellScriptLines.append("-----END PUBLIC KEY-----")
        shellScriptLines.append("EOF")
    } else {
        NSLog("match cast problem")
    }

    // Add a command to verify the signature.
    //
    // IMPORTANT: -ecdsa-with-SHA1 is a bit of a hack that works in the ancient version
    // of OpenSSL that's built in to OS X.  Alas, ecdsa-with-SHA256 does not work. If
    // you need to use a long digest, SHA256 say, you should replace this with "-sha256". 
    // That won't work on OS X but will work with other, more modern versions of OpenSSL,
    // where it detects the key type and does an ECDSA digest.

    shellScriptLines.append("openssl dgst -ecdsa-with-SHA1 -verify key.pem -signature signature.dat dataToSign.dat")
}

Finally, here’s the wrappers I use to access Common Crypto from Swift.

+ (NSData *)sha1DigestForData:(NSData *)data {
    NSMutableData *    result;

    result = [[NSMutableData alloc] initWithLength:CC_SHA1_DIGEST_LENGTH];
    assert(result != nil);

    CC_SHA1(data.bytes, (CC_LONG) data.length, result.mutableBytes);

    return result;
}

+ (NSData *)sha256DigestForData:(NSData *)data {
    NSMutableData *    result;

    result = [[NSMutableData alloc] initWithLength:CC_SHA256_DIGEST_LENGTH];
    assert(result != nil);

    CC_SHA256(data.bytes, (CC_LONG) data.length, result.mutableBytes);

    return result;
}

The end result here is a shell script that you can literally paste into a Terminal window to verify the key using the OpenSSL built in to OS X. To ensure that there wasn’t anything OS X-specific in play here, I ran the same shell script on Ubuntu 14.04.02. Verifying it using other crypto toolkits is left as an exercise for the reader (-:

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

DTS will close for the winter holidays at the end of business on Wed, 23 Dec 2015 and re-open on Mon, 4 Jan 2016.

Hi Eskimo,


Thank you very much for the detailed reply. We had a break through yesterday. We are able to verify using both Open SSL and Java now. The piece we were missing was the signing of the digest vs signing of the raw string. The hashing mechanism used to create the digest on iOS side has to match the argument used to create the Signature object on java side.

Here's a working repo of what quinn explains: https://github.com/hfossli/EskimoKeys/tree/master

Can't export EC kSecAttrTokenIDSecureEnclave public key
 
 
Q