Hi Quinn, thanks for the comments! I am indeed going down the hard path... Don't quite understand WHY it's the hard path and not set up to be easier to use but that's a separate issue. ( I really wish iOS had an implementation of the SecIdentityCreateWithCertificate macOS function )
I read those SecItem links as a starting point for this task and they were incredibly helpful! In large part, they got me to the point I'm at now.
Unfortunately, I cannot switch to Objective C or CPP. The entire library is written in native C and then bound out for use in CPP, Java, JavaScript, and Python. Swift is the next language we are adding support for which is why we're looking into using SecItem and iOS support.
I've generated a SecKeyRef and SecCertificateRef from the CFDataRefs to use kSecValueRef instead of kSecValueData as you suggested for both SecItemAdd certificate and private key. Those both are resulting in OSStatus of 0 in tests.
Definitely ran into the gotcha you mentioned related to PEM text in DER data and I've resolved that earlier with a conversion. Prior to the change to SecItemAdd above, I was already generating a SecCertificateRef to extract the serial from the certificate to use as a unique key during SecItemAdd via SecCertificateCopySerialNumberData. It's a bummer the framework doesn't do this PEM->DER conversion for you.
I'm happy to have set up the SecItemAdd functions in a way that will detect issues at import but so far, it's still resulting in zero kSecClassIdentity items being found in the keychain. I'll continue by reading through your linked docs and report back if I find a solution for anyone else attempting to do things the hard way. I'll add my updated import functions for certificate and key below.
int aws_secitem_add_certificate_to_keychain(
CFAllocatorRef cf_alloc,
SecCertificateRef cert_ref,
CFDataRef serial_data,
CFStringRef label,
SecCertificateRef *out_certificate) {
int result = AWS_OP_ERR;
OSStatus status;
CFDictionaryRef delete_query = NULL;
CFDictionaryRef attributes = NULL;
/* If the certificate is already in the keychain, delete it before adding. */
const void *delete_keys[] = {
kSecClass,
kSecAttrSerialNumber
};
const void *delete_values[] = {
kSecClassCertificate,
serial_data
};
delete_query = CFDictionaryCreate(
cf_alloc,
delete_keys,
delete_values,
2,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
status = SecItemDelete(delete_query);
if (status == errSecSuccess) {
AWS_LOGF_INFO(
AWS_LS_IO_PKI,
"static: keychain contains matching certificate that was previously imported. "
"Deleting existing certificate in keychain.");
}
// Attempt to add the certificate with all set attributes to the keychain.
const void *add_keys[] = {
kSecClass,
kSecAttrLabel,
kSecAttrSerialNumber,
kSecValueRef,
kSecReturnRef };
const void *add_values[] = {
kSecClassCertificate,
label,
serial_data,
cert_ref,
kCFBooleanTrue };
attributes = CFDictionaryCreate(
cf_alloc,
add_keys,
add_values,
5,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
status = SecItemAdd(attributes, (CFTypeRef *)out_certificate);
if (status != errSecSuccess) {
result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
goto done;
}
result = AWS_OP_SUCCESS;
done:
if (delete_query) CFRelease(delete_query);
if (attributes) CFRelease(attributes);
return result;
}
int aws_secitem_add_private_key_to_keychain(
CFAllocatorRef cf_alloc,
SecKeyRef key_ref,
CFStringRef key_type,
CFStringRef label,
CFStringRef application_label,
SecKeyRef *out_private_key) {
int result = AWS_OP_ERR;
OSStatus status;
CFDictionaryRef delete_query = NULL;
CFDictionaryRef attributes = NULL;
/* If the private key is already in the keychain, delete it before adding. */
const void *delete_keys[] = {
kSecClass,
kSecAttrKeyClass,
kSecAttrKeyType,
kSecAttrApplicationLabel
};
const void *delete_values[] = {
kSecClassKey,
kSecAttrKeyClassPrivate,
key_type,
application_label
};
delete_query = CFDictionaryCreate(
cf_alloc,
delete_keys,
delete_values,
2,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
status = SecItemDelete(delete_query);
if (status == errSecSuccess) {
AWS_LOGF_INFO(
AWS_LS_IO_PKI,
"static: keychain contains matching private key that was previously imported. "
"Deleting existing private key in keychain.");
}
// Attempt to add the private key with all set attributes to the keychain.
const void *add_keys[] = {
kSecClass,
kSecAttrKeyClass,
kSecAttrKeyType,
kSecAttrApplicationLabel,
kSecAttrLabel,
kSecValueRef,
kSecReturnRef };
const void *add_values[] = {
kSecClassKey,
kSecAttrKeyClassPrivate,
key_type,
application_label,
label,
key_ref,
kCFBooleanTrue };
attributes = CFDictionaryCreate(
cf_alloc,
add_keys,
add_values,
7,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (attributes == NULL) {
result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
goto done;
}
status = SecItemAdd(attributes, (CFTypeRef *)out_private_key);
if (status != errSecSuccess) {
result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
goto done;
}
result = AWS_OP_SUCCESS;
done:
if (delete_query) CFRelease(delete_query);
if (attributes) CFRelease(attributes);
return result;
}