Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor pem #605

Merged
merged 22 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# IDE Artifacts
.metadata
.build
.vscode
.idea
*.d
Debug
Expand Down
2 changes: 2 additions & 0 deletions include/aws/io/io.h
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ enum aws_io_errors {

AWS_IO_TLS_ERROR_READ_FAILURE,

AWS_ERROR_PEM_MALFORMED,

AWS_IO_ERROR_END_RANGE = AWS_ERROR_ENUM_END_RANGE(AWS_C_IO_PACKAGE_ID),
AWS_IO_INVALID_FILE_HANDLE = AWS_ERROR_INVALID_FILE_HANDLE,
};
Expand Down
1 change: 1 addition & 0 deletions include/aws/io/logging.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ enum aws_io_log_subject {
AWS_LS_IO_EXPONENTIAL_BACKOFF_RETRY_STRATEGY,
AWS_LS_IO_STANDARD_RETRY_STRATEGY,
AWS_LS_IO_PKCS11,
AWS_LS_IO_PEM,
AWS_IO_LS_LAST = AWS_LOG_SUBJECT_END_RANGE(AWS_C_IO_PACKAGE_ID)
};
AWS_POP_SANE_WARNING_LEVEL
Expand Down
99 changes: 99 additions & 0 deletions include/aws/io/pem.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#ifndef AWS_IO_PEM_H
#define AWS_IO_PEM_H

/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#include <aws/io/io.h>

AWS_EXTERN_C_BEGIN

/*
* Naming follows OpenSSL convention for PEM types.
* Refer to comment after each enum value for the type string it represents.
*/
enum aws_pem_object_type {
AWS_PEM_TYPE_UNKNOWN = 0,
AWS_PEM_TYPE_X509_OLD, /* X509 CERTIFICATE */
AWS_PEM_TYPE_X509, /* CERTIFICATE */
AWS_PEM_TYPE_X509_TRUSTED, /* TRUSTED CERTIFICATE */
AWS_PEM_TYPE_X509_REQ_OLD, /* NEW CERTIFICATE REQUEST */
AWS_PEM_TYPE_X509_REQ, /* CERTIFICATE REQUEST */
AWS_PEM_TYPE_X509_CRL, /* X509 CRL */
AWS_PEM_TYPE_EVP_PKEY, /* ANY PRIVATE KEY */
AWS_PEM_TYPE_PUBLIC_PKCS8, /* PUBLIC KEY */
AWS_PEM_TYPE_PRIVATE_RSA_PKCS1, /* RSA PRIVATE KEY */
AWS_PEM_TYPE_PUBLIC_RSA_PKCS1, /* RSA PUBLIC KEY */
AWS_PEM_TYPE_PRIVATE_DSA_PKCS1, /* RSA PRIVATE KEY */
AWS_PEM_TYPE_PUBLIC_DSA_PKCS1, /* RSA PUBLIC KEY */
AWS_PEM_TYPE_PKCS7, /* PKCS7 */
AWS_PEM_TYPE_PKCS7_SIGNED_DATA, /* PKCS #7 SIGNED DATA */
AWS_PEM_TYPE_PRIVATE_PKCS8_ENCRYPTED, /* ENCRYPTED PRIVATE KEY */
AWS_PEM_TYPE_PRIVATE_PKCS8, /* PRIVATE KEY */
AWS_PEM_TYPE_DH_PARAMETERS, /* X9.42 DH PARAMETERS */
AWS_PEM_TYPE_DH_PARAMETERS_X942, /* X9.42 DH PARAMETERS */
AWS_PEM_TYPE_SSL_SESSION_PARAMETERS, /* SSL SESSION PARAMETERS */
AWS_PEM_TYPE_DSA_PARAMETERS, /* DSA PARAMETERS */
AWS_PEM_TYPE_ECDSA_PUBLIC, /* ECDSA PUBLIC KEY */
AWS_PEM_TYPE_EC_PARAMETERS, /* EC PARAMETERS */
AWS_PEM_TYPE_EC_PRIVATE, /* EC PRIVATE KEY */
AWS_PEM_TYPE_PARAMETERS, /* PARAMETERS */
AWS_PEM_TYPE_CMS, /* CMS */
AWS_PEM_TYPE_SM2_PARAMETERS /* SM2 PARAMETERS */
};

/*
* Describes PEM object decoded from file.
* data points to raw data bytes of object (decoding will do additional base 64
* decoding for each object).
* type will be set to object type or to AWS_PEM_TYPE_UNKNOWN if it could not
* figure out type.
* type_string is the string between -----BEGIN and -----
*/
struct aws_pem_object {
enum aws_pem_object_type type;
struct aws_string *type_string;
struct aws_byte_buf data;
};

/**
* Cleans up elements of pem_objects list 'aws_pem_objects_init_from_file_contents()'
* and 'aws_pem_objects_init_from_file_path()'.
*/
AWS_IO_API void aws_pem_objects_clean_up(struct aws_array_list *pem_objects);

/**
* Decodes PEM data and reads objects sequentially adding them to pem_objects.
* If it comes across an object it cannot read, list of all object read until
* that point is returned.
* If no objects can be read from PEM or objects could not be base 64 decoded,
* AWS_ERROR_PEM_MALFORMED is raised.
* out_pem_objects stores aws_pem_object struct by value.
* Function will initialize pem_objects list.
* 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_pem_objects_init_from_file_contents(
struct aws_array_list *pem_objects,
struct aws_allocator *alloc,
struct aws_byte_cursor pem_cursor);

/**
* Decodes PEM data from file and reads objects sequentially adding them to pem_objects.
* If it comes across an object it cannot read, list of all object read until
* that point is returned.
* If no objects can be read from PEM or objects could not be base 64 decoded,
* AWS_ERROR_PEM_MALFORMED is raised.
* out_pem_objects stores aws_pem_object struct by value.
* Function will initialize pem_objects list.
* 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_pem_objects_init_from_file_path(
struct aws_array_list *pem_objects,
struct aws_allocator *allocator,
const char *filename);

AWS_EXTERN_C_END
#endif /* AWS_IO_PEM_H */
30 changes: 0 additions & 30 deletions include/aws/io/private/pki_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,43 +21,13 @@ struct aws_string;

AWS_EXTERN_C_BEGIN

/**
* Cleans up and securely zeroes out the outputs of 'aws_decode_pem_to_buffer_list()'
* and 'aws_read_and_decode_pem_file_to_buffer_list()'
*/
AWS_IO_API void aws_cert_chain_clean_up(struct aws_array_list *cert_chain);

/**
* Decodes a PEM file 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.
*/
AWS_IO_API int aws_decode_pem_to_buffer_list(
struct aws_allocator *alloc,
const struct aws_byte_cursor *pem_cursor,
struct aws_array_list *cert_chain_or_key);

/**
* Returns the path to the directory and file, respectively, which holds the
* SSL certificate trust store on the system.
*/
AWS_IO_API const char *aws_determine_default_pki_dir(void);
AWS_IO_API const char *aws_determine_default_pki_ca_file(void);

/**
* 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 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,
const char *filename,
struct aws_array_list *cert_chain_or_key);

#ifdef AWS_OS_APPLE
# if !defined(AWS_OS_IOS)
/**
Expand Down
54 changes: 23 additions & 31 deletions source/darwin/darwin_pki_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <aws/common/mutex.h>
#include <aws/common/string.h>
#include <aws/io/logging.h>
#include <aws/io/pem.h>

#include <Security/SecCertificate.h>
#include <Security/SecKey.h>
Expand All @@ -33,16 +34,13 @@ int aws_import_ecc_key_into_keychain(
SecKeychainRef import_keychain) {
// Ensure imported_keychain is not NULL
AWS_PRECONDITION(import_keychain != NULL);
AWS_PRECONDITION(private_key != 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)) {
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 private key to DER format.");
goto ecc_import_cleanup;
}
Expand All @@ -51,11 +49,11 @@ int aws_import_ecc_key_into_keychain(
// 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;
struct aws_pem_object *pem_object_ptr = 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_array_list_get_at_ptr(&decoded_key_buffer_list, (void **)&pem_object_ptr, index);
AWS_ASSERT(decoded_key_buffer);
CFDataRef key_data = CFDataCreate(cf_alloc, decoded_key_buffer->buffer, decoded_key_buffer->len);
CFDataRef key_data = CFDataCreate(cf_alloc, pem_object_ptr->data.buffer, pem_object_ptr->data.len);
if (!key_data) {
AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: error in creating ECC key data system call.");
continue;
Expand All @@ -68,6 +66,7 @@ int aws_import_ecc_key_into_keychain(
AWS_ZERO_STRUCT(import_params);
import_params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
import_params.passphrase = CFSTR("");
format = kSecFormatUnknown;

OSStatus key_status =
SecItemImport(key_data, NULL, &format, &item_type, 0, &import_params, import_keychain, NULL);
Expand All @@ -87,8 +86,7 @@ int aws_import_ecc_key_into_keychain(

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);
aws_pem_objects_clean_up(&decoded_key_buffer_list);
return result;
}

Expand All @@ -99,6 +97,8 @@ int aws_import_public_and_private_keys_to_identity(
const struct aws_byte_cursor *private_key,
CFArrayRef *identity,
const struct aws_string *keychain_path) {
AWS_PRECONDITION(public_cert_chain != NULL);
AWS_PRECONDITION(private_key != NULL);

int result = AWS_OP_ERR;

Expand Down Expand Up @@ -179,7 +179,7 @@ int aws_import_public_and_private_keys_to_identity(

/*
* 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.
* The PEM file might contains 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.");
Expand All @@ -203,30 +203,24 @@ int aws_import_public_and_private_keys_to_identity(
"Using key from Keychain instead of the one provided.");
struct aws_array_list cert_chain_list;

if (aws_array_list_init_dynamic(&cert_chain_list, alloc, 2, sizeof(struct aws_byte_buf))) {
result = AWS_OP_ERR;
goto done;
}

if (aws_decode_pem_to_buffer_list(alloc, public_cert_chain, &cert_chain_list)) {
if (aws_pem_objects_init_from_file_contents(&cert_chain_list, alloc, *public_cert_chain)) {
AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: decoding certificate PEM failed.");
aws_array_list_clean_up(&cert_chain_list);
aws_pem_objects_clean_up(&cert_chain_list);
result = AWS_OP_ERR;
goto done;
}

struct aws_byte_buf *root_cert_ptr = NULL;
struct aws_pem_object *root_cert_ptr = NULL;
aws_array_list_get_at_ptr(&cert_chain_list, (void **)&root_cert_ptr, 0);
AWS_ASSERT(root_cert_ptr);
CFDataRef root_cert_data = CFDataCreate(cf_alloc, root_cert_ptr->buffer, root_cert_ptr->len);
CFDataRef root_cert_data = CFDataCreate(cf_alloc, root_cert_ptr->data.buffer, root_cert_ptr->data.len);

if (root_cert_data) {
certificate_ref = SecCertificateCreateWithData(cf_alloc, root_cert_data);
CFRelease(root_cert_data);
}

aws_cert_chain_clean_up(&cert_chain_list);
aws_array_list_clean_up(&cert_chain_list);
aws_pem_objects_clean_up(&cert_chain_list);
} else {
certificate_ref = (SecCertificateRef)CFArrayGetValueAtIndex(cert_import_output, 0);
/* SecCertificateCreateWithData returns an object with +1 retain, so we need to match that behavior here */
Expand Down Expand Up @@ -316,13 +310,11 @@ int aws_import_trusted_certificates(
CFAllocatorRef cf_alloc,
const struct aws_byte_cursor *certificates_blob,
CFArrayRef *certs) {
struct aws_array_list certificates;
AWS_PRECONDITION(certificates_blob != NULL);

if (aws_array_list_init_dynamic(&certificates, alloc, 2, sizeof(struct aws_byte_buf))) {
return AWS_OP_ERR;
}
struct aws_array_list certificates;

if (aws_decode_pem_to_buffer_list(alloc, certificates_blob, &certificates)) {
if (aws_pem_objects_init_from_file_contents(&certificates, alloc, *certificates_blob)) {
AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: decoding CA PEM failed.");
aws_array_list_clean_up(&certificates);
return AWS_OP_ERR;
Expand All @@ -334,10 +326,10 @@ int aws_import_trusted_certificates(
int err = AWS_OP_SUCCESS;
aws_mutex_lock(&s_sec_mutex);
for (size_t i = 0; i < cert_count; ++i) {
struct aws_byte_buf *byte_buf_ptr = NULL;
aws_array_list_get_at_ptr(&certificates, (void **)&byte_buf_ptr, i);
struct aws_pem_object *pem_object_ptr = NULL;
aws_array_list_get_at_ptr(&certificates, (void **)&pem_object_ptr, i);

CFDataRef cert_blob = CFDataCreate(cf_alloc, byte_buf_ptr->buffer, byte_buf_ptr->len);
CFDataRef cert_blob = CFDataCreate(cf_alloc, pem_object_ptr->data.buffer, pem_object_ptr->data.len);

if (cert_blob) {
SecCertificateRef certificate_ref = SecCertificateCreateWithData(cf_alloc, cert_blob);
Expand All @@ -351,7 +343,7 @@ int aws_import_trusted_certificates(
aws_mutex_unlock(&s_sec_mutex);

*certs = temp_cert_array;
aws_cert_chain_clean_up(&certificates);
aws_pem_objects_clean_up(&certificates);
aws_array_list_clean_up(&certificates);
return err;
}
Expand Down
3 changes: 2 additions & 1 deletion source/io.c
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ static struct aws_error_info s_errors[] = {
AWS_DEFINE_ERROR_INFO_IO(
AWS_IO_TLS_ERROR_READ_FAILURE,
"Failure during TLS read."),
AWS_DEFINE_ERROR_INFO_IO(AWS_ERROR_PEM_MALFORMED, "Malformed PEM object encountered."),

};
/* clang-format on */
Expand Down Expand Up @@ -341,7 +342,7 @@ static struct aws_log_subject_info s_io_log_subject_infos[] = {
"standard-retry-strategy",
"Subject for standard retry strategy"),
DEFINE_LOG_SUBJECT_INFO(AWS_LS_IO_PKCS11, "pkcs11", "Subject for PKCS#11 library operations"),
};
DEFINE_LOG_SUBJECT_INFO(AWS_LS_IO_PEM, "pem", "Subject for pem operations")};

static struct aws_log_subject_info_list s_io_log_subject_list = {
.subject_list = s_io_log_subject_infos,
Expand Down
Loading