static int OnEncrypt()

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;
}