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.