Sign, verify, encrypt and decrypt using the Secure Enclave on iOS and MacOS.
Supports FaceID, TouchID, device pass code and application password.
Using the Security Framework can be a little bit confusing. That’s why I created this. You may use it as example code and guidance or you may use it as a micro framework.
I found it tricky to figure out how to use the SecKeyRawVerify
, SecKeyGeneratePair
and SecItemCopyMatching
C APIs in Swift 3, but the implementation is quite straight forward thanks to awesome Swift features.
Just drag Sources/EllipticCurveKeyPair.swift
and Sources/SHA256.swift
file into your Xcode project.
pod EllipticCurveKeyPair
github "agens-no/EllipticCurveKeyPair"
There are lots of great possibilities with Secure Enclave. Here are some examples
Only available on iOS 10 and above
A use case could be
For more examples see demo app.
struct KeyPair {
static let manager: EllipticCurveKeyPair.Manager = {
let publicAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAlwaysThisDeviceOnly, flags: [])
let privateAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, flags: [.userPresence, .privateKeyUsage])
let config = EllipticCurveKeyPair.Config(
publicLabel: "payment.sign.public",
privateLabel: "payment.sign.private",
operationPrompt: "Confirm payment",
publicKeyAccessControl: publicAccessControl,
privateKeyAccessControl: privateAccessControl,
token: .secureEnclave)
return EllipticCurveKeyPair.Manager(config: config)
}()
}
You can also gracefully fallback to use keychain if Secure Enclave is not available by using different access control flags:
let privateAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, flags: {
return EllipticCurveKeyPair.Device.hasSecureEnclave ? [.userPresence, .privateKeyUsage] : [.userPresence]
}())
In that case you need to remember to set token
variable in Config
object to .secureEnclaveIfAvailable
.
do {
let key = try KeyPair.manager.publicKey().data().DER // Data
} catch {
// handle error
}
See demo app for working example
do {
let key = try KeyPair.manager.publicKey().data().PEM // String
} catch {
// handle error
}
do {
let digest = "some text to sign".data(using: .utf8)!
let signature = try KeyPair.manager.sign(digest, hash: .sha256)
} catch {
// handle error
}
You may also pass a LAContext object when signing
do {
let digest = "some text to encrypt".data(using: .utf8)!
let encrypted = try KeyPair.manager.encrypt(digest, hash: .sha256)
} catch {
// handle error
}
You can also encrypt on a different device/OS/platform using the public key. If you want to know all the details about how to encrypt in a format the Secure Enclave understands, then you need definitely need to read this great write up by @dschuetz!
https://darthnull.org/security/2018/05/31/secure-enclave-ecies/
do {
let encrypted = ...
let decrypted = try KeyPair.manager.decrypt(encrypted, hash: .sha256)
let decryptedString = String(data: decrypted, encoding: .utf8)
} catch {
// handle error
}
You may also pass a LAContext object when decrypting
The most common thing is to catch error related to
With do/catch
:
do {
let decrypted = try KeyPair.manager.decrypt(encrypted)
} catch EllipticCurveKeyPair.Error.underlying(_, let underlying) where underlying.code == errSecUnimplemented {
print("Unsupported device")
} catch EllipticCurveKeyPair.Error.authentication(let authenticationError) where authenticationError.code == .userCancel {
print("User cancelled/dismissed authentication dialog")
} catch {
print("Some other error occurred. Error \(error)")
}
With if let
:
if case let EllipticCurveKeyPair.Error.underlying(_, underlying) = error, underlying.code == errSecUnimplemented {
print("Unsupported device")
} else if case let EllipticCurveKeyPair.Error.authentication(authenticationError), authenticationError.code == .userCancel {
print("User cancelled/dismissed authentication dialog")
} else {
print("Some other error occurred. Error \(error)")
}
In order to inspect the queries going back and forth to Keychain you may print to console every mutation this library does on Keychain like this
EllipticCurveKeyPair.logger = { print($0) }
In the demo app you’ll see that each time you create a signature some useful information is logged to console.
Example output
#! /bin/sh
echo 414243 | xxd -r -p > dataToSign.dat
echo 3046022100842512baa16a3ec9b977d4456923319442342e3fdae54f2456af0b7b8a09786b022100a1b8d762b6cb3d85b16f6b07d06d2815cb0663e067e0b2f9a9c9293bde8953bb | xxd -r -p > signature.dat
cat > key.pem <<EOF
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdDONNkwaP8OhqFTmjLxVcByyPa19
ifY2IVDinFei3SvCBv8fgY8AU+Fm5oODksseV0sd4Zy/biSf6AMr0HqHcw==
-----END PUBLIC KEY-----
EOF
/usr/local/opt/openssl/bin/openssl dgst -sha256 -verify key.pem -signature signature.dat dataToSign.dat
In order to run this script you can
verify.sh
chmod u+x verify.sh
./verify.sh
Then you should see
Verified OK
PS: This script will create 4 files in your current directory.
Security framework, Swift 3, Swift 4, Swift, SecKeyRawVerify, SecKeyGeneratePair, SecItemCopyMatching, secp256r1, Elliptic Curve Cryptography, ECDSA, ECDH, ASN.1, Apple, iOS, Mac OS, kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeyTypeEC, kSecAttrTokenIDSecureEnclave, LAContext, LocalAuthentication, FaceID, Face ID, TouchID, Touch ID, Application Password, Device Pin, Devicepin
TrailOfBits published some objective-c code a while back which was to great help! Thanks for sharing Tidas and SecureEnclaveCrypto. They also got some other most interesting projects. Check them out!
He shared som very valuable insights with regards to exporting the public key in the proper DER X.509 format.
The SHA256
class (originally SHA2.swift
) is found in the invaluable CryptoSwift library by Marcin Krzyżanowski. The class has been heavily altered in order to strip it down to its bare minimum for what we needed in this project.
Why am I not being prompted with touch id / device pin on simulator?
The simulator doesn’t possess any secure enclave and therefore trying to access it would just lead to errors. If you set
fallbackToKeychainIfSecureEnclaveIsNotAvailable
totrue
then the private key will be stored in keychain on simulator making it easy to test your application on simulator as well.
Where can I learn more?
Check out this video on WWDC 2015 about Security in general or click here to skip right to the section about the Secure Enclave.
Why is it wrapped in an enum?
I try to balance drag-and-drop the files you need into xcode and supporting dependency managers like carthage and cocoapods at the same time. If you have better ideas or don't agree with this decision I'm happy to discuss alternatives :)
We would 😍 to hear your opinion about this library. Wether you like or don’t. Please file an issue if there’s something you would like to see improved. You can reach me as @hfossli on twitter and elsewhere. 😀