iOS mTLS Client Certificate Authentication Fails in TestFlight with Error -25303
Problem
I'm building an iOS app that uses mTLS (client certificates received from server at runtime). Storing SecCertificate to keychain fails with error -25303 in both development and TestFlight builds, preventing SecIdentity creation needed for URLSession authentication.
Environment: iOS 18.2, iPad Pro, TestFlight internal testing, keychain-access-groups properly configured
Diagnostic Results
Testing keychain operations shows an interesting pattern:
✅ Generic Password - Works:
let addQuery: [CFString: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: "test",
kSecValueData: "password".data(using: .utf8)!
]
SecItemAdd(addQuery as CFDictionary, nil) // Returns: 0 (success)
✅ SecKey - Works:
let addKeyQuery: [CFString: Any] = [
kSecClass: kSecClassKey,
kSecValueRef: privateKey,
kSecAttrApplicationTag: tag
]
SecItemAdd(addKeyQuery as CFDictionary, nil) // Returns: 0 (success)
❌ SecCertificate - Fails:
let addCertQuery: [CFString: Any] = [
kSecClass: kSecClassCertificate,
kSecValueRef: certificate, // Created from server-provided PEM
kSecAttrApplicationTag: tag
]
SecItemAdd(addCertQuery as CFDictionary, nil) // Returns: -25303
Code Context
Attempting to create SecIdentity for mTLS:
private func createIdentity(fromCert certPEM: String, key keyPEM: String) throws -> SecIdentity {
// 1. Parse PEM to DER and create SecCertificate - succeeds
guard let certData = extractPEMData(from: certPEM, type: "CERTIFICATE"),
let certificate = SecCertificateCreateWithData(nil, certData as CFData) else {
throw CertificateError.invalidCertificate
}
// 2. Parse PEM key and create SecKey - succeeds
guard let keyData = extractPEMData(from: keyPEM, type: "PRIVATE KEY"),
let privateKey = SecKeyCreateWithData(keyData as CFData, attrs as CFDictionary, &error) else {
throw CertificateError.invalidKey
}
// 3. Add key to keychain - SUCCEEDS (errSecSuccess)
let tempTag = UUID().uuidString.data(using: .utf8)!
SecItemAdd([
kSecClass: kSecClassKey,
kSecValueRef: privateKey,
kSecAttrApplicationTag: tempTag
] as CFDictionary, nil) // ✅ Works
// 4. Add certificate to keychain - FAILS (-25303)
let status = SecItemAdd([
kSecClass: kSecClassCertificate,
kSecValueRef: certificate,
kSecAttrApplicationTag: tempTag
] as CFDictionary, nil) // ❌ Fails with -25303
guard status == errSecSuccess else {
throw CertificateError.keychainError(status)
}
// 5. Would query for SecIdentity (never reached)
// ...
}
Network Behavior
When mTLS fails, console shows:
Connection: asked for TLS Client Certificates
Connection: received response for client certificates (-1 elements)
Connection: providing TLS Client Identity (-1 elements)
Task received response, status 403
The -1 elements indicates no certificates were provided.
Entitlements
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.ellin.tshios</string>
</array>
Keychain Sharing capability is enabled.
What I've Tried
- Both
kSecValueRefandkSecValueDataapproaches - same error - Various
kSecAttrAccessiblevalues - same error - Different keychain access groups - same error
- TestFlight build (vs dev build) - same error
- PKCS#12 creation - requires complex ASN.1/DER encoding, no iOS API
Questions
- Is error -25303 expected when adding
SecCertificatein development/TestFlight builds? - Will App Store distribution resolve this? Or is there a fundamental limitation?
- Why does
SecKeysucceed butSecCertificatefails with identical entitlements? - Is there an alternative to create
SecIdentitywithout keychain access?
Constraints
- Certificates come from server at runtime (cannot bundle)
- Need
SecIdentityforURLSessionclient certificate authentication - Server provides PEM format certificates
- Tested on: Simulator (dev), iPad Pro (dev), iPad Pro (TestFlight) - all fail
Any insights appreciated - specifically whether this is a provisioning profile limitation that App Store distribution would resolve.
The error here, -25303, or errSecNoSuchAttr, suggests a problem with your attributes. And the only non-required attribute you’re supplying is kSecAttrApplicationTag. Which is, in fact, the cause of your issue. kSecAttrApplicationTag is only supported on keys.
I recommend that you have a read of SecItem: Fundamentals [1], and specifically the The Four Freedoms^H^H^H^H^H^H^H^H Functions section, which explains how you can use SQL to model the expected behaviour of the SecItem API. In this case, the SQL table for certificate items has no kSecAttrApplicationTag, and thus this error.
It also has links to the doc that explains which keychain item class supports which attributes.
Is there an alternative to create SecIdentity without keychain access?
We finally (finally finally!) have this. See the docs for the SecIdentityCreate function.
IMPORTANT This was added in Xcode 26 but backdeploys to much older systems. Or at least that’s the plan (-: This backdeployment was a bit of a challenge, and I’ve not actually tested it myself.
However, that may not be the right option here. An in-memory identity like this makes sense if this credential is ephemeral. My experience with setups like yours is that the credential tends to be long-lived, and thus storing it in the keychain is the correct choice.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
[1] I also recommend SecItem: Pitfalls and Best Practices, but it’s not directly relevant to this issue,