/* * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). You may not use * this file except in compliance with the License. A copy of the License is * located at * * http://aws.amazon.com/apache2.0/ * * or in the "license" file accompanying this file. This file is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include namespace Aws { namespace Cryptosdk { namespace Private { static const Aws::String KMS_STR = "kms"; static const Aws::String MRK_STR = "mrk-"; Aws::String aws_string_from_c_aws_byte_buf(const struct aws_byte_buf *byte_buf) { return Aws::String(reinterpret_cast(byte_buf->buffer), byte_buf->len); } Aws::Utils::ByteBuffer aws_utils_byte_buffer_from_c_aws_byte_buf(const struct aws_byte_buf *byte_buf) { return Aws::Utils::ByteBuffer(byte_buf->buffer, byte_buf->len); } Aws::String aws_string_from_c_aws_string(const struct aws_string *c_aws_string) { return Aws::String(reinterpret_cast(aws_string_bytes(c_aws_string)), c_aws_string->len); } int aws_byte_buf_dup_from_aws_utils( struct aws_allocator *allocator, struct aws_byte_buf *dest, const Aws::Utils::ByteBuffer &src) { struct aws_byte_buf data_key_bb = aws_byte_buf_from_array(src.GetUnderlyingData(), src.GetLength()); return aws_byte_buf_init_copy(dest, allocator, &data_key_bb); } Aws::Map aws_map_from_c_aws_hash_table(const struct aws_hash_table *hash_table) { Aws::Map result; if (hash_table == NULL) { return result; } for (struct aws_hash_iter iter = aws_hash_iter_begin(hash_table); !aws_hash_iter_done(&iter); aws_hash_iter_next(&iter)) { const struct aws_string *key = (struct aws_string *)iter.element.key; const struct aws_string *value = (struct aws_string *)iter.element.value; result[aws_string_from_c_aws_string(key)] = aws_string_from_c_aws_string(value); } return result; } int append_aws_byte_buf_key_dup_to_edks( struct aws_allocator *allocator, struct aws_array_list *encrypted_data_keys, const struct aws_byte_buf *encrypted_data_key, const struct aws_byte_buf *data_key_id, const struct aws_byte_buf *key_provider) { struct aws_cryptosdk_edk edk {}; edk.provider_id = { 0 }; edk.provider_info = { 0 }; edk.ciphertext = { 0 }; if (aws_byte_buf_init_copy(&edk.provider_id, allocator, key_provider) != AWS_OP_SUCCESS || aws_byte_buf_init_copy(&edk.provider_info, allocator, data_key_id) != AWS_OP_SUCCESS || aws_byte_buf_init_copy(&edk.ciphertext, allocator, encrypted_data_key) != AWS_OP_SUCCESS || aws_array_list_push_back(encrypted_data_keys, &edk) != AWS_OP_SUCCESS) { aws_cryptosdk_edk_clean_up(&edk); return AWS_OP_ERR; } return AWS_OP_SUCCESS; } int append_key_dup_to_edks( struct aws_allocator *allocator, struct aws_array_list *encrypted_data_keys, const Utils::ByteBuffer *encrypted_data_key, const Aws::String *data_key_id, const struct aws_byte_buf *key_provider) { // although this functions will not copy, append_aws_byte_buf_key_dup_to_edks will create a duplicate // of enc_data_key_byte, data_key_id_byte and key_provider before appending them struct aws_byte_buf enc_data_key_byte = aws_byte_buf_from_array(encrypted_data_key->GetUnderlyingData(), encrypted_data_key->GetLength()); struct aws_byte_buf data_key_id_byte = aws_byte_buf_from_array((const uint8_t *)data_key_id->data(), data_key_id->length()); return append_aws_byte_buf_key_dup_to_edks( allocator, encrypted_data_keys, &enc_data_key_byte, &data_key_id_byte, key_provider); } /** * Compares an aws_byte_buf (byte_buf_b) with a sequence of characters (char_buf_a) * @param char_buf_a Sequence of characters * @param a_idx_start Start position in char_buf_a * @param a_idx_end End position in char_buf_a * @param byte_buf_b aws_byte_buf to compare with * @return true if the sequence of characters in char_buf_a+idx_start matches the byte_buf_b, false otherwise */ inline static bool aws_byte_buf_eq_char_array( const char *char_buf_a, size_t a_idx_start, size_t a_idx_end, const struct aws_byte_buf &byte_buf_b) { if (a_idx_end == std::string::npos || a_idx_start == std::string::npos || char_buf_a == NULL) { return false; } const struct aws_byte_buf byte_buf_a = aws_byte_buf_from_array((uint8_t *)(char_buf_a + a_idx_start), a_idx_end - a_idx_start); return aws_byte_buf_eq(&byte_buf_a, &byte_buf_b); } Aws::String parse_region_from_kms_key_arn(const Aws::String &key_id) { static const struct aws_byte_buf arn_str = aws_byte_buf_from_c_str("arn"); static const struct aws_byte_buf kms_str = aws_byte_buf_from_c_str("kms"); Aws::String rv; size_t idx_start = 0; size_t idx_end = 0; // first group is "arn" idx_end = key_id.find(':', idx_start); if (aws_byte_buf_eq_char_array(key_id.data(), idx_start, idx_end, arn_str) == false) { return rv; } idx_start = idx_end + 1; // second group is "aws" but can vary idx_end = key_id.find(':', idx_start); if (idx_end == std::string::npos) { return rv; } idx_start = idx_end + 1; // third group is "kms" idx_end = key_id.find(':', idx_start); if (aws_byte_buf_eq_char_array(key_id.data(), idx_start, idx_end, kms_str) == false) { return rv; } idx_start = idx_end + 1; // forth group is region idx_end = key_id.find(':', idx_start); if (idx_end == std::string::npos || idx_start >= idx_end) { return rv; } return Aws::String(key_id.data() + idx_start, idx_end - idx_start); } /** * Returns a vector containing the substrings before and after the first '/' * character in the given string, or an empty vector if the string does not * contain a '/' character. */ static Aws::Vector split_arn_resource(const Aws::String &resource) { auto parts = Utils::StringUtils::Split(resource, '/', 2); if (parts.size() != 2) { parts.clear(); } return parts; } /** * Returns true if the first string starts with the second string, or false * otherwise. */ bool starts_with(const Aws::String &s1, const Aws::String &s2) { return s1.size() >= s2.size() && s1.compare(0, s2.size(), s2) == 0; } bool is_valid_kms_key_arn(const Aws::Utils::ARN &key_arn) { if (!( //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.5 //# MUST start with string "arn" bool(key_arn) //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.5 //# The partition MUST be a non-empty && key_arn.GetPartition().size() > 0 //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.5 //# The service MUST be the string "kms" && key_arn.GetService() == "kms" //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.5 //# The region MUST be a non-empty string && key_arn.GetRegion().size() > 0 //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.5 //# The account MUST be a non-empty string && key_arn.GetAccountId().size() > 0 //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.5 //# The resource section MUST be non-empty and MUST be split by a //# single "/" any additional "/" are included in the resource id && key_arn.GetResource().size() > 0)) { return false; } const auto resource_parts = split_arn_resource(key_arn.GetResource()); return resource_parts.size() == 2 //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.5 //# The resource type MUST be either "alias" or "key" && (resource_parts[0] == "alias" || resource_parts[0] == "key") //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.5 //# The resource id MUST be a non-empty string && resource_parts[1].size() > 0; } bool is_valid_kms_identifier(const Aws::String &ident) { // Precondition: A KMS key ARN is a valid KMS key identifier. Aws::Utils::ARN arn(ident); if (is_valid_kms_key_arn(arn)) { return true; } // Precondition: A non-ARN identifier cannot contain a colon. if (ident.find(':') != std::string::npos) { return false; } // Precondition: A KMS key identifier with a forward slash must be an alias if (ident.find('/') != std::string::npos) { return starts_with(ident, "alias/"); } // Anything else must be a raw key ID return true; } //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.8 //# This function MUST take a single AWS KMS ARN bool is_kms_mrk_arn(const Aws::Utils::ARN &key_arn) { //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.8 //# If the input is an invalid AWS KMS ARN this function MUST error. if (!is_valid_kms_key_arn(key_arn)) { return false; } const auto resource_parts = split_arn_resource(key_arn.GetResource()); //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.8 //# If resource type is "alias", this is an AWS KMS alias ARN and MUST //# return false. if (resource_parts[0] == "alias") { return false; } //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.8 //# If resource type is "key" and resource ID starts with //# "mrk-", this is a AWS KMS multi-Region key ARN and MUST return true. if (resource_parts[0] == "key") { return starts_with(resource_parts[1], MRK_STR); } //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.8 //# If resource type is "key" and resource ID does not start with "mrk-", //# this is a (single-region) AWS KMS key ARN and MUST return false. return false; } //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.9 //# This function MUST take a single AWS KMS identifier bool is_kms_mrk_identifier(const Aws::String &key_id) { static const Aws::String arn_str = "arn:"; static const Aws::String alias_str = "alias/"; //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.9 //# If the input starts with "arn:", this MUST return the output of //# identifying an an AWS KMS multi-Region ARN (aws-kms-key- //# arn.md#identifying-an-an-aws-kms-multi-region-arn) called with this //# input. if (starts_with(key_id, arn_str)) { Aws::Utils::ARN key_arn(key_id); return is_kms_mrk_arn(key_arn); } //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.9 //# If the input starts with "alias/", this an AWS KMS alias and //# not a multi-Region key id and MUST return false. if (starts_with(key_id, alias_str)) { return false; } //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.9 //# If the input starts //# with "mrk-", this is a multi-Region key id and MUST return true. if (starts_with(key_id, MRK_STR)) { return true; } //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.9 //# If //# the input does not start with any of the above, this is not a multi- //# Region key id and MUST return false. return false; } //= compliance/framework/aws-kms/aws-kms-mrk-match-for-decrypt.txt#2.5 //# The caller MUST provide: bool kms_mrk_match_for_decrypt(const Aws::String &key_id_1, const Aws::String &key_id_2) { //= compliance/framework/aws-kms/aws-kms-mrk-match-for-decrypt.txt#2.5 //# If both identifiers are identical, this function MUST return "true". if (key_id_1 == key_id_2) return true; //= compliance/framework/aws-kms/aws-kms-mrk-match-for-decrypt.txt#2.5 //# Otherwise if either input is not identified as a multi-Region key //# (aws-kms-key-arn.md#identifying-an-aws-kms-multi-region-key), then //# this function MUST return "false". if (!is_kms_mrk_identifier(key_id_1) || !is_kms_mrk_identifier(key_id_2)) return false; //= compliance/framework/aws-kms/aws-kms-mrk-match-for-decrypt.txt#2.5 //# Otherwise if both inputs are //# identified as a multi-Region keys (aws-kms-key-arn.md#identifying-an- //# aws-kms-multi-region-key), this function MUST return the result of //# comparing the "partition", "service", "accountId", "resourceType", //# and "resource" parts of both ARN inputs. Aws::Utils::ARN key_arn_1(key_id_1); Aws::Utils::ARN key_arn_2(key_id_2); if (!key_arn_1 || !key_arn_2) return false; return ( key_arn_1.GetPartition() == key_arn_2.GetPartition() && key_arn_1.GetService() == key_arn_2.GetService() && key_arn_1.GetAccountId() == key_arn_2.GetAccountId() && key_arn_1.GetResource() == key_arn_2.GetResource()); } //= compliance/framework/aws-kms/aws-kms-mrk-are-unique.txt#2.5 //# The caller MUST provide: Aws::Vector find_duplicate_kms_mrk_ids(const Aws::Vector &key_ids) { Aws::Map> mrk_resource_id_to_key_ids; for (auto key_id = key_ids.begin(); key_id != key_ids.end(); key_id++) { Aws::Utils::ARN key_arn(*key_id); if (is_kms_mrk_arn(key_arn)) { auto resource_type_and_id = split_arn_resource(key_arn.GetResource()); mrk_resource_id_to_key_ids[resource_type_and_id[1]].push_back(*key_id); } else if (is_kms_mrk_identifier(*key_id)) { mrk_resource_id_to_key_ids[*key_id].push_back(*key_id); } } //= compliance/framework/aws-kms/aws-kms-mrk-are-unique.txt#2.5 //# If the list does not contain any multi-Region keys (aws-kms-key- //# arn.md#identifying-an-aws-kms-multi-region-key) this function MUST //# exit successfully. Aws::Vector duplicates; if (mrk_resource_id_to_key_ids.size() == 0) { return duplicates; } for (auto kv = mrk_resource_id_to_key_ids.begin(); kv != mrk_resource_id_to_key_ids.end(); kv++) { const auto resource_id = kv->first; const auto matching_key_ids = kv->second; if (matching_key_ids.size() < 2) { continue; } for (auto duplicate = matching_key_ids.begin(); duplicate != matching_key_ids.end(); duplicate++) { duplicates.push_back(*duplicate); } } //= compliance/framework/aws-kms/aws-kms-mrk-are-unique.txt#2.5 //# If there are zero duplicate resource ids between the multi-region //# keys, this function MUST exit successfully //= compliance/framework/aws-kms/aws-kms-mrk-are-unique.txt#2.5 //# If any duplicate multi-region resource ids exist, this function MUST //# yield an error that includes all identifiers with duplicate resource //# ids not only the first duplicate found. return duplicates; } ListRaii::~ListRaii() { if (initialized) clean_up_fn(&list); } int ListRaii::Create(struct aws_allocator *alloc) { int rv = init_fn(alloc, &list); if (!rv) initialized = true; return rv; } } // namespace Private } // namespace Cryptosdk } // namespace Aws