123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 |
- /**
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0.
- */
- #include <aws/auth/private/key_derivation.h>
- #include <aws/auth/credentials.h>
- #include <aws/cal/ecc.h>
- #include <aws/cal/hash.h>
- #include <aws/cal/hmac.h>
- #include <aws/common/byte_buf.h>
- #include <aws/common/string.h>
- /*
- * The maximum number of iterations we will attempt to derive a valid ecc key for. The probability that this counter
- * value ever gets reached is vanishingly low -- with reasonable uniformity/independence assumptions, it's
- * approximately
- *
- * 2 ^ (-32 * 254)
- */
- #define MAX_KEY_DERIVATION_COUNTER_VALUE 254
- /*
- * The encoding (32-bit, big-endian) of the prefix to the FixedInputString when fed to the hmac function, per
- * the sigv4a key derivation specification.
- */
- AWS_STATIC_STRING_FROM_LITERAL(s_1_as_four_bytes_be, "\x00\x00\x00\x01");
- /*
- * The encoding (32-bit, big-endian) of the "Length" component of the sigv4a key derivation specification
- */
- AWS_STATIC_STRING_FROM_LITERAL(s_256_as_four_bytes_be, "\x00\x00\x01\x00");
- AWS_STRING_FROM_LITERAL(g_signature_type_sigv4a_http_request, "AWS4-ECDSA-P256-SHA256");
- AWS_STATIC_STRING_FROM_LITERAL(s_secret_buffer_prefix, "AWS4A");
- /*
- * This constructs the fixed input byte sequence of the Sigv4a key derivation specification. It also includes the
- * value (0x01 as a 32-bit big endian value) that is pre-pended to the fixed input before invoking the hmac to
- * generate the candidate key value.
- *
- * The final output looks like
- *
- * 0x00000001 || "AWS4-ECDSA-P256-SHA256" || 0x00 || AccessKeyId || CounterValue as uint8_t || 0x00000100 (Length)
- *
- * From this, we can determine the necessary buffer capacity when setting up the fixed input buffer:
- *
- * 4 + 22 + 1 + len(AccessKeyId) + 1 + 4 = 32 + len(AccessKeyId)
- */
- static int s_aws_build_fixed_input_buffer(
- struct aws_byte_buf *fixed_input,
- const struct aws_credentials *credentials,
- const uint8_t counter) {
- if (counter == 0 || counter > MAX_KEY_DERIVATION_COUNTER_VALUE) {
- return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
- }
- if (!aws_byte_buf_is_valid(fixed_input)) {
- return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
- }
- aws_byte_buf_reset(fixed_input, false);
- /*
- * A placeholder value that's not actually part of the fixed input string in the spec, but is always this value
- * and is always the first byte of the hmac-ed string.
- */
- struct aws_byte_cursor one_cursor = aws_byte_cursor_from_string(s_1_as_four_bytes_be);
- if (aws_byte_buf_append_dynamic(fixed_input, &one_cursor)) {
- return AWS_OP_ERR;
- }
- struct aws_byte_cursor sigv4a_algorithm_cursor = aws_byte_cursor_from_string(g_signature_type_sigv4a_http_request);
- if (aws_byte_buf_append(fixed_input, &sigv4a_algorithm_cursor)) {
- return AWS_OP_ERR;
- }
- if (aws_byte_buf_append_byte_dynamic(fixed_input, 0)) {
- return AWS_OP_ERR;
- }
- struct aws_byte_cursor access_key_cursor = aws_credentials_get_access_key_id(credentials);
- if (aws_byte_buf_append(fixed_input, &access_key_cursor)) {
- return AWS_OP_ERR;
- }
- if (aws_byte_buf_append_byte_dynamic(fixed_input, counter)) {
- return AWS_OP_ERR;
- }
- struct aws_byte_cursor encoded_bit_length_cursor = aws_byte_cursor_from_string(s_256_as_four_bytes_be);
- if (aws_byte_buf_append_dynamic(fixed_input, &encoded_bit_length_cursor)) {
- return AWS_OP_ERR;
- }
- return AWS_OP_SUCCESS;
- }
- /*
- * aws_be_bytes_compare_constant_time() and aws_be_bytes_add_one_constant_time() are constant-time arithmetic functions
- * that operate on raw bytes as if they were unbounded integers in a big-endian base 255 format.
- */
- /*
- * In the following function gt and eq are updated together. After each update, the variables will be
- * in one of the following states:
- *
- * (1) gt is 0, eq is 1, and from an ordering perspective, lhs == rhs, as checked "so far"
- * (2) gt is 1, eq is 0, (lhs > rhs)
- * (3) gt is 0, eq is 0, (lhs < rhs)
- *
- * States (2) and (3) are terminal states that cannot be exited since eq is 0 and is the and-wise mask of all
- * subsequent gt updates. Similarly, once eq is zero it cannot ever become non-zero.
- *
- * Intuitively these ideas match the standard way of comparing magnitude equality by considering digit count and
- * digits from most significant to least significant.
- *
- * Let l and r be the the two digits that we are
- * comparing between lhs and rhs. Assume 0 <= l, r <= 255 seated in 32-bit integers
- *
- * gt is maintained by the following bit trick:
- *
- * l > r <=>
- * (r - l) < 0 <=>
- * (r - l) as an int32 has the high bit set <=>
- * ((r - l) >> 31) & 0x01 == 1
- *
- * eq is maintained by the following bit trick:
- *
- * l == r <=>
- * l ^ r == 0 <=>
- * (l ^ r) - 1 == -1 <=>
- * (((l ^ r) - 1) >> 31) & 0x01 == 1
- *
- * We apply to the volatile type modifier to attempt to prevent all early-out optimizations that a compiler might
- * apply if it performed constraint-based reasoning on the logic. This is based on treating volatile
- * semantically as "this value can change underneath you at any time so you always have to re-read it and cannot
- * reason statically about program behavior when it reaches a certain value (like 0)"
- */
- /**
- * Compares two large unsigned integers in a raw byte format.
- * The two operands *must* be the same size (simplifies the problem significantly).
- *
- * The output parameter comparison_result is set to:
- * -1 if lhs_raw_be_bigint < rhs_raw_be_bigint
- * 0 if lhs_raw_be_bigint == rhs_raw_be_bigint
- * 1 if lhs_raw_be_bigint > rhs_raw_be_bigint
- */
- int aws_be_bytes_compare_constant_time(
- const struct aws_byte_buf *lhs_raw_be_bigint,
- const struct aws_byte_buf *rhs_raw_be_bigint,
- int *comparison_result) {
- AWS_FATAL_PRECONDITION(aws_byte_buf_is_valid(lhs_raw_be_bigint));
- AWS_FATAL_PRECONDITION(aws_byte_buf_is_valid(rhs_raw_be_bigint));
- /*
- * We only need to support comparing byte sequences of the same length here
- */
- const size_t lhs_len = lhs_raw_be_bigint->len;
- if (lhs_len != rhs_raw_be_bigint->len) {
- return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
- }
- volatile uint8_t gt = 0;
- volatile uint8_t eq = 1;
- const uint8_t *lhs_raw_bytes = lhs_raw_be_bigint->buffer;
- const uint8_t *rhs_raw_bytes = rhs_raw_be_bigint->buffer;
- for (size_t i = 0; i < lhs_len; ++i) {
- volatile int32_t lhs_digit = (int32_t)lhs_raw_bytes[i];
- volatile int32_t rhs_digit = (int32_t)rhs_raw_bytes[i];
- /*
- * For each digit, check for a state (1) => (2) ie lhs > rhs, or (1) => (3) ie lhs < rhs transition
- * based on comparing the two digits in constant time using the ideas explained in the giant comment
- * block above this function.
- */
- gt |= ((rhs_digit - lhs_digit) >> 31) & eq;
- eq &= (((lhs_digit ^ rhs_digit) - 1) >> 31) & 0x01;
- }
- *comparison_result = gt + gt + eq - 1;
- return AWS_OP_SUCCESS;
- }
- /**
- * Adds one to a large unsigned integer represented by a sequence of bytes.
- *
- * A maximal value will roll over to zero. This does not affect the correctness of the users
- * of this function.
- */
- void aws_be_bytes_add_one_constant_time(struct aws_byte_buf *raw_be_bigint) {
- AWS_FATAL_PRECONDITION(aws_byte_buf_is_valid(raw_be_bigint));
- const size_t byte_count = raw_be_bigint->len;
- volatile uint32_t carry = 1;
- uint8_t *raw_bytes = raw_be_bigint->buffer;
- for (size_t i = 0; i < byte_count; ++i) {
- const size_t index = byte_count - i - 1;
- volatile uint32_t current_digit = raw_bytes[index];
- current_digit += carry;
- carry = (current_digit >> 8) & 0x01;
- raw_bytes[index] = (uint8_t)(current_digit & 0xFF);
- }
- }
- /* clang-format off */
- /* In the spec, this is N-2 */
- static uint8_t s_n_minus_2[32] = {
- 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
- 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
- 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84,
- 0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x4F,
- };
- /* clang-format on */
- enum aws_key_derivation_result {
- AKDR_SUCCESS,
- AKDR_NEXT_COUNTER,
- AKDR_FAILURE,
- };
- static enum aws_key_derivation_result s_aws_derive_ecc_private_key(
- struct aws_byte_buf *private_key_value,
- const struct aws_byte_buf *k0) {
- AWS_FATAL_ASSERT(k0->len == aws_ecc_key_coordinate_byte_size_from_curve_name(AWS_CAL_ECDSA_P256));
- aws_byte_buf_reset(private_key_value, false);
- struct aws_byte_buf s_n_minus_2_buf = {
- .allocator = NULL,
- .buffer = s_n_minus_2,
- .capacity = AWS_ARRAY_SIZE(s_n_minus_2),
- .len = AWS_ARRAY_SIZE(s_n_minus_2),
- };
- int comparison_result = 0;
- if (aws_be_bytes_compare_constant_time(k0, &s_n_minus_2_buf, &comparison_result)) {
- return AKDR_FAILURE;
- }
- if (comparison_result > 0) {
- return AKDR_NEXT_COUNTER;
- }
- struct aws_byte_cursor k0_cursor = aws_byte_cursor_from_buf(k0);
- if (aws_byte_buf_append(private_key_value, &k0_cursor)) {
- return AKDR_FAILURE;
- }
- aws_be_bytes_add_one_constant_time(private_key_value);
- return AKDR_SUCCESS;
- }
- static int s_init_secret_buf(
- struct aws_byte_buf *secret_buf,
- struct aws_allocator *allocator,
- const struct aws_credentials *credentials) {
- struct aws_byte_cursor secret_access_key_cursor = aws_credentials_get_secret_access_key(credentials);
- size_t secret_buffer_length = secret_access_key_cursor.len + s_secret_buffer_prefix->len;
- if (aws_byte_buf_init(secret_buf, allocator, secret_buffer_length)) {
- return AWS_OP_ERR;
- }
- struct aws_byte_cursor prefix_cursor = aws_byte_cursor_from_string(s_secret_buffer_prefix);
- if (aws_byte_buf_append(secret_buf, &prefix_cursor)) {
- return AWS_OP_ERR;
- }
- if (aws_byte_buf_append(secret_buf, &secret_access_key_cursor)) {
- return AWS_OP_ERR;
- }
- return AWS_OP_SUCCESS;
- }
- struct aws_ecc_key_pair *aws_ecc_key_pair_new_ecdsa_p256_key_from_aws_credentials(
- struct aws_allocator *allocator,
- const struct aws_credentials *credentials) {
- if (allocator == NULL || credentials == NULL) {
- aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
- return NULL;
- }
- struct aws_ecc_key_pair *ecc_key_pair = NULL;
- struct aws_byte_buf fixed_input;
- AWS_ZERO_STRUCT(fixed_input);
- struct aws_byte_buf fixed_input_hmac_digest;
- AWS_ZERO_STRUCT(fixed_input_hmac_digest);
- struct aws_byte_buf private_key_buf;
- AWS_ZERO_STRUCT(private_key_buf);
- struct aws_byte_buf secret_buf;
- AWS_ZERO_STRUCT(secret_buf);
- size_t access_key_length = aws_credentials_get_access_key_id(credentials).len;
- /*
- * This value is calculated based on the format of the fixed input string as described above at
- * the definition of s_aws_build_fixed_input_buffer()
- */
- size_t required_fixed_input_capacity = 32 + access_key_length;
- if (aws_byte_buf_init(&fixed_input, allocator, required_fixed_input_capacity)) {
- goto done;
- }
- if (aws_byte_buf_init(&fixed_input_hmac_digest, allocator, AWS_SHA256_LEN)) {
- goto done;
- }
- size_t key_length = aws_ecc_key_coordinate_byte_size_from_curve_name(AWS_CAL_ECDSA_P256);
- AWS_FATAL_ASSERT(key_length == AWS_SHA256_LEN);
- if (aws_byte_buf_init(&private_key_buf, allocator, key_length)) {
- goto done;
- }
- if (s_init_secret_buf(&secret_buf, allocator, credentials)) {
- goto done;
- }
- struct aws_byte_cursor secret_cursor = aws_byte_cursor_from_buf(&secret_buf);
- uint8_t counter = 1;
- enum aws_key_derivation_result result = AKDR_NEXT_COUNTER;
- while ((result == AKDR_NEXT_COUNTER) && (counter <= MAX_KEY_DERIVATION_COUNTER_VALUE)) {
- if (s_aws_build_fixed_input_buffer(&fixed_input, credentials, counter++)) {
- break;
- }
- aws_byte_buf_reset(&fixed_input_hmac_digest, true);
- struct aws_byte_cursor fixed_input_cursor = aws_byte_cursor_from_buf(&fixed_input);
- if (aws_sha256_hmac_compute(allocator, &secret_cursor, &fixed_input_cursor, &fixed_input_hmac_digest, 0)) {
- break;
- }
- result = s_aws_derive_ecc_private_key(&private_key_buf, &fixed_input_hmac_digest);
- }
- if (result == AKDR_SUCCESS) {
- struct aws_byte_cursor private_key_cursor = aws_byte_cursor_from_buf(&private_key_buf);
- ecc_key_pair = aws_ecc_key_pair_new_from_private_key(allocator, AWS_CAL_ECDSA_P256, &private_key_cursor);
- }
- done:
- aws_byte_buf_clean_up_secure(&secret_buf);
- aws_byte_buf_clean_up_secure(&private_key_buf);
- aws_byte_buf_clean_up_secure(&fixed_input_hmac_digest);
- aws_byte_buf_clean_up(&fixed_input);
- return ecc_key_pair;
- }
|