diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml new file mode 100644 index 00000000..2d6ce4b6 --- /dev/null +++ b/.github/workflows/codecov.yml @@ -0,0 +1,32 @@ +name: Code coverage check + +on: + push: + +env: + BUILDER_VERSION: v0.9.74 + BUILDER_SOURCE: releases + BUILDER_HOST: https://d19elf31gohf1l.cloudfront.net + PACKAGE_NAME: aws-c-cal + RUN: ${{ github.run_id }}-${{ github.run_number }} + CRT_CI_ROLE: ${{ secrets.CRT_CI_ROLE_ARN }} + AWS_DEFAULT_REGION: us-east-1 + +permissions: + id-token: write # This is required for requesting the JWT + +jobs: + codecov-linux: + runs-on: ubuntu-24.04 + steps: + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} + - name: Checkout Sources + uses: actions/checkout@v4 + - name: Build ${{ env.PACKAGE_NAME }} + consumers + run: | + python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" + chmod a+x builder + ./builder build -p ${{ env.PACKAGE_NAME }} --compiler=gcc --cmake-extra=-DASSERT_LOCK_HELD=ON --coverage diff --git a/include/aws/cal/private/der.h b/include/aws/cal/private/der.h index cbec136b..8a904d8f 100644 --- a/include/aws/cal/private/der.h +++ b/include/aws/cal/private/der.h @@ -60,6 +60,11 @@ enum aws_der_type { /* forms */ AWS_DER_FORM_CONSTRUCTED = 0x20, AWS_DER_FORM_PRIMITIVE = 0x00, + + /* context specific */ + /* TODO: we should probably handle tags more generically, but for now first 2 tags cover all cases. */ + AWS_DER_CONTEXT_SPECIFIC_TAG0 = 0xa0, + AWS_DER_CONTEXT_SPECIFIC_TAG1 = 0xa1, }; AWS_EXTERN_C_BEGIN @@ -164,6 +169,14 @@ AWS_CAL_API int aws_der_encoder_get_contents(struct aws_der_encoder *encoder, st */ AWS_CAL_API struct aws_der_decoder *aws_der_decoder_new(struct aws_allocator *allocator, struct aws_byte_cursor input); +/** + * Initializes new decoder from string at the current location. + * Useful for cases where asn1 structure is nested inside another one, ex. ec pkcs8. + * @param decoder Current decoder + * @return Initialized decoder, or NULL + */ +AWS_CAL_API struct aws_der_decoder *aws_der_decoder_nested_tlv_decoder(struct aws_der_decoder *decoder); + /** * Cleans up a DER encoder * @param decoder The encoder to clean up @@ -177,6 +190,12 @@ AWS_CAL_API void aws_der_decoder_destroy(struct aws_der_decoder *decoder); */ AWS_CAL_API bool aws_der_decoder_next(struct aws_der_decoder *decoder); +/** + * Resets der decoder to the start. + * @param decoder The decoder to reset + */ +AWS_CAL_API void aws_der_decoder_reset(struct aws_der_decoder *decoder); + /** * The type of the current TLV * @param decoder The decoder to inspect diff --git a/include/aws/cal/private/ecc.h b/include/aws/cal/private/ecc.h index ec349251..b1608349 100644 --- a/include/aws/cal/private/ecc.h +++ b/include/aws/cal/private/ecc.h @@ -13,10 +13,16 @@ struct aws_der_decoder; AWS_EXTERN_C_BEGIN +/* + * Helper to load keypair from various ASN1 format. + * Note: there are several formats in the wild: Sec1 and PKCS8 for private key and X509 for public key. + * This function attempts to automatically recognize the format and load from it. + * Depending on data available in the asn, either private or public key might be empty (zeroed out). + */ AWS_CAL_API int aws_der_decoder_load_ecc_key_pair( struct aws_der_decoder *decoder, - struct aws_byte_cursor *out_public_x_coor, - struct aws_byte_cursor *out_public_y_coor, + struct aws_byte_cursor *out_public_x_coord, + struct aws_byte_cursor *out_public_y_coord, struct aws_byte_cursor *out_private_d, enum aws_ecc_curve_name *out_curve_name); diff --git a/source/der.c b/source/der.c index acee9c4f..195c7eab 100644 --- a/source/der.c +++ b/source/der.c @@ -400,6 +400,15 @@ struct aws_der_decoder *aws_der_decoder_new(struct aws_allocator *allocator, str return NULL; } +struct aws_der_decoder *aws_der_decoder_nested_tlv_decoder(struct aws_der_decoder *decoder) { + struct aws_byte_cursor cursor; + AWS_ZERO_STRUCT(cursor); + if (aws_der_decoder_tlv_string(decoder, &cursor)) { + return NULL; + } + return aws_der_decoder_new(decoder->allocator, cursor); +} + void aws_der_decoder_destroy(struct aws_der_decoder *decoder) { if (!decoder) { return; @@ -467,6 +476,10 @@ bool aws_der_decoder_next(struct aws_der_decoder *decoder) { return (++decoder->tlv_idx < (int)decoder->tlvs.length); } +void aws_der_decoder_reset(struct aws_der_decoder *decoder) { + decoder->tlv_idx = -1; +} + static struct der_tlv s_decoder_tlv(struct aws_der_decoder *decoder) { AWS_FATAL_ASSERT(decoder->tlv_idx < (int)decoder->tlvs.length); struct der_tlv tlv = {0}; diff --git a/source/ecc.c b/source/ecc.c index b5f5a5e0..5784cae3 100644 --- a/source/ecc.c +++ b/source/ecc.c @@ -189,97 +189,425 @@ size_t aws_ecc_key_coordinate_byte_size_from_curve_name(enum aws_ecc_curve_name } } -int aws_der_decoder_load_ecc_key_pair( +static void s_parse_public_key( + struct aws_byte_cursor public_key, + size_t key_coordinate_size, + struct aws_byte_cursor *out_public_x_coord, + struct aws_byte_cursor *out_public_y_coord) { + + aws_byte_cursor_advance(&public_key, 1); + *out_public_x_coord = aws_byte_cursor_advance(&public_key, key_coordinate_size); + *out_public_y_coord = public_key; +} + +/* + * Both pkcs8 and sec1 have a shared overlapped structure. + * This helper extracts common fields and then validation can differ in the caller. + */ +static int s_der_decoder_sec1_private_key_helper( struct aws_der_decoder *decoder, - struct aws_byte_cursor *out_public_x_coor, - struct aws_byte_cursor *out_public_y_coor, + struct aws_byte_cursor *out_private_cursor, + struct aws_byte_cursor *out_public_cursor, + enum aws_ecc_curve_name *out_curve_name, + bool *curve_name_set) { + + AWS_ZERO_STRUCT(*out_private_cursor); + AWS_ZERO_STRUCT(*out_public_cursor); + + if (!aws_der_decoder_next(decoder) || aws_der_decoder_tlv_type(decoder) != AWS_DER_SEQUENCE) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } + + struct aws_byte_cursor version_cur; + AWS_ZERO_STRUCT(version_cur); + if (!aws_der_decoder_next(decoder) || aws_der_decoder_tlv_unsigned_integer(decoder, &version_cur)) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } + + if (version_cur.len != 1 || version_cur.ptr[0] != 1) { + return aws_raise_error(AWS_ERROR_CAL_UNSUPPORTED_KEY_FORMAT); + } + + struct aws_byte_cursor private_key_cur; + AWS_ZERO_STRUCT(private_key_cur); + if (!aws_der_decoder_next(decoder) || aws_der_decoder_tlv_string(decoder, &private_key_cur)) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } + + struct aws_byte_cursor oid; + AWS_ZERO_STRUCT(oid); + struct aws_byte_cursor public_key_cur; + AWS_ZERO_STRUCT(public_key_cur); + enum aws_ecc_curve_name curve_name = {0}; + + *curve_name_set = false; + if (aws_der_decoder_next(decoder)) { + + bool version_found = false; + /* tag 0 is optional params */ + if (aws_der_decoder_tlv_type(decoder) == AWS_DER_CONTEXT_SPECIFIC_TAG0) { + if (!aws_der_decoder_next(decoder)) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } + + if (aws_der_decoder_tlv_type(decoder) != AWS_DER_OBJECT_IDENTIFIER) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } + + if (aws_der_decoder_tlv_blob(decoder, &oid)) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } + if (aws_ecc_curve_name_from_oid(&oid, &curve_name)) { + return aws_raise_error(AWS_ERROR_CAL_UNKNOWN_OBJECT_IDENTIFIER); + } + *curve_name_set = true; + version_found = true; + } + + /* tag 1 is optional public key + * Note: even though its optional, I could not get any modern tools to generate a key without one. + * So from practical perspective it almost always is present. + * */ + if (version_found && !aws_der_decoder_next(decoder)) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } + + if (aws_der_decoder_tlv_type(decoder) == AWS_DER_CONTEXT_SPECIFIC_TAG1) { + if (!aws_der_decoder_next(decoder)) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } + + if (aws_der_decoder_tlv_string(decoder, &public_key_cur)) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } + } + } + + *out_public_cursor = public_key_cur; + *out_private_cursor = private_key_cur; + *out_curve_name = curve_name; + + return AWS_OP_SUCCESS; +} + +/* + * Load key from sec1 container. Aka "EC PRIVATE KEY" in pem + * ECPrivateKey ::= SEQUENCE { + * version INTEGER { ecPrivkeyVer1(1) }, + * privateKey OCTET STRING, + * parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, + * publicKey [1] BIT STRING OPTIONAL + * } + */ +static int s_der_decoder_load_ecc_private_key_pair_from_sec1( + struct aws_der_decoder *decoder, + struct aws_byte_cursor *out_public_x_coord, + struct aws_byte_cursor *out_public_y_coord, struct aws_byte_cursor *out_private_d, enum aws_ecc_curve_name *out_curve_name) { - AWS_ZERO_STRUCT(*out_public_x_coor); - AWS_ZERO_STRUCT(*out_public_y_coor); + AWS_ZERO_STRUCT(*out_public_x_coord); + AWS_ZERO_STRUCT(*out_public_y_coord); AWS_ZERO_STRUCT(*out_private_d); - /* we could have private key or a public key, or a full pair. */ - struct aws_byte_cursor pair_part_1; - AWS_ZERO_STRUCT(pair_part_1); - struct aws_byte_cursor pair_part_2; - AWS_ZERO_STRUCT(pair_part_2); + struct aws_byte_cursor private_key_cur; + struct aws_byte_cursor public_key_cur; + enum aws_ecc_curve_name curve_name; + bool curve_name_set = false; - bool curve_name_recognized = false; + if (s_der_decoder_sec1_private_key_helper( + decoder, &private_key_cur, &public_key_cur, &curve_name, &curve_name_set)) { + return AWS_OP_ERR; + } - /* work with this pointer and move it to the next after using it. We need - * to know which curve we're dealing with before we can figure out which is which. */ - struct aws_byte_cursor *current_part = &pair_part_1; + if (!curve_name_set) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } - while (aws_der_decoder_next(decoder)) { - enum aws_der_type type = aws_der_decoder_tlv_type(decoder); + size_t key_coordinate_size = aws_ecc_key_coordinate_byte_size_from_curve_name(curve_name); + size_t public_key_blob_size = key_coordinate_size * 2 + 1; - if (type == AWS_DER_OBJECT_IDENTIFIER) { - struct aws_byte_cursor oid; - AWS_ZERO_STRUCT(oid); - aws_der_decoder_tlv_blob(decoder, &oid); - /* There can be other OID's so just look for one that is the curve. */ - if (!aws_ecc_curve_name_from_oid(&oid, out_curve_name)) { - curve_name_recognized = true; - } - continue; - } + if (private_key_cur.len != key_coordinate_size || + (public_key_cur.len != 0 && public_key_cur.len != public_key_blob_size)) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } + + *out_private_d = private_key_cur; + + if (public_key_cur.len > 0) { + s_parse_public_key(public_key_cur, key_coordinate_size, out_public_x_coord, out_public_y_coord); + } + + *out_curve_name = curve_name; + + return AWS_OP_SUCCESS; +} + +static uint8_t s_ec_private_key_oid[] = { + 0x2A, + 0x86, + 0x48, + 0xCE, + 0x3D, + 0x02, + 0x01, +}; +STATIC_INIT_BYTE_CURSOR(s_ec_private_key_oid, ec_private_key_oid_cursor) + +static uint8_t s_ec_public_key_oid[] = { + 0x2A, + 0x86, + 0x48, + 0xCE, + 0x3D, + 0x02, + 0x01, +}; +STATIC_INIT_BYTE_CURSOR(s_ec_public_key_oid, ec_public_key_oid_cursor) + +/* + * Load key from PKCS8 container with the following format and "PRIVATE KEY" in pem + * PrivateKeyInfo ::= SEQUENCE { + * version Integer, + * privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, + * privateKey PrivateKey + * } + * PrivateKeyAlgorithmIdentifier ::= SEQUENCE { + * algorithm OBJECT IDENTIFIER, + * parameters ANY DEFINED BY algorithm OPTIONAL + * } + * ECPrivateKey ::= SEQUENCE { + * version INTEGER { ecPrivkeyVer1(1) }, + * privateKey OCTET STRING, + * parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, + * publicKey [1] BIT STRING OPTIONAL + * } + */ +static int s_der_decoder_load_ecc_private_key_pair_from_pkcs8( + struct aws_der_decoder *decoder, + struct aws_byte_cursor *out_public_x_coord, + struct aws_byte_cursor *out_public_y_coord, + struct aws_byte_cursor *out_private_d, + enum aws_ecc_curve_name *out_curve_name) { - /* you'd think we'd get some type hints on which key this is, but it's not consistent - * as far as I can tell. */ - if (type == AWS_DER_BIT_STRING || type == AWS_DER_OCTET_STRING) { - aws_der_decoder_tlv_string(decoder, current_part); - current_part = &pair_part_2; + AWS_ZERO_STRUCT(*out_public_x_coord); + AWS_ZERO_STRUCT(*out_public_y_coord); + AWS_ZERO_STRUCT(*out_private_d); + + if (!aws_der_decoder_next(decoder) || aws_der_decoder_tlv_type(decoder) != AWS_DER_SEQUENCE) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } + + if (!aws_der_decoder_next(decoder)) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } + + /* version */ + struct aws_byte_cursor version_cur; + AWS_ZERO_STRUCT(version_cur); + /* Note: this is not really spec compliant, but some implementations ignore the top level version. + * So be lax here and treat version as optional. */ + if (aws_der_decoder_tlv_unsigned_integer(decoder, &version_cur) == AWS_OP_SUCCESS) { + if (version_cur.len != 1 || version_cur.ptr[0] != 0) { + return aws_raise_error(AWS_ERROR_CAL_UNSUPPORTED_KEY_FORMAT); + } + if (!aws_der_decoder_next(decoder)) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); } } - if (!curve_name_recognized) { + if (aws_der_decoder_tlv_type(decoder) != AWS_DER_SEQUENCE) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } + + struct aws_byte_cursor algo_oid; + AWS_ZERO_STRUCT(algo_oid); + if (!aws_der_decoder_next(decoder) || aws_der_decoder_tlv_type(decoder) != AWS_DER_OBJECT_IDENTIFIER || + aws_der_decoder_tlv_blob(decoder, &algo_oid)) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } + + /* + * Why check both public and private? Cause in real world standards are mostly a suggestion. + * A lot of private keys in the wild use public also oid and its defacto standard for a lot of libs. + */ + if (!aws_byte_cursor_eq(&algo_oid, &s_ec_public_key_oid_cursor) && + !aws_byte_cursor_eq(&algo_oid, &s_ec_private_key_oid_cursor)) { return aws_raise_error(AWS_ERROR_CAL_UNKNOWN_OBJECT_IDENTIFIER); } - size_t key_coordinate_size = aws_ecc_key_coordinate_byte_size_from_curve_name(*out_curve_name); + struct aws_byte_cursor curve_oid; + AWS_ZERO_STRUCT(curve_oid); + if (!aws_der_decoder_next(decoder) || aws_der_decoder_tlv_type(decoder) != AWS_DER_OBJECT_IDENTIFIER || + aws_der_decoder_tlv_blob(decoder, &curve_oid)) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } + + enum aws_ecc_curve_name curve_name; + if (aws_ecc_curve_name_from_oid(&curve_oid, &curve_name)) { + return aws_raise_error(AWS_ERROR_CAL_UNKNOWN_OBJECT_IDENTIFIER); + } + + /* private key string */ + if (!aws_der_decoder_next(decoder) || aws_der_decoder_tlv_type(decoder) != AWS_DER_OCTET_STRING) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } + + struct aws_der_decoder *nested_decoder = aws_der_decoder_nested_tlv_decoder(decoder); - struct aws_byte_cursor *private_key = NULL; - struct aws_byte_cursor *public_key = NULL; + if (!nested_decoder) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } + + struct aws_byte_cursor private_key_cur; + struct aws_byte_cursor public_key_cur; + enum aws_ecc_curve_name inner_curve_name; + bool curve_name_set = false; + if (s_der_decoder_sec1_private_key_helper( + nested_decoder, &private_key_cur, &public_key_cur, &inner_curve_name, &curve_name_set)) { + aws_der_decoder_destroy(nested_decoder); + return AWS_OP_ERR; + } + aws_der_decoder_destroy(nested_decoder); + + if (curve_name_set && inner_curve_name != curve_name) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } + + size_t key_coordinate_size = aws_ecc_key_coordinate_byte_size_from_curve_name(curve_name); size_t public_key_blob_size = key_coordinate_size * 2 + 1; - if (pair_part_1.ptr && pair_part_1.len) { - if (pair_part_1.len == key_coordinate_size) { - private_key = &pair_part_1; - } else if (pair_part_1.len == public_key_blob_size) { - public_key = &pair_part_1; - } + if (private_key_cur.len != key_coordinate_size || + (public_key_cur.len != 0 && public_key_cur.len != public_key_blob_size)) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); } - if (pair_part_2.ptr && pair_part_2.len) { - if (pair_part_2.len == key_coordinate_size) { - private_key = &pair_part_2; - } else if (pair_part_2.len == public_key_blob_size) { - public_key = &pair_part_2; - } + *out_private_d = private_key_cur; + + if (public_key_cur.len > 0) { + s_parse_public_key(public_key_cur, key_coordinate_size, out_public_x_coord, out_public_y_coord); + } + + *out_curve_name = curve_name; + + return AWS_OP_SUCCESS; +} + +/* + * Load public key from x509 ec key structure, "EC PUBLIC KEY" or "PUBLIC KEY" in pem + * SubjectPublicKeyInfo ::= SEQUENCE { + * algorithm AlgorithmIdentifier, + * subjectPublicKey BIT STRING + * } + * AlgorithmIdentifier ::= SEQUENCE { + * algorithm OBJECT IDENTIFIER, + * parameters ANY DEFINED BY algorithm OPTIONAL + * } + */ +static int s_der_decoder_load_ecc_public_key_pair_from_asn1( + struct aws_der_decoder *decoder, + struct aws_byte_cursor *out_public_x_coord, + struct aws_byte_cursor *out_public_y_coord, + struct aws_byte_cursor *out_private_d, + enum aws_ecc_curve_name *out_curve_name) { + + AWS_ZERO_STRUCT(*out_public_x_coord); + AWS_ZERO_STRUCT(*out_public_y_coord); + AWS_ZERO_STRUCT(*out_private_d); + + /* sequence */ + if (!aws_der_decoder_next(decoder) || aws_der_decoder_tlv_type(decoder) != AWS_DER_SEQUENCE) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } + + /* algo identifier sequence */ + if (!aws_der_decoder_next(decoder) || aws_der_decoder_tlv_type(decoder) != AWS_DER_SEQUENCE) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } + + struct aws_byte_cursor algo_oid; + AWS_ZERO_STRUCT(algo_oid); + if (!aws_der_decoder_next(decoder) || aws_der_decoder_tlv_type(decoder) != AWS_DER_OBJECT_IDENTIFIER || + aws_der_decoder_tlv_blob(decoder, &algo_oid)) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } + + if (!aws_byte_cursor_eq(&algo_oid, &s_ec_public_key_oid_cursor)) { + return aws_raise_error(AWS_ERROR_CAL_UNKNOWN_OBJECT_IDENTIFIER); + } + + struct aws_byte_cursor curve_oid; + AWS_ZERO_STRUCT(curve_oid); + if (!aws_der_decoder_next(decoder) || aws_der_decoder_tlv_type(decoder) != AWS_DER_OBJECT_IDENTIFIER || + aws_der_decoder_tlv_blob(decoder, &curve_oid)) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); + } + + enum aws_ecc_curve_name curve_name; + if (aws_ecc_curve_name_from_oid(&curve_oid, &curve_name)) { + return aws_raise_error(AWS_ERROR_CAL_UNKNOWN_OBJECT_IDENTIFIER); } - if (!private_key && !public_key) { - return aws_raise_error(AWS_ERROR_CAL_MISSING_REQUIRED_KEY_COMPONENT); + struct aws_byte_cursor public_key_cur; + AWS_ZERO_STRUCT(public_key_cur); + if (!aws_der_decoder_next(decoder) || aws_der_decoder_tlv_string(decoder, &public_key_cur)) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); } - if (private_key) { - *out_private_d = *private_key; + size_t key_coordinate_size = aws_ecc_key_coordinate_byte_size_from_curve_name(curve_name); + if ((public_key_cur.len != 0 && public_key_cur.len != (key_coordinate_size * 2 + 1))) { + return aws_raise_error(AWS_ERROR_CAL_MALFORMED_ASN1_ENCOUNTERED); } - if (public_key) { - aws_byte_cursor_advance(public_key, 1); - *out_public_x_coor = *public_key; - out_public_x_coor->len = key_coordinate_size; - out_public_y_coor->ptr = public_key->ptr + key_coordinate_size; - out_public_y_coor->len = key_coordinate_size; + if (public_key_cur.len > 0) { + s_parse_public_key(public_key_cur, key_coordinate_size, out_public_x_coord, out_public_y_coord); } + *out_curve_name = curve_name; + return AWS_OP_SUCCESS; } +int aws_der_decoder_load_ecc_key_pair( + struct aws_der_decoder *decoder, + struct aws_byte_cursor *out_public_x_coord, + struct aws_byte_cursor *out_public_y_coord, + struct aws_byte_cursor *out_private_d, + enum aws_ecc_curve_name *out_curve_name) { + + AWS_ERROR_PRECONDITION(decoder); + AWS_ERROR_PRECONDITION(out_public_x_coord); + AWS_ERROR_PRECONDITION(out_public_y_coord); + AWS_ERROR_PRECONDITION(out_private_d); + AWS_ERROR_PRECONDITION(out_curve_name); + + /** + * Since this is a generic api to parse from ans1, we can encounter several key structures. + * Just go through them one by one and see if any match the expected type. + * Note: We should at least get some id in the structure or unique enough layout so ordering does not matter. + */ + if (s_der_decoder_load_ecc_public_key_pair_from_asn1( + decoder, out_public_x_coord, out_public_y_coord, out_private_d, out_curve_name) == AWS_OP_SUCCESS) { + return AWS_OP_SUCCESS; + } + + aws_der_decoder_reset(decoder); + + if (s_der_decoder_load_ecc_private_key_pair_from_pkcs8( + decoder, out_public_x_coord, out_public_y_coord, out_private_d, out_curve_name) == AWS_OP_SUCCESS) { + return AWS_OP_SUCCESS; + } + + aws_der_decoder_reset(decoder); + if (s_der_decoder_load_ecc_private_key_pair_from_sec1( + decoder, out_public_x_coord, out_public_y_coord, out_private_d, out_curve_name) == AWS_OP_SUCCESS) { + return AWS_OP_SUCCESS; + } + + return aws_raise_error(AWS_ERROR_CAL_UNSUPPORTED_KEY_FORMAT); +} + void aws_ecc_key_pair_acquire(struct aws_ecc_key_pair *key_pair) { aws_atomic_fetch_add(&key_pair->ref_count, 1); } diff --git a/source/unix/opensslcrypto_ecc.c b/source/unix/opensslcrypto_ecc.c index f8d33316..e771f93e 100644 --- a/source/unix/opensslcrypto_ecc.c +++ b/source/unix/opensslcrypto_ecc.c @@ -228,58 +228,43 @@ struct aws_ecc_key_pair *aws_ecc_key_pair_new_generate_random( return NULL; } -struct aws_ecc_key_pair *aws_ecc_key_pair_new_from_public_key_impl( - struct aws_allocator *allocator, - enum aws_ecc_curve_name curve_name, - const struct aws_byte_cursor *public_key_x, - const struct aws_byte_cursor *public_key_y) { - struct libcrypto_ecc_key *key_impl = aws_mem_calloc(allocator, 1, sizeof(struct libcrypto_ecc_key)); - BIGNUM *pub_x_num = NULL; - BIGNUM *pub_y_num = NULL; - EC_POINT *point = NULL; - - if (!key_impl) { - return NULL; +static int s_ec_key_set_private_key(EC_KEY *key, struct aws_byte_cursor priv) { + BIGNUM *priv_n = BN_bin2bn(priv.ptr, priv.len, NULL); + if (!priv_n) { + return aws_raise_error(AWS_ERROR_CAL_CRYPTO_OPERATION_FAILED); } - key_impl->ec_key = EC_KEY_new_by_curve_name(s_curve_name_to_nid(curve_name)); - key_impl->key_pair.curve_name = curve_name; - key_impl->key_pair.allocator = allocator; - key_impl->key_pair.vtable = &vtable; - key_impl->key_pair.impl = key_impl; - aws_atomic_init_int(&key_impl->key_pair.ref_count, 1); - - if (aws_byte_buf_init_copy_from_cursor(&key_impl->key_pair.pub_x, allocator, *public_key_x)) { - s_key_pair_destroy(&key_impl->key_pair); - return NULL; + if (!EC_KEY_set_private_key(key, priv_n)) { + BN_free(priv_n); + return AWS_ERROR_CAL_CRYPTO_OPERATION_FAILED; } - if (aws_byte_buf_init_copy_from_cursor(&key_impl->key_pair.pub_y, allocator, *public_key_y)) { - s_key_pair_destroy(&key_impl->key_pair); - return NULL; - } + BN_free(priv_n); + return AWS_OP_SUCCESS; +} - pub_x_num = BN_bin2bn(public_key_x->ptr, public_key_x->len, NULL); - pub_y_num = BN_bin2bn(public_key_y->ptr, public_key_y->len, NULL); +static int s_ec_key_set_public_key(EC_KEY *key, struct aws_byte_cursor pub_x, struct aws_byte_cursor pub_y) { + BIGNUM *pub_x_num = BN_bin2bn(pub_x.ptr, pub_x.len, NULL); + BIGNUM *pub_y_num = BN_bin2bn(pub_y.ptr, pub_y.len, NULL); - const EC_GROUP *group = EC_KEY_get0_group(key_impl->ec_key); - point = EC_POINT_new(group); + const EC_GROUP *group = EC_KEY_get0_group(key); + EC_POINT *point = EC_POINT_new(group); if (EC_POINT_set_affine_coordinates_GFp(group, point, pub_x_num, pub_y_num, NULL) != 1) { - goto error; + goto on_error; } - if (EC_KEY_set_public_key(key_impl->ec_key, point) != 1) { - goto error; + if (EC_KEY_set_public_key(key, point) != 1) { + goto on_error; } EC_POINT_free(point); BN_free(pub_x_num); BN_free(pub_y_num); - return &key_impl->key_pair; + return AWS_OP_SUCCESS; -error: +on_error: if (point) { EC_POINT_free(point); } @@ -288,10 +273,41 @@ struct aws_ecc_key_pair *aws_ecc_key_pair_new_from_public_key_impl( BN_free(pub_x_num); } - if (pub_y_num) { + if (pub_x_num) { BN_free(pub_y_num); } + return aws_raise_error(AWS_ERROR_CAL_CRYPTO_OPERATION_FAILED); +} + +struct aws_ecc_key_pair *aws_ecc_key_pair_new_from_public_key_impl( + struct aws_allocator *allocator, + enum aws_ecc_curve_name curve_name, + const struct aws_byte_cursor *public_key_x, + const struct aws_byte_cursor *public_key_y) { + struct libcrypto_ecc_key *key_impl = aws_mem_calloc(allocator, 1, sizeof(struct libcrypto_ecc_key)); + + if (!key_impl) { + return NULL; + } + + key_impl->ec_key = EC_KEY_new_by_curve_name(s_curve_name_to_nid(curve_name)); + key_impl->key_pair.curve_name = curve_name; + key_impl->key_pair.allocator = allocator; + key_impl->key_pair.vtable = &vtable; + key_impl->key_pair.impl = key_impl; + aws_atomic_init_int(&key_impl->key_pair.ref_count, 1); + + aws_byte_buf_init_copy_from_cursor(&key_impl->key_pair.pub_x, allocator, *public_key_x); + aws_byte_buf_init_copy_from_cursor(&key_impl->key_pair.pub_y, allocator, *public_key_y); + + if (s_ec_key_set_public_key(key_impl->ec_key, *public_key_x, *public_key_y)) { + goto on_error; + } + + return &key_impl->key_pair; + +on_error: s_key_pair_destroy(&key_impl->key_pair); return NULL; @@ -320,43 +336,45 @@ struct aws_ecc_key_pair *aws_ecc_key_pair_new_from_asn1( if (priv_d.ptr) { struct libcrypto_ecc_key *key_impl = aws_mem_calloc(allocator, 1, sizeof(struct libcrypto_ecc_key)); key_impl->key_pair.curve_name = curve_name; - /* as awkward as it seems, there's not a great way to manually set the public key, so let openssl just parse - * the der document manually now that we know what parts are what. */ - if (!d2i_ECPrivateKey(&key_impl->ec_key, (const unsigned char **)&encoded_keys->ptr, encoded_keys->len)) { + + key_impl->ec_key = EC_KEY_new_by_curve_name(s_curve_name_to_nid(curve_name)); + + if (s_ec_key_set_private_key(key_impl->ec_key, priv_d)) { aws_mem_release(allocator, key_impl); - aws_raise_error(AWS_ERROR_CAL_MISSING_REQUIRED_KEY_COMPONENT); + aws_raise_error(AWS_ERROR_CAL_CRYPTO_OPERATION_FAILED); goto error; } + /* if pub is missing, generate it */ + if (!pub_x.ptr || !pub_y.ptr) { + if (!EC_KEY_generate_key(key_impl->ec_key)) { + aws_mem_release(allocator, key_impl); + aws_raise_error(AWS_ERROR_CAL_CRYPTO_OPERATION_FAILED); + goto error; + } + } else { + if (s_ec_key_set_public_key(key_impl->ec_key, pub_x, pub_y)) { + aws_mem_release(allocator, key_impl); + aws_raise_error(AWS_ERROR_CAL_CRYPTO_OPERATION_FAILED); + goto error; + } + } + key_impl->key_pair.allocator = allocator; key_impl->key_pair.vtable = &vtable; key_impl->key_pair.impl = key_impl; aws_atomic_init_int(&key_impl->key_pair.ref_count, 1); key = &key_impl->key_pair; - struct aws_byte_buf temp_buf; - AWS_ZERO_STRUCT(temp_buf); - if (pub_x.ptr) { - temp_buf = aws_byte_buf_from_array(pub_x.ptr, pub_x.len); - if (aws_byte_buf_init_copy(&key->pub_x, allocator, &temp_buf)) { - goto error; - } + aws_byte_buf_init_copy_from_cursor(&key->pub_x, allocator, pub_x); } if (pub_y.ptr) { - temp_buf = aws_byte_buf_from_array(pub_y.ptr, pub_y.len); - if (aws_byte_buf_init_copy(&key->pub_y, allocator, &temp_buf)) { - goto error; - } + aws_byte_buf_init_copy_from_cursor(&key->pub_y, allocator, pub_y); } - if (priv_d.ptr) { - temp_buf = aws_byte_buf_from_array(priv_d.ptr, priv_d.len); - if (aws_byte_buf_init_copy(&key->priv_d, allocator, &temp_buf)) { - goto error; - } - } + aws_byte_buf_init_copy_from_cursor(&key->priv_d, allocator, priv_d); } else { key = aws_ecc_key_pair_new_from_public_key(allocator, curve_name, &pub_x, &pub_y); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 73a46a30..c618fb33 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -78,6 +78,7 @@ add_test_case(ecdsa_p384_test_key_gen) add_test_case(ecdsa_p256_test_key_gen_export) add_test_case(ecdsa_p384_test_key_gen_export) add_test_case(ecdsa_p256_test_import_asn1_key_pair) +add_test_case(ecdsa_p256_test_import_asn1_pkcs8_key_pair) add_test_case(ecdsa_p384_test_import_asn1_key_pair) add_test_case(ecdsa_test_import_asn1_key_pair_public_only) add_test_case(ecdsa_test_import_asn1_key_pair_invalid_fails) diff --git a/tests/ecc_test.c b/tests/ecc_test.c index efa35f44..bbf34ad8 100644 --- a/tests/ecc_test.c +++ b/tests/ecc_test.c @@ -506,9 +506,29 @@ static int s_ecdsa_p256_test_import_asn1_key_pair_fn(struct aws_allocator *alloc return s_ecdsa_test_import_asn1_key_pair(allocator, asn1_encoded_key, AWS_CAL_ECDSA_P256); } - AWS_TEST_CASE(ecdsa_p256_test_import_asn1_key_pair, s_ecdsa_p256_test_import_asn1_key_pair_fn) +static int s_ecdsa_p256_test_import_asn1_pkcs8_key_pair_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + uint8_t asn1_encoded_key_raw[] = { + 0x30, 0x81, 0x87, 0x02, 0x01, 0x00, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, + 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x04, 0x6D, 0x30, 0x6B, 0x02, 0x01, 0x01, 0x04, 0x20, + 0x18, 0xCD, 0x6B, 0x61, 0x25, 0xB5, 0x37, 0x61, 0xB8, 0xF4, 0x6A, 0xBA, 0x42, 0xAF, 0xA9, 0x70, 0x14, 0x9D, + 0x72, 0x40, 0x6D, 0x84, 0x00, 0xA8, 0x40, 0x02, 0x13, 0x86, 0x8C, 0xA9, 0x2D, 0x63, 0xA1, 0x44, 0x03, 0x42, + 0x00, 0x04, 0x95, 0x4E, 0x22, 0xE2, 0xDB, 0x79, 0xCC, 0xA7, 0x56, 0x6F, 0xC2, 0x29, 0xA1, 0x0B, 0x05, 0x96, + 0x22, 0x66, 0x06, 0xC3, 0x6D, 0x0A, 0xD1, 0xD8, 0x57, 0x82, 0x02, 0x19, 0x74, 0xCD, 0x52, 0x4D, 0x5A, 0x57, + 0x18, 0xCA, 0xDF, 0xF5, 0x81, 0x9B, 0x9D, 0x0C, 0xBF, 0x2E, 0xB9, 0x91, 0xD2, 0x1A, 0xBB, 0x76, 0x8A, 0x06, + 0x66, 0xB3, 0xBF, 0x7C, 0x6C, 0x30, 0xF6, 0xBF, 0x8C, 0x84, 0x73, 0x96, + }; + + struct aws_byte_cursor asn1_encoded_key = + aws_byte_cursor_from_array(asn1_encoded_key_raw, sizeof(asn1_encoded_key_raw)); + + return s_ecdsa_test_import_asn1_key_pair(allocator, asn1_encoded_key, AWS_CAL_ECDSA_P256); +} +AWS_TEST_CASE(ecdsa_p256_test_import_asn1_pkcs8_key_pair, s_ecdsa_p256_test_import_asn1_pkcs8_key_pair_fn) + static int s_ecdsa_p384_test_import_asn1_key_pair_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -636,14 +656,14 @@ static int s_ecdsa_test_import_asn1_key_pair_invalid_fails_fn(struct aws_allocat struct aws_ecc_key_pair *signing_key = aws_ecc_key_pair_new_from_asn1(allocator, &bad_full_key_asn1_1); ASSERT_NULL(signing_key); - ASSERT_INT_EQUALS(AWS_ERROR_CAL_UNKNOWN_OBJECT_IDENTIFIER, aws_last_error()); + ASSERT_INT_EQUALS(AWS_ERROR_CAL_UNSUPPORTED_KEY_FORMAT, aws_last_error()); struct aws_byte_cursor bad_full_key_asn1_2 = aws_byte_cursor_from_array(bad_asn1_encoded_full_key_raw_2, sizeof(bad_asn1_encoded_full_key_raw_2)); signing_key = aws_ecc_key_pair_new_from_asn1(allocator, &bad_full_key_asn1_2); ASSERT_NULL(signing_key); - ASSERT_INT_EQUALS(AWS_ERROR_CAL_UNKNOWN_OBJECT_IDENTIFIER, aws_last_error()); + ASSERT_INT_EQUALS(AWS_ERROR_CAL_UNSUPPORTED_KEY_FORMAT, aws_last_error()); aws_cal_library_clean_up(); return AWS_OP_SUCCESS; @@ -654,9 +674,9 @@ AWS_TEST_CASE(ecdsa_test_import_asn1_key_pair_invalid_fails, s_ecdsa_test_import /* this test exists because we have to manually handle signature encoding/decoding on windows. this takes an encoded signature and makes sure we decode and verify it properly. How do we know we encode properly b.t.w? Well we have tests that verify signatures we generated, so we already know - that anything we signed can be decoded. What we don't have proven is that we're not just symetrically + that anything we signed can be decoded. What we don't have proven is that we're not just symmetrically wrong. So, let's take the format we know signatures must be in ASN.1 DER encoded, and make sure we can - verify it. Since we KNOW the signing and verifying code is symetric, verifying the verification side should + verify it. Since we KNOW the signing and verifying code is symmetric, verifying the verification side should prove our encoding/decoding code is correct to the spec. */ static int s_ecdsa_test_signature_format_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx;