// 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; }