Hi pnelson, I've seen some info on PKCS12 import using SecPKCS12Import and that could be a reasonable fallback but the requirements I've got at the moment are to allow users to bring a separate certificate and key file as a baseline and add support for PKCS12, PKCS11, etc... in the future.
Are you using native C to set up your connections? Do you have a sample anywhere I could check out?
Thanks for the input!
Post
Replies
Boosts
Views
Activity
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;
}
I've re-read the links provided and it appears as though adding the key/cert is working as expected with the following logs:
After adding the Certificate, CFShow on the returned SecCertificateRef logs:
<cert(0x105f04df0) s: AWS IoT Certificate i: Amazon Web Services O=Amazon.com Inc. L=Seattle ST=Washington C=US>
After adding the Private Key, CFShow on the returned SecKeyRef logs:
<SecKeyRef algorithm id: 1, key type: RSAPrivateKey, version: 4, 2048 bits (block size: 256), addr: 0x600000c21570>
The "Digital Identities Aren't Real" section of pitfalls and best practices mentions that when a certificate or private key are added that pair up, a digital identity is "added". I am running the following search after SecItemAdd of the cert/key and it is resulting in OSStatus -25300 (errSecInteractionNotAllowed).
CFArrayRef stored = NULL;
const void *keys[] = { kSecClass, kSecReturnRef, kSecMatchLimit };
const void *values[] = { kSecClassIdentity, kCFBooleanTrue, kSecMatchLimitAll };
CFDictionaryRef query = CFDictionaryCreate(NULL, keys, values, 3,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
status = SecItemCopyMatching(query, (CFTypeRef *)&stored);
if (status == errSecSuccess) {
CFIndex count = CFArrayGetCount(stored);
for (CFIndex i = 0; i < count; i++) {
SecIdentityRef identity = (SecIdentityRef)CFArrayGetValueAtIndex(stored, i);
CFShow(identity);
}
CFRelease(stored);
} else {
printf("No identities found or error: %d\n", (int)status);
}
This status is returned whether I'm doing a SecItemCopyMatching for a specific identity using the certificate's serial or for any kSecClassIdentity items. This feels weird as I think I'm authorized based on being able to add to the keychain.
I have tried adding the attribute kSecAttrAccessible with kSecAttrAccessibleAlways for both the key and the certificate to make them as openly accessible as possible and am still running into the errSecInteractionNotAllowed status result.
Is there something else I can try or may be doing wrong?
Sorry, I missread the OSStatus. -25300 is errSecItemNotFound. So I guess after adding the cert and key into the keychain, there is no kSecClassIdentity being "added".
Everything looks in order, is there any way to generate an identity manually in iOS the way there was for macOS? Or is there anything else that can be tried at this point?
I think I may have run into part of why an identity isn't being generated.
I'm running the following to check whether the public key data in the certificate matches the private key:
SecKeyRef public_key = SecCertificateCopyKey(secitem_certificate);
CFDataRef publicKeyData = SecKeyCopyExternalRepresentation(public_key, &error);
CFDataRef privateKeyData = SecKeyCopyExternalRepresentation(secitem_private_key, &error);
Boolean match = CFEqual(publicKeyData, privateKeyData);
if (match) { printf("it matches"); }
This fails to produce a match. CFShow on the two also result in pretty different results.
CFShow(publicKeyData):
<CFData 0x600000c78ff0 [0x7ff86002ed20]>{length = 270, capacity = 1024, bytes = 0x3082010a0282010100bf3c1fb172fccf ... dd76fb0203010001}
CFShow(privateKeyData):
<CFData 0x11181b010 [0x60000390c340]>{length = 1190, capacity = 1190, bytes = 0x308204a20201000282010100bf3c1fb1 ... 5bcd75daed351328}
using openssl to check the certificate and key, they are in fact paired properly.
$ openssl x509 -in cert.pem > cert_pub_key.pem
$ openssl rsa -in priv_key.pem > priv_key_pub_key.pem
$ diff cert_pub_key.pem priv_key_pub_key.pem
<results in no diff>
I'm stumped as CFShow on the certificate and the privatekey appear to be producing the expected results. Will keep digging...
Additional wrinkle is when I extract the public key from both the certificate and private key
SecKeyRef public_key = SecCertificateCopyKey(*secitem_certificate);
SecKeyRef public_key_from_private = SecKeyCopyPublicKey(*secitem_private_key);
and CFShow on the resulting public keys, the output matches exactly:
public key extracted from certificate
<SecKeyRef algorithm id: 1, key type: RSAPublicKey, version: 4, 2048 bits (block size: 256), exponent: {hex: 10001, decimal: 65537}, modulus: BF3C1FB172FCCF6E1C99D1FEFB4A4CBBD1AADC994923C483AF201B9F33DF256339F951860E77629E9C43114837A0A5F902E59A90521E4EB12B10DAE865942A15B293A4D646F7DC6384AFDA050EFFD0EEC4C8AF35F1A97A0A0935FD514A6634E4857D03EAA38756146F43D4BAB6E78F0DB906809B8C7AB3D454C3FC64F820C3F6C13AAFB910523CBB29C268B38B6124AC97E9A186FACBAA804F2E5692BCAE390056FD4B3C95DA81E1C287D5953BDE77344079B5C751535ED51946C5F5C2146852BA0BC101B984E6BA74AD429F2155C929FBC0FFED004C74EFB44BD5D7852520080C65EB6F71877B5AC95A57B2B0428AA9A14CF06C4FFEAEF69CB02C8634DD76FB, addr: 0x600000297780>
public key extracted from private key
<SecKeyRef algorithm id: 1, key type: RSAPublicKey, version: 4, 2048 bits (block size: 256), exponent: {hex: 10001, decimal: 65537}, modulus: BF3C1FB172FCCF6E1C99D1FEFB4A4CBBD1AADC994923C483AF201B9F33DF256339F951860E77629E9C43114837A0A5F902E59A90521E4EB12B10DAE865942A15B293A4D646F7DC6384AFDA050EFFD0EEC4C8AF35F1A97A0A0935FD514A6634E4857D03EAA38756146F43D4BAB6E78F0DB906809B8C7AB3D454C3FC64F820C3F6C13AAFB910523CBB29C268B38B6124AC97E9A186FACBAA804F2E5692BCAE390056FD4B3C95DA81E1C287D5953BDE77344079B5C751535ED51946C5F5C2146852BA0BC101B984E6BA74AD429F2155C929FBC0FFED004C74EFB44BD5D7852520080C65EB6F71877B5AC95A57B2B0428AA9A14CF06C4FFEAEF69CB02C8634DD76FB, addr: 0x600000c35300>
At a loss as to what's going on here. Maybe I can add the public key into the keychain in a way that will cause the private key and the certificate to match up to form an identity.
Hey! Thanks for the continued help!
Don’t get me wrong, there should be better APIs for this. The Security framework APIs are both very limited and tricky to use correctly. There’s no good reason for that )-: [1]
As someone who builds SDKs and APIs, I totally get this and especially support for C not being on anyone's list of things to do. I think the work you're doing with documenting a lot of the pitfalls of using SecItem are an incredible resource (and honestly, some of the only I've been able to find) out there and it'd be great if some amount of it could be integrated into the Framework's API documentation.
Life it too short to be wrangling CF dictionaries by hand (-:
Believe me, nobody feels this more than I do. This specific library has to be written in native C and only using native C. If we used Objective C, it'd be as a separate bindings repo the same way we have for CPP. I don't make the rules, I just have to live with them >,<
The next debugging step is to check that the attributes responsible for identity formation are correct. So, something like:
Since last communication, I've checked and compared individual attributes kSecAttrKeySizeInBits and kSecAttrApplicationLabel, from the extracted public keys which matched as well as the private key itself. They all matched up as expected.
Oh, wait! After writing the above I took another look at your code and it seems that you’re overriding kSecAttrApplicationLabel on your key. That’s almost certainly the cause of this failure to form an identity.
Removing the kSecAttrApplicationLabel from the private key with SecItemAdd appears to have resolved the issue (Hurray!). At least to the point that I'm able to find an identity.
As kSecAttrApplicationLabel is listed as a unique attribute for use with queries, I was setting this manually. Looking at the documentation, it appears as though this is normally set to the hash of the public key so I'm assuming it's also used as the point of comparison when generating identities. So even though the public keys extracted from both private key and certificate match, if this doesn't, it's game over.
Thanks again for all your help! Hopefully this is the last block to getting TLS working in native C for our library >,<
Thanks for the link! I came across it during my search on the forums and it's where I got the sec_protocol_options_set_local_identity from to supply a client identity as you mentioned.
The post also includes some information related to customizing the server trust evaluation as well as responding to client challenges from the server. Are these only required if I want to take a custom action and is setting the local identity enough to handle a mutual TLS connection handshake or do I need to set up one or both of them and provide blocks that complete the requests?
Good call on looking through the header files themselves. And yeah, I'm constantly running into "This feels like a common enough issue that people would have run into it before" which led me to searches here. It's also great that you've composed posts that address common issues and questions. Thanks again for the help, it's very much appreciated!
This is how I've currently set up the parameter creation as straight forward as I can based on the linked post.
nw_parameters_create_secure_tcp(
^(nw_protocol_options_t tls_options) {
sec_protocol_options_t sec_options = nw_tls_copy_sec_protocol_options(tls_options);
sec_protocol_options_set_local_identity(sec_options, transport_ctx->secitem_identity);
// Set the minimum TLS version to TLS 1.2
sec_protocol_options_set_min_tls_protocol_version(sec_options, tls_protocol_version_TLSv12);
// Set the maximum TLS version to TLS 1.3
sec_protocol_options_set_max_tls_protocol_version(sec_options, tls_protocol_version_TLSv13);
// Set up the verify block for custom server trust evaluation
sec_protocol_options_set_verify_block(sec_options,
^(sec_protocol_metadata_t metadata, sec_trust_t trust, sec_protocol_verify_complete_t complete) {
// Perform custom trust evaluation
SecTrustResultType trustResult;
OSStatus status = SecTrustEvaluate(trust, &trustResult);
// EAccept the server trust if it is valid
if (status == errSecSuccess && (trustResult == kSecTrustResultProceed || trustResult == kSecTrustResultUnspecified)) {
complete(true); // Trust is accepted
} else {
complete(false); // Trust is rejected
}
}, dispatch_get_main_queue());
// Set up the challenge block for handling authentication challenges
sec_protocol_options_set_challenge_block(sec_options,
^(sec_protocol_metadata_t metadata, sec_protocol_challenge_complete_t complete) {
// Handle a challenge here
// simply complete with true for now
complete(true);
}, dispatch_get_main_queue());
},
I am encountering an issue where after creating the nw_connection using nw_connection_create with an endpoint and the parameters, setting the state changed handler, and attempting to connect with nw_connection_start, the state appropriately changes to nw_connection_state_preparing but then immediately hits a EXC_BAD_ACCESS on a movq in CoreFoundation CFRetain.
tracking packets, a TCP connection is initiated with a SYN, responded to from the server with a SYN ACK and the local connection is sending a ACK. After TCP is established is the point it appears as though something is trying to access memory that's bad.
Will continue trying to track it down but I'm not CFReleasing anything so I'm wondering if there's something in the tls_options or the sec_protocol_options I need to explicitly retain a reference on in the code block. If you've got any clue or guess as to why this is happening only when I provide a TLS block, it'd help track things down >,<
Thanks for the response! Knowing that setting the identity is all I need for the Mutual TLS handshake will make it easier to limit the pieces in play.
Have you tried selectively apply your verify and challenge blocks to see if just one of them causes the crash?
I originally tried just setting the identity and encountered the crash. I individually added the verify and challenge blocks one at a time and then both to check that not having set them wasn't causing the crash. I've also input print statements into the blocks and neither are being called so whatever is crashing is happening prior to them being hit.
I'm just getting back to this now after the long weekend and will try tracking memory addresses to see if I can't isolate what exactly is being accessed.
Update: I am still running into the EXC_BAD_ACCESS when using sec_protocol_options_set_local_identity
I've checked at the point of setting up the nw_protocol_options_t tls_options block that the SecIdentityRef is valid. It is not NULL and running SecIdentityCopyCertificate and SecIdentityCopyPrivateKey against it returns a valid cert and key that depict the correct contents when CFShow is applied to the results.
Applying an additional CFRetain on the identity does not prevent the issue.
Other manipulation of the sec_protocol_options_t retrieved using nw_tls_copy_sec_protocol_options(tls_options) such as sec_protocol_options_set_min_tls_protocol_version does not result in the crash. Only sec_protocol_options_set_local_identity.
Not using sec_protocol_options_set_local_identity results in the TCP socket being opened to the remote host followed by a Client Hello being sent via TLS 1.2, a Server Hello being sent back, and then a failure during certificate request phase of the TLS handshake. If the identity is set, the EXC_BAD_ACCESS occurs after the TCP socket is opened but before the Client Hello is sent out.
My guess it that something is hitting the EXC_BAD_ACCESS after the TCP handshake is completed and during the construction of the Client Hello that is to go out next. The log shows the following:
CoreFoundation`CFRetain:
0x7ff8003f9494 <+0>: pushq %rbp
0x7ff8003f9495 <+1>: movq %rsp, %rbp
0x7ff8003f9498 <+4>: pushq %rbx
0x7ff8003f9499 <+5>: pushq %rax
0x7ff8003f949a <+6>: testq %rdi, %rdi
0x7ff8003f949d <+9>: je 0x7ff8003f94e2 ; <+78>
0x7ff8003f949f <+11>: movq %rdi, %rbx
0x7ff8003f94a2 <+14>: jns 0x7ff8003f94ae ; <+26>
0x7ff8003f94a4 <+16>: movq %rbx, %rax
0x7ff8003f94a7 <+19>: addq $0x8, %rsp
0x7ff8003f94ab <+23>: popq %rbx
0x7ff8003f94ac <+24>: popq %rbp
0x7ff8003f94ad <+25>: retq
-> 0x7ff8003f94ae <+26>: movq 0x8(%rbx), %rdi
0x7ff8003f94b2 <+30>: shrl $0x8, %edi
0x7ff8003f94b5 <+33>: andl $0x3ff, %edi ; imm = 0x3FF
0x7ff8003f94bb <+39>: movq %rbx, %rsi <- EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
0x7ff8003f94be <+42>: callq 0x7ff8003f8e99 ; CF_IS_OBJC
0x7ff8003f94c3 <+47>: movq %rbx, %rdi
0x7ff8003f94c6 <+50>: testb %al, %al
0x7ff8003f94c8 <+52>: je 0x7ff8003f94d5 ; <+65>
0x7ff8003f94ca <+54>: addq $0x8, %rsp
0x7ff8003f94ce <+58>: popq %rbx
0x7ff8003f94cf <+59>: popq %rbp
0x7ff8003f94d0 <+60>: jmp 0x7ff800528fae ; symbol stub for: objc_retain
0x7ff8003f94d5 <+65>: xorl %esi, %esi
0x7ff8003f94d7 <+67>: addq $0x8, %rsp
0x7ff8003f94db <+71>: popq %rbx
0x7ff8003f94dc <+72>: popq %rbp
0x7ff8003f94dd <+73>: jmp 0x7ff8003f9aca ; _CFRetain
0x7ff8003f94e2 <+78>: callq 0x7ff800521daa ; CFRetain.cold.1
0x7ff8003f94e7 <+83>: xorl %eax, %eax
0x7ff8003f94e9 <+85>: jmp 0x7ff8003f94a7 ; <+19>
Will continue to try to figure out what's going on.
Further update:
In all cases below, setting the identity using sec_protocol_options_set_local_identity results in the aforementioned EXC_BAD_ACCESS during what looks like a CFRetain occurring within code outside of my control. This crash occurs AFTER a successful TCP [SYN], [SYN, ACK], [ACK] handshake between the client and the remote host but before sending a Client Hello to the remote host.
I've tried removing all nw_release and CFRelease calls within the entire package to allow any and all memory leaks to prevent decrementing references under my control and the EXC_BAD_ACCESS is still being hit.
I've tried retrieving the identity from just outside the TLS options block as well as within the TLS options block and used the identity and run into the same issue in those situations.
I have in all variations extracted the certificate and private key from the identity being used just prior to calling sec_protocol_options_set_local_identity and they are both valid and the expected private key and certificate pair.
I have tried using the sec_options that is being used with sec_protocol_options_set_local_identity without setting the identity and see the changes to sec_options reflected appropriately in the incoming/outgoing packets. When identity is not used, the TCP handshake successfully completes followed by an outbound Client Hello, an inbound Server Hello along with a Certificate, Server Key Exchange, and Certificate Request. The Client sends an ack for the Server Hello and then sends the requested information in the next packet. This packet does not contain a Certificate as one hasn't been set with identity. The following ack from the server closes the connection due to a failed handshake.
I have tried setting up the challenge block and when I call complete(identity) from within the same crash occurs.
This all leads me to believe something is wrong within the Apple Network Framework's handling of the provided Identity. I will attempt to use the same framework but with a PKCS12 file to provide the certificate and key creating the identity instead of a separate private key and certificate being added to the keychain (which successfully creates an identity that can be copied).
Question at this point is whether there is anywhere I can submit a ticket or request investigation into this being a potential bug that needs fixing. The repro steps appear to be as simple as adding a private key and certificate separately into the keychain, then retrieving them as an identity and setting them to the sec_options using sec_protocol_options_set_local_identity and then trying to establish a new connection using Mutual TLS.
I'll report back once I have attempted to setup and use PKCS12 within the same framework I am testing the above on. Thanks!
Well, PKCS#12 appears to also not be working and crashing in the exact same fashion. I am successfully importing a PKCS12 file with password into the keychain and getting an identity with the following:
int aws_secitem_import_pkcs12(
CFAllocatorRef cf_alloc,
const struct aws_byte_cursor *pkcs12_cursor,
const struct aws_byte_cursor *password,
SecIdentityRef *secitem_identity) {
int result = AWS_OP_ERR;
CFArrayRef items = NULL;
CFDataRef pkcs12_data = NULL;
pkcs12_data = CFDataCreate(cf_alloc, pkcs12_cursor->ptr, pkcs12_cursor->len);
CFStringRef password_ref = NULL;
if (password->len) {
password_ref = CFStringCreateWithBytes(
cf_alloc,
password->ptr,
password->len,
kCFStringEncodingUTF8,
false);
} else {
password_ref = CFSTR("");
}
CFMutableDictionaryRef dictionary = CFDictionaryCreateMutable(cf_alloc, 0, NULL, NULL);
CFDictionaryAddValue(dictionary, kSecImportExportPassphrase, password_ref);
OSStatus status = SecPKCS12Import(pkcs12_data, dictionary, &items);
if (status != errSecSuccess || CFArrayGetCount(items) == 0) {
AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed to import PKCS#12 file with OSStatus:%d", (int)status);
result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
goto done;
}
// Extract the identity from the first item in the array
// identity_and_trust does not need to be released as it is not a copy or created CF object.
CFDictionaryRef identity_and_trust = CFArrayGetValueAtIndex(items, 0);
*secitem_identity = (SecIdentityRef)CFDictionaryGetValue(identity_and_trust, kSecImportItemIdentity);
// Retain the identity for use outside this function
if (*secitem_identity != NULL) {
CFRetain(*secitem_identity);
AWS_LOGF_INFO(
AWS_LS_IO_PKI,
"static: Successfully imported identity into SecItem keychain.");
} else {
status = errSecItemNotFound;
AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed to retrieve identity from PKCS#12 with OSStatus %d", (int)status);
goto done;
}
result = AWS_OP_SUCCESS;
done:
//cleanup
if (pkcs12_data) CFRelease(pkcs12_data);
if (dictionary) CFRelease(dictionary);
if (password_ref) CFRelease(password_ref);
if (items) CFRelease(items);
return result;
}
Then using the SecIdentityRef in the same way as before resulting in the identical EXC_BAD_ACCESS crash.
Getting a backtrace on the crash I'm getting the following:
* thread #9, queue = 'com.apple.network.connections', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
* frame #0: 0x00007ff8003f94ae CoreFoundation`CFRetain + 26
frame #1: 0x00007ff807459c91 libboringssl.dylib`boringssl_identity_create_from_identity + 70
frame #2: 0x00007ff80747a4a0 libboringssl.dylib`boringssl_context_set_identity + 326
frame #3: 0x00007ff80740d756 libboringssl.dylib`__boringssl_session_apply_protocol_options_for_transport_block_invoke + 2527
frame #4: 0x00007ff807b955cd Network`nw_protocol_options_access_handle + 93
frame #5: 0x00007ff80740cd49 libboringssl.dylib`boringssl_session_apply_protocol_options_for_transport + 179
frame #6: 0x00007ff8074186f0 libboringssl.dylib`nw_protocol_boringssl_begin_connection + 1255
frame #7: 0x00007ff807415b58 libboringssl.dylib`nw_protocol_boringssl_connected + 356
frame #8: 0x00007ff808086728 Network`invocation function for block in nw_socket_init_socket_event_source(nw_socket*, unsigned int) + 1720
frame #9: 0x000000010671162c libclang_rt.asan_iossim_dynamic.dylib`__wrap_dispatch_source_set_event_handler_block_invoke + 204
frame #10: 0x00000001065079f7 libdispatch.dylib`_dispatch_client_callout + 8
frame #11: 0x000000010650ac04 libdispatch.dylib`_dispatch_continuation_pop + 812
frame #12: 0x0000000106520a2d libdispatch.dylib`_dispatch_source_invoke + 2228
frame #13: 0x00000001065116d4 libdispatch.dylib`_dispatch_workloop_invoke + 872
frame #14: 0x000000010651d76e libdispatch.dylib`_dispatch_root_queue_drain_deferred_wlh + 318
frame #15: 0x000000010651cb69 libdispatch.dylib`_dispatch_workloop_worker_thread + 590
frame #16: 0x0000000105ca8b84 libsystem_pthread.dylib`_pthread_wqthread + 327
frame #17: 0x0000000105ca7acf libsystem_pthread.dylib`start_wqthread + 15
It looks like the identity is probably the thing that is trying to be retained but I can't be certain. Will have to keep digging but hoping this may be something someone has some familiarity with.
Thanks for continuing down this rabbit hole with me.
I have set up the bare-bones test that you suggested and when using Native C instead of Objective C, I am getting "Segmentation fault: 11" at the exact same point I was observing the EXC_BAD_ACCESS before. (Code below)
When using Objective C, the identity appears to be passed along with no crash/segfault as expected. Unless there's something additional I'm doing wrong in the pretty straightforward native C code, I think the issue is with the Network framework... Which is truly unfortunate because that means even if it gets fixed, it's not useful for us because we cannot force the minimum version to be the latest release of the framework.
If you have the bandwidth, maybe you can reproduce the Native C issue on your end to confirm. Next step for me will be to investigate whether there's any way I can set up a more concrete way than CFRetain to manage the identity before passing it to the sec_options that will prevent the segfault. I will also need to look into what you meant by:
Keep in mind that you can port to MRR on a file-by-file basis, so it’s feasible to do this stuff piecemeal.
Other alternatives may be to somehow create a bridge that will allow the TLS handshake or parameter creation portion of the connection to Objective C and using those with the Native C Network Framework socket... Though I'm not sure that's really feasible.
Here is the Native C code implementation of what you provided:
// Function to get the client identity from the keychain
static SecIdentityRef clientIdentityNamed(const char *name) {
CFTypeRef copyResult = NULL;
const void *keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef};
const void *values[] = {kSecClassIdentity, kSecMatchLimitAll, kCFBooleanTrue};
CFDictionaryRef query = CFDictionaryCreate(NULL, keys, values, 3, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
OSStatus err = SecItemCopyMatching(query, ©Result);
CFRelease(query);
if (err != errSecSuccess) {
return NULL;
}
CFArrayRef identities = (CFArrayRef)copyResult;
for (CFIndex i = 0; i < CFArrayGetCount(identities); i++) {
SecIdentityRef identity = (SecIdentityRef)CFArrayGetValueAtIndex(identities, i);
SecCertificateRef cert = NULL;
err = SecIdentityCopyCertificate(identity, &cert);
if (err != errSecSuccess) { return NULL; }
CFStringRef certName = SecCertificateCopySubjectSummary(cert);
CFRelease(cert);
if (certName == NULL) { return NULL; }
// Compare certificate name with provided name
Boolean match = CFStringCompare(certName, CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8), 0) == kCFCompareEqualTo;
CFRelease(certName);
if (match) {
CFRetain(identity);
return identity;
}
}
return NULL;
}
// receive func omitted because it doesn't get hit.
int main(int argc, const char *argv[]) {
(void)argc;
(void)argv;
printf("connection will start\n");
// Get the client identity
SecIdentityRef ident = clientIdentityNamed("AWS IoT Certificate");
if (ident == NULL) {
printf("Failed to get client identity\n");
return EXIT_FAILURE;
}
// Create network parameters for a secure TCP connection
nw_parameters_t params = nw_parameters_create_secure_tcp(^(nw_protocol_options_t tls_options) {
sec_protocol_options_t sec_options = nw_tls_copy_sec_protocol_options(tls_options);
sec_protocol_options_set_local_identity(sec_options, ident);
}, NW_PARAMETERS_DEFAULT_CONFIGURATION);
// Create endpoint and connection
const char *host = "client-cert-missing.badssl.com";
nw_endpoint_t endpoint = nw_endpoint_create_host(host, "443");
nw_connection_t connection = nw_connection_create(endpoint, params);
// Set state change handler for the connection
nw_connection_set_state_changed_handler(connection, ^(nw_connection_state_t state, nw_error_t error) {
printf("connection did change state, new: %d\n", (int)state);
if (error != NULL) {
printf("connection error occurred\n");
}
});
// Create an HTTP request and send it over the connection
char *request = NULL;
asprintf(&request, "GET / HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", host);
dispatch_data_t requestData = dispatch_data_create(request, strlen(request), NULL, DISPATCH_DATA_DESTRUCTOR_FREE);
nw_connection_send(connection, requestData, NW_CONNECTION_DEFAULT_STREAM_CONTEXT, false, ^(nw_error_t error) {
if (error != NULL) {
printf("connection did not send, error occurred\n");
} else {
printf("connection did send\n");
}
});
receive(connection);
// Set the connection queue and start the connection
nw_connection_set_queue(connection, dispatch_get_main_queue());
nw_connection_start(connection);
printf("connection did start\n");
dispatch_main();
return EXIT_SUCCESS;
}
Running the above results in the following logs:
connection will start
connection did start
connection did change state, new: 2
Segmentation fault: 11
Commenting out the setting of identity results in the following logs:
connection will start
connection did start
connection did change state, new: 2
connection did change state, new: 3
connection did send
connection did receive bytes: HTTP/1.1 400 Bad Request
Server: nginx/1.10.3 (Ubuntu)
Date: Mon, 09 Sep 2024 21:57:00 GMT
Content-Type: text/html
Content-Length: 262
Connection: close
<html>
<head><title>400 No required SSL certificate was sent</title></head>
<body bgcolor="white">
<center><h1>400 Bad Request</h1></center>
<center>No required SSL certificate was sent</center>
<hr><center>nginx/1.10.3 (Ubuntu)</center>
</body>
</html>
I also tried setting the challenge block instead of setting the identity at which point the logs showed that the challenge block was hit, and then the segfault occurs at the point where complete(ident) is called.
sec_identity_create was definitely the key to the segfaults!
It looks like the identity is now being set properly. Unsure if this resolves ALL the issues on the identity end of establishing a mutual TLS handshake but it's got the vibe of a light at the end of the tunnel.
Immediately following I started attempting to setup a certificate authority for use as it appears Amazon's CA isn't installed on iOS by default. Running into a host of verification failures that I'm working through on that end but it feels like a separate problem from this one, which hopefully is resolved once I get through this CA thing.
Thanks for all your help! I'm rotating on-call for a week but will be back at this task afterwards. May end up opening some more "issues" in the future as I try to wrap up this task.