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 to store certificate to &#96;com.apple.token&#96; keychain access group.
 
 
Q