Please excuse my lack of understanding of what are probably fundamental concepts in iOS/iPadOS development but I have searched far and wide for documentation and haven't had much luck so far. I am not sure that what I want to do is even possible with an iPad iPadOS app.
Goals: Develop a Swift iPadOS app that can digitally sign a file using a PIV SmartCard/Token (Personal Identity Verification Card):
-
Insert a PIV SmartCard/Token (such as a Yubikey 5Ci) into the lightning port of an iPadOS device iPad (NOT MacOS)
-
Interface with the SmartCard/Token to access the user's PIV certificate/signature and "use it" to sign a file
Question 1: How to get the PIV Certificate from SmartCard/Token/Yubikey into iPadOS keychain?
* Do we need to get the PIV certificate into the iOS keychain? Is there another way to interact with a SmartCard directly?
* This should prompt the user for their PIN?
Question 2: How to get our Swift app to hook into the event that the SmartCard/Token is inserted into the device and then interface with the user's certificate?
* When is the user prompted to enter their PIN for SmartCard/Token/Yubikey?
* Do we need to use CyrptoTokenKit to interface with a smartcard inserted into the lightning port of an iOS device?
This is all quite feasible. You don’t need to use CTK here, although there are some situations where you might want to do that [1]. However, for basic functionality all you need to do is:
-
On iOS only, add the
com.apple.token
keychain access group to yourkeychain-access-groups
entitlement [2]. -
When querying the keychain with
SecItemCopyMatching
, pass in thekSecAttrAccessGroup
attribute with the value set tokSecAttrAccessGroupToken
.
Pasted in below are a few code snippets from a test project I have lying around. I tested it with a YubiKey 5 NFC.
IMPORTANT This snippet uses deliberately bad crypto, .rsaSignatureMessagePSSSHA1
.
This snippet uses the secCall
helpers from here.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
[1] For example, using TKTokenWatcher
to watch tokens come and go.
[2] The docs are not at all clear about this, something I recently filed a bug about. Coulda sworn I filed a bug about that but I can’t find the bug number right now )-:
func list() {
do {
print("will list")
let certificates = try secCall { SecItemCopyMatching([
kSecClass: kSecClassCertificate,
kSecAttrAccessGroup: kSecAttrAccessGroupToken,
kSecMatchLimit: kSecMatchLimitAll,
kSecReturnRef: true,
] as NSDictionary, $0) } as! [SecCertificate]
let names = try certificates.map { cert in
try secCall { SecCertificateCopySubjectSummary(cert) }
}
print("did list, names:")
print(names.map { " \($0)" }.joined(separator: "\n"))
} catch {
print("did not list, error: \(error)")
}
}
func sign() {
do {
print("will sign")
let identity = try secCall { SecItemCopyMatching([
kSecClass: kSecClassIdentity,
kSecAttrAccessGroup: kSecAttrAccessGroupToken,
kSecReturnRef: true,
] as NSDictionary, $0) } as! SecIdentity
let privateKey = try secCall { SecIdentityCopyPrivateKey(identity, $0) }
let dataToSign = Data("Hello Cruel World! \(Date())".utf8)
let signature = try secCall { SecKeyCreateSignature(privateKey, .rsaSignatureMessagePSSSHA1, dataToSign as NSData, $0) }
print("did sign, identity: \(identity), signature: \(signature)")
print("will verify")
let cert = try secCall { SecIdentityCopyCertificate(identity, $0) }
let publicKey = try secCall { SecCertificateCopyKey(cert) }
let didVerify = SecKeyVerifySignature(publicKey, .rsaSignatureMessagePSSSHA1, dataToSign as NSData, signature as NSData, nil)
if didVerify {
print("did verify")
} else {
print("did not verify")
}
} catch {
print("did not sign, error: \(error)")
}
}