diff --git a/include/aws/io/private/pki_utils.h b/include/aws/io/private/pki_utils.h index 8a99038a6..7533f0317 100644 --- a/include/aws/io/private/pki_utils.h +++ b/include/aws/io/private/pki_utils.h @@ -40,9 +40,11 @@ AWS_IO_API int aws_decode_pem_to_buffer_list( /** * Decodes a PEM file at 'filename' and adds the results to 'cert_chain_or_key' if successful. - * Otherwise, 'cert_chain_or_key' will be empty. The type stored in 'cert_chain_or_key' - * is 'struct aws_byte_buf' by value. This code is slow, and it allocates, so please try - * not to call this in the middle of something that needs to be fast or resource sensitive. + * Otherwise, 'cert_chain_or_key' will be empty. + * The passed-in parameter 'cert_chain_or_key' should be empty and dynamically initialized array_list + * with item type 'struct aws_byte_buf' in value. + * This code is slow, and it allocates, so please try not to call this in the middle of + * something that needs to be fast or resource sensitive. */ AWS_IO_API int aws_read_and_decode_pem_file_to_buffer_list( struct aws_allocator *alloc, diff --git a/source/darwin/darwin_pki_utils.c b/source/darwin/darwin_pki_utils.c index d88223b47..d33f09b8d 100644 --- a/source/darwin/darwin_pki_utils.c +++ b/source/darwin/darwin_pki_utils.c @@ -18,6 +18,80 @@ static struct aws_mutex s_sec_mutex = AWS_MUTEX_INIT; #if !defined(AWS_OS_IOS) +/* + * Helper function to import ECC private key in PEM format into `import_keychain`. Return + * AWS_OP_SUCCESS if successfully imported a private key or find a duplicate key in the + * `import_keychain`, otherwise return AWS_OP_ERR. + * `private_key`: UTF-8 key data in PEM format. If the key file contains multiple key sections, + * the function will only import the first valid key. + * `import_keychain`: The keychain to be imported to. `import_keychain` should not be NULL. + */ +int aws_import_ecc_key_into_keychain( + struct aws_allocator *alloc, + CFAllocatorRef cf_alloc, + const struct aws_byte_cursor *private_key, + SecKeychainRef import_keychain) { + // Ensure imported_keychain is not NULL + AWS_PRECONDITION(import_keychain != NULL); + + int result = AWS_OP_ERR; + struct aws_array_list decoded_key_buffer_list; + /* Init empty array list, ideally, the PEM should only has one key included. */ + if (aws_array_list_init_dynamic(&decoded_key_buffer_list, alloc, 1, sizeof(struct aws_byte_buf))) { + return result; + } + + /* Decode PEM format file to DER format */ + if (aws_decode_pem_to_buffer_list(alloc, private_key, &decoded_key_buffer_list)) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: Failed to decode PEM private key to DER format."); + goto ecc_import_cleanup; + } + AWS_ASSERT(decoded_key_buffer_list); + + // A PEM file could contains multiple PEM data section. Try importing each PEM section until find the first + // succeed key. + for (size_t index = 0; index < aws_array_list_length(&decoded_key_buffer_list); index++) { + struct aws_byte_buf *decoded_key_buffer = NULL; + /* We only check the first pem section. Currently, we dont support key with multiple pem section. */ + aws_array_list_get_at_ptr(&decoded_key_buffer_list, (void **)&decoded_key_buffer, index); + AWS_ASSERT(decoded_key_buffer); + CFDataRef key_data = CFDataCreate(cf_alloc, decoded_key_buffer->buffer, decoded_key_buffer->len); + if (!key_data) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: error in creating ECC key data system call."); + continue; + } + + /* Import ECC key data into keychain. */ + SecExternalFormat format = kSecFormatOpenSSL; + SecExternalItemType item_type = kSecItemTypePrivateKey; + SecItemImportExportKeyParameters import_params; + AWS_ZERO_STRUCT(import_params); + import_params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION; + import_params.passphrase = CFSTR(""); + + OSStatus key_status = + SecItemImport(key_data, NULL, &format, &item_type, 0, &import_params, import_keychain, NULL); + + /* Clean up key buffer */ + CFRelease(key_data); + + // As long as we found an imported key, ignore the rest of keys + if (key_status == errSecSuccess || key_status == errSecDuplicateItem) { + result = AWS_OP_SUCCESS; + break; + } else { + // Log the error code for key importing + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: error importing ECC private key with OSStatus %d", (int)key_status); + } + } + +ecc_import_cleanup: + // Zero out the array list and release it + aws_cert_chain_clean_up(&decoded_key_buffer_list); + aws_array_list_clean_up(&decoded_key_buffer_list); + return result; +} + int aws_import_public_and_private_keys_to_identity( struct aws_allocator *alloc, CFAllocatorRef cf_alloc, @@ -104,10 +178,16 @@ int aws_import_public_and_private_keys_to_identity( goto done; } - if (key_status != errSecSuccess && key_status != errSecDuplicateItem) { - AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: error importing private key with OSStatus %d", (int)key_status); - result = aws_raise_error(AWS_IO_FILE_VALIDATION_FAILURE); - goto done; + /* + * If the key format is unknown, we tried to decode the key into DER format import it. + * The PEM file might contians multiple key sections, we will only add the first succeed key into the keychain. + */ + if (key_status == errSecUnknownFormat) { + AWS_LOGF_TRACE(AWS_LS_IO_PKI, "static: error reading private key format, try ECC key format."); + if (aws_import_ecc_key_into_keychain(alloc, cf_alloc, private_key, import_keychain)) { + result = aws_raise_error(AWS_IO_FILE_VALIDATION_FAILURE); + goto done; + } } /* if it's already there, just convert this over to a cert and then let the keychain give it back to us. */ @@ -180,7 +260,7 @@ int aws_import_public_and_private_keys_to_identity( return result; } -#endif /* AWS_OS_IOS */ +#endif /* !AWS_OS_IOS */ int aws_import_pkcs12_to_identity( CFAllocatorRef cf_alloc, diff --git a/source/pki_utils.c b/source/pki_utils.c index 47c9dec04..7da9dc9af 100644 --- a/source/pki_utils.c +++ b/source/pki_utils.c @@ -162,43 +162,39 @@ int aws_decode_pem_to_buffer_list( if (aws_base64_compute_decoded_len(&byte_cur, &decoded_len)) { aws_raise_error(AWS_IO_FILE_VALIDATION_FAILURE); - goto cleanup_output_due_to_error; + goto cleanup_all; } struct aws_byte_buf decoded_buffer; - if (aws_byte_buf_init(&decoded_buffer, alloc, decoded_len)) { - goto cleanup_output_due_to_error; + goto cleanup_all; } if (aws_base64_decode(&byte_cur, &decoded_buffer)) { aws_raise_error(AWS_IO_FILE_VALIDATION_FAILURE); - aws_byte_buf_clean_up(&decoded_buffer); - goto cleanup_output_due_to_error; + aws_byte_buf_clean_up_secure(&decoded_buffer); + goto cleanup_all; } if (aws_array_list_push_back(cert_chain_or_key, &decoded_buffer)) { - aws_byte_buf_clean_up(&decoded_buffer); - goto cleanup_output_due_to_error; + aws_byte_buf_clean_up_secure(&decoded_buffer); + goto cleanup_all; } } err_code = AWS_OP_SUCCESS; +cleanup_all: + if (err_code != AWS_OP_SUCCESS) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: Invalid PEM buffer."); + aws_cert_chain_clean_up(cert_chain_or_key); + } + cleanup_base64_buffer_list: aws_cert_chain_clean_up(&base_64_buffer_list); aws_array_list_clean_up(&base_64_buffer_list); return err_code; - -cleanup_output_due_to_error: - AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: Invalid PEM buffer."); - aws_cert_chain_clean_up(&base_64_buffer_list); - aws_array_list_clean_up(&base_64_buffer_list); - - aws_cert_chain_clean_up(cert_chain_or_key); - - return AWS_OP_ERR; } int aws_read_and_decode_pem_file_to_buffer_list(