Skip to content

Commit b34351a

Browse files
committed
Use file-based keychains
1 parent 1af325b commit b34351a

File tree

2 files changed

+100
-13
lines changed

2 files changed

+100
-13
lines changed

CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,12 @@ if (AWS_USE_SECITEM)
188188
target_compile_definitions(${PROJECT_NAME} PUBLIC "-DAWS_USE_SECITEM")
189189
endif()
190190

191+
# Darwin means Apple stationary operating systems, e.g. macOS
192+
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
193+
# Target file-based keychain on macOS.
194+
target_compile_definitions(${PROJECT_NAME} PUBLIC "-DAWS_SECITEM_FILEBASED_KEYCHAIN")
195+
endif()
196+
191197
if (BYO_CRYPTO)
192198
target_compile_definitions(${PROJECT_NAME} PUBLIC "-DBYO_CRYPTO")
193199
endif()

source/darwin/darwin_pki_utils.c

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,10 @@ static int s_aws_secitem_add_certificate_to_keychain(
381381
CFDictionaryAddValue(add_attributes, kSecAttrSerialNumber, serial_data);
382382
CFDictionaryAddValue(add_attributes, kSecAttrLabel, label);
383383
CFDictionaryAddValue(add_attributes, kSecValueRef, cert_ref);
384+
#ifdef AWS_SECITEM_FILEBASED_KEYCHAIN
385+
/* Target file-based keychain instead of data protection keychain. */
386+
CFDictionaryAddValue(add_attributes, kSecUseDataProtectionKeychain, kCFBooleanFalse);
387+
#endif
384388

385389
// Initial attempt to add certificate to keychain.
386390
status = SecItemAdd(add_attributes, NULL);
@@ -435,6 +439,10 @@ static int s_aws_secitem_add_certificate_to_keychain(
435439
CFDictionaryCreateMutable(cf_alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
436440
CFDictionaryAddValue(delete_query, kSecClass, kSecClassCertificate);
437441
CFDictionaryAddValue(delete_query, kSecAttrSerialNumber, serial_data);
442+
#ifdef AWS_SECITEM_FILEBASED_KEYCHAIN
443+
/* Target file-based keychain instead of data protection keychain. */
444+
CFDictionaryAddValue(delete_query, kSecUseDataProtectionKeychain, kCFBooleanFalse);
445+
#endif
438446

439447
// delete the existing certificate from keychain
440448
status = SecItemDelete(delete_query);
@@ -483,6 +491,10 @@ static int s_aws_secitem_add_private_key_to_keychain(
483491
CFDictionaryAddValue(add_attributes, kSecAttrApplicationLabel, application_label);
484492
CFDictionaryAddValue(add_attributes, kSecAttrLabel, label);
485493
CFDictionaryAddValue(add_attributes, kSecValueRef, key_ref);
494+
#ifdef AWS_SECITEM_FILEBASED_KEYCHAIN
495+
/* Target file-based keychain instead of data protection keychain. */
496+
CFDictionaryAddValue(add_attributes, kSecUseDataProtectionKeychain, kCFBooleanFalse);
497+
#endif
486498

487499
// Initial attempt to add private key to keychain.
488500
status = SecItemAdd(add_attributes, NULL);
@@ -535,7 +547,10 @@ static int s_aws_secitem_add_private_key_to_keychain(
535547
CFDictionaryAddValue(delete_query, kSecClass, kSecClassKey);
536548
CFDictionaryAddValue(delete_query, kSecAttrKeyClass, kSecAttrKeyClassPrivate);
537549
CFDictionaryAddValue(delete_query, kSecAttrApplicationLabel, application_label);
538-
550+
#ifdef AWS_SECITEM_FILEBASED_KEYCHAIN
551+
/* Target file-based keychain instead of data protection keychain. */
552+
CFDictionaryAddValue(delete_query, kSecUseDataProtectionKeychain, kCFBooleanFalse);
553+
#endif
539554
// delete the existing private key from keychain
540555
status = SecItemDelete(delete_query);
541556
if (status != errSecSuccess) {
@@ -565,12 +580,23 @@ static int s_aws_secitem_add_private_key_to_keychain(
565580
return result;
566581
}
567582

568-
static int s_aws_secitem_get_identity(CFAllocatorRef cf_alloc, CFDataRef serial_data, sec_identity_t *out_identity) {
583+
static bool s_compare_serial_numbers(CFDataRef a, CFDataRef b) {
584+
return CFDataGetLength(a) == CFDataGetLength(b) &&
585+
memcmp(CFDataGetBytePtr(a), CFDataGetBytePtr(b), CFDataGetLength(a)) == 0;
586+
}
587+
588+
static int s_aws_secitem_get_identity(
589+
CFAllocatorRef cf_alloc,
590+
CFDataRef serial_data,
591+
SecCertificateRef cert_ref,
592+
sec_identity_t *out_identity) {
593+
594+
(void)cert_ref;
569595

570596
int result = AWS_OP_ERR;
571597
OSStatus status;
572598
CFMutableDictionaryRef search_query = NULL;
573-
SecIdentityRef sec_identity_ref = NULL;
599+
CFArrayRef sec_identity_array = NULL;
574600

575601
/*
576602
* SecItem identity is created when a certificate matches a private key in the keychain.
@@ -584,34 +610,84 @@ static int s_aws_secitem_get_identity(CFAllocatorRef cf_alloc, CFDataRef serial_
584610
CFDictionaryAddValue(search_query, kSecAttrSerialNumber, serial_data);
585611
CFDictionaryAddValue(search_query, kSecReturnRef, kCFBooleanTrue);
586612

613+
#ifdef AWS_SECITEM_FILEBASED_KEYCHAIN
614+
/* Target file-based keychain instead of data protection keychain. */
615+
CFDictionaryAddValue(search_query, kSecUseDataProtectionKeychain, kCFBooleanFalse);
616+
/* The kSecAttrSerialNumber filter attribute does not work for kSecClassIdentity when SecItem targets file-based
617+
* keychain. So, use additional filtering by a certificate provided by a user. */
618+
CFArrayRef cert_filter = CFArrayCreate(cf_alloc, (const void **)&cert_ref, 1L, &kCFTypeArrayCallBacks);
619+
CFDictionaryAddValue(search_query, kSecMatchItemList, cert_filter);
620+
#endif
621+
622+
/* Though the kSecAttrSerialNumber and kSecMatchItemList attributes filter out unmatching identities, request
623+
* SecItemCopyMatching to return all results. */
624+
CFDictionaryAddValue(search_query, kSecMatchLimit, kSecMatchLimitAll);
625+
587626
/*
588627
* Copied or created CF items must have CFRelease called on them or you leak memory. This identity needs to
589628
* have CFRelease called on it at some point or it will leak.
590629
*/
591-
status = SecItemCopyMatching(search_query, (CFTypeRef *)&sec_identity_ref);
630+
status = SecItemCopyMatching(search_query, (CFTypeRef *)&sec_identity_array);
592631

593632
if (status != errSecSuccess) {
594633
AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemCopyMatching identity failed with OSStatus %d", (int)status);
595634
aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
596635
goto done;
597636
}
598637

599-
*out_identity = sec_identity_create(sec_identity_ref);
600-
if (*out_identity == NULL) {
601-
AWS_LOGF_ERROR(
602-
AWS_LS_IO_PKI, "sec_identity_create failed to create a sec_identity_t from provided SecIdentityRef.");
603-
aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
604-
goto done;
638+
CFIndex identity_num = CFArrayGetCount(sec_identity_array);
639+
AWS_LOGF_DEBUG(AWS_LS_IO_PKI, "Found %d identities", (int)identity_num);
640+
641+
/* With the kSecAttrSerialNumber and kSecMatchItemList filters in place, the following additional serial number
642+
* verification is NOT needed. However, I'll keep it for now to be on the safe side.
643+
* NOTE: Only public data is processed here, i.e. any logged user already has access to it without any additional
644+
* verification. */
645+
for (CFIndex i = 0; i < identity_num; i++) {
646+
/* The identity object here is basically two references: one to a cert and one to a private key. No actual
647+
* data is retrieved from keychain yet. We can use special functions provided by Security framework to get them:
648+
* SecIdentityCopyCertificate and SecIdentityCopyPrivateKey.
649+
* Certificate data can be obtained with no additional verification since it's public. Private key data can be
650+
* obtained only if the user has access to the keychain (i.e. either provide password into keychain's popup or
651+
* add the app to a list of allowed in the Keychain Access app).
652+
* Since we process only certificate here, no user verification will be requested in this function. Later,
653+
* during establishing a TLS connection, user verification will be done when Network framework will try to retrieve
654+
* private key data from the provided identity. That part remains the same. */
655+
const SecIdentityRef sec_identity_ref = (const SecIdentityRef)CFArrayGetValueAtIndex(sec_identity_array, i);
656+
657+
SecCertificateRef found_cert = NULL;
658+
OSStatus copy_cert_status = SecIdentityCopyCertificate(sec_identity_ref, &found_cert);
659+
if (copy_cert_status != errSecSuccess) {
660+
AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecIdentityCopyCertificate failed with OSStatus %d", (int)copy_cert_status);
661+
aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
662+
goto done;
663+
}
664+
665+
CFDataRef found_cert_serial_data = SecCertificateCopySerialNumberData(found_cert, NULL);
666+
667+
if (s_compare_serial_numbers(serial_data, found_cert_serial_data)) {
668+
AWS_LOGF_TRACE(AWS_LS_IO_PKI, "Found a matching identity");
669+
*out_identity = sec_identity_create(sec_identity_ref);
670+
if (*out_identity == NULL) {
671+
AWS_LOGF_ERROR(
672+
AWS_LS_IO_PKI,
673+
"sec_identity_create failed to create a sec_identity_t from provided SecIdentityRef.");
674+
aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
675+
goto done;
676+
}
677+
678+
break;
679+
}
605680
}
606681

607682
AWS_LOGF_INFO(AWS_LS_IO_PKI, "static: Successfully retrieved identity from keychain.");
608-
609683
result = AWS_OP_SUCCESS;
610684

611685
done:
612686
// cleanup
613687
aws_cf_release(search_query);
614-
aws_cf_release(sec_identity_ref);
688+
aws_cf_release(cert_filter);
689+
// TODO Release elements.
690+
aws_cf_release(sec_identity_array);
615691

616692
return result;
617693
}
@@ -774,6 +850,11 @@ int aws_secitem_import_cert_and_key(
774850
CFDictionaryCreateMutable(cf_alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
775851
CFDictionaryAddValue(key_attributes, kSecAttrKeyClass, kSecAttrKeyClassPrivate);
776852
CFDictionaryAddValue(key_attributes, kSecAttrKeyType, key_type);
853+
#ifdef AWS_SECITEM_FILEBASED_KEYCHAIN
854+
/* Target file-based keychain instead of data protection keychain. */
855+
CFDictionaryAddValue(key_attributes, kSecUseDataProtectionKeychain, kCFBooleanFalse);
856+
#endif
857+
777858
key_ref = SecKeyCreateWithData(key_data, key_attributes, &error);
778859

779860
// Get the hash of the public key stored within the private key by extracting it from the key_ref's attributes
@@ -808,7 +889,7 @@ int aws_secitem_import_cert_and_key(
808889
goto done;
809890
}
810891

811-
if (s_aws_secitem_get_identity(cf_alloc, cert_serial_data, secitem_identity)) {
892+
if (s_aws_secitem_get_identity(cf_alloc, cert_serial_data, cert_ref, secitem_identity)) {
812893
goto done;
813894
}
814895

0 commit comments

Comments
 (0)