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!

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.

Hi jmarne,


Same here, I generated a keypair in openssl using the curve secp256r1, the one that's using SecKeyGeneratePair with kSecAttrKeyTypeEC. By trial and error I discovered that I had to add a standard ASN.1 OID header exactly like yours to the raw public key bytes returned by SecItemCopyMatching:

$ hexdump -C header_secp256r1.bytes

00000000 30 59 30 13 06 07 2a 86 48 ce 3d 02 01 06 08 2a |0Y0...*.H.=....*|

00000010 86 48 ce 3d 03 01 07 03 42 00 |.H.=....B.|


And then I'm able to use it with openssl. Now I'm trying to use this key to verify a signature sent by the iOS App, but I'm clueless on how to hash the data with the kind of signature hash algorithm that OpenSSL/PHP is waiting (that's "ecdsa-with-SHA1"). I have tried with openssl's generated keypair and it's working, so apparently there should be a way fo doing the same with the result of SecKeyRawSign.

Digital Leaves, Sorry but I don't know anything about the openssl/php library. However, after a quick google search, I'm guessing that you need to pass in this hash algorithm: "[14] => ecdsa-with-SHA1" as documented on this page: http://php.net/manual/en/function.openssl-get-md-methods.phpto the verify function. Also, I'm not sure you have to re-hash the original data during verification in PHP. If I'm reading this function correctly, http://php.net/manual/en/function.openssl-verify.php.You just have to pass the original data that was used to generate the signature along with the hash algorithm used and php will hash it for you during the verify call.


As an FYI, my ANS1 Dump of the iOS ECC public key produces this OID:


ObjectIdentifier(1.2.840.113549.1.1.1)


Which appears to correspond to an RSA encryption header (according to RFC 5480 https://tools.ietf.org/html/rfc5480).


-- RSA PK Algorithm and Key


rsaEncryption OBJECT IDENTIFIER ::= {

iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) 1 }


So, if I'm guessing correctly, we're getting an RSA header on an ECC public key which is reason for the need to swap out OID headers.

Hi Jmarne,


Thank you. In my opinion, for some reason, Apple does not store the public key header information. I guess this is for the same reason that they only allow ECC keys when using secure enclave, the size of the keys (ECC keys are way shorter and space-efficient than, say, RSA ones). The header actually corresponds to a header for a secp256r1 curve, only they won't include that, which is very unfortunate, and leave us guessing how to transform/use that key.


The problem with the signing process (for me at least) is that the signature usually occurs in conjuction with a hash of the data, not directly to the data, but I have no way of knowing if SecKeyRawSign is doing that for ECC keys, and if so, if it's using what openssl will call a SHA1 hash algorithm or the ecdsa-with-SHA1 one (I can't find any ECC reference in all the CC_SHAXXX functions in CC). I will definitely have to check out, but due to signature verification being a blackbox (0-1), and the lack of Apple documentation regarding EC key signing-hashing process, I will need to spend some time to make it work I guess.


Anyway thanks a lot, that info really helps me to verify that I'm in the right track.

At the end, I finally managed to solve this. Meaning, I am able to generate the key pair, protecting the private key in the secure enclave and touch id, export the public key, sign data using the private key and finally verify the signature using openssl.


As described in the posts above, the public key is missing the header bytes, so those need to be added.

Now when signing the data on the device, I did this:

SecKeyRawSign(privateKey, kSecPaddingSigRaw, hashBytes, hashBytesSize, signedHashBytes, &signedHashBytesSize);

hashBytes: this is the SHA1 hash of the data I want to sign.


Now to verify I use openssl like this:

openssl dgst -ecdsa-with-SHA1 -verify pubkey.pem -signature signature.bin data.txt

signature.bin: this just contains the output of SecKeyRawSign(), no manipulation needed.


Hope this is helpful.

Hi,

Thanks csgodenzim, I tried with that padding also (all paddings, actually without success). Is there a way I can talk to you by email or something?

Are you doing a Hash by using CC_SHA1 of the data in iOS prior to calling SecKeyRawVerify? I'm getting mad trying to make this work. Here is my code. I tried "ecdsa-with-SHA1", "SHA1", etc at the server side with no luck (well, I think I tried everything).


            if let privateKeyRef = self.getPrivateKeyReference() { // <==== Private key is valid, EC key ref from secure enclave (checked)
                let resultData       = NSMutableData(length: LargeEnoughBufferSize)!
                let resultPointer    = UnsafeMutablePointer<UInt8>(resultData.mutableBytes)
                var resultLength     = resultData.length
          
                if let plainData = message.dataUsingEncoding(NSUTF8StringEncoding) {
                    let hashData = NSMutableData(length: Int(CC_SHA1_DIGEST_LENGTH))!
                    let hash = UnsafeMutablePointer<UInt8>(hashData.mutableBytes)
                    CC_SHA1(UnsafePointer<Void>(plainData.bytes), CC_LONG(CC_SHA1_DIGEST_LENGTH), hash)

                    let status = SecKeyRawSign(privateKeyRef, SecPadding.PKCS1, hash, hashData.length, resultPointer, &resultLength) // <== SigRaw shows equal results
                    if status != errSecSuccess {
                        ... // handle error.
                    } else { // <========= Status is errSecSuccess, this is reached and resultData is shown and looks valid.
                        resultData.length = resultLength
                        print("Result: \(status). Generated result: \(resultData)")
                    }
                } else { ... } // handle error
                // <========= now send resultData to server. Server is unable to get it.
            } else { ... } // handle error

Please, any help REALLY appreciated. Thanks in advance.

A developer wrote into DTS asking about Secure Enclave cryptography, which gave me the opportunity to spend some time looking at this. Let’s explain what’s going on here.

When you generate a key pair as per the KeychainTouchID sample code, the private key gets store in the Secure Enclave leaving the public key represented solely in memory. You can add that public key to the keychain with code like this:

let addErr = SecItemAdd([
    kSecClass as String:                kSecClassKey,
    kSecValueRef as String:             publicKey,
    kSecAttrApplicationTag as String:   uuidStr
] as NSDictionary, nil)

Note In my case I wanted to tag the key with a UUID so that I could come back to it later.

If you then do a

SecItemCopyMatching
on the public key to get back some data (via
kSecReturnData
), you see something like this:
04
B24DB122 E2DDDC97 FB0F58ED 7836F2CA
6868C040 6B483FDA 473FDDD0 41D380B0
4EDB6183 3273046A 33D7A9A8 D66B93AA
A10B8732 93C68114 9CC7FA5E CD3523CE

This is

secp256r1
public key as defined by:
  • SEC 1 Elliptic Curve Cryptography

  • SEC 2 Recommended Elliptic Curve Domain Parameters

The latter standard shows examples that look just like this (see Section 2.4.2).

Notably, the leading 04 indicates that the key is uncompressed. You could potentially see a 02 or 03 in the same place, indicating a compressed key. Either way, that first byte is a good way to detect that you’re seeing the data you’re expecting to see.

When you put such a key into a certificate, you have to wrap it in a

SubjectPublicKeyInfo
ASN.1 structure. That wrapping is defined by RFC 5480 “Elliptic Curve Cryptography Subject Public Key Information. The structure looks like this:
SubjectPublicKeyInfo  ::=  SEQUENCE  {
    algorithm         AlgorithmIdentifier,
    subjectPublicKey  BIT STRING
}

where

subjectPublicKey
is the
secp256r1
public key as discussed earlier and
algorithm
is defined like this:
AlgorithmIdentifier  ::=  SEQUENCE  {
    algorithm   OBJECT IDENTIFIER,
    parameters  ANY DEFINED BY algorithm OPTIONAL
}

Its

algorithm
OID is always 1.2.840.10045.2.1 (that is,
id-ecPublicKey
) and there’s a single key parameter, another OID, 1.2.840.10045.3.1.7, or
secp256r1
, which identifies a
secp256r1
public key.

IMPORTANT iOS tends to deal with raw keys whereas most other security toolkits, like OpenSSL, tend to deal with public keys wrapped in a

SubjectPublicKeyInfo
structure.

Now, for a given EC key type the first N bytes of the

SubjectPublicKeyInfo
are fixed, hence the
headerBytes
array that jmarne uncovered earlier. If you take those header bytes:
30593013 06072A86 48CE3D02 0106082A
8648CE3D 03010703 4200

and slap them on the front of a

secp256r1
public key.
04
B24DB122 E2DDDC97 FB0F58ED 7836F2CA
6868C040 6B483FDA 473FDDD0 41D380B0
4EDB6183 3273046A 33D7A9A8 D66B93AA
A10B8732 93C68114 9CC7FA5E CD3523CE

you end up with a file that you can dump with dumpasn1:

$ dumpasn1 -p tmp.asn1
SEQUENCE {
  SEQUENCE {
    OBJECT IDENTIFIER '1 2 840 10045 2 1'
    OBJECT IDENTIFIER ansiX9p256r1 (1 2 840 10045 3 1 7)
    }
  BIT STRING
    04 B2 4D B1 22 E2 DD DC 97 FB 0F 58 ED 78 36 F2
    CA 68 68 C0 40 6B 48 3F DA 47 3F DD D0 41 D3 80
    B0 4E DB 61 83 32 73 04 6A 33 D7 A9 A8 D6 6B 93
    AA A1 0B 87 32 93 C6 81 14 9C C7 FA 5E CD 35 23
    CE
  }

And, lo!, a nicely wrapped EC public key suitable for use with OpenSSL and so on.

WARNING This ‘prefix with a fixed header’ approach only works if the public key is of the right type and is not compressed. In an ideal world you would build up the

SubjectPublicKeyInfo
structure properly, which would allow you to work with both compressed and uncompressed keys. That would, however, require you to write some ASN.1 generation code.

Share and Enjoy

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

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

hi Eskimo,


Thank you so very much for this comprehensive explanation. it really makes a lot more sense now. I suspect the signature generated by the SecKeyRawSign has a similar issue with the wrapping that prevents it for being properly verified from OpenSSL, which is expecting some kind of ASN.1 wrapping?


thanks again.

I suspect the signature generated by the SecKeyRawSign has a similar issue with the wrapping that prevents it for being properly verified from OpenSSL, which is expecting some kind of ASN.1 wrapping?

When last I looked at that side of things I definitely had fun mapping OpenSSL’s padding options to those used by our Security framework. I eventually got it working, see the CryptoCompatibility sample code, but that was for RSA, not EC.

Share and Enjoy

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

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

When last I looked at that side of things I definitely had fun mapping OpenSSL’s padding options to those used by our Security framework. I eventually got it working, see the CryptoCompatibility sample code, but that was for RSA, not EC.

I had cause to play around with this today and managed to implement an end-to-end test from iOS to OpenSSL:

  1. iOS app generates an EC key pair with the private key in the Secure Enclave

  2. iOS app exports the public key

  3. iOS app signs some data with the private key and prints the data, the signature, and the public key

  4. on the Mac I put that data into various files

  5. I prepended the SubjectPublicKeyInfo to the raw EC public key

  6. I converted the public key to PEM (see below)

  7. I then verified the public key with OpenSSL’s command line tool (see below)

$ openssl ec -pubin -inform DER -in EC\ key.asn1 -pubout -outform PEM -out EC\ key.pem
read EC key
writing EC key
$ openssl dgst -ecdsa-with-SHA1 -verify EC\ key.pem -signature signature.dat dataToSign.dat
Verified OK

While working on this I encountered two gotchas:

  • When verifying the signature, you have to use

    -ecdsa-with-SHA1
    because OpenSSL treats
    -sha1
    as implying RSA.
  • When calling

    SecKeyRawSign
    with an EC key, you have to use a padding of
    kSecPaddingPKCS1
    because
    kSecPaddingPKCS1SHA1
    implies RSA (probably for the same reason).

Share and Enjoy

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

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

Hi Eskimo,


Thanks. Unfortunately, it's not working on my side. I knew that I had to use ecdsa-with-SHA1, and tried with both PKCS1SHA1 and PKCS1 with no luck.


I have the following scenario:

1. I generate an EC keypair with secure enclave, private key is held there, public key is extracted as NSData after adding to the keychain, pre-pended with the SubjectPublicKeyInfo containing the ASN.1 data, and sent to the server.


        let privateKeyParams: [String: AnyObject] = [
            kSecAttrAccessControl as String: accessControl,
            kSecAttrIsPermanent as String: true,
            kSecAttrApplicationTag as String: "privateAppTag"
        ]
   
        let publicKeyParams: [String: AnyObject] = [
            kSecAttrApplicationTag as String: "publicAppTag"
        ]
    
        let parameters: [String: AnyObject] = [
            kSecAttrKeyType as String:          kSecAttrKeyTypeEC,
            kSecAttrKeySizeInBits as String:    256,
            kSecAttrTokenID as String:          kSecAttrTokenIDSecureEnclave,
            kSecPublicKeyAttrs as String:       publicKeyParams,
            kSecPrivateKeyAttrs as String:      privateKeyParams
        ]
   
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in
            var pubKey, privKey: SecKeyRef?
            let status = SecKeyGeneratePair(parameters, &pubKey, &privKey)
            if status == errSecSuccess {
               // add public key to keychain, prepend SubjectPublicKeyInfo to raw data and send to the server.
            } else {
               // handle error
            }
        }
    }


2. The server receives the key, properly parses it and converts it to PEM. I know the key is successfully processed because openSSL recognizes it:

$ openssl ec -pubin -noout -text -in public_key.pem
read EC key
pub:
    04:1f:48:a0:df:0f:ca:c9:95:59:91:65:e2:d0:bf:
    4c:03:53:a0:f7:c1:a4:e6:c4:37:e9:bc:0a:af:1e:
    ab:a2:9a:1d:5f:33:1a:fb:e5:30:e8:7e:b6:49:e1:
    01:2c:83:7a:55:39:39:87:4c:97:3d:f3:a0:c3:51:
    b6:ad:12:77:58
ASN1 OID: prime256v1


This is the ASN.1 dump of the key:


$ openssl asn1parse -in public_key.pem
    0:d=0  hl=2 l=  89 cons: SEQUENCE
    2:d=1  hl=2 l=  19 cons: SEQUENCE
    4:d=2  hl=2 l=   7 prim: OBJECT            :id-ecPublicKey
   13:d=2  hl=2 l=   8 prim: OBJECT            :prime256v1
   23:d=1  hl=2 l=  66 prim: BIT STRING


3. I sign a plain text (from the iOS side) to send to the server for verification. I use SecKeyRawSign with SecPadding.PKCS1. Data is a SHA1 of the plain text using the CC_SHA1 function.


signing plain text: haGXtJfQBLPmUlCW+ArdtOiBW4Yckv8hCJhhrGKcwo8yj46o0YgaQg==
Result: 0. Generated signature: <30460221 00ed3648 6788fa05 1eff6b3b 0f09438a 4032a358 2bcd37b9 f3db9429 497a12f6 7e022100 a4ff57e1 6cb763c7 a7f2f0d4 b44add73 e939019c 4eb75df6 36590f22 2ce29258>


After that I do a verification in iOS that passes OK (just to be sure). I use the same CC_SHA1 function to get the digest and the same padding.

Verifying signature...
Text to verify: haGXtJfQBLPmUlCW+ArdtOiBW4Yckv8hCJhhrGKcwo8yj46o0YgaQg==
Signature to verify: Optional(<30460221 00ed3648 6788fa05 1eff6b3b 0f09438a 4032a358 2bcd37b9 f3db9429 497a12f6 7e022100 a4ff57e1 6cb763c7 a7f2f0d4 b44add73 e939019c 4eb75df6 36590f22 2ce29258>)
Verifying self signature with public key:
text as data:<68614758 744a6651 424c506d 556c4357 2b417264 744f6942 57345963 6b763868 434a6868 72474b63 776f3879 6a34366f 30596761 51673d3d>
signature as data:<30460221 00ed3648 6788fa05 1eff6b3b 0f09438a 4032a358 2bcd37b9 f3db9429 497a12f6 7e022100 a4ff57e1 6cb763c7 a7f2f0d4 b44add73 e939019c 4eb75df6 36590f22 2ce29258>
Key: <SecKeyRef curve type: kSecECCurveSecp256r1, algorithm id: 3, key type: ECPublicKey, version: 3, block size: 256 bits, y: 587712ADB651C3A0F33D974C873939557A832C01E149B67EE830E5FB1A335F1D01, x: 9AA2AB1EAF0ABCE937C4E6A4C1F7A053034CBFD0E265915995C9CA0FDFA0481F587712ADB651C3A0F33D974C873939557A832C01E149B67EE830E5FB1A335F1D01, addr: 0x135a20870>
Status: 0 (errSecSuccess)


4. However, if I get the signature data, copy and paste it on a binary file, and verify it with openssl (same command as you used):


openssl dgst -ecdsa-with-SHA1 -verify public_key.pem -signature signature.bin plaintext.txt

Verification Failure



If I generate the keypair using openSSL, sign and verify, everything goes fine:

1. create the private EC key w/ public key.
openssl ecparam -genkey -name prime256v1 -noout -out myprivatekey.pem
2. generate the public EC key from the private EC key
openssl ec -in myprivatekey.pem -pubout -out mypubkey.pem
3. Sign the plaintext.txt file
openssl dgst -ecdsa-with-SHA1 -sign myprivatekey.pem plaintext.txt > signature.bin
4. Verify signature
openssl dgst -ecdsa-with-SHA1 -verify mypubkey.pem -signature signature.bin plaintext.txt

Verified OK


The Swift signing code is really simple, I think, and I don't know where the problem may be:


let resultData       = NSMutableData(length: 1024)!
let resultPointer    = UnsafeMutablePointer<UInt8>(resultData.mutableBytes)
var resultLength     = resultData.length

if let plainData = message.dataUsingEncoding(NSUTF8StringEncoding) {
    /
    let hashData = NSMutableData(length: Int(CC_SHA1_DIGEST_LENGTH))!
    let hash = UnsafeMutablePointer<UInt8>(hashData.mutableBytes)
    CC_SHA1(UnsafePointer<Void>(plainData.bytes), CC_LONG(CC_SHA1_DIGEST_LENGTH), hash)

    let status = SecKeyRawSign(privateKeyRef, SecPadding.PKCS1, hash, hashData.length, resultPointer, &resultLength)
    if status != errSecSuccess {
        error = .UnableToEncrypt
    } else {
        resultData.length = resultLength
        print("Result: \(status). Generated signature: \(resultData)")
        // base64 encode resultData and send to the server for verification.

    }
} else { error = .WrongInputDataFormat }


I really don't know what can be wrong or how to get out of this. Thanks in advance for your time.


Best

Can't export EC kSecAttrTokenIDSecureEnclave public key
 
 
Q