Create an SecIdentityRef from a certificate and private key

Hi, I am working on a react native module used for tis connection and I am trying to implement the possibility to use a custom certificate/Private key. I have already implemented on android but on iOS I am getting hard times, we cannot find lots of resources, api is different on macOS and iOS with subtle differences so after having tested SO, chatgpt, ... I am trying here: I even tried to use an internal api since it seems ffmpeg uses it but with no success.

I have attached my current code because it does not fit here.

to sump up after having inserted cert and private key I try to get a SecIdentityRef but it fails. I assume that it's not enough to simply add certain and private key...

// Query for the identity with correct attributes
    NSDictionary *identityQuery = @{
        (__bridge id)kSecClass: (__bridge id)kSecClassIdentity,
        (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne,
        (__bridge id)kSecReturnRef: @YES,
        (__bridge id)kSecReturnData: @YES,
        (__bridge id)kSecAttrLabel: @"My Certificate",
        //(__bridge id)kSecUseDataProtectionKeychain: @YES
    };
    
    SecIdentityRef identity = NULL;
    status = SecItemCopyMatching((__bridge CFDictionaryRef)identityQuery, (CFTypeRef *)&identity);

// https://stackoverflow.com/questions/45997841/how-to-get-a-secidentityref-from-a-seccertificateref-and-a-seckeyref
// We use a private API call here; it's good enough for WebKit.
SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator, SecCertificateRef certificate, SecKeyRef privateKey);



static NSString * const kKeychainApplicationTag = @"com.mycompany.myapp";
static NSString * const kKeychainPrivateKeyLabel = @"My PrivateKey";
static NSString * const kKeychainCertificateLabel = @"My Certificate";

- (SecIdentityRef)createIdentityWithCert:(NSString *)pemCert privateKey:(NSString *)pemKey {
    RCTLogWarn(@"createIdentity: Starting identity creation");
    
    // Strip PEM headers and convert to data
    NSString *cleanedCert = [self stripPEMHeader:pemCert prefix:@"CERTIFICATE"];
    NSString *cleanedKey = [self stripPEMHeader:pemKey prefix:@"PRIVATE KEY"];
    
    NSData *certData = [[NSData alloc] initWithBase64EncodedString:cleanedCert
                                                           options:NSDataBase64DecodingIgnoreUnknownCharacters];
    NSData *keyData = [[NSData alloc] initWithBase64EncodedString:cleanedKey
                                                          options:NSDataBase64DecodingIgnoreUnknownCharacters];
    
    if (!certData || !keyData) {
        RCTLogWarn(@"createIdentity: Failed to create data from base64");
        return NULL;
    }
    
    // First, delete any existing key with the same tag
    NSDictionary *deleteKeyQuery = @{
        (__bridge id)kSecClass: (__bridge id)kSecClassKey,
        (__bridge id)kSecAttrLabel: @"My PrivateKey",
        //(__bridge id)kSecAttrApplicationTag: [@"com.yourdomain.app.key" dataUsingEncoding:NSUTF8StringEncoding]
    };
    OSStatus deleteStatus = SecItemDelete((__bridge CFDictionaryRef)deleteKeyQuery);
    if (deleteStatus != errSecSuccess && deleteStatus != errSecItemNotFound) {
        RCTLogWarn(@"createIdentity: Error deleting existing key, status: %d", (int)deleteStatus);
    }
    
    // Add private key in keychain
    NSDictionary *keyAttributes = @{
        (__bridge id)kSecClass: (__bridge id)kSecClassKey,
        (__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeRSA,
        (__bridge id)kSecAttrKeyClass: (__bridge id)kSecAttrKeyClassPrivate,
        (__bridge id)kSecValueData: keyData,
        (__bridge id)kSecReturnPersistentRef: @YES,
        //(__bridge id)kSecAttrKeySizeInBits: @2048,
        (__bridge id)kSecAttrIsPermanent: @YES,
        (__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleAfterFirstUnlock,
        (__bridge id)kSecAttrLabel: @"My PrivateKey",
        //(__bridge id)kSecAttrApplicationTag: [kKeychainApplicationTag dataUsingEncoding:NSUTF8StringEncoding],
    };
    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)keyAttributes, NULL);
    if (status != errSecSuccess && status != errSecDuplicateItem) {
        RCTLogWarn(@"createIdentity: Failed to store private key, status: %d", (int)status);
        return NULL;
    }
    
    // Create certificate reference
    SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
    if (!cert) {
        RCTLogWarn(@"createIdentity: Failed to create certificate from data");
        return NULL;
    }
    
    // Verify the certificate is valid
    CFStringRef summary = SecCertificateCopySubjectSummary(cert);
    if (!summary) {
        RCTLogWarn(@"createIdentity: Certificate appears to be invalid - no subject summary");
        CFRelease(cert);
        return NULL;
    }
    RCTLogWarn(@"Certificate subject: %@", (__bridge NSString *)summary);
    CFRelease(summary);
    
    // Delete any existing certificate
    NSDictionary *deleteCertQuery = @{
        (__bridge id)kSecClass: (__bridge id)kSecClassCertificate
    };
    deleteStatus = SecItemDelete((__bridge CFDictionaryRef)deleteCertQuery);
    if (deleteStatus != errSecSuccess && deleteStatus != errSecItemNotFound) {
        RCTLogWarn(@"createIdentity: Error deleting existing certificate, status: %d", (int)deleteStatus);
    }
    
    // Store certificate in keychain
    NSDictionary *certAttributes = @{
        (__bridge id)kSecClass: (__bridge id)kSecClassCertificate,
        (__bridge id)kSecValueRef: (__bridge id)cert,
        (__bridge id)kSecAttrLabel: @"My Certificate",
        (__bridge id)kSecAttrSubject: (__bridge id)SecCertificateCopySubjectSummary(cert),
        (__bridge id)kSecReturnRef: @YES
    };
    
    status = SecItemAdd((__bridge CFDictionaryRef)certAttributes, NULL);
    if (status != errSecSuccess && status != errSecDuplicateItem) {
        RCTLogWarn(@"createIdentity: Failed to store certificate, status: %d", (int)status);
        if (cert) CFRelease(cert);
        return NULL;
    }
    
//    // SecItemCopyMatching find the certificate
//    if (true) {
//        NSDictionary *certQuery = @{
//            (__bridge id)kSecClass: (__bridge id)kSecClassCertificate,
//            (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne,
//            (__bridge id)kSecReturnRef: @YES,
//            (__bridge id)kSecAttrLabel: @"My Certificate"
//            //(__bridge id)kSecUseDataProtectionKeychain: @YES
//        };
//        SecCertificateRef cert2 = NULL;
//        status = SecItemCopyMatching((__bridge CFDictionaryRef)certQuery, (CFTypeRef *)&cert2);
//    }
    
    // Query for the identity with correct attributes
    NSDictionary *identityQuery = @{
        (__bridge id)kSecClass: (__bridge id)kSecClassIdentity,
        (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne,
        (__bridge id)kSecReturnRef: @YES,
        (__bridge id)kSecReturnData: @YES,
        (__bridge id)kSecAttrLabel: @"My Certificate",
        //(__bridge id)kSecUseDataProtectionKeychain: @YES
    };
    //    NSDictionary *identityQuery = @{
    //            (__bridge id)kSecClass: (__bridge id)kSecClassIdentity,
    //            (__bridge id)kSecReturnRef: @YES,
    //            (__bridge id)kSecMatchItemList:@[(__bridge id)cert],
    //            (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne,
    //            (__bridge id)kSecAttrLabel: @"My Certificate",
    //            (__bridge id)kSecUseDataProtectionKeychain: @YES
    //        };
    
    SecIdentityRef identity = NULL;
    status = SecItemCopyMatching((__bridge CFDictionaryRef)identityQuery, (CFTypeRef *)&identity);
    if (status != errSecSuccess || !identity) {
        RCTLogWarn(@"createIdentity: Failed to find identity, status: %d", (int)status);
    } else {
        RCTLogWarn(@"createIdentity: Successfully found identity");
    }
    
    // Try to retrieve private key from keychain
    NSDictionary *privateKeyQuery = @{
        (__bridge id)kSecClass: (__bridge id)kSecClassKey,
        (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne,
        (__bridge id)kSecReturnRef: @YES,
        (__bridge id)kSecReturnData: @YES,
        (__bridge id)kSecAttrLabel: @"My PrivateKey",
        //(__bridge id)kSecAttrApplicationTag: [kKeychainApplicationTag dataUsingEncoding:NSUTF8StringEncoding],
        //(__bridge id)kSecUseDataProtectionKeychain: @YES
    };
    SecKeyRef privateKey = NULL;
    status = SecItemCopyMatching((__bridge CFDictionaryRef)privateKeyQuery, (CFTypeRef *)&privateKey);
    
    // API below is private but seems to be able to create an identity from a cert and a private key
    // Once other parts work I will try to replace it
    // UPDATE: even with private API it does not work it returns NULL
    identity = SecIdentityCreate(kCFAllocatorDefault, cert, privateKey);
    if (cert) CFRelease(cert);
    
    return identity;
}

SecItemCopyMatching with kSecClassIdentity fails, SecIdentityCreate return NULL... So please help and indicates what I am doing wrong and how I am supposed getting a SecIdentityRef. Thanks

Answered by DTS Engineer in 823700022

Honestly, I’m not sure why this is failing for you. I’m struggling to think of ways I can debug this remotely, so instead I decided to write up an end-to-end example that shows how to do this. See Importing a PEM-based RSA Private Key and its Certificate.

I suggest you do the following:

  1. Put that code into a simple test app and confirm that it works. If it does, that rules out any environmental issues.

  2. Then modify that test app to use your private key and certificate PEMs. If it continues to work, that rules out any issues with your credentials.

  3. Finally, try adapting this code for your project. If you’re gonna convert this to Objective-C, do that one routine at a time. That way you can test your new code within your test app that you know works in general.

Good luck!

Share and Enjoy

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

First up, read the following:

That’ll help you use the SecItem API effectively.

Looking at your code, it seems to be adding data directly to the keychain, rather than importing the data first and then adding the resulting credential. Your approach can work but it’s hard to get right. I just updated SecItem: Pitfalls and Best Practices with a new section, Import, Then Add, that explains this in more detail.

Share and Enjoy

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

Thanks, personally I would prefer a GitHub with some examples instead of reading all the documentation. But I have followed what is described ie:

// Creates a certificate object from a DER representation of a certificate
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);

// Import certificate in keychain
NSDictionary *certAttributes = @{
        (__bridge id)kSecClass: (__bridge id)kSecClassCertificate,
        (__bridge id)kSecValueRef: (__bridge id)cert,
        (__bridge id)kSecAttrLabel: kKeychainCertificateLabel
    };
    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)certAttributes, NULL);

NSDictionary *privateKeyAttributes = @{
        (__bridge id)kSecClass: (__bridge id)kSecClassKey,
        (__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeRSA,
        (__bridge id)kSecAttrKeyClass: (__bridge id)kSecAttrKeyClassPrivate,
        (__bridge id)kSecAttrKeySizeInBits: @(keySize),
        (__bridge id)kSecAttrLabel: @"My PrivateKey",
        //(__bridge id)kSecAttrApplicationTag: [kKeychainApplicationTag dataUsingEncoding:NSUTF8StringEncoding],
    };
    
// Creating private key
    CFErrorRef error = NULL;
    SecKeyRef privateKey = SecKeyCreateWithData((__bridge CFDataRef)keyData,
                                                (__bridge CFDictionaryRef)privateKeyAttributes,
                                                &error);

but SecKeyCreateWithData return NULL, am I supposed to use this API or should I directly use SecItemAdd for for the private key ?

Thanks

Written by vricosti in 822378022
I would prefer a GitHub with some examples instead of reading all the documentation

With SecItem, it’s really important that you understand the fundamentals. Without that, it’s easy to take a working example, tweak it slightly, and end up with code that works most of the time and then fails in specific circumstances. To avoid problems like that you need to have a mental model of the API as a whole [1].

Written by vricosti in 822378022
but SecKeyCreateWithData return NULL

Right, so you have an import problems, and it was being masked by the fact that you were trying to import and add at the same time.

I have another post that covers import issues: Importing Cryptographic Keys. That should be sufficient for you to resolve this issue. If not, please post a hex dump of an example key.

IMPORTANT Make sure that this is a test key. You don’t want to be sharing real credentials on a public forum.

Share and Enjoy

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

[1] Now, you could argue that the keychain API shouldn’t require that, and I’d probably agree with you (-: However, right now, we have to work with the API we have )-:

Ok so I really don"t know what I am doing wrong...

// Function declaration from private API
SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator, SecCertificateRef certificate, SecKeyRef privateKey);

static NSString * const kKeychainPrivateKeyLabel = @"My PrivateKey";
static NSString * const kKeychainCertificateLabel = @"My Certificate";

- (SecIdentityRef)createIdentityWithCert:(NSString *)pemCert privateKey:(NSString *)pemKey {
    RCTLogWarn(@"createIdentity: Starting identity creation");
    
    // Strip PEM headers and convert to data
    NSString *cleanedCert = [self stripPEMHeader:pemCert prefix:@"CERTIFICATE"];
    NSString *cleanedKey = [self stripPEMHeader:pemKey prefix:@"PRIVATE KEY"];
    
    NSData *certData = [[NSData alloc] initWithBase64EncodedString:cleanedCert
                                                         options:NSDataBase64DecodingIgnoreUnknownCharacters];
    NSData *keyData = [[NSData alloc] initWithBase64EncodedString:cleanedKey
                                                        options:NSDataBase64DecodingIgnoreUnknownCharacters];
    
    if (!certData || !keyData) {
        RCTLogWarn(@"createIdentity: Failed to create data from base64");
        return NULL;
    }

    // Creates a certificate object from its DER representation
    SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
    if (!cert) {
        RCTLogWarn(@"createIdentity: Failed to create certificate from data");
        return NULL;
    }

    // Creates a private key object from its DER representation
    NSDictionary *privateKeyAttributes = @{
        //(__bridge id)kSecClass: (__bridge id)kSecClassKey,
        (__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeRSA,
        (__bridge id)kSecAttrKeyClass: (__bridge id)kSecAttrKeyClassPrivate,
        //(__bridge id)kSecAttrLabel: kKeychainPrivateKeyLabel,
        //(__bridge id)kSecAttrApplicationTag: [kKeychainApplicationTag dataUsingEncoding:NSUTF8StringEncoding],
    };
    
    
    CFErrorRef error = NULL;
    SecKeyRef privateKey = SecKeyCreateWithData((__bridge CFDataRef)keyData,
                                                (__bridge CFDictionaryRef)privateKeyAttributes,
                                                &error);
    if (!privateKey) {
        RCTLogWarn(@"createIdentity: Failed to create private key: %@", error);
        CFRelease(cert);
        if (error) CFRelease(error);
        return NULL;
    }

I can see inside the debugger that cleanedKey contains the same string without the header as the attached file client-selfsigned.key.txt but privateKey is NULL

-----BEGIN PRIVATE KEY-----
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
5SmeOZLrHF201mdGncFOfV+afqr1zx0=
-----END PRIVATE KEY-----

-----BEGIN CERTIFICATE-----
MIIFlTCCA32gAwIBAgIUXCV21EdFYZs8q2LrzEAtGXQRUNQwDQYJKoZIhvcNAQEL
BQAwWTELMAkGA1UEBhMCRlIxDDAKBgNVBAgMA1NUQTEMMAoGA1UEBwwDTE9DMQow
CAYDVQQKDAFPMQswCQYDVQQLDAJPVTEVMBMGA1UEAwwMU2VydmljZSBOYW1lMCAX
DTI0MDUyMDE5NDYwOFoYDzIxMjQwNDI2MTk0NjA4WjBZMQswCQYDVQQGEwJGUjEM
MAoGA1UECAwDU1RBMQwwCgYDVQQHDANMT0MxCjAIBgNVBAoMAU8xCzAJBgNVBAsM
Ak9VMRUwEwYDVQQDDAxTZXJ2aWNlIE5hbWUwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQDIIcLKNJB8/Oy3iI0CD/ScLZkysmGv1xarjW+oqgImHynj6cVu
vlIhGS7CHEjj0rChvF52YnS7+0DEYXFiXcchnoTf/whd3F4Zw6A6WqW7tVBsU60V
9RBfQ7QF1ZI4OiOCAmppmz+y2XKo/sn3ji0+wfZRQX9K/odv/hyH3KgG4UhTrJOf
bcbVENzlOrjm3X79NN0ZaZUMYFNVR9yKRWWqgiv5rszJFCtOeqdxgvFZZ9gf+vrT
Gfg9mG5/Av4W5QhcaFkRyvqfgSufFhVSmKMPdcmlZ28SN+N61ljHhtnxDsZZQchj
f974+KhO+sHCZtJljtGOjml3CS7atqSnek0oScVk/OZta67aYYNs0X2W0owLSEt/
uZhVsuttpSatE8VYhevDxql8W6GVZbfjz3nAD7FNyjn0XiA1/l/5+zrB47sOZRsM
Kir5PL6OH5cmB5M8rWCkoBNdVkjdo/+hKyzJlF4SDFq+0re9CD1lOFeeUoWM4ZLE
Yw+uIxWxnda5JT5Jz22SO/9KX5+VArzwBUfduKbGBXRKDdAp29E3MrP9BN8xnIR/
ayQgYYwvjxxEFsOVlBEeUo/gH99NvKrnFKkOwxHX5k3nhDtPamRM58ahuw52a7C5
7C9VskbXOD/opKsmhvIsDNwL1WvZpNNKzo0u0qDp6Y1HnUOEwj1LbHjFWwIDAQAB
o1MwUTAdBgNVHQ4EFgQUMS7eAA3wNms7X7VZpIF3yiMhVRYwHwYDVR0jBBgwFoAU
MS7eAA3wNms7X7VZpIF3yiMhVRYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B
AQsFAAOCAgEAUF6PquYwj30CWaQ5oxfu+Vocu4oSYaD0SNoVS4MrvTm3wljjKBC8
zcDmV+iYGa8NN8TwZSvThpf0U4RY6x3+I5Dx8zlhF3aeNKDIi71ziEJZyV0ghlVT
CitqZAlOc+glUWHBeDgUqa5Cq/hcZRauDz/+Tq9fW7pTssfYsyoBI5YtbwLbr1eT
DPg31DevzlJ0c2BOSIUA0bKCD1cECVkvsZcPe1KZIqUzDHxSbDhBM7pq20svus/r
YL7ORwTt8GWfIWFP+fW0Eb8IH4sR5GQL8sH2p+O0jX+097v6ClH8UaKRc7vFelYg
amWLz+fALud9iieLgrpqmfAoHseYugY/LsoSoCmnE0Wz7T/J/QEGNRejC6AkO4vS
TtA7aWbcP5C8H9UHHgJVsf/sKj3zdlgTnr+IebNhxv9ic4xVIo/D+kMfB6Si+qS0
CkdmqkmyQb4zgztIcs2B3kCqurkIJeSgcBIXwbcUBKFy0RDCZoaf0omgpRD96Y7+
fUdbRdT2sL7ruM5monfrH4glVBkqJbVQ5LHSlDAKD2evJIT+EUjU2F1q38okFF+a
c1UiEpADPYF21CmR0ETzcXdWlaEzNw4sFH0JwmsJHzbHMVEyluijSow2fsLQnbNi
NetvtNEvAsacziwLM9kVjdWBpAyp2d2Y7hjzCEmTVI0dSKWhsJjVxdQ=
-----END CERTIFICATE-----

You wrote:

// Function declaration from private API
SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator, SecCertificateRef certificate, SecKeyRef privateKey);

I’m not sure what you’re planning to do with that, but it shouldn’t be necessary and will likely cause problems. I recommend that you remove it.

Looking at client-selfsigned.key.txt I see this:

% dumpasn1 private-key.asn1 
   0 2371: SEQUENCE {
   4    1:   INTEGER 0
   7   13:   SEQUENCE {
   9    9:     OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
  20    0:     NULL
         :     }
  22 2349:   OCTET STRING, encapsulates {
  26 2345:     SEQUENCE {
  30    1:       INTEGER 0
  33  513:       INTEGER
         :         00 C8 21 C2 CA 34 90 7C FC EC B7 88 8D 02 0F F4
         :         9C 2D 99 32 B2 61 AF D7 16 AB 8D 6F A8 AA 02 26
         :         1F 29 E3 E9 C5 6E BE 52 21 19 2E C2 1C 48 E3 D2
         :         B0 A1 BC 5E 76 62 74 BB FB 40 C4 61 71 62 5D C7
         :         21 9E 84 DF FF 08 5D DC 5E 19 C3 A0 3A 5A A5 BB
         :         B5 50 6C 53 AD 15 F5 10 5F 43 B4 05 D5 92 38 3A
         :         23 82 02 6A 69 9B 3F B2 D9 72 A8 FE C9 F7 8E 2D
         :         3E C1 F6 51 41 7F 4A FE 87 6F FE 1C 87 DC A8 06
         :                 [ Another 385 bytes skipped ]
 550    3:       INTEGER 65537
 …

The stuff inside the encapsulation is an RSAPrivateKey structure, per On Cryptographic Key Formats. That’s what SecKeyCreateWithData is looking for. To import such a key, use the code from the Import RSA Keys section of Importing Cryptographic Keys.

The issue here is that this is encapsulated within a PKCS#8 structure. Security framework can’t import that directly. The best path to a solution here is to switch to elliptic curve. That’s because:

  • Elliptic curve has numerous technical advantages over RSA.

  • Apple CryptoKit has support for importing SECG directly from PEM.

However, I’m gonna presume that you have to stick to RSA. In that case you’ll need to find a way to remove that prefix yourself. For a quick hack you can just drop a fixed number of bytes:

let keyBytes: [UInt8] = [
    0x30, 0x82, 0x09, 0x43, 0x02, 0x01, 0x00, 0x30,
    0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
    0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82,
    0x09, 0x2d, 0x30, 0x82, 0x09, 0x29, 0x02, 0x01,
    … elided …
]

func test() -> SecKey? {
    let keyData = Data(keyBytes).dropFirst(26)
    guard let privateKey = SecKeyCreateWithData(keyData as NSData, [
        kSecAttrKeyType: kSecAttrKeyTypeRSA,
        kSecAttrKeyClass: kSecAttrKeyClassPrivate,
    ] as NSDictionary, nil) else {
        return nil
    }
    return privateKey
}

A real solution involves parsing the ASN.1 header, checking for the right key type OID, and then extracting the encapsulated data. iOS does not have an ASN.1 API, so you’ll need to either write or acquire one. I generally reach for SwiftASN1. IIRC, that package actually uses PKCS#8 as an example in its tests.

Share and Enjoy

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

Thanks a lot for your help! Just to be sure in you code you wrote kSecAttrKeyClassPublic, you wanted to write Private right ?

And finallly SecIdentityCreate seems to work while I don't even know how to get it without this, I tried with status = SecItemCopyMatching((__bridge CFDictionaryRef)identityQuery, (CFTypeRef *)&identity); with no success

it's so obvious that even on SO they still are looking for how to do it:

https://stackoverflow.com/questions/45997841/how-to-get-a-secidentityref-from-a-seccertificateref-and-a-seckeyref

https://stackoverflow.com/questions/53897612/how-to-get-secidentity-after-seccertificatecreatewithdata-and-secitemadd

Answer is either to use SecIdentityCreate or use some SecItemExport/SecItemImport ...

So now my code is:

                              privateKey:(NSString *)pemKey
                                settings:(NSDictionary *)settings {
    RCTLogWarn(@"createIdentity: Starting identity creation");
    
    SecIdentityRef identity = NULL;
    
    // Strip PEM headers and convert to data
    NSString *cleanedCert = [self stripPEMHeader:pemCert prefix:@"CERTIFICATE"];
    NSString *cleanedPrivateKey = [self stripPEMHeader:pemKey prefix:@"PRIVATE KEY"];
    NSString *certAlias = settings[@"certAlias"];
    NSString *keyAlias= settings[@"keyAlias"];
    
    NSData *certData = [[NSData alloc] initWithBase64EncodedString:cleanedCert
                                                           options:NSDataBase64DecodingIgnoreUnknownCharacters];
    NSData *pkcs8KeyData = [[NSData alloc] initWithBase64EncodedString:cleanedPrivateKey
                                                               options:NSDataBase64DecodingIgnoreUnknownCharacters];
    
    if (!certData || !pkcs8KeyData) {
        RCTLogWarn(@"createIdentity: Failed to create data from base64");
        return NULL;
    }
    
    // Creates a certificate object from its DER representation
    SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
    if (!cert) {
        RCTLogWarn(@"createIdentity: Failed to create certificate from data");
        return NULL;
    }
    
    // Extract RSA key from PKCS#8 - TODO use a ASN1 decoder to detect the key format ...
    // For my own use I know it's a pem but I prefer not to trust a file extension and
    // it's better to check from asn1 data
    NSData *rsaKeyData = [self extractRSAKeyFromPKCS8:pkcs8KeyData];
    if (!rsaKeyData) {
        RCTLogWarn(@"Failed to extract RSA key from PKCS#8");
        CFRelease(cert);
        return NULL;
    }
    
    NSDictionary *privateKeyAttributes = @{
        //(__bridge id)kSecClass: (__bridge id)kSecClassKey,
        (__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeRSA,
        (__bridge id)kSecAttrKeyClass: (__bridge id)kSecAttrKeyClassPrivate,
        //(__bridge id)kSecAttrLabel: kKeychainPrivateKeyLabel,
        //(__bridge id)kSecAttrApplicationTag: [kKeychainApplicationTag dataUsingEncoding:NSUTF8StringEncoding],
    };
    
    CFErrorRef error = NULL;
    SecKeyRef privateKey = SecKeyCreateWithData((__bridge CFDataRef)rsaKeyData,
                                                (__bridge CFDictionaryRef)privateKeyAttributes,
                                                &error);
    if (!privateKey) {
        RCTLogWarn(@"createIdentity: Failed to create private key: %@", error);
        CFRelease(cert);
        if (error) CFRelease(error);
        return NULL;
    }
    
    NSDictionary *deleteKeyQuery = @{
        (__bridge id)kSecClass: (__bridge id)kSecClassKey,
        (__bridge id)kSecAttrLabel: keyAlias
    };
    SecItemDelete((__bridge CFDictionaryRef)deleteKeyQuery);
    
    // Import certificate in keychain
    NSDictionary *deleteCertQuery = @{
        (__bridge id)kSecClass: (__bridge id)kSecClassCertificate,
        (__bridge id)kSecAttrLabel: certAlias
    };
    SecItemDelete((__bridge CFDictionaryRef)deleteCertQuery);
    
    NSDictionary *certAttributes = @{
        (__bridge id)kSecClass: (__bridge id)kSecClassCertificate,
        (__bridge id)kSecValueRef: (__bridge id)cert,
        (__bridge id)kSecAttrLabel: certAlias
    };
    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)certAttributes, NULL);
    if (status != errSecSuccess && status != errSecDuplicateItem) {
        RCTLogWarn(@"createIdentity: Failed to store certificate, status: %d", (int)status);
        CFRelease(cert);
        CFRelease(privateKey);
        return NULL;
    }
    
    // Add the private key to keychain
    NSDictionary *keyAttributes = @{
        (__bridge id)kSecClass: (__bridge id)kSecClassKey,
        (__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeRSA,
        (__bridge id)kSecAttrKeyClass: (__bridge id)kSecAttrKeyClassPrivate,
        //(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleAfterFirstUnlock,
        (__bridge id)kSecAttrLabel: keyAlias,
        //(__bridge id)kSecAttrApplicationTag: [kKeychainApplicationTag dataUsingEncoding:NSUTF8StringEncoding],
    };
    status = SecItemAdd((__bridge CFDictionaryRef)keyAttributes, NULL);
    
    if (status != errSecSuccess && status != errSecDuplicateItem) {
        RCTLogWarn(@"createIdentity: Failed to store private key, status: %d", (int)status);
        CFRelease(cert);
        CFRelease(privateKey);
        return NULL;
    }
    
    //------ PRIVATE API: need to find the proper way of doing it -------
    if (YES) {
        identity = SecIdentityCreate(NULL, cert, privateKey);
    } else {
        NSDictionary *identityQuery = @{
            (__bridge id)kSecClass: (__bridge id)kSecClassIdentity,
            (__bridge id)kSecReturnRef: @YES,
            (__bridge id)kSecMatchItemList:@[(__bridge id)cert],
            (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne,
            //(__bridge id)kSecAttrLabel: @"My Certificate",
            //(__bridge id)kSecUseDataProtectionKeychain: @YES
        };
        status = SecItemCopyMatching((__bridge CFDictionaryRef)identityQuery, (CFTypeRef *)&identity);
        
        if (status != errSecSuccess || !identity) {
            RCTLogWarn(@"createIdentity: Failed to find identity, status: %d", (int)status);
        } else {
            RCTLogWarn(@"createIdentity: Successfully found identity");
        }
    }
    
    // Clean up
    CFRelease(cert);
    CFRelease(privateKey);
    
    return identity;
}

One last effort and it should be ok ...

Written by vricosti in 822725022
Just to be sure in you code you wrote kSecAttrKeyClassPublic

D’oh! Yes. I’ve gone back and fixed the post [1]. Thanks for the sharp eyes!

Written by vricosti in 822778022
I don't even know how to get it without this

You’re on the right path with SecItemCopyMatching. For that to work you need to add both the certificate and the private key to the keychain. Moreover, those items need to have the matching values in the identity formation attributes, as described in the Digital Identities Aren’t Real section of SecItem: Pitfalls and Best Practices. Normally the system adds those attributes correctly; indeed, that’s a key reason why it’s better to add import and then add.

If digital identity formation isn’t working correctly, write some small test code that calls SecItemCopyMatching with kSecReturnAttributes. Run that against both the key and the certificate and then look at the returned attributes. Check that the items are present as you expect and that the identity formation attributes both have the same value.

Oh, wait. You’re using kSecMatchItemList. Don’t do that. When debugging this, request all digital identities and then look through the results. That’ll let you distinguish between two important cases:

  • The items failing to form a digital identity

  • You matching the correct digital identity

Share and Enjoy

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

[1] I try to avoid doing that, but in this case I think it’s justified because folks are likely to copy’n’paste the code.

Accepted Answer
Written by DTS Engineer in 823021022
write some small test code that calls SecItemCopyMatching with kSecReturnAttributes.

Oh, hey. I made a note to myself to update SecItem: Pitfalls and Best Practices to discuss these debugging ideas in more detail, but it turns out that Past Quinn™ has got you covered. Check out the Lost Keychain Items, Redux section of that post for a general explanation of this idea.

Share and Enjoy

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

thanks for your help. it should be ok now.

Finally nothing work anymore since now every time I try to insert the private key I get a errSecDuplicateItem but when I try to delete it before it says it does not exist. I also tried to uninstall the app and restart the phone but same results. So I am having a break for a few weeks and if I have motivated I will try again to make it work...

Hum actually if we pass a Label to SecItemDelete it fails ... no error is allowed. So finally I am dumping attributes:

Key Attributes: { accc = "<SecAccessControlRef: ak>"; agrp = "D27Q6535KW.com.lotalogic.testandroidtvremoteapp"; atag = ""; bsiz = 0; cdat = "2025-02-01 11:09:49 +0000"; crtr = 0; edat = "2001-01-01 00:00:00 +0000"; esiz = 0; kcls = 1; klbl = {length = 0, bytes = 0x}; labl = cert; mdat = "2025-02-01 11:09:49 +0000"; musr = {length = 0, bytes = 0x}; pdmn = ak; sdat = "2001-01-01 00:00:00 +0000"; sha1 = {length = 20, bytes = 0xdc6b0c25a45f014b2d6da9f668e0d4cd325f3b35}; sync = 0; tomb = 0; type = 42; }

Cert Attributes: { accc = "<SecAccessControlRef: dk>"; agrp = "D27Q6535KW.com.lotalogic.testandroidtvremoteapp"; cdat = "2025-02-01 11:10:04 +0000"; cenc = 3; ctyp = 3; issr = {length = 89, bytes = 0x310b3009 06035504 06130246 52310c30 ... 69636520 4e616d65 }; labl = cert; mdat = "2025-02-01 11:10:04 +0000"; musr = {length = 0, bytes = 0x}; pdmn = dk; pkhh = {length = 20, bytes = 0x312ede000df0366b3b5fb559a48177ca23215516}; sha1 = {length = 20, bytes = 0xaf3c77416df73b8d8ba4449c797f16ef8f5ac59c}; skid = {length = 20, bytes = 0x312ede000df0366b3b5fb559a48177ca23215516}; slnr = {length = 20, bytes = 0x5c2576d44745619b3cab62ebcc402d19741150d4}; subj = {length = 89, bytes = 0x310b3009 06035504 06130246 52310c30 ... 69636520 4e616d65 }; sync = 0; tomb = 0;

but still no identity:

NSDictionary *identityQuery = @{
            (__bridge id)kSecClass: (__bridge id)kSecClassIdentity,
            (__bridge id)kSecReturnRef: @YES,
        };
        status = SecItemCopyMatching((__bridge CFDictionaryRef)identityQuery, (CFTypeRef *)&identity);

I think I will reverse SecIdentityCreate it will be easier.

hum klbl seems empty, it should be the reason because per documentation:

The system forms a digital identity by matching the kSecAttrApplicationLabel (klbl) attribute of the private key with the kSecAttrPublicKeyHash (pkhh) attribute of the certificate

Create an SecIdentityRef from a certificate and private key
 
 
Q