in aws-encryption-sdk-cpp/source/kms_mrk_keyring.cpp [377:608]
static int OnEncrypt(
struct aws_cryptosdk_keyring *keyring,
struct aws_allocator *request_alloc,
struct aws_byte_buf *unencrypted_data_key,
struct aws_array_list *keyring_trace,
struct aws_array_list *edk_list,
const struct aws_hash_table *enc_ctx,
enum aws_cryptosdk_alg_id alg) {
if (!keyring || !request_alloc || !unencrypted_data_key || !edk_list || !enc_ctx) {
abort();
}
auto self = static_cast<KmsMrkAwareSymmetricKeyringImpl *>(keyring);
//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.7
//# This function MUST fail.
if (is_discovery(self)) {
AWS_LOGSTREAM_ERROR(AWS_CRYPTO_SDK_KMS_MRK_CLASS_TAG, "Cannot encrypt with a KMS keyring in discovery mode");
return aws_raise_error(AWS_CRYPTOSDK_ERR_BAD_STATE);
}
Private::ListRaii my_edks(aws_cryptosdk_edk_list_init, aws_cryptosdk_edk_list_clean_up);
Private::ListRaii my_keyring_trace(aws_cryptosdk_keyring_trace_init, aws_cryptosdk_keyring_trace_clean_up);
int rv = my_edks.Create(request_alloc);
if (rv) return rv;
rv = my_keyring_trace.Create(request_alloc);
if (rv) return rv;
const auto enc_ctx_cpp = aws_map_from_c_aws_hash_table(enc_ctx);
//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.7
//# If the input encryption materials (structures.md#encryption-
//# materials) do not contain a plaintext data key OnEncrypt MUST attempt
//# to generate a new plaintext data key by calling AWS KMS
//# GenerateDataKey (https://docs.aws.amazon.com/kms/latest/APIReference/
//# API_GenerateDataKey.html).
bool generated_new_data_key = false;
if (!unencrypted_data_key->buffer) {
const struct aws_cryptosdk_alg_properties *alg_prop = aws_cryptosdk_alg_props(alg);
if (alg_prop == NULL) {
AWS_LOGSTREAM_ERROR(AWS_CRYPTO_SDK_KMS_MRK_CLASS_TAG, "Invalid encryption materials algorithm properties");
return aws_raise_error(AWS_CRYPTOSDK_ERR_CRYPTO_UNKNOWN);
}
// Already checked on keyring build that this will succeed.
Aws::String key_kms_region = Private::parse_region_from_kms_key_arn(self->key_id);
//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.7
//# If the keyring calls AWS KMS GenerateDataKeys, it MUST use the
//# configured AWS KMS client to make the call.
std::function<void()> report_success;
auto kms_client = self->kms_client_supplier->GetClient(key_kms_region, report_success);
if (!kms_client) {
/* Client supplier is allowed to return NULL if, for example, user wants to exclude particular
* regions. But if we are here it means that user configured keyring with a KMS key that was
* incompatible with the client supplier in use.
*/
return aws_raise_error(AWS_CRYPTOSDK_ERR_BAD_STATE);
}
//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.7
//# The keyring MUST call
//# AWS KMS GenerateDataKeys with a request constructed as follows:
Aws::KMS::Model::GenerateDataKeyRequest kms_request;
kms_request.WithKeyId(self->key_id)
.WithNumberOfBytes((int)alg_prop->data_key_len)
.WithEncryptionContext(enc_ctx_cpp)
.WithGrantTokens(self->grant_tokens);
Aws::KMS::Model::GenerateDataKeyOutcome outcome = kms_client->GenerateDataKey(kms_request);
//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.7
//# If the call to AWS KMS GenerateDataKey
//# (https://docs.aws.amazon.com/kms/latest/APIReference/
//# API_GenerateDataKey.html) does not succeed, OnEncrypt MUST NOT modify
//# the encryption materials (structures.md#encryption-materials) and
//# MUST fail.
if (!outcome.IsSuccess()) {
AWS_LOGSTREAM_ERROR(AWS_CRYPTO_SDK_KMS_MRK_CLASS_TAG, "Invalid encryption materials algorithm properties");
return aws_raise_error(AWS_CRYPTOSDK_ERR_KMS_FAILURE);
}
//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.7
//# If the Generate Data Key call succeeds, OnEncrypt MUST verify that
//# the response "Plaintext" length matches the specification of the
//# algorithm suite (algorithm-suites.md)'s Key Derivation Input Length
//# field.
if (outcome.GetResult().GetPlaintext().GetLength() != alg_prop->content_key_len) {
AWS_LOGSTREAM_ERROR(AWS_CRYPTO_SDK_KMS_MRK_CLASS_TAG, "Malformed plaintext in response");
return aws_raise_error(AWS_CRYPTOSDK_ERR_KMS_FAILURE);
}
//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.7
//# The Generate Data Key response's "KeyId" MUST be A valid AWS
//# KMS key ARN (aws-kms-key-arn.md#identifying-an-aws-kms-multi-region-
//# key).
Aws::Utils::ARN response_key_arn(outcome.GetResult().GetKeyId());
if (!Private::is_valid_kms_key_arn(response_key_arn)) {
AWS_LOGSTREAM_ERROR(AWS_CRYPTO_SDK_KMS_MRK_CLASS_TAG, "Malformed key ARN in response");
return aws_raise_error(AWS_CRYPTOSDK_ERR_KMS_FAILURE);
}
report_success();
//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.7
//# If verified, OnEncrypt MUST do the following with the response
//# from AWS KMS GenerateDataKey
//# (https://docs.aws.amazon.com/kms/latest/APIReference/
//# API_GenerateDataKey.html):
// * append a new [encrypted data key](structures.md#encrypted-data-key) to the encrypted data key
// list in the [encryption materials](structures.md#encryption-materials)
rv = append_key_dup_to_edks(
request_alloc,
&my_edks.list,
&outcome.GetResult().GetCiphertextBlob(),
&outcome.GetResult().GetKeyId(),
&self->key_provider);
if (rv != AWS_OP_SUCCESS) return rv;
// * set the plaintext data key on the [encryption materials](structures.md#encryption-materials) as the
// response Plaintext
rv = aws_byte_buf_dup_from_aws_utils(request_alloc, unencrypted_data_key, outcome.GetResult().GetPlaintext());
if (rv != AWS_OP_SUCCESS) return rv;
generated_new_data_key = true;
aws_cryptosdk_keyring_trace_add_record_c_str(
request_alloc,
&my_keyring_trace.list,
KEY_PROVIDER_STR,
self->key_id.c_str(),
AWS_CRYPTOSDK_WRAPPING_KEY_GENERATED_DATA_KEY | AWS_CRYPTOSDK_WRAPPING_KEY_ENCRYPTED_DATA_KEY |
AWS_CRYPTOSDK_WRAPPING_KEY_SIGNED_ENC_CTX);
//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.7
//# * OnEncrypt MUST output the modified encryption materials
//# (structures.md#encryption-materials)
// (implicit)
} else {
const auto unencrypted_data_key_cpp = aws_utils_byte_buffer_from_c_aws_byte_buf(unencrypted_data_key);
//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.7
//# Given a plaintext data key in the encryption materials
//# (structures.md#encryption-materials), OnEncrypt MUST attempt to
//# encrypt the plaintext data key using the configured AWS KMS key
//# identifier.
// Already checked on keyring build that this will succeed.
Aws::String key_kms_region = Private::parse_region_from_kms_key_arn(self->key_id);
//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.7
//# The keyring MUST call AWS KMS Encrypt
//# (https://docs.aws.amazon.com/kms/latest/APIReference/
//# API_Encrypt.html) using the configured AWS KMS client.
std::function<void()> report_success;
auto kms_client = self->kms_client_supplier->GetClient(key_kms_region, report_success);
if (!kms_client) {
/* Client supplier is allowed to return NULL if, for example, user wants to exclude particular
* regions. But if we are here it means that user configured keyring with a KMS key that was
* incompatible with the client supplier in use.
*/
rv = aws_raise_error(AWS_CRYPTOSDK_ERR_BAD_STATE);
goto out;
}
//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.7
//# The keyring
//# MUST AWS KMS Encrypt call with a request constructed as follows:
Aws::KMS::Model::EncryptRequest kms_request;
kms_request.WithKeyId(self->key_id)
.WithPlaintext(unencrypted_data_key_cpp)
.WithEncryptionContext(enc_ctx_cpp)
.WithGrantTokens(self->grant_tokens);
//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.7
//# If the call to AWS KMS Encrypt
//# (https://docs.aws.amazon.com/kms/latest/APIReference/
//# API_Encrypt.html) does not succeed, OnEncrypt MUST fail.
Aws::KMS::Model::EncryptOutcome outcome = kms_client->Encrypt(kms_request);
if (!outcome.IsSuccess()) {
AWS_LOGSTREAM_ERROR(
AWS_CRYPTO_SDK_KMS_MRK_CLASS_TAG,
"KMS encryption error : " << outcome.GetError().GetExceptionName()
<< " Message: " << outcome.GetError().GetMessage());
rv = aws_raise_error(AWS_CRYPTOSDK_ERR_KMS_FAILURE);
goto out;
}
//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.7
//# If the Encrypt call succeeds The response's "KeyId" MUST be A valid
//# AWS KMS key ARN (aws-kms-key-arn.md#identifying-an-aws-kms-multi-
//# region-key).
if (!Private::is_valid_kms_key_arn(outcome.GetResult().GetKeyId())) {
AWS_LOGSTREAM_ERROR(AWS_CRYPTO_SDK_KMS_MRK_CLASS_TAG, "Malformed key ARN in response");
return aws_raise_error(AWS_CRYPTOSDK_ERR_KMS_FAILURE);
}
report_success();
//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.7
//# If verified, OnEncrypt MUST do the following with the
//# response from AWS KMS Encrypt
//# (https://docs.aws.amazon.com/kms/latest/APIReference/
//# API_Encrypt.html):
// * append a new [encrypted data key](structures.md#encrypted-data-key)
// to the encrypted data key list in the
// [encryption materials](structures.md#encryption-materials)
rv = append_key_dup_to_edks(
request_alloc,
&my_edks.list,
&outcome.GetResult().GetCiphertextBlob(),
&outcome.GetResult().GetKeyId(),
&self->key_provider);
if (rv != AWS_OP_SUCCESS) {
goto out;
}
aws_cryptosdk_keyring_trace_add_record_c_str(
request_alloc,
&my_keyring_trace.list,
KEY_PROVIDER_STR,
self->key_id.c_str(),
AWS_CRYPTOSDK_WRAPPING_KEY_ENCRYPTED_DATA_KEY | AWS_CRYPTOSDK_WRAPPING_KEY_SIGNED_ENC_CTX);
}
//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.7
//# If all Encrypt calls succeed, OnEncrypt MUST output the modified
//# encryption materials (structures.md#encryption-materials).
// (implicit)
rv = aws_cryptosdk_transfer_list(edk_list, &my_edks.list);
if (rv == AWS_OP_SUCCESS) {
aws_cryptosdk_transfer_list(keyring_trace, &my_keyring_trace.list);
}
out:
if (rv != AWS_OP_SUCCESS && generated_new_data_key) {
aws_byte_buf_clean_up(unencrypted_data_key);
}
return rv;
}