I'm trying to send secure message between our server and iOS app. I see the following forum post about how to do this:
https://forums.developer.apple.com/message/84684#84684
except it doesn't work. I even use the sample code in the git repo which also fails key verification:
https://github.com/hfossli/EskimoKeys/tree/master
FWIW, here is my output of running the sample code:
#! /bin/sh
echo 36ead61ad53f77e2222223cbb71dfae17cfd1ad92a86457a6b051dfcce323cc6 | xxd -r -p > digest.dat
echo 304502207c71eb3c78c658d0677c845cfacdb1f9ff3af79269abc34d4028842000316600022100edab9c28e7dd1f9bb041ddf535b9f12c1b0a4d69c43563289a223f493471b746 | xxd -r -p > signature.dat
cat > key.pem <<EOF
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAER9cNNUo+aE5URYaETT9K9clKstFt
+/1X5qP8f2NxDQgP7VBkRArwzRlyltp8rh668JUPwWYKCjdtHqLFA/h3KA==
-----END PUBLIC KEY-----
EOF
openssl dgst -ecdsa-with-SHA1 -verify key.pem -signature signature.dat digest.dat
Runing the script I get the following error message:
"Verification Failure"
Unfortunately, I don't know enough about openssl to figure what the problem is. FWIW I can analyze the public key with the following script:
$ openssl asn1parse -in tempkey.b64 -inform 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
So the public key looks valid, but I can't do the digest verification as the above-mentioned forum post describes.
Any ideas? Thanks.
Doug Hill
Since posting about this on that other thread I’ve had a chance to dig into it in depth, even so far as to get it working on the Mac (thanks to my boss for getting me a shiny new Touch Bar MacBook Pro :-). The resulting project is too big to post in its entirety but I can post some snippets.
First, here’s how I set up the dictionary passed to
SecKeyGeneratePair
.
let access = SecAccessControlCreateWithFlags(
nil,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
[.touchIDAny, .privateKeyUsage],
nil
)!
paramsDict = [
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecAttrKeyType as String: kSecAttrKeyTypeEC,
kSecAttrKeySizeInBits as String: 256,
kSecAttrApplicationTag as String: appTag,
kSecPrivateKeyAttrs as String: [
kSecAttrIsPermanent as String: true,
kSecAttrAccessControl as String: access,
],
]
Notes:
This puts the private key on the Secure Enclave but leaves the public key only in memory. I then manually add the public key to the keychain at this point. AFAIK there’s no way to generate both keys in permanent storage (r. 30040961).
Of course I only need to add the public key to the keychain if I’m using
to get the key bits, which isn’t necessary on iOS 10 (see below).SecItemCopyMatching
Here’s how I wrap the key in a DER-encoded ASN.1
SubjectPublicKeyInfo
structure:
private static func subjectPublicKeyInfo(for key: SecKey) throws -> Data {
let keyData = try SecKeyCopyExternalRepresentationCompat(key)
let keyInfo = try SecKeyGetKeyInfoCompat(key)
// Create an OID based on the key type and size.
let algorithm: NanoDER
switch keyInfo.keyType {
…
case kSecAttrKeyTypeEC as NSString as String:
// These values per RFC 5480.
//
// <http://www.ietf.org/rfc/rfc5480.txt>
let namedCurve: NanoDER
switch keyInfo.keySizeInBits {
case 192: namedCurve = .objectIdentifier([1, 2, 840, 10045, 3, 1, 1]) // secp192r1
case 224: namedCurve = .objectIdentifier([1, 3, 132, 0, 33]) // secp224r1
case 256: namedCurve = .objectIdentifier([1, 2, 840, 10045, 3, 1, 7]) // secp256r1
case 384: namedCurve = .objectIdentifier([1, 3, 132, 0, 34]) // secp384r1
case 512: namedCurve = .objectIdentifier([1, 3, 132, 0, 35]) // secp521r1
default:
throw NSError(domain: NSOSStatusErrorDomain, code: Int(errSecUnimplemented), userInfo: nil)
}
algorithm = .sequence([
.objectIdentifier([1, 2, 840, 10045, 2, 1]), // ecPublicKey
namedCurve
])
default:
throw NSError(domain: NSOSStatusErrorDomain, code: Int(errSecUnimplemented), userInfo: nil)
}
// Wrap the algorithm and key data in a `SubjectPublicKeyInfo` structure.
return Data(bytes: NanoDER.sequence([
algorithm,
.bitString([UInt8](keyData), unusedBits: 0)
]).bytes)
}
Notes:
is a wrapper aroundSecKeyCopyExternalRepresentationCompat
. It exists to provide iOS 9 compatibility, where it gets the raw key bits viaSecKeyCopyExternalRepresentation
.SecItemCopyMatching
is basically a wrapper aroundSecKeyGetKeyInfoCompat
, again with iOS 9 compatibility usingSecKeyCopyAttributes
.SecItemCopyMatching
is a really small DER builder library of my own creation. I’m using it to add theNanoDER
header to the front of the key bytes. In the specific case of a Secure Enclave key you can hard code that header, as discussed in that earlier thread you referenced.SubjectPublicKeyInfo
With this in place I can sign data like this:
let signature: Data
do {
signature = try SecKeyCreateSignatureCompat(privateKey, DigestAlgorithm.sha1, dataToSign: dataToSign)
} catch {
NSLog("signing problem")
throw error
}
and export the key as a PEM like this:
let keyBase64 = keyData.base64EncodedString(options: [.lineLength64Characters])
lines.append("-----BEGIN PUBLIC KEY-----")
lines.append( contentsOf: keyBase64.components(separatedBy: "\r\n") )
lines.append("-----END PUBLIC KEY-----")
Notes:
is another compatibility wrapper, bouncing toSecKeyCreateSignatureCompat
on iOS 10 and later andSecKeyCreateSignature
on iOS 9.SecKeyRawSign
I’m using SHA-1 to simplify my testing. In a real app you’d want to use something better.
phew
Share and Enjoy
—
Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"