Swift iOS iPadOS app for Smartcard Token PIV using CryptoTokenKit

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):

  1. Insert a PIV SmartCard/Token (such as a Yubikey 5Ci) into the lightning port of an iPadOS device iPad (NOT MacOS)

  2. 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?

Accepted Reply

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:

  1. On iOS only, add the com.apple.token keychain access group to your keychain-access-groups entitlement [2].

  2. When querying the keychain with SecItemCopyMatching, pass in the kSecAttrAccessGroup attribute with the value set to kSecAttrAccessGroupToken.

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)")
    }
}

Replies

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:

  1. On iOS only, add the com.apple.token keychain access group to your keychain-access-groups entitlement [2].

  2. When querying the keychain with SecItemCopyMatching, pass in the kSecAttrAccessGroup attribute with the value set to kSecAttrAccessGroupToken.

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)")
    }
}

Thank you eskimo! I am grateful to have your help on this issue and also sorry to have wasted your time.

It turns out your code snippet works like a charm on iOS 16 and iPadOS 16 apps as long as I am using a USB-C connector NOT lightning

I had to go back and re-read the release announcement here: https://support.apple.com/guide/deployment/intro-to-smart-card-integration-depd0b888248/1/web/1.0

I realized that two things need to be in place to get the native smartcard support on iOS/iPadOS 16 devices:

  1. CCID-compliant smartcard reader
  2. USB-C connection

Once I got my hands on a 10th gen iPad with a USB-C port, I was able to use my Yubikey 5Ci USB-C connector and the example snippet you provided worked like a charm! It is my understanding that as long as you use a CCID-compliant USB-C smartcard reader then the PIV identities on the Token will be placed onto the iOS keychain and available for my app to query using SecItemCopyMatching

The list() helper function was really nice for debugging as well.

Hope my post can help some future devs who may be confused about the iOS 16 smartcard support.

Cheers to eskimo the GOAT!

Hi,

I am trying to implement an app which performs cert based authentication through smart card. I have few queries related to the same

I have included com.apple.token in the key chain accessory group. I am able to fetch the certificates from the keychain using Yubi key Type c. But this is not working for Yubi key lighting port. Does Apple support lighting port readers?

What is the need of crypto token kit extension if we are able to list the certificates from key chain just by adding com.apple.token in the entitlements file.

  • There is a subtle but very important point to make regarding the built-in support for PIV card readers.

    iOS and iPadOS support PIV card reader out of the box ONLY if the PIV card reader is connected via USB-C.

    If you need to support PIV card readers that connect via lightning or NFC (wireless) then you will need to develop a CryptoTokenKit app extension that is bundled alongside your app.

Add a Comment

Does Apple support lighting port readers?

Depends on what you mean by “support”. The iOS security infrastructure is able to work with smart card readers connected via Lightning, USB, NFC, and others. The exact mechanics of how that works, and who supports it if it fails!, depends on the connection type.

What is the need of crypto token kit extension if we are able to list the certificates from key chain just by adding com.apple.token in the entitlements file.

The built-in infrastructure can work the SE and with PIV hardware token. If your hardware token needs custom support, or you want to create a virtual token that’s not backed by directly connected hardware, you create a CTK appex.

For example, my Yubikey 5Ci has NFC support. That only works if Yubico’s app is installed, because the app contains the CTK appex that bridges to NFC [1].

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Via a very convoluted path, I might add, but that’s because a CTK appex can’t use Core NFC directly.

Thanks for the details.

**The built-in infrastructure can work the SE and with PIV hardware token. If your hardware token needs custom support, or you want to create a virtual token that’s not backed by directly connected hardware, you create a CTK appex. **

If we want to fetch the certificates from smart cards connected via Bluetooth/lighting port, Do we have to write an CryptoTokenKitExtension app? I see very limited documentation on how exactly CryptoTokenKit can detect the readers and fetch certificates from smart card.

Can you please assist here? @eskimo

I see very limited documentation on how exactly CryptoTokenKit can detect the readers and fetch certificates from smart card.

That’s because detecting the reader and fetching the certificates is done by the CTK appex, and how it does that is entirely up to it.

Consider my YubiKey 5 NFC. It has multiple hardware interfaces, including USB and NFC. If I plug it into the USB-C port on my iPad, using a standard USB to USB-C adapter, it Just Works™. The token supports PIV and iOS has built-in PIV support. OTOH, if I try to use it over NFC, I must install Yubico’s app so that its CTK appex can route the request to the token’s NFC interface.

If we want to fetch the certificates from smart cards connected via Bluetooth/lighting port, do we have to write an CryptoTokenKitExtension app?

That depends on the token’s vendor:

  • If the vendor has already written a CTK appex, your app can talk to the token that way. This is exactly what happens when I use my YubiKey over NFC.

  • If not, you need to ping the vendor because:

    • They might actively support third-party access — by publish an interface spec, say — and that would allow you to create your own CTK appex.

    • They might not, in which case you can’t work with that token.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks for the details.

They might actively support third-party access — by publish an interface spec, say — and that would allow you to create your own CTK appex.

So CTK extension app can't be implemented independently by any app who needs to detect any smart card readers and fetch certificates from the smart card? This needs a collaboration with smart card reader vendors who can provide an additional interface which allows app to talk to the token?

If the smart card is completely custom, someone must write an app that contains a CTK appex that can talk to the card. This exposes the digital identities on the smart card via the keychain, at which point any app can use them.

To implement such a CTK appex the app’s author must know how to talk to the smart card. There are a variety of ways that could happen:

  • The author might be the vendor.

  • The author might use an SDK provided by the vendor.

  • The author might use an interface spec provided by the vendor.

If the smart card supports PIV, the system’s PIV support kicks in. This comes in the form of a CTK appex, which gets you to the same end state.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks.

**If the smart card supports PIV, the system’s PIV support kicks in. This comes in the form of a CTK appex, which gets you to the same end state. **

Does this mean through CTK we can detect PIV smart card through any reader(lighting/USB/NFC) without the need of any vendor intervention?

Does this mean through CTK we can detect PIV smart card through any reader (lighting/USB/NFC) without the need of any vendor intervention?

If the PIV smart card is supported by the built-in PIV CTK appex, you don’t need any extra vendor support.

You don’t even need CTK! You can work with the credentials on the smart card using just Security framework APIs.

As to whether a specific PIV smart card is supported by iOS, I don’t have a comprehensive list of what is or isn’t supported [1]. I can only relate my own experience, namely, that a YubiKey 5 NFC works out of the box, with no Yubico software, when I plug it into the USB-C port on my iPad.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] You might find more info about this on the Apple Support website.