diff --git a/include/aws/io/private/pki_utils.h b/include/aws/io/private/pki_utils.h index af0465560..9c25c7266 100644 --- a/include/aws/io/private/pki_utils.h +++ b/include/aws/io/private/pki_utils.h @@ -15,6 +15,8 @@ #ifdef AWS_OS_APPLE /* It's ok to include external headers because this is a PRIVATE header file */ # include +#include +struct aws_secitem_options; #endif /* AWS_OS_APPLE */ struct aws_string; @@ -41,7 +43,21 @@ int aws_import_public_and_private_keys_to_identity( const struct aws_byte_cursor *private_key, CFArrayRef *identity, const struct aws_string *keychain_path); -# endif /* AWS_OS_IOS */ +# endif /* !AWS_OS_IOS */ + +/** + * Imports a PEM armored PKCS#7 public/private key pair + * into protected data keychain for use with Apple Network Framework. + * Currently only implemented for iOS. + */ +int aws_secitem_import_cert_and_key( + struct aws_allocator *alloc, + CFAllocatorRef cf_alloc, + const struct aws_byte_cursor *public_cert_chain, + const struct aws_byte_cursor *private_key, + SecCertificateRef *secitem_certificate, + SecKeyRef *secitem_private_key, + const struct aws_secitem_options *secitem_options); /** * Imports a PKCS#12 file into identity for use with diff --git a/include/aws/io/tls_channel_handler.h b/include/aws/io/tls_channel_handler.h index f44335b12..18310bd5b 100644 --- a/include/aws/io/tls_channel_handler.h +++ b/include/aws/io/tls_channel_handler.h @@ -146,6 +146,37 @@ struct aws_tls_connection_options { */ struct aws_tls_key_operation; +#ifdef __APPLE__ + +/** + * A struct containing parameters used during import of Certificate and Private Key into a + * data protection keychain using Apple's SecItem API. + */ +struct aws_secitem_options { + /** + * Human-Readable identifier tag for certificate being used in keychain. + * Value will be used with kSecAttrLabel Key in SecItem functions. + * If one is not provided, we generate it ourselves. + */ + struct aws_string *cert_label; + + /** + * Human-Readable identifier tag for private key being used in keychain. + * Value will be used with kSecAttrLabel Key in SecItem functions. + * If one is not provided, we generate it ourselves. + */ + struct aws_string *key_label; + + /** + * Human-Readable unique identifier tag for private key being used in keychain. + * Value will be used with kSecAttrLabel Key in SecItem functions. + * If one is not provided, we generate it ourselves. + */ + struct aws_string *application_label; +}; + +#endif /* __APPLE__ */ + struct aws_tls_ctx_options { struct aws_allocator *allocator; @@ -214,15 +245,24 @@ struct aws_tls_ctx_options { */ struct aws_byte_buf pkcs12_password; -# if !defined(AWS_OS_IOS) + /** + * When adding items to the keychain, SecItem allows the setting of attributes + * that control various options and settings related to access of the items. This + * struct contains the various attributes we currently support. + */ + struct aws_secitem_options *secitem_options; + +# if !defined(AWS_OS_IOS) /** * On Apple OS you can also use a custom keychain instead of * the default keychain of the account. */ struct aws_string *keychain_path; -# endif -#endif +# endif /* !AWS_OS_IOS */ + + +#endif /* __APPLE__ */ /** max tls fragment size. Default is the value of g_aws_channel_max_fragment_size. */ size_t max_fragment_size; diff --git a/source/darwin/darwin_pki_utils.c b/source/darwin/darwin_pki_utils.c index 20fcb82e0..687ab0fce 100644 --- a/source/darwin/darwin_pki_utils.c +++ b/source/darwin/darwin_pki_utils.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -382,3 +383,554 @@ void aws_release_identity(CFArrayRef identity) { void aws_release_certificates(CFArrayRef certs) { CFRelease(certs); } + +/* + * Apple's Network framework and SecItem API use of the data protection keychain is currently only implemented + * on iOS. We may add support for macOS at a later date. + * + * macOS migration from currently deprecated Secure Transport API and file based keychain to + * Network framework will require it we also migrate from BSD Sockets to Apple's Network Framework. + * + * From a breaking existing users perspective, we must also find a way to continue support for the + * keychain_path field which is currently only bound out to aws-crt-cpp. + */ + +int aws_secitem_add_certificate_to_keychain( + CFAllocatorRef cf_alloc, + CFDataRef cert_data, + CFDataRef serial_data, + CFStringRef label, + SecCertificateRef *out_certificate) { + + int result = AWS_OP_ERR; + OSStatus status; + + CFDictionaryRef attributes = NULL; + CFDictionaryRef update_query = NULL; + CFDictionaryRef update_attributes = NULL; + CFDictionaryRef copy_query = NULL; + + // We first attempt to add the certificate with all set attributes to the keychain. + const void *add_keys[] = { + kSecClass, + kSecAttrLabel, + kSecAttrSerialNumber, + kSecValueData, + kSecReturnRef }; + const void *add_values[] = { + kSecClassCertificate, + label, + serial_data, + cert_data, + kCFBooleanTrue }; + attributes = CFDictionaryCreate( + cf_alloc, + add_keys, + add_values, + 5, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + if (attributes == NULL) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed creating CFDictionary of certificate attributes."); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + status = SecItemAdd(attributes, (CFTypeRef *)out_certificate); + + // We only handle a duplicate item error. All other errors fail the operation. + if (status != errSecSuccess && status != errSecDuplicateItem) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemAdd certificate failed with OSStatus %d", (int)status); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + // A duplicate item error suggests there is already a certificate that matches some or all of the + // attributes used when attempting to add the certificate to the keychain. + if (status == errSecDuplicateItem) { + AWS_LOGF_INFO( + AWS_LS_IO_PKI, + "static: keychain contains existing certificate that was previously imported into the Keychain. " + "Updating certificate in keychain."); + + /* + * query should be made up of primary keys only. Optional/non-unique attributes in the query + * can result in not finding the matching certificate and cause the update operation to fail. + * + * Certificate item primary keys we use for the query: + * kSecAttrSerialNumber: (CFStringRef) value indicates the item's serial number + * - We explicity set this value, extracted from the certificate itself as our primary method of determining uniqueness + * of the certificate. + * + * Certificate primary keys we do not use for the query: + * These can be added in the future if we require a more specified search query. + * kSecAttrCertificateType: (CFNumberRef) value indicates the item's certificate type + * - values see the CSSM_CERT_TYPE enumeration in cssmtype.h https://opensource.apple.com/source/Security/Security-55471/libsecurity_cssm/lib/cssmtype.h.auto.html + * - default will try to add common value such as X.509. We do not pass this attribute and allow default value to be used. + * If we decide to support other types of certificates, we should set and use this value explicitly. + * kSecAttrIssuer: (CFStringRef) value indicates the item's issuer + * - default will try to extract issuer from the certificate itself. + * We will not set this attribute and allow default value to be used. + */ + const void *update_query_keys[] = { + kSecClass, + kSecAttrSerialNumber }; + const void *update_query_values[] = { + kSecClassCertificate, + serial_data }; + update_query = CFDictionaryCreate( + cf_alloc, + update_query_keys, + update_query_values, + 2, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + // update_attributes should only contain non-unique attributes. All of these attributes will be + // updated to the latest provided or default settings along with the certificate data itself. + const void *update_keys[] = { + kSecValueData, + kSecAttrLabel }; + const void *update_values[] = { + cert_data, + label }; + update_attributes = CFDictionaryCreate( + cf_alloc, + update_keys, + update_values, + 2, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + if (update_attributes == NULL || update_query == NULL) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed during update dictionary creation of certificate."); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + // Update the existing certificate item + status = SecItemUpdate(update_query, update_attributes); + if (status != errSecSuccess) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemUpdate certificate failed with OSStatus %d", (int)status); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + // We now set out_certificate to the newly updated certificate in the keychain. The search query for + // the item will be the same we used during the update which only contains unique identifiers for the + // certificate. + const void *copy_query_keys[] = { + kSecClass, + kSecAttrSerialNumber, + kSecReturnRef }; + const void *copy_query_values[] = { + kSecClassCertificate, + serial_data, + kCFBooleanTrue }; + copy_query = CFDictionaryCreate( + cf_alloc, + copy_query_keys, + copy_query_values, + 3, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + if (copy_query == NULL) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed during copy dictionary creation of certificate."); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + status = SecItemCopyMatching(copy_query, (CFTypeRef *)out_certificate); + if (status != errSecSuccess){ + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemCopyMatching certificate failed with OSStatus %d", (int)status); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + } + + result = AWS_OP_SUCCESS; + +done: + // cleanup + if (attributes) CFRelease(attributes); + if (update_query) CFRelease(update_query); + if (update_attributes) CFRelease(update_attributes); + if (copy_query) CFRelease(copy_query); + + return result; +} + +int aws_secitem_add_private_key_to_keychain( + CFAllocatorRef cf_alloc, + CFDataRef key_data, + CFStringRef key_type, + CFStringRef label, + CFStringRef application_label, + SecKeyRef *out_private_key) { + + int result = AWS_OP_ERR; + OSStatus status; + + CFDictionaryRef attributes = NULL; + CFDictionaryRef update_query = NULL; + CFDictionaryRef update_attributes = NULL; + CFDictionaryRef copy_query = NULL; + + // We first attempt to add the private key with all set attributes to the keychain. + const void *add_keys[] = { + kSecClass, + kSecAttrKeyClass, + kSecAttrKeyType, + kSecAttrApplicationLabel, + kSecAttrLabel, + kSecValueData, + kSecReturnRef }; + const void *add_values[] = { + kSecClassKey, + kSecAttrKeyClassPrivate, + key_type, + application_label, + label, + key_data, + kCFBooleanTrue }; + attributes = CFDictionaryCreate( + cf_alloc, + add_keys, + add_values, + 7, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + if (attributes == NULL) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed creating CFDictionary of private key attributes."); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + status = SecItemAdd(attributes, (CFTypeRef *)out_private_key); + + // We only handle a duplicate item error. All other errors fail the operation. + if (status != errSecSuccess && status != errSecDuplicateItem) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemAdd private key failed with OSStatus %d", (int)status); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + // A duplicate item error suggests there is already a private key that matches some or all of the + // attributes used when attempting to add the private key to the keychain. + if (status == errSecDuplicateItem) { + AWS_LOGF_INFO( + AWS_LS_IO_PKI, + "static: keychain contains existing private key that was previously imported into the Keychain. " + "Updating private key in keychain."); + + /* + * query should be made up of primary keys only. Optional/non-unique attributes in the query + * can result in not finding the matching private key and cause the update operation to fail. + * + * Private Key item primary keys we use for the query: + * kSecAttrKeyType: (CFNumberRef) value indicates the item's algorithm. + * - supported algorithms: kSecAttrKeyTypeRSA, kSecAttrKeyTypeEC + * kSecAttrKeyClass: (CFTypeRef) value indicates item's cryptographic key class + * - We explicitly set this value to kSecAttrKeyClassPrivate + * kSecAttrApplicationLabel: (CFStringRef) value indicates item's application label. + * - We currently set this to a default value but can expose it to be user defined in the future. + * + * Private Key primary keys we do not use for the query: + * These can be added in the future if we require a more specified search query. + * kSecAttrApplicationTag: (CFDataRef) value indicates the item's private tag. + * kSecAttrKeySizeInBits: (CFNumberRef) value indicates the number of bits in a cryptographic key. + * kSecAttrEffectiveKeySize: (CFNumberRef) value indicates the effective number of bits in a crytographic key. + */ + const void *update_query_keys[] = { + kSecClass, + kSecAttrKeyClass, + kSecAttrKeyType, + kSecAttrApplicationLabel }; + const void *update_query_values[] = { + kSecClassKey, + kSecAttrKeyClassPrivate, + key_type, + application_label }; + update_query = CFDictionaryCreate( + cf_alloc, + update_query_keys, + update_query_values, + 4, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + // update_attributes should only contain non-unique attributes. All of these attributes will be + // updated to the latest provided or default settings along with the private key data itself. + const void *update_keys[] = { + kSecValueData, + kSecAttrLabel }; + const void *update_values[] = { + key_data, + label }; + update_attributes = CFDictionaryCreate( + cf_alloc, + update_keys, + update_values, + 2, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + if (update_attributes == NULL || update_query == NULL) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed during update dictionary creation of private key."); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + // Update the existing private key item + status = SecItemUpdate(update_query, update_attributes); + if (status != errSecSuccess) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemUpdate certificate failed with OSStatus %d", (int)status); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + // We now set out_private_key to the newly updated certificate in the keychain. The search query for + // the item will be the same we used during the update which only contains unique identifiers for the + // private key. + const void *copy_query_keys[] = { + kSecClass, + kSecAttrKeyClass, + kSecAttrKeyType, + kSecAttrApplicationLabel, + kSecReturnRef }; + const void *copy_query_values[] = { + kSecClassKey, + kSecAttrKeyClassPrivate, + key_type, + application_label, + kCFBooleanTrue }; + copy_query = CFDictionaryCreate( + cf_alloc, + copy_query_keys, + copy_query_values, + 4, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + if (copy_query == NULL) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed during copy dictionary creation of certificate."); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + status = SecItemCopyMatching(copy_query, (CFTypeRef *)out_private_key); + if (status != errSecSuccess){ + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemCopyMatching private key failed with OSStatus %d", (int)status); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + } + + result = AWS_OP_SUCCESS; +done: + // cleanup + if (attributes) CFRelease(attributes); + if (update_query) CFRelease(update_query); + if (update_attributes) CFRelease(update_attributes); + if (copy_query) CFRelease(copy_query); + + return result; +} + +int aws_secitem_import_cert_and_key( + struct aws_allocator *alloc, + CFAllocatorRef cf_alloc, + const struct aws_byte_cursor *public_cert_chain, + const struct aws_byte_cursor *private_key, + SecCertificateRef *secitem_certificate, + SecKeyRef *secitem_private_key, + const struct aws_secitem_options *secitem_options) { + + // We currently only support Apple's network framework and SecItem keychain API on iOS. + #if !defined(AWS_OS_IOS) + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: Secitem not supported on this platform."); + return aws_raise_error(AWS_ERROR_PLATFORM_NOT_SUPPORTED); + #endif /* !AWS_OS_IOS */ + + AWS_PRECONDITION(public_cert_chain != NULL); + AWS_PRECONDITION(private_key != NULL); + + int result = AWS_OP_ERR; + + CFErrorRef error = NULL; + CFDataRef cert_data = NULL; + SecCertificateRef cert_ref = NULL; + CFDataRef cert_serial_data = NULL; + + CFDataRef key_data = NULL; + CFStringRef key_type = NULL; + CFStringRef cert_label_ref = NULL; + CFStringRef key_label_ref = NULL; + CFStringRef application_label_ref = NULL; + struct aws_array_list decoded_cert_buffer_list; + AWS_ZERO_STRUCT(decoded_cert_buffer_list); + struct aws_array_list decoded_key_buffer_list; + AWS_ZERO_STRUCT(decoded_key_buffer_list); + + // STEVE DEBUG not implemented yet + CFDataRef root_cert_data = NULL; + + // iOS SecItem requires DER encoded files so we first convert the provided PEM encoded cert and key + // into a list of aws_pem_object that strips headers/footers and Base64 decodes the data into a byte buf. + if (aws_pem_objects_init_from_file_contents(&decoded_cert_buffer_list, alloc, *public_cert_chain)) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: Failed to decode PEM certificate to DER format."); + goto done; + } + AWS_ASSERT(aws_array_list_is_valid(&decoded_cert_buffer_list)); + + if (aws_pem_objects_init_from_file_contents(&decoded_key_buffer_list, alloc, *private_key)) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: Failed to decode PEM certificate to DER format."); + goto done; + } + AWS_ASSERT(aws_array_list_is_valid(&decoded_key_buffer_list)); + + // A PEM certificate file could contains multiple PEM data sections. We currently decode and use the first + // certificate data only. Certificate chaining support could be added for iOS in the future. + if (aws_array_list_length(&decoded_cert_buffer_list) > 1) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Certificate chains not currently supported on iOS."); + result = aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + goto done; + } + + // The aws_pem_object preserves the type of encoding found in the PEM file. We can use the type_string + // member to set the appropriate attribute on storage. + struct aws_pem_object *pem_cert_ptr = NULL; + aws_array_list_get_at_ptr(&decoded_cert_buffer_list, (void **)&pem_cert_ptr, 0); + AWS_ASSERT(pem_cert_ptr); + + struct aws_pem_object *pem_key_ptr = NULL; + aws_array_list_get_at_ptr(&decoded_key_buffer_list, (void **)&pem_key_ptr, 0); + AWS_ASSERT(pem_key_ptr); + + // CFDataRef is the expected format from SecItem API for storing or updating items on the keychain + cert_data = CFDataCreate(cf_alloc, pem_cert_ptr->data.buffer, pem_cert_ptr->data.len); + if (!cert_data) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Error creating certificate data system call."); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + key_data = CFDataCreate(cf_alloc, pem_key_ptr->data.buffer, pem_key_ptr->data.len); + if (!key_data) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Error creating private key data system call."); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + // Set the format of the key + switch(pem_key_ptr->type) { + case AWS_PEM_TYPE_PRIVATE_RSA_PKCS1: + key_type = kSecAttrKeyTypeRSA; + break; + + case AWS_PEM_TYPE_EC_PRIVATE: + key_type = kSecAttrKeyTypeEC; + break; + + default: + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Unsupported private key format."); + result = aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + goto done; + } + + // Attributes + + // We create a SecCertificateRef here to extract the serial number for use as a + // unique identifier when storing and updating a certificate in the keychain. + cert_ref = SecCertificateCreateWithData(cf_alloc, cert_data); + if (!cert_ref) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed creating SecCertificateRef from cert_data."); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + cert_serial_data = SecCertificateCopySerialNumberData(cert_ref, &error); + if (error) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed extracting serial number data from cert_ref."); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + cert_label_ref = CFStringCreateWithBytes( + cf_alloc, + (const UInt8 *)aws_string_bytes(secitem_options->cert_label), + secitem_options->cert_label->len, + kCFStringEncodingUTF8, + false); + if (!cert_label_ref) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed creating certificate label."); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + key_label_ref = CFStringCreateWithBytes( + cf_alloc, + (const UInt8 *)aws_string_bytes(secitem_options->key_label), + secitem_options->key_label->len, + kCFStringEncodingUTF8, + false); + if (!key_label_ref) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed creating private key label."); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + application_label_ref = CFStringCreateWithBytes( + cf_alloc, + (const UInt8 *)aws_string_bytes(secitem_options->application_label), + secitem_options->application_label->len, + kCFStringEncodingUTF8, + false); + if (!application_label_ref) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed creating private key application label."); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + // Add certificate and key to keychain +#if !defined(AWS_OS_IOS) + aws_mutex_lock(&s_sec_mutex); +#endif /* !AWS_OS_IOS */ + + if (aws_secitem_add_certificate_to_keychain( + cf_alloc, + cert_data, + cert_serial_data, + cert_label_ref, + secitem_certificate)) { + goto done; + } + + if (aws_secitem_add_private_key_to_keychain( + cf_alloc, key_data, key_type, + key_label_ref, + application_label_ref, + secitem_private_key)) { + goto done; + } + + result = AWS_OP_SUCCESS; + +done: +#if !defined(AWS_OS_IOS) + aws_mutex_unlock(&s_sec_mutex); +#endif /* !AWS_OS_IOS */ + + //cleanup + if (error != NULL) CFRelease(error); + if (cert_data != NULL) CFRelease(cert_data); + if (cert_ref != NULL) CFRelease(cert_ref); + if (cert_serial_data != NULL) CFRelease(cert_serial_data); + if (key_data != NULL) CFRelease(key_data); + if (key_type != NULL) CFRelease(key_type); + if (cert_label_ref) CFRelease(cert_label_ref); + if (key_label_ref) CFRelease(key_label_ref); + if (application_label_ref) CFRelease(application_label_ref); + if (root_cert_data != NULL) CFRelease(root_cert_data); + + // Zero out the array list and release it + aws_pem_objects_clean_up(&decoded_cert_buffer_list); + aws_pem_objects_clean_up(&decoded_key_buffer_list); + + return result; +} diff --git a/source/darwin/secure_transport_tls_channel_handler.c b/source/darwin/secure_transport_tls_channel_handler.c index 7ab3d0ac5..15683a3e7 100644 --- a/source/darwin/secure_transport_tls_channel_handler.c +++ b/source/darwin/secure_transport_tls_channel_handler.c @@ -24,6 +24,8 @@ #include #include +#include + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-variable" #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -779,6 +781,8 @@ struct secure_transport_ctx { struct aws_tls_ctx ctx; CFAllocatorRef wrapped_allocator; CFArrayRef certs; + SecCertificateRef secitem_certificate; + SecKeyRef secitem_private_key; CFArrayRef ca_cert; enum aws_tls_versions minimum_version; struct aws_string *alpn_list; @@ -959,6 +963,8 @@ static void s_aws_secure_transport_ctx_destroy(struct secure_transport_ctx *secu if (secure_transport_ctx->certs) { aws_release_identity(secure_transport_ctx->certs); } + if (secure_transport_ctx->secitem_certificate) CFRelease(secure_transport_ctx->secitem_certificate); + if (secure_transport_ctx->secitem_private_key) CFRelease(secure_transport_ctx->secitem_private_key); if (secure_transport_ctx->ca_cert) { aws_release_certificates(secure_transport_ctx->ca_cert); @@ -974,9 +980,6 @@ static void s_aws_secure_transport_ctx_destroy(struct secure_transport_ctx *secu static struct aws_tls_ctx *s_tls_ctx_new(struct aws_allocator *alloc, const struct aws_tls_ctx_options *options) { struct secure_transport_ctx *secure_transport_ctx = aws_mem_calloc(alloc, 1, sizeof(struct secure_transport_ctx)); - if (!secure_transport_ctx) { - return NULL; - } if (!aws_tls_is_cipher_pref_supported(options->cipher_pref)) { aws_raise_error(AWS_IO_TLS_CIPHER_PREF_UNSUPPORTED); @@ -1002,6 +1005,8 @@ static struct aws_tls_ctx *s_tls_ctx_new(struct aws_allocator *alloc, const stru secure_transport_ctx->veriify_peer = options->verify_peer; secure_transport_ctx->ca_cert = NULL; secure_transport_ctx->certs = NULL; + secure_transport_ctx->secitem_certificate = NULL; + secure_transport_ctx->secitem_private_key = NULL; secure_transport_ctx->ctx.alloc = alloc; secure_transport_ctx->ctx.impl = secure_transport_ctx; aws_ref_count_init( @@ -1010,7 +1015,6 @@ static struct aws_tls_ctx *s_tls_ctx_new(struct aws_allocator *alloc, const stru (aws_simple_completion_callback *)s_aws_secure_transport_ctx_destroy); if (aws_tls_options_buf_is_set(&options->certificate) && aws_tls_options_buf_is_set(&options->private_key)) { -#if !defined(AWS_OS_IOS) AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "static: certificate and key have been set, setting them up now."); if (!aws_text_is_utf8(options->certificate.buffer, options->certificate.len)) { @@ -1027,6 +1031,8 @@ static struct aws_tls_ctx *s_tls_ctx_new(struct aws_allocator *alloc, const stru struct aws_byte_cursor cert_chain_cur = aws_byte_cursor_from_buf(&options->certificate); struct aws_byte_cursor private_key_cur = aws_byte_cursor_from_buf(&options->private_key); + +#if !defined(AWS_OS_IOS) if (aws_import_public_and_private_keys_to_identity( alloc, secure_transport_ctx->wrapped_allocator, @@ -1038,7 +1044,24 @@ static struct aws_tls_ctx *s_tls_ctx_new(struct aws_allocator *alloc, const stru AWS_LS_IO_TLS, "static: failed to import certificate and private key with error %d.", aws_last_error()); goto cleanup_wrapped_allocator; } -#endif +#endif /* !AWS_OS_IOS */ +#if defined(AWS_OS_IOS) + + if (aws_secitem_import_cert_and_key( + alloc, + secure_transport_ctx->wrapped_allocator, + &cert_chain_cur, + &private_key_cur, + &secure_transport_ctx->secitem_certificate, + &secure_transport_ctx->secitem_private_key, + options->secitem_options)) { + AWS_LOGF_ERROR( + AWS_LS_IO_TLS, "static: failed to import certificate and private key with error %d.", aws_last_error()); + goto cleanup_wrapped_allocator; + } + +#endif /* AWS_OS_IOS */ + } else if (aws_tls_options_buf_is_set(&options->pkcs12)) { AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "static: a pkcs$12 certificate and key has been set, setting it up now."); @@ -1090,3 +1113,19 @@ struct aws_tls_ctx *aws_tls_client_ctx_new(struct aws_allocator *alloc, const st } #pragma clang diagnostic pop + +/* + * Apple Network Framework TLS Implementation. + * Currently only supports iOS platforms. + */ + +static int wip_apple_network_tls_implementation(struct aws_tls_connection_options *options) { + int result = AWS_OP_ERR; + + nw_endpoint_t endpoint = nw_endpoint_create_host(options->server_name, "8883"); + nw_parameters_t parameters = nw_parameters_create_secure_tcp(NW_PARAMETERS_DEFAULT_CONFIGURATION, NW_PARAMETERS_DEFAULT_CONFIGURATION); + + nw_protocol_options_t tls_options = nw_tls_create_options(); + + return result; +} diff --git a/source/tls_channel_handler.c b/source/tls_channel_handler.c index 5c6426872..12cc47770 100644 --- a/source/tls_channel_handler.c +++ b/source/tls_channel_handler.c @@ -24,6 +24,15 @@ void aws_tls_ctx_options_init_default_client(struct aws_tls_ctx_options *options options->cipher_pref = AWS_IO_TLS_CIPHER_PREF_SYSTEM_DEFAULT; options->verify_peer = true; options->max_fragment_size = g_aws_channel_max_fragment_size; + + #ifdef __APPLE__ + + options->secitem_options = aws_mem_calloc(allocator, 1, sizeof(struct aws_secitem_options)); + options->secitem_options->cert_label = aws_string_new_from_c_str(allocator, "aws-crt-default-certificate-label"); + options->secitem_options->key_label = aws_string_new_from_c_str(allocator, "aws-crt-default-key-label"); + options->secitem_options->application_label = aws_string_new_from_c_str(allocator, "aws-crt-default-application-label"); + + #endif /* __APPLE__ */ } void aws_tls_ctx_options_clean_up(struct aws_tls_ctx_options *options) { @@ -33,13 +42,20 @@ void aws_tls_ctx_options_clean_up(struct aws_tls_ctx_options *options) { aws_byte_buf_clean_up_secure(&options->private_key); #ifdef __APPLE__ + aws_byte_buf_clean_up_secure(&options->pkcs12); aws_byte_buf_clean_up_secure(&options->pkcs12_password); -# if !defined(AWS_OS_IOS) + aws_string_destroy(options->secitem_options->cert_label); + aws_string_destroy(options->secitem_options->key_label); + aws_string_destroy(options->secitem_options->application_label); + aws_mem_release(options->allocator, options->secitem_options); + +# if !defined(AWS_OS_IOS) aws_string_destroy(options->keychain_path); -# endif -#endif +# endif /* !AWS_OS_IOS */ + +#endif /* __APPLE__ */ aws_string_destroy(options->alpn_list); aws_custom_key_op_handler_release(options->custom_key_op_handler); @@ -53,8 +69,6 @@ int aws_tls_ctx_options_init_client_mtls( const struct aws_byte_cursor *cert, const struct aws_byte_cursor *pkey) { -#if !defined(AWS_OS_IOS) - aws_tls_ctx_options_init_default_client(options, allocator); if (aws_byte_buf_init_copy_from_cursor(&options->certificate, allocator, *cert)) { @@ -79,15 +93,6 @@ int aws_tls_ctx_options_init_client_mtls( error: aws_tls_ctx_options_clean_up(options); return AWS_OP_ERR; - -#else - (void)allocator; - (void)cert; - (void)pkey; - AWS_ZERO_STRUCT(*options); - AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: This platform does not support PEM certificates"); - return aws_raise_error(AWS_ERROR_PLATFORM_NOT_SUPPORTED); -#endif } int aws_tls_ctx_options_init_client_mtls_from_path( @@ -95,8 +100,6 @@ int aws_tls_ctx_options_init_client_mtls_from_path( struct aws_allocator *allocator, const char *cert_path, const char *pkey_path) { - -#if !defined(AWS_OS_IOS) aws_tls_ctx_options_init_default_client(options, allocator); if (aws_byte_buf_init_from_file(&options->certificate, allocator, cert_path)) { @@ -121,15 +124,6 @@ int aws_tls_ctx_options_init_client_mtls_from_path( error: aws_tls_ctx_options_clean_up(options); return AWS_OP_ERR; - -#else - (void)allocator; - (void)cert_path; - (void)pkey_path; - AWS_ZERO_STRUCT(*options); - AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: This platform does not support PEM certificates"); - return aws_raise_error(AWS_ERROR_PLATFORM_NOT_SUPPORTED); -#endif } int aws_tls_ctx_options_init_client_mtls_with_custom_key_operations( @@ -277,7 +271,7 @@ int aws_tls_ctx_options_set_keychain_path( (void)keychain_path_cursor; AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: Keychain path can only be set on MacOS."); return aws_raise_error(AWS_ERROR_PLATFORM_NOT_SUPPORTED); -#endif +#endif /* __APPLE__ && !AWS_OS_IOS*/ } int aws_tls_ctx_options_init_client_mtls_from_system_path(