Hello,
Thank you for sharing the provided solution. I followed all the steps and used the same API set. However, while retrieving the SecIdentity using the certificate fetch API SecItemCopyMatching, I am encountering the following error:
Error: Could not retrieve client identity, status = -25300
Please find the relevant code snippets below:
Step 1 to 3 : Generate the private key in the keychain.
Derive the public key from the private key.
Export the public key bits and send that your certificate issuing infrastructure.
func generateCSR(_ deviceId: String? = UserProfile.deviceId) -> String? {
do {
// Define subject DN for CSR
let subject = try DistinguishedName([
.init(type: .NameAttributes.countryName, printableString: "US"),
.init(type: .NameAttributes.stateOrProvinceName, printableString: "stateProvince_Name"),
.init(type: .NameAttributes.localityName, printableString: "locality_Name"),
.init(type: .NameAttributes.organizationName, printableString: "organization_Name"),
.init(type: .NameAttributes.organizationalUnitName, printableString: "Engineering"),
.init(type: .NameAttributes.commonName, utf8String: deviceId ?? "NA"),
])
// Application tag for persistent key storage in Keychain
let tagData = MTLSTag.clientkey.data(using: .utf8)! as NSData
// Generate EC keypair (private key in Keychain, permanent)
let keyAttributes: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits as String: 256,
kSecAttrIsPermanent as String: true, // retain key in Keychain
kSecAttrApplicationTag as String: tagData // allow lookup by tag
]
// In step 1, use SecKeyCreateRandomKey.
var error: Unmanaged<CFError>?
guard let privateKey = SecKeyCreateRandomKey(keyAttributes as CFDictionary, &error) else {
print("Failed to create private key: \(error!.takeRetainedValue())")
return nil
}
// In step 2, use SecKeyCopyPublicKey.
// Extract public key (needed for CSR building)
let publicKey = SecKeyCopyPublicKey(privateKey)!
// Wrap private key so Swift `Certificate.PrivateKey` can use it
let wrappedPrivateKey = try Certificate.PrivateKey(privateKey)
// In step 3, use SecKeyCopyExternalRepresentation.
// (Optional) Validate we can export the public key (for debugging or backend raw SPKI cases)
if let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? {
print("Public key exported, length: \(publicKeyData.count) bytes")
}
// Extensions to request in CSR
let extensions = try Certificate.Extensions {
BasicConstraints.notCertificateAuthority
KeyUsage(digitalSignature: true, keyCertSign: true)
}
let extensionRequest = ExtensionRequest(extensions: extensions)
let csrAttributes = try CertificateSigningRequest.Attributes([.init(extensionRequest)])
// Build CSR object with subject & keypair
let csr = try CertificateSigningRequest(
version: .v1,
subject: subject,
privateKey: wrappedPrivateKey,
attributes: csrAttributes,
signatureAlgorithm: .ecdsaWithSHA256
)
// Double-check CSR is signed correctly
guard csr.publicKey.isValidSignature(csr.signature, for: csr) else {
print("Invalid CSR signature")
return nil
}
// Return PEM string
return try csr.serializeAsPEM().pemString
} catch {
print("CSR generation failed: \(error)")
return nil
}
}
Step 4 : The server sends you back a PEM, convert into DER
class CertificateUtils {
static func getCRTCertificate() -> SecCertificate? {
if let certificateString = UserAccount.getUserCRT() {
return Certificates.convertStringToCertificate(certificateString: certificateString)
}
return nil
}
static func getCACertificates() -> [SecCertificate]? {
if let certificateString = UserAccount.getUserCA() {
return Certificates.convertMultiCertStringToCertificates(certificatesString: certificateString)
}
return nil
}
}
Step 5 : Add that to your keychain.
private func storeCertificatesInKeychain() {
// Get client cert
guard let clientCert = CertificateUtils.getCRTCertificate() else {
print("No client certificate found")
return
}
// Add client certificate to Keychain
let clientQuery: [String: Any] = [
kSecClass as String: kSecClassCertificate,
kSecValueRef as String: clientCert,
kSecAttrLabel as String: MTLSTag.clientcert
]
let status = SecItemAdd(clientQuery as CFDictionary, nil)
if status == errSecDuplicateItem {
print("Client certificate already in Keychain")
} else if status != errSecSuccess {
print("Failed to add client certificate: \(status)")
} else {
print("Client certificate added to Keychain")
}
// Add intermediate CA certs (optional)
if let caCerts = CertificateUtils.getCACertificates() {
for (index, cert) in caCerts.enumerated() {
let caQuery: [String: Any] = [
kSecClass as String: kSecClassCertificate,
kSecValueRef as String: cert,
kSecAttrLabel as String: "\(MTLSTag.clientcert).\(index)"
]
let caStatus = SecItemAdd(caQuery as CFDictionary, nil)
if caStatus == errSecDuplicateItem {
print("CA certificate \(index) already in Keychain")
} else if caStatus != errSecSuccess {
print("Failed to add CA certificate \(index): \(caStatus)")
} else {
print("Added CA certificate \(index)")
}
}
}
}