diff --git a/CMakeLists.txt b/CMakeLists.txt index 4fd7a1f99..d41126742 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -182,6 +182,16 @@ foreach(EVENT_LOOP_DEFINE IN LISTS EVENT_LOOP_DEFINES) target_compile_definitions(${PROJECT_NAME} PUBLIC "-DAWS_ENABLE_${EVENT_LOOP_DEFINE}") endforeach() +if (AWS_USE_SECITEM) + target_compile_definitions(${PROJECT_NAME} PUBLIC "-DAWS_USE_SECITEM") +endif() + +# Darwin means Apple stationary operating systems, e.g. macOS +if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + # Target file-based keychain on macOS. + target_compile_definitions(${PROJECT_NAME} PUBLIC "-DAWS_SECITEM_FILEBASED_KEYCHAIN") +endif() + if (BYO_CRYPTO) target_compile_definitions(${PROJECT_NAME} PUBLIC "-DBYO_CRYPTO") endif() diff --git a/source/darwin/darwin_pki_utils.c b/source/darwin/darwin_pki_utils.c index 14a853722..390b6eae9 100644 --- a/source/darwin/darwin_pki_utils.c +++ b/source/darwin/darwin_pki_utils.c @@ -381,6 +381,10 @@ static int s_aws_secitem_add_certificate_to_keychain( CFDictionaryAddValue(add_attributes, kSecAttrSerialNumber, serial_data); CFDictionaryAddValue(add_attributes, kSecAttrLabel, label); CFDictionaryAddValue(add_attributes, kSecValueRef, cert_ref); +#ifdef AWS_SECITEM_FILEBASED_KEYCHAIN + /* Target file-based keychain instead of data protection keychain. */ + CFDictionaryAddValue(add_attributes, kSecUseDataProtectionKeychain, kCFBooleanFalse); +#endif // Initial attempt to add certificate to keychain. status = SecItemAdd(add_attributes, NULL); @@ -391,8 +395,9 @@ static int s_aws_secitem_add_certificate_to_keychain( case errSecMissingEntitlement: AWS_LOGF_ERROR( AWS_LS_IO_PKI, - "SecItemAdd certificate failed with OSStatus %d : errSecMissingEntitlement. The process - attempting " "to access the keychain is missing the necessary entitlements.", (int)status); + "SecItemAdd certificate failed with OSStatus %d : errSecMissingEntitlement. The process " + "attempting to access the keychain is missing the necessary entitlements.", + (int)status); break; default: AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemAdd certificate failed with OSStatus %d", (int)status); @@ -435,6 +440,10 @@ static int s_aws_secitem_add_certificate_to_keychain( CFDictionaryCreateMutable(cf_alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionaryAddValue(delete_query, kSecClass, kSecClassCertificate); CFDictionaryAddValue(delete_query, kSecAttrSerialNumber, serial_data); +#ifdef AWS_SECITEM_FILEBASED_KEYCHAIN + /* Target file-based keychain instead of data protection keychain. */ + CFDictionaryAddValue(delete_query, kSecUseDataProtectionKeychain, kCFBooleanFalse); +#endif // delete the existing certificate from keychain status = SecItemDelete(delete_query); @@ -483,6 +492,10 @@ static int s_aws_secitem_add_private_key_to_keychain( CFDictionaryAddValue(add_attributes, kSecAttrApplicationLabel, application_label); CFDictionaryAddValue(add_attributes, kSecAttrLabel, label); CFDictionaryAddValue(add_attributes, kSecValueRef, key_ref); +#ifdef AWS_SECITEM_FILEBASED_KEYCHAIN + /* Target file-based keychain instead of data protection keychain. */ + CFDictionaryAddValue(add_attributes, kSecUseDataProtectionKeychain, kCFBooleanFalse); +#endif // Initial attempt to add private key to keychain. status = SecItemAdd(add_attributes, NULL); @@ -535,7 +548,10 @@ static int s_aws_secitem_add_private_key_to_keychain( CFDictionaryAddValue(delete_query, kSecClass, kSecClassKey); CFDictionaryAddValue(delete_query, kSecAttrKeyClass, kSecAttrKeyClassPrivate); CFDictionaryAddValue(delete_query, kSecAttrApplicationLabel, application_label); - +#ifdef AWS_SECITEM_FILEBASED_KEYCHAIN + /* Target file-based keychain instead of data protection keychain. */ + CFDictionaryAddValue(delete_query, kSecUseDataProtectionKeychain, kCFBooleanFalse); +#endif // delete the existing private key from keychain status = SecItemDelete(delete_query); if (status != errSecSuccess) { @@ -565,12 +581,23 @@ static int s_aws_secitem_add_private_key_to_keychain( return result; } -static int s_aws_secitem_get_identity(CFAllocatorRef cf_alloc, CFDataRef serial_data, sec_identity_t *out_identity) { +static bool s_compare_serial_numbers(CFDataRef a, CFDataRef b) { + return CFDataGetLength(a) == CFDataGetLength(b) && + memcmp(CFDataGetBytePtr(a), CFDataGetBytePtr(b), CFDataGetLength(a)) == 0; +} + +static int s_aws_secitem_get_identity( + CFAllocatorRef cf_alloc, + CFDataRef serial_data, + SecCertificateRef cert_ref, + sec_identity_t *out_identity) { + + (void)cert_ref; int result = AWS_OP_ERR; OSStatus status; CFMutableDictionaryRef search_query = NULL; - SecIdentityRef sec_identity_ref = NULL; + CFArrayRef sec_identity_array = NULL; /* * SecItem identity is created when a certificate matches a private key in the keychain. @@ -584,11 +611,24 @@ static int s_aws_secitem_get_identity(CFAllocatorRef cf_alloc, CFDataRef serial_ CFDictionaryAddValue(search_query, kSecAttrSerialNumber, serial_data); CFDictionaryAddValue(search_query, kSecReturnRef, kCFBooleanTrue); +#ifdef AWS_SECITEM_FILEBASED_KEYCHAIN + /* Target file-based keychain instead of data protection keychain. */ + CFDictionaryAddValue(search_query, kSecUseDataProtectionKeychain, kCFBooleanFalse); + /* The kSecAttrSerialNumber filter attribute does not work for kSecClassIdentity when SecItem targets file-based + * keychain. So, use additional filtering by a certificate provided by a user. */ + CFArrayRef cert_filter = CFArrayCreate(cf_alloc, (const void **)&cert_ref, 1L, &kCFTypeArrayCallBacks); + CFDictionaryAddValue(search_query, kSecMatchItemList, cert_filter); +#endif + + /* Though the kSecAttrSerialNumber and kSecMatchItemList attributes filter out unmatching identities, request + * SecItemCopyMatching to return all results. */ + CFDictionaryAddValue(search_query, kSecMatchLimit, kSecMatchLimitAll); + /* * Copied or created CF items must have CFRelease called on them or you leak memory. This identity needs to * have CFRelease called on it at some point or it will leak. */ - status = SecItemCopyMatching(search_query, (CFTypeRef *)&sec_identity_ref); + status = SecItemCopyMatching(search_query, (CFTypeRef *)&sec_identity_array); if (status != errSecSuccess) { AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemCopyMatching identity failed with OSStatus %d", (int)status); @@ -596,22 +636,59 @@ static int s_aws_secitem_get_identity(CFAllocatorRef cf_alloc, CFDataRef serial_ goto done; } - *out_identity = sec_identity_create(sec_identity_ref); - if (*out_identity == NULL) { - AWS_LOGF_ERROR( - AWS_LS_IO_PKI, "sec_identity_create failed to create a sec_identity_t from provided SecIdentityRef."); - aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); - goto done; + CFIndex identity_num = CFArrayGetCount(sec_identity_array); + AWS_LOGF_DEBUG(AWS_LS_IO_PKI, "Found %d identities", (int)identity_num); + + /* With the kSecAttrSerialNumber and kSecMatchItemList filters in place, the following additional serial number + * verification is NOT needed. However, I'll keep it for now to be on the safe side. + * NOTE: Only public data is processed here, i.e. any logged user already has access to it without any additional + * verification. */ + for (CFIndex i = 0; i < identity_num; i++) { + /* The identity object here is basically two references: one to a cert and one to a private key. No actual + * data is retrieved from keychain yet. We can use special functions provided by Security framework to get them: + * SecIdentityCopyCertificate and SecIdentityCopyPrivateKey. + * Certificate data can be obtained with no additional verification since it's public. Private key data can be + * obtained only if the user has access to the keychain (i.e. either provide password into keychain's popup or + * add the app to a list of allowed in the Keychain Access app). + * Since we process only certificate here, no user verification will be requested in this function. Later, + * during establishing a TLS connection, user verification will be done when Network framework will try to + * retrieve private key data from the provided identity. That part remains the same. */ + const SecIdentityRef sec_identity_ref = (const SecIdentityRef)CFArrayGetValueAtIndex(sec_identity_array, i); + + SecCertificateRef found_cert = NULL; + OSStatus copy_cert_status = SecIdentityCopyCertificate(sec_identity_ref, &found_cert); + if (copy_cert_status != errSecSuccess) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecIdentityCopyCertificate failed with OSStatus %d", (int)copy_cert_status); + aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + CFDataRef found_cert_serial_data = SecCertificateCopySerialNumberData(found_cert, NULL); + + if (s_compare_serial_numbers(serial_data, found_cert_serial_data)) { + AWS_LOGF_TRACE(AWS_LS_IO_PKI, "Found a matching identity"); + *out_identity = sec_identity_create(sec_identity_ref); + if (*out_identity == NULL) { + AWS_LOGF_ERROR( + AWS_LS_IO_PKI, + "sec_identity_create failed to create a sec_identity_t from provided SecIdentityRef."); + aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + break; + } } AWS_LOGF_INFO(AWS_LS_IO_PKI, "static: Successfully retrieved identity from keychain."); - result = AWS_OP_SUCCESS; done: // cleanup aws_cf_release(search_query); - aws_cf_release(sec_identity_ref); + aws_cf_release(cert_filter); + // TODO Release elements. + aws_cf_release(sec_identity_array); return result; } @@ -774,6 +851,11 @@ int aws_secitem_import_cert_and_key( CFDictionaryCreateMutable(cf_alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionaryAddValue(key_attributes, kSecAttrKeyClass, kSecAttrKeyClassPrivate); CFDictionaryAddValue(key_attributes, kSecAttrKeyType, key_type); +#ifdef AWS_SECITEM_FILEBASED_KEYCHAIN + /* Target file-based keychain instead of data protection keychain. */ + CFDictionaryAddValue(key_attributes, kSecUseDataProtectionKeychain, kCFBooleanFalse); +#endif + key_ref = SecKeyCreateWithData(key_data, key_attributes, &error); // Get the hash of the public key stored within the private key by extracting it from the key_ref's attributes @@ -807,7 +889,7 @@ int aws_secitem_import_cert_and_key( goto done; } - if (s_aws_secitem_get_identity(cf_alloc, cert_serial_data, secitem_identity)) { + if (s_aws_secitem_get_identity(cf_alloc, cert_serial_data, cert_ref, secitem_identity)) { goto done; } diff --git a/source/s2n/s2n_tls_channel_handler.c b/source/s2n/s2n_tls_channel_handler.c index 2d5dccd83..e0ffa0962 100644 --- a/source/s2n/s2n_tls_channel_handler.c +++ b/source/s2n/s2n_tls_channel_handler.c @@ -1539,7 +1539,7 @@ static struct aws_tls_ctx *s_tls_ctx_new( security_policy = "AWS-CRT-SDK-TLSv1.2-2025"; break; case AWS_IO_TLS_CIPHER_PREF_TLSV1_0_2023_06: - security_policy = "AWS-CRT-SDK-TLSv1.2-2025"; + security_policy = "AWS-CRT-SDK-TLSv1.0-2023"; break; default: AWS_LOGF_ERROR(AWS_LS_IO_TLS, "Unrecognized TLS Cipher Preference: %d", options->cipher_pref);