How to store certificate to `com.apple.token` keychain access group.

I’m developing an iOS application and aiming to install a PKCS#12 (.p12) certificate into the com.apple.token keychain access group so that Microsoft Edge for iOS, managed via MDM/Intune, can read and use it for client certificate authentication.

I’m attempting to save to the com.apple.token keychain access group, but I’m getting error -34018 (errSecMissingEntitlement) and the item isn’t saved. This occurs on both a physical device and the simulator.

I’m using SecItemAdd from the Security framework to store it. Is this the correct approach? https://developer.apple.com/documentation/security/secitemadd(::)

I have added com.apple.token to Keychain Sharing.

I have also added com.apple.token to the app’s entitlements.

Here is the code I’m using to observe this behavior:

public static func installToTokenGroup(p12Data: Data, password: String) throws -> SecIdentity {
    // First, import the P12 to get the identity
    let options: [String: Any] = [
        kSecImportExportPassphrase as String: password
    ]
    var items: CFArray?
    let importStatus = SecPKCS12Import(p12Data as CFData, options as CFDictionary, &items)
    guard importStatus == errSecSuccess,
          let array = items as? [[String: Any]],
          let dict = array.first
    else {
        throw NSError(domain: NSOSStatusErrorDomain,
                      code: Int(importStatus),
                      userInfo: [NSLocalizedDescriptionKey: "Failed to import P12: \(importStatus)"])
    }
    let identity = dict[kSecImportItemIdentity as String] as! SecIdentity

    let addQuery: [String: Any] = [
        kSecClass as String: kSecClassIdentity,
        kSecValueRef as String: identity,
        kSecAttrLabel as String: kSecAttrAccessGroupToken,
        kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
        kSecAttrAccessGroup as String: kSecAttrAccessGroupToken
    ]
    let status = SecItemAdd(addQuery as CFDictionary, nil)
    if status != errSecSuccess && status != errSecDuplicateItem {
        throw NSError(domain: NSOSStatusErrorDomain,
                      code: Int(status),
                      userInfo: [NSLocalizedDescriptionKey: "Failed to add to token group: \(status)"])
    }
    return identity
}
Answered by DTS Engineer in 880816022

Thanks for bringing this to the forums.

The com.apple.token keychain access group, aka kSecAttrAccessGroupToken, isn’t a normal keychain access group. Rather, it’s a special group that holds all of the credentials that the system finds in CryptoTokenKit (CTK) tokens. Given that, you can’t add credentials to this group directly.

It is possible to create a persistent CTK token, that is, one that’s not tied to smart card hardware. If you do that then the credentials published by that token will be available to all apps that are set up to use token-based credentials.

It’s not clear whether this approach will work for your ultimate goal:

so that Microsoft Edge for iOS … can … use it for client certificate authentication

My advice is that you first prototype this with an actual smart card [1]. If you can get that working, it’d be worth exploring the virtual token option.

Finally, if you’re curious how an app can work with token-based credentials, see this post.

Share and Enjoy

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

[1] I have a YubiKey that I use for tests like this.

Thanks for bringing this to the forums.

The com.apple.token keychain access group, aka kSecAttrAccessGroupToken, isn’t a normal keychain access group. Rather, it’s a special group that holds all of the credentials that the system finds in CryptoTokenKit (CTK) tokens. Given that, you can’t add credentials to this group directly.

It is possible to create a persistent CTK token, that is, one that’s not tied to smart card hardware. If you do that then the credentials published by that token will be available to all apps that are set up to use token-based credentials.

It’s not clear whether this approach will work for your ultimate goal:

so that Microsoft Edge for iOS … can … use it for client certificate authentication

My advice is that you first prototype this with an actual smart card [1]. If you can get that working, it’d be worth exploring the virtual token option.

Finally, if you’re curious how an app can work with token-based credentials, see this post.

Share and Enjoy

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

[1] I have a YubiKey that I use for tests like this.

Thank you very much for the clear explanation. It clarifies a lot for us.

Based on your response, we now understand that:

  1. com.apple.token is a special system-managed group for CryptoTokenKit tokens
  2. We cannot add credentials to it directly via kSecAttrAccessGroup
  3. We need to create a persistent CryptoTokenKit token (not tied to smart card hardware)

We have a few follow-up questions to implement this correctly:

Question 1: Creating Persistent Virtual Token

How do we create a persistent CTK token that's not tied to physical smart card hardware?

Our current attempt:

private static func configureTokenForIdentity(_ identity: SecIdentity) throws {
    var certRef: SecCertificate?
    SecIdentityCopyCertificate(identity, &certRef)
    guard let certificate = certRef else { return }
    
    let driverClassID: TKTokenDriver.ClassID = "com.example.ExampleTokenExtension"
    guard let driverConfig = TKTokenDriver.Configuration.driverConfigurations[driverClassID] else {
        throw NSError(domain: NSCocoaErrorDomain, code: -1, userInfo: nil)
    }
    
    let instanceID = UUID().uuidString
    let certItem = TKTokenKeychainCertificate(certificate: certificate, objectID: "com.example.cert1")
    let keyItem = TKTokenKeychainKey(certificate: certificate, objectID: "com.example.key1")
    keyItem?.canSign = true
    keyItem?.isSuitableForLogin = true
    
    let tokenConfig = TKToken.Configuration(instanceID: instanceID)
    tokenConfig.keychainItems = [certItem, keyItem].compactMap { $0 }
    
    // ❓ What is the correct API to persist this token configuration?
    // In iOS SDK 26, these APIs don't exist:
    // - driverConfig.addTokenConfiguration()
    // - driverConfig.openTokenConfiguration()
}

What is the correct API in SDK 26 to register a persistent token configuration?

Question 2: Private Key Storage and Access

Our token extension implements TKTokenSessionDelegate:

func tokenSession(_ session: TKTokenSession, sign dataToSign: Data, 
                 keyObjectID: Any, algorithm: TKTokenKeyAlgorithm) throws -> Data {
    guard let privateKey = getPrivateKeyFromKeychain() else {
        throw NSError(domain: TKErrorDomain, code: TKError.Code.objectNotFound.rawValue, userInfo: nil)
    }
    
    var error: Unmanaged<CFError>?
    guard let signature = SecKeyCreateSignature(
        privateKey,
        .rsaSignatureMessagePKCS1v15SHA256,
        dataToSign as CFData,
        &error
    ) else {
        throw error?.takeRetainedValue() as! Error
    }
    
    return signature as Data
}

private func getPrivateKeyFromKeychain() -> SecKey? {
    let query: [String: Any] = [
        kSecClass as String: kSecClassIdentity,
        kSecAttrLabel as String: "com.example.clientcert",
        kSecReturnRef as String: kCFBooleanTrue!,
        kSecMatchLimit as String: kSecMatchLimitOne
    ]
    
    var result: AnyObject?
    let status = SecItemCopyMatching(query as CFDictionary, &result)
    
    guard status == errSecSuccess, let identity = result as? SecIdentity else {
        return nil
    }
    
    var privateKey: SecKey?
    SecIdentityCopyPrivateKey(identity, &privateKey)
    return privateKey
}

❓ How should the token session retrieve the private key imported from the PKCS#12 bundle?

  • Should the private key be stored in the app's keychain (current approach)?
  • Or should it be stored in the token's keychain items?
  • What's the correct data flow between the app and token extension?

Question 3: Microsoft Edge Integration

According to Microsoft's documentation, Edge for iOS can read from com.apple.token when the MDM policy ReadExtendedCertificateLocationsEnabled is enabled:

https://learn.microsoft.com/en-us/deployedge/microsoft-edge-browser-policies/readextendedcertificatelocationsenabled

❓ Will Edge be able to access certificates from a persistent CTK token created by a third-party app?

Question 4: Development Approach

You mentioned: "first prototype this with an actual smart card"

For development/testing purposes:

  • ❓ Can we use a software-based virtual token instead of a physical smart card?
  • ❓ Is there sample code or a template for creating persistent virtual tokens?
  • ❓ What's the difference between a physical smart card token and a persistent virtual token (from the system's perspective)?

Our Goal

We want to understand the correct architecture to:

  1. Import a PKCS#12 certificate in a third-party app
  2. Create a persistent CryptoTokenKit virtual token that publishes this certificate
  3. Make it available system-wide for managed apps like Microsoft Edge for iOS

Key Question: Is this use case supported for third-party apps, or is it restricted to specific Apple-approved scenarios?

Thank you again for your guidance!


Entitlements Configuration (for reference):

<!-- CertificateInstaller.entitlements -->
<key>keychain-access-groups</key>
<array>
    <string>$(AppIdentifierPrefix)com.apple.token</string>
    <string>$(AppIdentifierPrefix)com.example.app</string>
</array>

<!-- ExampleTokenExtension.entitlements -->
<key>com.apple.security.token</key>
<array>
    <string>TEAMID.com.apple.token</string>
</array>
<key>keychain-access-groups</key>
<array>
    <string>$(AppIdentifierPrefix)com.apple.token</string>
</array>
How do we create a persistent CTK token that's not tied to physical smart card hardware?

Start with the macOS > Persistent Token Extension template, which creates the necessary app extension target along with three subclasses (of TKTokenDriver, TKToken, and TKTokenSession) that you need to fill in.

Beyond that, I have some other hints that will help you get started. I can’t include them all here, so please open a DTS code-level support request and I’ll pass them along via that channel.

IMPORTANT When you fill in the form for the support request, where is asks if someone told you to contact DTS, enter a reference to this forums thread. That way your request will come straight to me.

How should the token session retrieve the private key imported from the PKCS#12 bundle?

That’s kinda up to you. Most persistent tokens are working with real hardware at some point — for example, they might implement a network protocol that talks to a remote token — and thus the answer to this question is obvious. If you’re not doing that then you’ll need to design a system that meets your specific requirements.

Should the private key be stored in the app's keychain (current approach)?

Your CTK appex should avoid using the keychain. Conceptually, it exists ‘below’ the keychain, and thus by using the keychain you create a dependency loop. I’ve seen it work, but I’ve also seen it fail in hard-to-debug ways.

What's the correct data flow between the app and token extension?

There are two apps in play here:

  • The app using the keychain items stored on your token
  • The app in which your CTK appex is embedded, that is, the container app

You don’t have to worry about the first; the Security framework takes care of those details.

For the second, the only critical involvement is that the container app has to create a record of the credentials that are stored in the token so that the system knows to:

  1. Publish those credentials to other apps, and
  2. Fire up your CTK appex in order to perfect cryptographic operations using those credentials.

The container app does this by setting the keychainItems property on the token configuration.

Beyond that, it’s best if the appex works independently of the container app. It is possible for the appex to pass work to the container app, but it’s tricky to set up and it results in a suboptimal user experience. I’d only do that if there’s no other way [1].

Will Edge be able to access certificates from a persistent CTK token created by a third-party app?

I can’t give you definitive answers about third-party products. If you can’t figure this out for yourself, per my advice below, you’ll have to escalate your questions via the support channel for those products.

However, I can say that:

  • Persistent tokens work with Safari on iOS.
  • There are no technical restrictions that would prevent them from working with third-party browsers.
Can we use a software-based virtual token instead of a physical smart card?

I think you missed the point of my “prototype this with an actual smart card” comment. Your question is Will my persistent token work with a specific third-party browser? I can’t answer that, as I’ve mentioned above. However, you don’t need to invest time in creating a persistent token in order to answer this for yourself. Rather:

  1. Buy a physical smart card.
  2. Install a digital identity on it.
  3. Test whether you can use that digital identity in Safari. That should just work.
  4. Then test it again in your third-party browser.

If it works, then you have reasonable confidence that your third-party browser will work with your persistent token, because smart cards and persistent tokens look pretty much the same from the ‘client’ perspective.

OTOH, if it fails, then you’ve saved yourself a bunch of time (-:

I have a YubiKey lying around that I use for this sort of testing.

Share and Enjoy

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

[1] I’ve seen folks do this on iOS when they have to talk to a token over a specific interface that’s only available to apps, and thus the appex can’t do the work directly.

How to store certificate to &#96;com.apple.token&#96; keychain access group.
 
 
Q