iOS mTLS Client Certificate Authentication Fails in TestFlight with Error -25303

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

  1. Both kSecValueRef and kSecValueData approaches - same error
  2. Various kSecAttrAccessible values - same error
  3. Different keychain access groups - same error
  4. TestFlight build (vs dev build) - same error
  5. PKCS#12 creation - requires complex ASN.1/DER encoding, no iOS API

Questions

  1. Is error -25303 expected when adding SecCertificate in development/TestFlight builds?
  2. Will App Store distribution resolve this? Or is there a fundamental limitation?
  3. Why does SecKey succeed but SecCertificate fails with identical entitlements?
  4. Is there an alternative to create SecIdentity without keychain access?

Constraints

  • Certificates come from server at runtime (cannot bundle)
  • Need SecIdentity for URLSession client 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.

Answered by DTS Engineer in 875929022

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,

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,

Our SSH certificates are in OpenSSH format (RFC 4253), not X.509. These use a custom binary encoding that SecCertificate doesn't understand.

Since kSecClassCertificate expects X.509 DER format:

  1. Should we store OpenSSH certificates as kSecClassGenericPassword with descriptive metadata?
  2. Is there any Keychain support for non-X.509 certificate formats?
  3. Or is the recommendation to keep them as files and only move X.509 TLS certs to Keychain?

Example OpenSSH Certificate (text format):

ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQt...

Our current thinking is to store these as generic passwords:

let query: [String: Any] = [
    kSecClass: kSecClassGenericPassword,
    kSecAttrService: "com.teleport.ssh.cert",
    kSecAttrAccount: "\(cluster).\(user)",
    kSecAttrLabel: "Teleport SSH Certificate",
    kSecValueData: sshCertData,
    kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]

Is this an acceptable approach for non-X.509 certificates?

Is there any Keychain support for non-X.509 certificate formats?

No.

Or, more specifically, the keychain only supports X.509 for kSecClassCertificate items. That doesn’t stop you storing the raw data as kSecClassGenericPassword.

As to what you should do, that depends on how you’re using the word “certificate”:

  • Apple uses [1] certificate to refer to the signed wrapper around the public key.
  • We then use digital identity to refer to the combination of a certificate and the private key that matches the public key in that certificate.

See TN3161 Inside Code Signing: Certificates for more on this.

However, it’s common for folks to use a single term, certificate, to refer to both the certificate and digital identity, with ‘hilarious’ confusion being the result.

So, if you’re using certificate ‘correctly’ then the certificate contains just the public key and you can store it wherever you want [2]. OTOH, if your certificate is actually a digital identity, and thus contains a private key, then storing it in the keychain is critical. And if you can’t use any of the other keychain item classes then kSecClassGenericPassword is fine.

Share and Enjoy

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

[1] Well, strives to use. There’s been a lot of terminological confusion over the years.

[2] If a certificate only contains a public key, why store it in the keychain at all? Because its presence in the keychain allows us to synthesise a digital identity. See See Digital Identities Aren’t Real in SecItem: Pitfalls and Best Practices.

You can’t benefit from this because you’re certificates are not in X.509 format, so there’s less incentive for you to store certificates in the keychain. Assuming these certificates are actually certificates (-:

iOS mTLS Client Certificate Authentication Fails in TestFlight with Error -25303
 
 
Q