How to create NSURLCredential from certificate and private key strings for client authentication?

Hello ,

I have obtained three strings from the server: ca (the root certificate), cert (the client certificate), and privateKey (the private key) for authentication between the iOS client and server. I have successfully used ca for server authentication.

However, I am having trouble generating an NSURLCredential from the cert and privateKey strings for client authentication. Can anyone guide me on how to convert these strings into an NSURLCredential? Any example code would be greatly appreciated!

Thank you for your help!

Answered by DTS Engineer in 809673022

It’s tricky to offer specific advice without seeing an example of the credentials that you’re trying to import. If you post some examples, I may be able to help more.

My general advice is that you no try to add the data you have directly to the keychain. That can get really tricky. Instead, create objects from the data you have and then add those to the keychain. That way you learn about any import failures when you create the objects, not when trying to read stuff back from the keychain.

To import your private key, call SecKeyCreateWithData. See Importing Cryptographic Keys for a lot more detail on that.

To import your certificate, call SecCertificateCreateWithData.

Don’t both importing the public key. You won’t need it, and if you do you can get it from the certificate via SecCertificateCopyKey.

Once you have these objects, add each one to the keychain using SecItemAdd. I have two posts that explain the ins and outs of that API:

Share and Enjoy

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

Here is the code client authentication

if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
            // get identity
            SecIdentityRef identity = [[YCCertificateManager instance] clientIdentity];

            SecCertificateRef certificate = [[YCCertificateManager instance] certificateRef];

            if (identity) {
                *credential = [NSURLCredential credentialWithIdentity:identity certificates:@[] persistence:NSURLCredentialPersistencePermanent];
                return NSURLSessionAuthChallengeUseCredential;
            } else {
                return NSURLSessionAuthChallengePerformDefaultHandling;
            }
        }

Therefore, I need a SecIdentityRef parameter, so I am looking for a method to obtain SecIdentityRef.

- (SecIdentityRef)clientIdentity {
    SecCertificateRef certificateRef = [self certificateRef];
    SecKeyRef privateKeyRef = [self privateKeyRef];
    SecKeyRef publicKeyRef = SecCertificateCopyKey(certificateRef);

    NSString *privateTag = @"yc_clinet_pri_tag";
    NSString *publicTag = @"yc_clinet_pub_tag";
    NSString *certTag = @"yc_clinet_cert_tag";
    
    NSMutableDictionary * privateKeyAttr = [[NSMutableDictionary alloc] init];
    [privateKeyAttr setObject:(id)kSecClassKey forKey:(id)kSecClass];
    [privateKeyAttr setObject:(id)kSecAttrKeyTypeEC forKey:(id)kSecAttrKeyType];
    [privateKeyAttr setObject:privateTag forKey:(id)kSecAttrLabel];
    [privateKeyAttr setObject:privateTag forKey:(id)kSecAttrApplicationTag];
    [privateKeyAttr setObject:(__bridge id _Nonnull)(privateKeyRef) forKey:(id)kSecValueRef];
    [privateKeyAttr setObject:(id)kSecAttrKeyClassPrivate forKey:(id)kSecAttrKeyClass];
    [privateKeyAttr setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnPersistentRef];
    [privateKeyAttr setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlock forKey:(id)kSecAttrAccessible];

    OSStatus privateKeyAttrCheck = SecItemAdd((CFDictionaryRef) privateKeyAttr, nil);
    NSString *priKeyMsg = (__bridge_transfer NSString *)SecCopyErrorMessageString(privateKeyAttrCheck, NULL) ?: [NSString stringWithFormat:@"%d", (int)privateKeyAttrCheck];
    NSLog(@"query privateKey: %@", priKeyMsg);
    if ((privateKeyAttrCheck != noErr) && (privateKeyAttrCheck != errSecDuplicateItem)){
        return nil;
    }
    
    NSMutableDictionary * publicKeyAttr = [[NSMutableDictionary alloc] init];
    
    [publicKeyAttr setObject:(id)kSecClassKey forKey:(id)kSecClass];
    [publicKeyAttr setObject:(id)kSecAttrKeyTypeEC forKey:(id)kSecAttrKeyType];
    [publicKeyAttr setObject:publicTag forKey:(id)kSecAttrApplicationTag];
//    [publicKeyAttr setObject:publicTag forKey:(id)kSecAttrPublicKeyHash];
    [publicKeyAttr setObject:(__bridge id _Nonnull)(publicKeyRef) forKey:(id)kSecValueRef];
    [publicKeyAttr setObject:(id)kSecAttrKeyClassPublic forKey:(id)kSecAttrKeyClass];
    [publicKeyAttr setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnPersistentRef];
    [publicKeyAttr setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlock forKey:(id)kSecAttrAccessible];

    OSStatus pubKeyCheck = SecItemAdd((CFDictionaryRef) publicKeyAttr, nil);
    NSString *pubKeyMsg = (__bridge_transfer NSString *)SecCopyErrorMessageString(pubKeyCheck, NULL) ?: [NSString stringWithFormat:@"%d", (int)pubKeyCheck];
    NSLog(@"query publicKey: %@", pubKeyMsg);

    if ((pubKeyCheck != noErr) && (pubKeyCheck != errSecDuplicateItem)){
        return nil;
    }

    NSMutableDictionary * queryCertificate = [[NSMutableDictionary alloc] init];
    
    [queryCertificate setObject:(id)kSecClassCertificate forKey:(id)kSecClass];
    [queryCertificate setObject:certTag forKey:(id)kSecAttrLabel];
    [queryCertificate setObject:(__bridge id)certificateRef forKey:(id)kSecValueRef];
    [queryCertificate setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlock forKey:(id)kSecAttrAccessible];

    OSStatus certCheck = SecItemAdd((CFDictionaryRef)queryCertificate, nil);
    NSString *certMsg = (__bridge_transfer NSString *)SecCopyErrorMessageString(certCheck, NULL) ?: [NSString stringWithFormat:@"%d", (int)certCheck];
    NSLog(@"query certificate: %@", certMsg);
    if ((certCheck != noErr) && (certCheck != errSecDuplicateItem)) {
        return nil;
    }
    
    SecIdentityRef identityRef = NULL;
    
    NSMutableDictionary * queryIdentityRef = [[NSMutableDictionary alloc] init];
    [queryIdentityRef setObject:(id)kSecClassIdentity forKey:(id)kSecClass];
    [queryIdentityRef setObject:privateTag forKey:(id)kSecAttrApplicationTag];
    [queryIdentityRef setObject:certTag forKey:(id)kSecAttrLabel];
    [queryIdentityRef setObject:(id)kSecAttrKeyTypeEC forKey:(id)kSecAttrKeyType];
    [queryIdentityRef setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnRef];
    
    OSStatus identityCheck = SecItemCopyMatching((CFDictionaryRef)queryIdentityRef, (CFTypeRef *)&identityRef);
    NSString *identityMsg = (__bridge_transfer NSString *)SecCopyErrorMessageString(identityCheck, NULL) ?: [NSString stringWithFormat:@"%d", (int)identityCheck];
    NSLog(@"query identity: %@", identityMsg);

    if (identityCheck != noErr) {
        return nil;
    }
    
    return identityRef;
}


query identity: The specified item could not be found in the keychain.

Everything works fine except for the part where obtaining SecIdentityRef throws an error: "query identity: The specified item could not be found in the keychain."

please help

It’s tricky to offer specific advice without seeing an example of the credentials that you’re trying to import. If you post some examples, I may be able to help more.

My general advice is that you no try to add the data you have directly to the keychain. That can get really tricky. Instead, create objects from the data you have and then add those to the keychain. That way you learn about any import failures when you create the objects, not when trying to read stuff back from the keychain.

To import your private key, call SecKeyCreateWithData. See Importing Cryptographic Keys for a lot more detail on that.

To import your certificate, call SecCertificateCreateWithData.

Don’t both importing the public key. You won’t need it, and if you do you can get it from the certificate via SecCertificateCopyKey.

Once you have these objects, add each one to the keychain using SecItemAdd. I have two posts that explain the ins and outs of that API:

Share and Enjoy

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

My ultimate goal is to perform client-side authentication when making requests, so I need to generate an NSURLCredential. To create the NSURLCredential, I need a SecIdentityRef. From my research, I found that storing the private key and certificate in the keychain allows me to retrieve the SecIdentityRef. Below is my code, which includes the private key and certificate. I would appreciate any help.

    NSString *certString = @"-----BEGIN CERTIFICATE-----MIICJjCCAcugAwIBAgIQbR5jIkUPfd6lID5G3+vSIDAKBggqhkjOPQQDAjBOMQswCQYDVQQGEwJVUzELMAkGA1UEBxMCR1oxDjAMBgNVBAoTBUNUWVVOMRAwDgYDVQQLEwdBRFYtREVWMRAwDgYDVQQDEwdyb290LWNhMB4XDTI0MDMyODA3MjA1NVoXDTM0MDMyNjA3MjE1NVowTjELMAkGA1UEBhMCVVMxCzAJBgNVBAcTAkdaMQ4wDAYDVQQKEwVDVFlVTjEQMA4GA1UECxMHQURWLURFVjEQMA4GA1UEAxMHcm9vdC1jYTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPa/ZkLJSjZI/0KWZA54iGmfkkXcMh00vjmK1k+ZDdKepDDsa8gkHPI7I67qSARMk/Aq3+mZHx1rpf63w/sxpKCjgYowgYcwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE8Dk3jsnNaJ3gLz/caeaDU4YaE9MB8GA1UdIwQYMBaAFE8Dk3jsnNaJ3gLz/caeaDU4YaE9MCQGA1UdEQQdMBuGGXNwaWZmZTovL2MxLmVjaGEuY3R5dW4uY24wCgYIKoZIzj0EAwIDSQAwRgIhAP6K83hDw8MQVftyTzsiEiqavndUELQV2JZpYGDhS3XGAiEA0R9ShXvK/0qs+rJyKdzttmNTBQmcfq1S/nbTm983Igc=-----END CERTIFICATE-----";
    NSData *certData = [self dataFromPEMString:certString replacing:@"CERTIFICATE"];

    SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
    return certificate;
}
    NSString *priKeyString = @"-----BEGIN EC PRIVATE KEY-----MHcCAQEEIDS6kb3mrIXtqtDS4Fuh6eYB3XK0lq7waNA6UR6Q8hY5oAoGCCqGSM49AwEHoUQDQgAE9r9mQslKNkj/QpZkDniIaZ+SRdwyHTS+OYrWT5kN0p6kMOxryCQc8jsjrupIBEyT8Crf6ZkfHWul/rfD+zGkoA==-----END EC PRIVATE KEY-----";
    NSData *privateKeyData = [self dataFromPEMString:priKeyString replacing:@"EC PRIVATE KEY"];

    NSMutableDictionary *privateKeyOptions = [@{
        (id)kSecAttrKeyType: (id)kSecAttrKeyTypeEC,
        (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
        (id)kSecAttrKeySizeInBits: @256
    } mutableCopy];

    #if !TARGET_OS_SIMULATOR
        [privateKeyOptions setObject:(id)kSecAttrTokenIDSecureEnclave forKey:(id)kSecAttrTokenID];
    #endif

    CFErrorRef error = NULL;
    SecKeyRef privateKey = SecKeyCreateWithData((__bridge CFDataRef)privateKeyData,
                                                (__bridge CFDictionaryRef)privateKeyOptions,
                                                &error);

    if (error) {
        CFStringRef errorDescription = CFErrorCopyDescription(error);
        NSLog(@"Error creating private key: %@", errorDescription);
        CFRelease(errorDescription);  
        return NULL; 
    }
    
    return privateKey;
}

Let’s talk about your certificate first. Consider this program:

import Foundation
import CryptoKit

let certPEM = """
    -----BEGIN CERTIFICATE-----
    MIICJjCCAcugAwIBAgIQbR5jIkUPfd6lID5G3+vSIDAKBggqhkjOPQQDAjBOMQsw
    CQYDVQQGEwJVUzELMAkGA1UEBxMCR1oxDjAMBgNVBAoTBUNUWVVOMRAwDgYDVQQL
    EwdBRFYtREVWMRAwDgYDVQQDEwdyb290LWNhMB4XDTI0MDMyODA3MjA1NVoXDTM0
    MDMyNjA3MjE1NVowTjELMAkGA1UEBhMCVVMxCzAJBgNVBAcTAkdaMQ4wDAYDVQQK
    EwVDVFlVTjEQMA4GA1UECxMHQURWLURFVjEQMA4GA1UEAxMHcm9vdC1jYTBZMBMG
    ByqGSM49AgEGCCqGSM49AwEHA0IABPa/ZkLJSjZI/0KWZA54iGmfkkXcMh00vjmK
    1k+ZDdKepDDsa8gkHPI7I67qSARMk/Aq3+mZHx1rpf63w/sxpKCjgYowgYcwDgYD
    VR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE8Dk3jsnNaJ
    3gLz/caeaDU4YaE9MB8GA1UdIwQYMBaAFE8Dk3jsnNaJ3gLz/caeaDU4YaE9MCQG
    A1UdEQQdMBuGGXNwaWZmZTovL2MxLmVjaGEuY3R5dW4uY24wCgYIKoZIzj0EAwID
    SQAwRgIhAP6K83hDw8MQVftyTzsiEiqavndUELQV2JZpYGDhS3XGAiEA0R9ShXvK
    /0qs+rJyKdzttmNTBQmcfq1S/nbTm983Igc=
    -----END CERTIFICATE-----
    """

func certificateFromPEM(_ pemRepresentation: String) -> SecCertificate? {
    let lines = pemRepresentation.split(separator: "\n")
    guard
        lines.count > 2,
        lines.first == "-----BEGIN CERTIFICATE-----",
        lines.last == "-----END CERTIFICATE-----",
        let data = Data(base64Encoded: lines.dropFirst().dropLast().joined()),
        let cert = SecCertificateCreateWithData(nil, data as NSData)
    else { return nil }
    return cert
}

func testCert() {
    print(certificateFromPEM(certPEM))
}

func main() {
    testCert()
}

main()

It prints:

Optional(<cert(0x128805c20) s: root-ca i: root-ca>)

So, you’ve imported the certificate successfully, and now you just need to add it to the keychain with SecItemAdd.


Your key is more interesting. You have a Base64 encoding ECPrivateKey structure. The good news here is that CryptoKit is able to import it directly. Consider this code:

import Foundation
import CryptoKit

let keyPEM = """
    -----BEGIN EC PRIVATE KEY-----
    MHcCAQEEIDS6kb3mrIXtqtDS4Fuh6eYB3XK0lq7waNA6UR6Q8hY5oAoGCCqGSM49
    AwEHoUQDQgAE9r9mQslKNkj/QpZkDniIaZ+SRdwyHTS+OYrWT5kN0p6kMOxryCQc
    8jsjrupIBEyT8Crf6ZkfHWul/rfD+zGkoA==
    -----END EC PRIVATE KEY-----
    """

func p256PrivateKeyFromPEM(_ pemRepresentation: String) -> SecKey? {
    guard
        let p256 = try? P256.Signing.PrivateKey(pemRepresentation: keyPEM)
    else { return nil }
    let data = p256.x963Representation
    return SecKeyCreateWithData(data as NSData, [
        kSecAttrKeyClass: kSecAttrKeyClassPrivate,
        kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom
    ] as NSDictionary, nil)
}

func testKey() {
    let key = p256PrivateKeyFromPEM(keyPEM)
    print(key)
}

func main() {
    testKey()
}

main()

It prints:

Optional(<SecKeyRef curve type: kSecECCurveSecp256r1, algorithm id: 3, key type: ECPrivateKey, version: 4, block size: 256 bits, addr: 0x11d604430>)

Again, you can that to the keychain using SecItemAdd.


Generally I recommend that you do this sort of work in Swift rather than in Objective-C. However, if you must do it in Objective-C, let me know and I’ll try to point you in the right direction.

Share and Enjoy

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

Hello, my main project uses Objective-C, so my code here is in Objective-C as well, but feel free to provide examples in Swift—I can translate them into Objective-C myself.

I am able to obtain both SecCertificateRef and SecKeyRef without any issues. However, my problem lies in the next step, where I need to use them to generate a SecIdentityRef. I attempted to follow the suggestions found online to store SecCertificateRef and SecKeyRef in the keychain and then retrieve the SecIdentityRef, but this approach failed, as shown in the demo in my email, or in the example below.

    SecCertificateRef certificateRef = [self certificateRef]; // get it not null
    SecKeyRef privateKeyRef = [self privateKeyRef]; // get it not null too

    NSString *privateTag = @"yc_clinet_pri_tag";
    NSString *certTag = @"yc_clinet_cert_tag";
    
    NSMutableDictionary * privateKeyAttr = [[NSMutableDictionary alloc] init];
    [privateKeyAttr setObject:(id)kSecClassKey forKey:(id)kSecClass];
    [privateKeyAttr setObject:(id)kSecAttrKeyTypeEC forKey:(id)kSecAttrKeyType];
    [privateKeyAttr setObject:privateTag forKey:(id)kSecAttrLabel];
    [privateKeyAttr setObject:privateTag forKey:(id)kSecAttrApplicationTag];
    [privateKeyAttr setObject:(__bridge id _Nonnull)(privateKeyRef) forKey:(id)kSecValueRef];
    [privateKeyAttr setObject:(id)kSecAttrKeyClassPrivate forKey:(id)kSecAttrKeyClass];
    [privateKeyAttr setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnPersistentRef];
    [privateKeyAttr setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlock forKey:(id)kSecAttrAccessible];

    OSStatus privateKeyAttrCheck = SecItemAdd((CFDictionaryRef) privateKeyAttr, nil);
    NSString *priKeyMsg = (__bridge_transfer NSString *)SecCopyErrorMessageString(privateKeyAttrCheck, NULL) ?: [NSString stringWithFormat:@"%d", (int)privateKeyAttrCheck];
    NSLog(@"query privateKey: %@", priKeyMsg);
    if ((privateKeyAttrCheck != noErr) && (privateKeyAttrCheck != errSecDuplicateItem)){
        return nil;
    }

    NSMutableDictionary * queryCertificate = [[NSMutableDictionary alloc] init];
    [queryCertificate setObject:(id)kSecClassCertificate forKey:(id)kSecClass];
    [queryCertificate setObject:certTag forKey:(id)kSecAttrLabel];
    [queryCertificate setObject:(__bridge id)certificateRef forKey:(id)kSecValueRef];
    [queryCertificate setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlock forKey:(id)kSecAttrAccessible];

    OSStatus certCheck = SecItemAdd((CFDictionaryRef)queryCertificate, nil);
    NSString *certMsg = (__bridge_transfer NSString *)SecCopyErrorMessageString(certCheck, NULL) ?: [NSString stringWithFormat:@"%d", (int)certCheck];
    NSLog(@"query certificate: %@", certMsg);
    if ((certCheck != noErr) && (certCheck != errSecDuplicateItem)) {
        return nil;
    }
    
    SecIdentityRef identityRef = NULL;
    
    NSMutableDictionary * queryIdentityRef = [[NSMutableDictionary alloc] init];
    [queryIdentityRef setObject:(id)kSecClassIdentity forKey:(id)kSecClass];
    [queryIdentityRef setObject:privateTag forKey:(id)kSecAttrApplicationTag];
    [queryIdentityRef setObject:certTag forKey:(id)kSecAttrLabel];
    [queryIdentityRef setObject:(id)kSecAttrKeyTypeEC forKey:(id)kSecAttrKeyType];
    [queryIdentityRef setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnRef];
    
    OSStatus identityCheck = SecItemCopyMatching((CFDictionaryRef)queryIdentityRef, (CFTypeRef *)&identityRef);
    NSString *identityMsg = (__bridge_transfer NSString *)SecCopyErrorMessageString(identityCheck, NULL) ?: [NSString stringWithFormat:@"%d", (int)identityCheck];
    NSLog(@"query identity: %@", identityMsg); // here print null

    if (identityCheck != noErr) {
        return nil;
    }
    
    return identityRef;

please help

How to create NSURLCredential from certificate and private key strings for client authentication?
 
 
Q