"use strict";
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
Object.defineProperty(exports, "__esModule", { value: true });
exports.AwsKmsMrkAwareSymmetricDiscoveryKeyringClass = void 0;
const material_management_1 = require("@aws-crypto/material-management");
const arn_parsing_1 = require("./arn_parsing");
const helpers_1 = require("./helpers");
function AwsKmsMrkAwareSymmetricDiscoveryKeyringClass(BaseKeyring) {
    class AwsKmsMrkAwareSymmetricDiscoveryKeyring
    //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.5
    //# MUST implement that AWS Encryption SDK Keyring interface (../keyring-
    //# interface.md#interface)
     extends BaseKeyring {
        //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.6
        //# On initialization the caller MUST provide:
        constructor({ client, grantTokens, discoveryFilter, }) {
            super();
            //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.6
            //# The keyring MUST know what Region the AWS KMS client is in.
            //
            //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.6
            //# It
            //# SHOULD obtain this information directly from the client as opposed to
            //# having an additional parameter.
            //
            //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.6
            //# However if it can not, then it MUST
            //# NOT create the client itself.
            //
            //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.6
            //# It SHOULD have a Region parameter and
            //# SHOULD try to identify mismatched configurations.
            //
            // @ts-ignore the V3 client has set the config to protected
            const clientRegion = client.config.region;
            (0, material_management_1.needs)(clientRegion, 'Client must be configured to a region.');
            /* Precondition: The AwsKmsMrkAwareSymmetricDiscoveryKeyring Discovery filter *must* be able to match something.
             * I am not going to wait to tell you
             * that no CMK can match an empty account list.
             * e.g. [], [''], '' are not valid.
             */
            (0, material_management_1.needs)(!discoveryFilter ||
                (discoveryFilter.accountIDs &&
                    discoveryFilter.accountIDs.length &&
                    !!discoveryFilter.partition &&
                    discoveryFilter.accountIDs.every((a) => typeof a === 'string' && !!a)), 'A discovery filter must be able to match something.');
            (0, material_management_1.readOnlyProperty)(this, 'client', client);
            // AWS SDK v3 stores the clientRegion behind an async function
            if (typeof clientRegion == 'function') {
                /* Postcondition: Store the AWS SDK V3 region promise as the clientRegion.
                 * This information MUST be communicated to OnDecrypt.
                 * If a caller creates a keyring,
                 * and then calls OnDecrypt all in synchronous code
                 * then the v3 region will not have been able to resolve.
                 * If clientRegion was null,
                 * then the keyring would filter out all EDKs
                 * because the region does not match.
                 */
                this.clientRegion = clientRegion().then((region) => {
                    /* Postcondition: Resolve the AWS SDK V3 region promise and update clientRegion. */
                    (0, material_management_1.readOnlyProperty)(this, 'clientRegion', region);
                    /* Postcondition: Resolve the promise with the value set. */
                    return region;
                });
            }
            else {
                (0, material_management_1.readOnlyProperty)(this, 'clientRegion', clientRegion);
            }
            (0, material_management_1.readOnlyProperty)(this, 'grantTokens', grantTokens);
            (0, material_management_1.readOnlyProperty)(this, 'discoveryFilter', discoveryFilter
                ? Object.freeze({
                    ...discoveryFilter,
                    accountIDs: Object.freeze(discoveryFilter.accountIDs),
                })
                : discoveryFilter);
        }
        async _onEncrypt() {
            //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.7
            //# This function MUST fail.
            throw new Error('AwsKmsMrkAwareSymmetricDiscoveryKeyring cannot be used to encrypt');
        }
        //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.8
        //# OnDecrypt MUST take decryption materials (structures.md#decryption-
        //# materials) and a list of encrypted data keys
        //# (structures.md#encrypted-data-key) as input.
        async _onDecrypt(material, encryptedDataKeys) {
            //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.8
            //# If the decryption materials (structures.md#decryption-materials)
            //# already contained a valid plaintext data key OnDecrypt MUST
            //# immediately return the unmodified decryption materials
            //# (structures.md#decryption-materials).
            if (material.hasValidKey())
                return material;
            // See the constructor, this is to support both AWS SDK v2 and v3.
            (0, material_management_1.needs)(typeof this.clientRegion === 'string' ||
                /* Precondition: AWS SDK V3 region promise MUST have resolved to a string.
                 * In the constructor the region promise resolves
                 * to the same value that is then set.
                 */
                // @ts-ignore
                typeof (await this.clientRegion) == 'string', 'clientRegion MUST be a string.');
            const { client, grantTokens, clientRegion } = this;
            //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.8
            //# The set of encrypted data keys MUST first be filtered to match this
            //# keyring's configuration.
            const decryptableEDKs = encryptedDataKeys.filter(filterEDKs(this));
            const cmkErrors = [];
            //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.8
            //# For each encrypted data key in the filtered set, one at a time, the
            //# OnDecrypt MUST attempt to decrypt the data key.
            for (const edk of decryptableEDKs) {
                //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.8
                //# Otherwise it MUST
                //# be the provider info.
                let keyId = edk.providerInfo;
                //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.8
                //# *  "KeyId": If the provider info's resource type is "key" and its
                //# resource is a multi-Region key then a new ARN MUST be created
                //# where the region part MUST equal the AWS KMS client region and
                //# every other part MUST equal the provider info.
                const keyArn = (0, arn_parsing_1.parseAwsKmsKeyArn)(edk.providerInfo);
                (0, material_management_1.needs)(keyArn, 'Unexpected EDK ProviderInfo for AWS KMS EDK');
                if ((0, arn_parsing_1.isMultiRegionAwsKmsArn)(keyArn)) {
                    keyId = (0, arn_parsing_1.constructArnInOtherRegion)(keyArn, clientRegion);
                }
                let dataKey = false;
                try {
                    //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.8
                    //# When calling AWS KMS Decrypt
                    //# (https://docs.aws.amazon.com/kms/latest/APIReference/
                    //# API_Decrypt.html), the keyring MUST call with a request constructed
                    //# as follows:
                    dataKey = await (0, helpers_1.decrypt)(
                    //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.8
                    //# To attempt to decrypt a particular encrypted data key
                    //# (structures.md#encrypted-data-key), OnDecrypt MUST call AWS KMS
                    //# Decrypt (https://docs.aws.amazon.com/kms/latest/APIReference/
                    //# API_Decrypt.html) with the configured AWS KMS client.
                    client, {
                        providerId: edk.providerId,
                        providerInfo: keyId,
                        encryptedDataKey: edk.encryptedDataKey,
                    }, material.encryptionContext, grantTokens);
                    /* This should be impossible given that decrypt only returns false if the client supplier does
                     * or if the providerId is not "aws-kms", which we have already filtered out
                     */
                    if (!dataKey)
                        continue;
                    //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.8
                    //# *  The "KeyId" field in the response MUST equal the requested "KeyId"
                    (0, material_management_1.needs)(dataKey.KeyId === keyId, 'KMS Decryption key does not match the requested key id.');
                    const flags = material_management_1.KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY |
                        material_management_1.KeyringTraceFlag.WRAPPING_KEY_VERIFIED_ENC_CTX;
                    const trace = {
                        keyNamespace: helpers_1.KMS_PROVIDER_ID,
                        keyName: dataKey.KeyId,
                        flags,
                    };
                    //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.8
                    //# *  The length of the response's "Plaintext" MUST equal the key
                    //# derivation input length (algorithm-suites.md#key-derivation-input-
                    //# length) specified by the algorithm suite (algorithm-suites.md)
                    //# included in the input decryption materials
                    //# (structures.md#decryption-materials).
                    //
                    //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.8
                    //# Since the response does satisfies these requirements then OnDecrypt
                    //# MUST do the following with the response:
                    //
                    // setUnencryptedDataKey will throw if the plaintext does not match the algorithm suite requirements.
                    material.setUnencryptedDataKey(dataKey.Plaintext, trace);
                    return material;
                }
                catch (e) {
                    //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.8
                    //# If the response does not satisfies these requirements then an error
                    //# is collected and the next encrypted data key in the filtered set MUST
                    //# be attempted.
                    cmkErrors.push({ errPlus: e });
                }
            }
            //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.8
            //# If OnDecrypt fails to successfully decrypt any encrypted data key
            //# (structures.md#encrypted-data-key), then it MUST yield an error that
            //# includes all collected errors.
            (0, material_management_1.needs)(material.hasValidKey(), [
                `Unable to decrypt data key${!decryptableEDKs.length ? ': No EDKs supplied' : ''}.`,
                ...cmkErrors.map((e, i) => `Error #${i + 1}  \n${e.errPlus.stack}`),
            ].join('\n'));
            return material;
        }
    }
    (0, material_management_1.immutableClass)(AwsKmsMrkAwareSymmetricDiscoveryKeyring);
    return AwsKmsMrkAwareSymmetricDiscoveryKeyring;
}
exports.AwsKmsMrkAwareSymmetricDiscoveryKeyringClass = AwsKmsMrkAwareSymmetricDiscoveryKeyringClass;
function filterEDKs({ discoveryFilter, clientRegion, }) {
    return function filter({ providerId, providerInfo }) {
        //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.8
        //# *  Its provider ID MUST exactly match the value "aws-kms".
        if (providerId !== helpers_1.KMS_PROVIDER_ID)
            return false;
        //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.8
        //# *  The provider info MUST be a valid AWS KMS ARN (aws-kms-key-
        //# arn.md#a-valid-aws-kms-arn) with a resource type of "key" or
        //# OnDecrypt MUST fail.
        const edkArn = (0, arn_parsing_1.parseAwsKmsKeyArn)(providerInfo);
        (0, material_management_1.needs)(edkArn && edkArn.ResourceType === 'key', 'Unexpected EDK ProviderInfo for AWS KMS EDK');
        const { AccountId: account, Partition: partition, Region: edkRegion, } = edkArn;
        //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.8
        //# *  If the provider info is not identified as a multi-Region key (aws-
        //# kms-key-arn.md#identifying-an-aws-kms-multi-region-key), then the
        //# provider info's Region MUST match the AWS KMS client region.
        if (!(0, arn_parsing_1.isMultiRegionAwsKmsArn)(edkArn) && clientRegion !== edkRegion) {
            return false;
        }
        //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.8
        //# *  If a discovery filter is configured, its partition and the
        //# provider info partition MUST match.
        //
        //= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.8
        //# *  If a discovery filter is configured, its set of accounts MUST
        //# contain the provider info account.
        return (!discoveryFilter ||
            (discoveryFilter.partition === partition &&
                discoveryFilter.accountIDs.includes(account)));
    };
}
//# sourceMappingURL=data:application/json;base64, 