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!

Post not yet marked as solved Up vote post of fijibill Down vote post of fijibill
31k views

Replies

hi

i‘ve encounter exactaly the same problem. but after days of coding and searching,

stil can't find a way to solve it. have you solve this yet?

Hi,

Also trapped by this. Any solution now?

Hi guys,


I think I figured out how to intepret SecKeyRef data. It's mainly about endianess. Verified on iOS9 beta 4.


To ease the explanation, I removed kSecAttrTokenIDSecureEnclave when generating the keys so that I can read the public key's data by using kSecReturnData : @YES in SecItemCopyMatching. Now we have the following data for analysis.


In non Secure Enclave mode, the data I got on my device is:

Generated public key: <SecKeyRef curve type: kSecECCurveSecp256r1, algorithm id: 3, key type: ECPublicKey, version: 3, block size: 256 bits, y: C327638AB594B9D13E6C25D9817888E8DA03E3B10F43479B088F793301BB040601, x: CBC9F4AE68D0A519FC3BB15A94F286FE9AC14DCCE75128F9ADD8DBF49FF13394C327638AB594B9D13E6C25D9817888E8DA03E3B10F43479B088F793301BB040601, addr: 0x146a6b90>


Public key data retrieved: <04 9433f19ff4dbd8adf92851e7cc4dc19afe86f2945ab13bfc19a5d068aef4c9cb 0604bb0133798f089b47430fb1e303dae8887881d9256c3ed1b994b58a6327c3>


The expected public data shoud begin with 1 byte of value 0x04, then following by two 32bytes unsigned integers in big-endian (for x and y values). Here in this case is x value: <0x9433f19ff4dbd8adf92851e7cc4dc19afe86f2945ab13bfc19a5d068aef4c9cb> and y value: <0x0604bb0133798f089b47430fb1e303dae8887881d9256c3ed1b994b58a6327c3>.


Now let's look back at the SecKeyRef printouts. y value in the printout is <C327638AB594B9D13E6C25D9817888E8DA03E3B10F43479B088F793301BB040601>. If we exclude the ending <0x01> bytes and convert the y value from little-endian into big-endian, we have <0x0604bb0133798f089b47430fb1e303dae8887881d9256c3ed1b994b58a6327c3>, which is exactly the value we have for y in the public key data. I'm not sure what's the meaning of <0x01>, but it seems to be a constant there. Hence, we can construct y value from SecKeyRef.


Now let's look at x value. The x value in SecKeyRef is way too long compared to the expected 32bytes value. But I noticed that the ending part of the long x value is exactly the same as the y value in SecKeyRef. By removing the redundant y part from the long x value, we have <CBC9F4AE68D0A519FC3BB15A94F286FE9AC14DCCE75128F9ADD8DBF49FF13394>. Convert it from little-endian to big-endian and we got <0x9433f19ff4dbd8adf92851e7cc4dc19afe86f2945ab13bfc19a5d068aef4c9cb>, which is exactly the x value we read in the actual public key data.


Therefore, although we cannot use SecItemCopyMatching in Secure Enclave mode to query pulic key data, the infomation in SecKeyRef is sufficient to re-construct the needed public key information and get stored for server side usage.


In your example, <SecKeyRef curve type: kSecECCurveSecp256r1, algorithm id: 3, key type: ECPublicKey, version: 3, block size: 256 bits, y: 0620A1AE78F7EA7D79F1CA6F63F5954BD710BDBCEA9F03838A5F939F60140A7E01, x: 120DE3D293CF8B6F8A6049942ABD2C206BC7050B2330C348FDBA2999A8CB1AD90620A1AE78F7EA7D79F1CA6F63F5954BD710BDBCEA9F03838A5F939F60140A7E01, addr: 0x134672110>, using the method above, the public key data would be

<0x04 d91acba89929bafd48c330230b05c76b202cbd2a9449608a6f8bcf93d2e30d12 7e0a14609f935f8a83039feabcbd10d74b95f5636fcaf1797deaf778aea12006>

Hi everybody,


I'm facing the same problem, I want to generate the public-private key pair and store the private key in the Secure Enclave, I want also to be able to extract the public key to send it to my server. I have read CarySalt workaround and I think it's brilliant (thanks for sharing it with us CarySalt). However, it seems quite "patchy" and not convenient at all for a serious production environment.


Have any of you managed to find a way to get the bytes or data of the public key?


Thank you!

Digital Leaves.

I actually managed to do it in a "right" way. It involves storing the public key reference (returned by SecKeyGeneratePair) into the keychain again (but publicly available), and then reading its data. I'm going to write a blog post about the matter, so if anyone is interested, please let me know and I will keep you up-to-date.

Has anyone found a good solution for this?

I am stuck at the same point and parsing the SecKeyRef description string is not a valid solution for me.


To elaborate, I also tried to save the generated public key into the keychain, so that I would be able to later use the SecItemCopyMatching() function to get at the data, but the SecItemAdd() operation always fails with errSecParam error.


This is what I am doing.


Key generation:


let publicKeyAttributes : [String:AnyObject] = [
     kSecAttrApplicationTag as String: publicKeyTag
]

let privateKeyAttributes : [String:AnyObject] = [
     kSecAttrIsPermanent as String: kCFBooleanTrue,
     kSecAttrApplicationTag as String: privateKeyTag
]

var errorRef : Unmanaged<CFErrorRef>? = nil
let acl = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
     kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
     SecAccessControlCreateFlags.PrivateKeyUsage,
     &errorRef)!


if errorRef != nil {
     throw Error.Unknown
}


let keyPairAttributes : [String:AnyObject] = [
     kSecAttrKeyType as String: kSecAttrKeyTypeEC as String,
     kSecAttrKeySizeInBits as String: 256,
     kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave as String,
     kSecAttrAccessControl as String: acl,
     kSecPublicKeyAttrs as String: publicKeyAttributes,
     kSecPrivateKeyAttrs as String: privateKeyAttributes
]


var publicKey : SecKey? = nil
var privateKey : SecKey? = nil
let status = SecKeyGeneratePair(keyPairAttributes, &publicKey, &privateKey)
if !IsSuccess(status) {
     throw Error.SecItemError(status)
}


And my attempt to add the public key to the keychain:


CFErrorRef error = NULL;
SecAccessControlRef acl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleAfterFirstUnlock, kNilOptions, &error);
NSDictionary * attributes = @{
     (id)kSecUseItemList: @[(__bridge id)key],
     (id)kSecClass: (id)kSecClassKey,
     (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPublic,
     (id)kSecAttrAccessControl: (__bridge id)acl,
     (id)kSecAttrApplicationTag: tag};
OSStatus status = SecItemAdd((CFDictionaryRef)attributes, &result); // status is always -50 (errSecParam)

Can you share your solution?

Has anyone found a good solution for this?

If you get completely stuck, you should open a DTS tech support incident for this.

Share and Enjoy

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

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

A DTS tech support won't help you (at least it didn't help me, the guy who answered me was even more clueless than myself).


Once you generate the keys with SecKeyGeneratePair, you have to add the public key to the keychain, using the reference returned by SecKeyGeneratePair:


If you specify kSecReturnData = true in the SecItemAdd dictionary, you will get the key data directly, and then you are able to send it to your server.


let parameters = [

kSecClass as String: kSecClassKey,

kSecAttrKeyType as String: kSecAttrKeyTypeEC,

kSecAttrLabel as String: "...",

kSecAttrIsPermanent as String: true,

kSecValueRef as String: publicKey,

kSecAttrKeyClass as String: kSecAttrKeyClassPublic,

kSecReturnData as String: true

]

var data: AnyObject?

let status = SecItemAdd(parameters, &data)

return status == errSecSuccess ? data as? NSData : nil


I'm still trying to figure out how to use this EC key bytes in PHP (i.e: getting it to a standard OpenSSL format), so if anyone has a clue, please let me know.

If I can be of any further help, I have the code working on the iOS side, so let me know.

Best.

Please see below. If you need further explanations, please let me know.

Any hint on how to use these ECSDA secp256r1 keys in PHP would be greatly appreciated. I tried phpecc without success. I think it's not a standard format, or at least not readable by openssl (openssl ec -text -noout -pubin -in mypubkey.pem).

A DTS tech support won't help you (at least it didn't help me, the guy who answered me was even more clueless than myself).

Can you email me the incident number? My email address is in my signature.

Share and Enjoy

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

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

ssent you an email, thank you!

Hello,


I managed to generate the keypair but I think I am having issues with the ACL.

If I use the freshly generated SecKey reference for the keypair I can sign data.

But, if I try to retrieve it later with SecItemCopyMatching() I get SecKey reference but any attempt to sign results in errSecAuthFailed.

I've tried many ACL combinations and kSecUseOperationPrompt when retrieving the key but nothing sticks.


Has anyone done this successfuly?

I am on iOS 9.0 final.

So, I'm able to generate a public key using the new kSecAttrKeyTypeEC and kSecAttrTokenIDSecureEnclave. However, I'm finding that I cannot parse the public key as a standard X.509 formatted key. In digging, I've discovered that the header bytes (everything before the 0x04) are what's causing my problems.


I would like to know if anyone knows how the header bytes are generated on the ECDSA public key in this case? In my examples, I get these bytes:


0x30,0x53,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x42,0x00


However, I've found that I can parse the public key as anormal X.509 public key if I substitute the header bytes above for the ones from the SecKey::SecKeyCreateFromPublicData function documented in:

http://www.opensource.apple.com/source/Security/Security-55471/libsecurity_keychain/lib/SecKey.cpp



} else if (kSecECDSAAlgorithmID == algorithmID) {

CFMutableDataRef tempData;

uint8 headerBytes[] = { 0x30,0x59,0x30,0x13,0x06,0x07,0x2a,0x86,

0x48,0xce,0x3d,0x02,0x01,0x06,0x08,0x2a,

0x86,0x48,0xce,0x3d,0x03,0x01,0x07,0x03,

0x42,0x00 };


However, I would like to confirm that this is not just a happy coincidence. Ideally, I'd like for the public key bytes I get from the SecKeyGeneratePair to be X.509 compliant so I don't have to monkey with the bits.