Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,12 @@ 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()
Expand Down
107 changes: 94 additions & 13 deletions source/darwin/darwin_pki_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -435,6 +439,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);
Expand Down Expand Up @@ -483,6 +491,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);
Expand Down Expand Up @@ -535,7 +547,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) {
Expand Down Expand Up @@ -565,12 +580,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.
Expand All @@ -584,34 +610,84 @@ 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);
aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
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;
}
Expand Down Expand Up @@ -774,6 +850,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
Expand Down Expand Up @@ -808,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;
}

Expand Down