Since it is going to be a be long post, I'll start by resuming what my goals are:
- ✅ Create an EC private key inside the Secure Enclave ℹ (only secp256r1 is supported atm. A11 and before)
- ✅ Get the public key so I can export it ℹ (you can only get a binary format, so you may want to wrap it)
- ✖ Sign an already hashed Data to an x509 DER format ℹ (the hash used is SHA256) (see EDIT3)
- ✅ Remove the private key
xCode: 9.0
iPhone6s: iOS11.0.2
Swift: 3
Here are the attributes used to generate my key pair:
let attributes:[NSObject:AnyObject] = [
kSecAttrIsPermanent: kCFBooleanTrue,
kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
kSecAttrApplicationTag: ApplicationPrivateKeyTag as CFString,
kSecAttrLabel: ApplicationPrivateKeyLabel as CFString,
kSecAttrKeyType: ( iOS10Available ) ? (kSecAttrKeyTypeECSECPrimeRandom) : (kSecAttrKeyTypeEC),
kSecAttrKeySizeInBits: ECKeySize as CFNumber,
kSecAttrTokenID: kSecAttrTokenIDSecureEnclave
]
note1: iOS10Avaibale is a boolean that already checked which version of iOS we're dealing with, so we can give the correct parameter
note2: ECKeySize == 256
I generate my keypair with:
SecKeyGeneratePair(attributes as CFDictionary, nil, nil)
Though I also tried this one, without actually getting the difference between them, is there a difference other than the function's signature?
var error: Unmanaged<CFError>?
SecKeyCreateRandomKey(attributes as CFDictionary, &error)
I am then able to get the public key (and format it to x509 format):
// Query to get the private ref
let query: [NSObject: AnyObject] = [
kSecAttrApplicationTag: ApplicationPrivateKeyTag as AnyObject,
kSecAttrLabel: ApplicationPrivateKeyLabel as AnyObject,
kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecReturnRef: kCFBooleanTrue
]
// Get private key..
// SecItemCopyMatching(query as NSDictionary, &extractedData)
// Get public SecKey..
// SecKeyCopyPublicKey(privateSecKeyRef)
// Get public key as Data
// SecKeyCopyExternalRepresentation(publicSecKey!, &error)
// Wrap public key to x509 format
// x509Header: Data = Data(bytes: [UInt8]([48, 89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7, 3, 66, 0]))
var pubkeyWraped = x509Header
pubkeyWraped.append(publicKey)
pubkeyWraped.hexEncodedString() // <- here is the hex public key
If you wonder about:
- x509 header: see https://forums.developer.apple.com/message/84684#84684
- hexEncodedString(): see stackoverflow dot com /questions/39075043/how-to-convert-data-to-hex-string-in-swift#40089462
- the other functions: check the doc
note: I keep it in hex format instead of base64 format
=======================================================================================
=======================================================================================
Ok now, some precisions about my test:
- The original data is: "wubba lubba dub dub"
- dataToSign is a hash SHA256 : "23a0944d11b5a54f1970492b5265c732044ae824b7d5656acb193e7f0e51e5fa"
And once again I need to make a choice, but I can't figure it out whether I should use:
Method 1: (the one that should be used no?)
let signature = SecKeyCreateSignature(privateSecKeyRef,
.ecdsaSignatureDigestX962SHA256,
digestToSign as CFData,
&error) as Data?
I don't get the difference between:
- .ecdsaSignatureRFC4754
- .ecdsaSignatureDigestX962
- .ecdsaSignatureDigestX962SHA256 (the one documented, it should be the one to use right?)
Doc is here: https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/signing_and_verifying
This function returns this error:
"bad digest size for signing with algorithm algid:sign:ECDSA:digest-X962:SHA256"
What am I doing wrong here?
Isn't the size expected 64 bytes?
Am I bigger, smaller?
What is it related to?
As seen on these close-related threads, secp256r1 curve should be compatible with SHA256 hash right?
- crypto.stackexchange dot com /questions/19793/does-the-size-of-a-ecdsa-key-determine-the-hash-algorithm
- crypto.stackexchange dot com /questions/18488/ecdsa-with-sha256-and-sepc192r1-curve-impossible-or-how-to-calculate-e
EDIT1:
If I give the .ecdsaSignatureMessageX962SHA256 option and that I don't pre-hash the message, I am able to verify the signature both with SecKeyVerifySignature(...) and an external function made with go.
(my hash is good since I still give the hashed form to my external go function, and well, you can check it too)
===========
FWIW: I am converting my string to data that way:
let str = "wubba lubba dub dub"
// let str = "23a0944d11b5a54f1970492b5265c732044ae824b7d5656acb193e7f0e51e5fa"
let toSign = str.data(using: .utf8)!
EDIT3: str.data(using .utf8)! will convert every single character to create a byte, not 2 character... Thus creating a 64 bytes long Data instead of a required one of 32 bytes.
Method 2:
let digestToSignLen = digestToSign.count
var rawSig = Data(count: 128)
var rawSigLen = 128
let status = digestToSign.withUnsafeBytes { (digestToSign) -> OSStatus in
rawSig.withUnsafeMutableBytes({ (rawSig) -> OSStatus in
return SecKeyRawSign(privateKeyRef!, SecPadding.sigRaw, digestToSign, digestToSignLen, rawSig, &rawSigLen)
})
}
if status == errSecSuccess {
signature = rawSig.subdata(in: 0..<rawSigLen)
}
If you wonder about why nesting everything, take a look at:
https://forums.developer.apple.com/thread/63965
if you wonder about the PKCS1* options of SecPadding for ec keys, take a look at:
security.stackexchange dot com /questions/84327/converting-ecc-private-key-to-pkcs1-format
Not sure about the actual effect of SecPadding.sigRaw
Is it supposed to refer to:
- the format of what it receives?
- the way it is going to (not) format the signature?
- both?
When I execute this code using the .sigRaw options:
- with the private key attribute kSecAttrTokenID: kSecAttrTokenIDSecureEnclave
I get an OSStatus -50 (bad argument(s))
- without the private key attribute kSecAttrTokenID: kSecAttrTokenIDSecureEnclave
- I get an OSStatus -50 (bad argument(s))
- 😮 I get what looks like a raw signature (that needs to be wraped to DER format then?)
EDIT2: After wraping to ASN.1 and checking the signature, it appear that the key is not verifed 😟
ℹ I must make it work with a pkey stored in the Secure Enclave
btw: you can find a deprecated link pointing at kSecPaddingNone in padding section of the SecKeyRawSign(...) page?
Method 3
This function is only available on macOS
func SecSignTransformCreate(_ key: UnsafeMutablePointer<Unmanaged<CFError>?>?) -> SecTransform?
I am lost 😢
So could someone help me to clarify:
What is the way to go, to correctly sign an already hashed (sha256) data to DER format, thanks to an ec-256-secure-enclave private key?
Here is the code I use to check the signature in go: play.golang dot org/p/XH2bi5lBhK
Thank you