コード例 #1
0
def _key_resource_match(key1, key2):
    """Given two KMS key identifiers, determines whether they use the same key type resource ID.
    This method works with either bare key IDs or key ARNs; if an input cannot be parsed as an ARN
    it is assumed to be a bare key ID. Will output false if either input is an alias arn.
    """
    try:
        arn1 = arn_from_str(key1)
        if arn1.resource_type == "alias":
            return False
        resource_id_1 = arn1.resource_id
    except MalformedArnError:
        # We need to handle the case where the key id is not ARNs,
        # treat it as a bare id
        resource_id_1 = key1
    try:
        arn2 = arn_from_str(key2)
        if arn2.resource_type == "alias":
            return False
        resource_id_2 = arn2.resource_id
    except MalformedArnError:
        # We need to handle the case where the key id is not ARNs,
        # treat it as a bare id
        resource_id_2 = key2

    return resource_id_1 == resource_id_2
コード例 #2
0
def _check_mrk_arns_equal(key1, key2):
    """Given two KMS key arns, determines whether they refer to related KMS MRKs.
    Returns an error if inputs are not equal and either input cannot be parsed as an ARN.
    """
    # //= compliance/framework/aws-kms/aws-kms-mrk-match-for-decrypt.txt#2.5
    # //# The caller MUST provide:
    if key1 == key2:
        # //= compliance/framework/aws-kms/aws-kms-mrk-match-for-decrypt.txt#2.5
        # //# If both identifiers are identical, this function MUST return "true".
        return True

    # Note that we will fail here if the input keys are not ARNs at this point
    arn1 = arn_from_str(key1)
    arn2 = arn_from_str(key2)

    if not arn1.indicates_multi_region_key(
    ) or not arn2.indicates_multi_region_key():
        # //= 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".
        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.
    return (arn1.partition == arn2.partition and arn1.service == arn2.service
            and arn1.account_id == arn2.account_id
            and arn1.resource_type == arn2.resource_type
            and arn1.resource_id == arn2.resource_id)
コード例 #3
0
 def validate_config(self):
     """Validates the provided configuration."""
     # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key.txt#2.6
     # //# The AWS KMS
     # //# key identifier MUST be a valid identifier (aws-kms-key-arn.md#a-
     # //# valid-aws-kms-identifier).
     # If it starts with "arn:" ensure it's a valid arn by attempting to parse it.
     # Otherwise, we don't do any validation on bare ids or bare aliases.
     if self._key_id.startswith("arn:"):
         arn_from_str(self._key_id)
コード例 #4
0
    def test_malformed_arn_missing_arn(self):
        # //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.5
        # //= type=test
        # //# MUST start with string "arn"
        arn = ":aws:kms:us-east-1:222222222222:key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb"

        with pytest.raises(MalformedArnError) as excinfo:
            arn_from_str(arn)
        excinfo.match("Resource {} could not be parsed as an ARN".format(arn))
        excinfo.match("Missing 'arn' string")
コード例 #5
0
    def test_malformed_arn_unknown_resource_type(self):
        # //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.5
        # //= type=test
        # //# The resource type MUST be either "alias" or "key"
        arn = "arn:aws:kms:us-east-1:222222222222:s3bucket/foo"

        with pytest.raises(MalformedArnError) as excinfo:
            arn_from_str(arn)
        excinfo.match("Resource {} could not be parsed as an ARN".format(arn))
        excinfo.match("Unknown resource type")
コード例 #6
0
    def test_parse_key_arn_missing_partition(self):
        arn = "arn::kms:us-east-1:222222222222:key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb"
        # //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.5
        # //= type=test
        # //# The partition MUST be a non-empty

        with pytest.raises(MalformedArnError) as excinfo:
            arn_from_str(arn)
        excinfo.match("Resource {} could not be parsed as an ARN".format(arn))
        excinfo.match("Missing partition")
コード例 #7
0
    def test_malformed_arn_service_not_kms(self):
        # //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.5
        # //= type=test
        # //# The service MUST be the string "kms"
        arn = "arn:aws:notkms:us-east-1:222222222222:key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb"

        with pytest.raises(MalformedArnError) as excinfo:
            arn_from_str(arn)
        excinfo.match("Resource {} could not be parsed as an ARN".format(arn))
        excinfo.match("Unknown service")
コード例 #8
0
    def test_malformed_arn_missing_account(self):
        # //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.5
        # //= type=test
        # //# The account MUST be a non-empty string
        arn = "arn:aws:kms:us-east-1::key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb"

        with pytest.raises(MalformedArnError) as excinfo:
            arn_from_str(arn)
        excinfo.match("Resource {} could not be parsed as an ARN".format(arn))
        excinfo.match("Missing account")
コード例 #9
0
    def test_malformed_arn_missing_resource_type(self):
        # //= compliance/framework/aws-kms/aws-kms-key-arn.txt#2.5
        # //= type=test
        # //# The resource section MUST be non-empty and MUST be split by a
        # //# single "/" any additional "/" are included in the resource id
        arn = "arn:aws:kms:us-east-1:222222222222:"

        with pytest.raises(MalformedArnError) as excinfo:
            arn_from_str(arn)
        excinfo.match("Resource {} could not be parsed as an ARN".format(arn))
        excinfo.match("Missing resource")
    def test_add_master_keys_mrk_with_discovery_region(self):
        """Check that an MRK-aware provider with an explicit discovery_region uses its configured region when creating
        new keys if the requested keys are MRKs."""
        grant_tokens = (sentinel.grant_token2, sentinel.grant_token2)
        original_arn = arn_from_str(
            "arn:aws:kms:eu-west-2:222222222222:key/mrk-aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb"
        )
        configured_region = "us-east-1"
        provider = MRKAwareDiscoveryAwsKmsMasterKeyProvider(
            discovery_region=configured_region, grant_tokens=grant_tokens)
        master_key = provider._new_master_key(original_arn.to_string())

        # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.7
        # //= type=test
        # //# In discovery mode a AWS KMS MRK Aware Master Key (aws-kms-mrk-aware-
        # //# master-key.md) MUST be returned configured with
        assert master_key.__class__ == MRKAwareKMSMasterKey
        self.mock_boto3_session.assert_called_with(botocore_session=ANY)
        self.mock_boto3_session_instance.client.assert_called_with(
            "kms",
            region_name=configured_region,
            config=provider._user_agent_adding_config,
        )
        assert configured_region in master_key._key_id
        assert original_arn.region not in master_key._key_id
        assert master_key.config.grant_tokens is grant_tokens
コード例 #11
0
    def _new_master_key(self, key_id):
        """Returns a KMSMasterKey for the specified key_id.

        :param bytes key_id: KMS CMK ID
        :returns: KMS Master Key based on key_id
        :rtype: aws_encryption_sdk.key_providers.kms.KMSMasterKey
        :raises InvalidKeyIdError: if key_id is not a valid KMS CMK ID to which this key provider has access
        :raises MasterKeyProviderError: if this MasterKeyProvider is in discovery mode and key_id is not allowed
        """
        _key_id = to_str(key_id)  # KMS client requires str, not bytes

        if self.config.discovery_filter:
            arn = arn_from_str(_key_id)

            if (arn.partition != self.config.discovery_filter.partition
                    or arn.account_id
                    not in self.config.discovery_filter.account_ids):
                # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.7
                # //# In discovery mode if a discovery filter is configured the requested AWS
                # //# KMS key ARN's "partition" MUST match the discovery filter's
                # //# "partition" and the AWS KMS key ARN's "account" MUST exist in the
                # //# discovery filter's account id set.
                raise MasterKeyProviderError(
                    "Key {} not allowed by this Master Key Provider".format(
                        key_id))
        return self._new_master_key_impl(key_id)
コード例 #12
0
    def test_decrypt_data_key_unsuccessful_srks_different_region(
            self, config_class, key_class):
        """For SRKs, if the configured key id is identical to the EDK key except for region, no provider should treat
        them as equivalent (since they are SRKs). This is a slightly more specific case than the general "mismatched
        key id" in a previous test.

        Note that the chances of having two identical SRK key ids from different regions is tiny, but we should handle
        the case anyway."""
        key_id1 = VALUES["arn"]
        arn = arn_from_str(VALUES["arn_str"])
        arn.region = "ap-southeast-1"
        key_id2 = arn.to_string()

        # Config uses the first SRK
        config = config_class(key_id=key_id1, client=self.mock_client)
        test = key_class(config=config)

        # EDK contains the second SRK
        self.mock_encrypted_data_key.key_provider.key_info = key_id2

        with pytest.raises(DecryptKeyError) as excinfo:
            test._decrypt_data_key(
                encrypted_data_key=self.mock_encrypted_data_key,
                algorithm=self.mock_algorithm)
        excinfo.match(
            "Cannot decrypt EDK wrapped by .*, because it does not match this provider"
        )
    def test_add_master_keys_mrk_sdk_default(self):
        """Check that an MRK-aware provider without an explicit discovery_region uses its default region when creating
        new keys if the requested keys are MRKs."""
        grant_tokens = (sentinel.grant_token2, sentinel.grant_token2)
        original_arn = arn_from_str(
            "arn:aws:kms:eu-west-2:222222222222:key/mrk-aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb"
        )
        with patch.object(self.mock_botocore_session,
                          "get_config_variable",
                          return_value="us-west-2") as mock_get_config:
            provider = MRKAwareDiscoveryAwsKmsMasterKeyProvider(
                botocore_session=self.mock_botocore_session,
                grant_tokens=grant_tokens)
            mock_get_config.assert_called_once_with("region")

            master_key = provider._new_master_key(original_arn.to_string())

            # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.7
            # //= type=test
            # //# Otherwise if the mode is discovery then
            # //# the AWS Region MUST be the discovery MRK region.

            assert master_key.__class__ == MRKAwareKMSMasterKey
            self.mock_boto3_session.assert_called_with(botocore_session=ANY)
            self.mock_boto3_session_instance.client.assert_called_with(
                "kms",
                region_name=provider.default_region,
                config=provider._user_agent_adding_config,
            )
            assert provider.default_region in master_key._key_id
            assert original_arn.region not in master_key._key_id
            assert master_key.config.grant_tokens is grant_tokens
    def test_decrypt_failure_discovery_disallowed_partition(self):
        """Test that a KMS Master Key Provider in filtered discovery mode fails to
        decrypt an EDK if the EDK was wrapped by a KMS Master Key in an
        AWS partition that is not allowed by the filter.
        """
        cmk_arn = get_cmk_arn()
        encrypt_provider = StrictAwsKmsMasterKeyProvider(key_ids=[cmk_arn])

        ciphertext, _ = aws_encryption_sdk.EncryptionSDKClient().encrypt(
            source=VALUES["plaintext_128"],
            key_provider=encrypt_provider,
            encryption_context=VALUES["encryption_context"],
            frame_length=1024,
        )

        # Check that we can decrypt the ciphertext using the original provider
        plaintext, _ = aws_encryption_sdk.EncryptionSDKClient().decrypt(
            source=ciphertext, key_provider=encrypt_provider
        )
        assert plaintext == VALUES["plaintext_128"]

        # Check that we cannot decrypt the ciphertext using a discovery provider that does not match this key's
        # partition
        arn = arn_from_str(cmk_arn)
        discovery_filter = DiscoveryFilter(partition="aws-cn", account_ids=[arn.account_id])
        decrypt_provider = DiscoveryAwsKmsMasterKeyProvider(discovery_filter=discovery_filter)

        with pytest.raises(MasterKeyProviderError) as excinfo:
            aws_encryption_sdk.EncryptionSDKClient().decrypt(source=ciphertext, key_provider=decrypt_provider)
        excinfo.match("not allowed by this Master Key Provider")
    def test_decrypt_success_discovery_filter(self):
        """Test that a Discovery KMS Master Key Provider in filtered discovery mode can
        decrypt a ciphertext when it is configured with the correct account id and partition.
        """
        cmk_arn = get_cmk_arn()
        encrypt_provider = StrictAwsKmsMasterKeyProvider(key_ids=[cmk_arn])

        ciphertext, _ = aws_encryption_sdk.EncryptionSDKClient().encrypt(
            source=VALUES["plaintext_128"],
            key_provider=encrypt_provider,
            encryption_context=VALUES["encryption_context"],
            frame_length=1024,
        )

        # Check that we can decrypt the ciphertext using the original provider
        plaintext, _ = aws_encryption_sdk.EncryptionSDKClient().decrypt(
            source=ciphertext, key_provider=encrypt_provider
        )
        assert plaintext == VALUES["plaintext_128"]

        # Check that we can decrypt the ciphertext using a discovery provider that allows this account and partition
        arn = arn_from_str(cmk_arn)
        discovery_filter = DiscoveryFilter(partition=arn.partition, account_ids=[arn.account_id])
        decrypt_provider = DiscoveryAwsKmsMasterKeyProvider(discovery_filter=discovery_filter)

        plaintext, _ = aws_encryption_sdk.EncryptionSDKClient().decrypt(
            source=ciphertext, key_provider=decrypt_provider
        )
        assert plaintext == VALUES["plaintext_128"]
コード例 #16
0
    def _generate_data_key(self, algorithm, encryption_context=None):
        """Generates data key and returns plaintext and ciphertext of key.

        :param algorithm: Algorithm on which to base data key
        :type algorithm: aws_encryption_sdk.identifiers.Algorithm
        :param dict encryption_context: Encryption context to pass to KMS
        :returns: Generated data key
        :rtype: aws_encryption_sdk.structures.DataKey
        """
        kms_params = self._build_generate_data_key_request(
            algorithm, encryption_context)
        # Catch any boto3 errors and normalize to expected EncryptKeyError
        try:
            response = self.config.client.generate_data_key(**kms_params)
            # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key.txt#2.10
            # //# The response's "Plaintext" MUST be the plaintext in the output.
            plaintext = response["Plaintext"]
            # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key.txt#2.10
            # //# The response's cipher text blob MUST be used as the returned as the
            # //# ciphertext for the encrypted data key in the output.
            ciphertext = response["CiphertextBlob"]
            key_id = response["KeyId"]
        except (ClientError, KeyError):
            error_message = "Master Key {key_id} unable to generate data key".format(
                key_id=self._key_id)
            _LOGGER.exception(error_message)
            raise GenerateKeyError(error_message)

        # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key.txt#2.10
        # //# The response's "KeyId" MUST be valid.
        # arn_from_str will error if given an invalid key ARN
        try:
            key_id_str = to_str(key_id)
            arn_from_str(key_id_str)
        except MalformedArnError:
            error_message = "Retrieved an unexpected KeyID in response from KMS: {key_id}".format(
                key_id=key_id)
            _LOGGER.exception(error_message)
            raise GenerateKeyError(error_message)

        return DataKey(
            key_provider=MasterKeyInfo(provider_id=self.provider_id,
                                       key_info=key_id),
            data_key=plaintext,
            encrypted_data_key=ciphertext,
        )
コード例 #17
0
    def _encrypt_data_key(self, data_key, algorithm, encryption_context=None):
        """Encrypts a data key and returns the ciphertext.

        :param data_key: Unencrypted data key
        :type data_key: :class:`aws_encryption_sdk.structures.RawDataKey`
            or :class:`aws_encryption_sdk.structures.DataKey`
        :param algorithm: Placeholder to maintain API compatibility with parent
        :param dict encryption_context: Encryption context to pass to KMS
        :returns: Data key containing encrypted data key
        :rtype: aws_encryption_sdk.structures.EncryptedDataKey
        :raises EncryptKeyError: if Master Key is unable to encrypt data key
        """
        kms_params = self._build_encrypt_request(data_key, encryption_context)
        # Catch any boto3 errors and normalize to expected EncryptKeyError
        try:
            response = self.config.client.encrypt(**kms_params)
            # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key.txt#2.11
            # //# The response's cipher text blob MUST be used as the "ciphertext" for the
            # //# encrypted data key.
            ciphertext = response["CiphertextBlob"]
            key_id = response["KeyId"]
        except (ClientError, KeyError):
            error_message = "Master Key {key_id} unable to encrypt data key".format(
                key_id=self._key_id)
            _LOGGER.exception(error_message)
            raise EncryptKeyError(error_message)

        # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key.txt#2.11
        # //# The AWS KMS Encrypt response MUST contain a valid "KeyId".
        # arn_from_str will error if given an invalid key ARN
        try:
            key_id_str = to_str(key_id)
            arn_from_str(key_id_str)
        except MalformedArnError:
            error_message = "Retrieved an unexpected KeyID in response from KMS: {key_id}".format(
                key_id=key_id)
            _LOGGER.exception(error_message)
            raise EncryptKeyError(error_message)

        return EncryptedDataKey(key_provider=MasterKeyInfo(
            provider_id=self.provider_id, key_info=key_id),
                                encrypted_data_key=ciphertext)
コード例 #18
0
    def test_parse_alias_arn_success(self):
        arn_str = "arn:aws:kms:us-east-1:222222222222:alias/aws/service"

        arn = arn_from_str(arn_str)

        assert arn.partition == "aws"
        assert arn.service == "kms"
        assert arn.region == "us-east-1"
        assert arn.account_id == "222222222222"
        assert arn.resource_type == "alias"
        assert arn.resource_id == "aws/service"
コード例 #19
0
    def test_parse_key_arn_success(self):
        arn_str = "arn:aws:kms:us-east-1:222222222222:key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb"

        arn = arn_from_str(arn_str)

        assert arn.partition == "aws"
        assert arn.service == "kms"
        assert arn.region == "us-east-1"
        assert arn.account_id == "222222222222"
        assert arn.resource_type == "key"
        assert arn.resource_id == "aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb"
コード例 #20
0
def encrypt_decrypt(key_arn, source_plaintext, botocore_session=None):
    """Encrypts a string under one KMS customer master key (CMK), then decrypts it using discovery mode.

    :param str key_arn: Amazon Resource Name (ARN) of the KMS CMK
    :param bytes source_plaintext: Data to encrypt
    :param botocore_session: existing botocore session instance
    :type botocore_session: botocore.session.Session
    """
    encrypt_kwargs = dict(key_ids=[key_arn])

    if botocore_session is not None:
        encrypt_kwargs["botocore_session"] = botocore_session

    # Set up an encryption client with an explicit commitment policy. Note that if you do not explicitly choose a
    # commitment policy, REQUIRE_ENCRYPT_REQUIRE_DECRYPT is used by default.
    client = aws_encryption_sdk.EncryptionSDKClient(commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT)

    # Create strict master key provider that is only allowed to encrypt and decrypt using the ARN of the provided key.
    strict_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(**encrypt_kwargs)

    # Encrypt the plaintext using the AWS Encryption SDK. It returns the encrypted message and the header
    ciphertext, encrypted_message_header = client.encrypt(source=source_plaintext, key_provider=strict_key_provider)

    # Create a second master key provider in discovery mode that does not explicitly list the key used to encrypt.
    # Note: The discovery_filter argument is optional; if you omit this, the AWS Encryption SDK attempts to
    # decrypt any ciphertext it receives.
    arn = arn_from_str(key_arn)
    decrypt_kwargs = dict(discovery_filter=DiscoveryFilter(account_ids=[arn.account_id], partition=arn.partition))
    if botocore_session is not None:
        encrypt_kwargs["botocore_session"] = botocore_session
    discovery_key_provider = aws_encryption_sdk.DiscoveryAwsKmsMasterKeyProvider(**decrypt_kwargs)

    # Decrypt the encrypted message using the AWS Encryption SDK. It returns the decrypted message and the header.
    plaintext, decrypted_message_header = client.decrypt(source=ciphertext, key_provider=discovery_key_provider)

    # Verify that the original message and the decrypted message are the same
    assert source_plaintext == plaintext

    # Verify that the encryption context of the encrypted message and decrypted message match
    assert all(
        pair in encrypted_message_header.encryption_context.items()
        for pair in decrypted_message_header.encryption_context.items()
    )
コード例 #21
0
    def _new_master_key_impl(self, key_id):
        """Creation of new master keys. Compared to the base class, this class has smarts to use either the configured
        discovery region or, if not present, the default SDK region, to create new keys.
        """
        # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.7
        # //# In discovery mode, the requested
        # //# AWS KMS key identifier MUST be a well formed AWS KMS ARN.
        _key_id = to_str(key_id)
        arn = arn_from_str(_key_id)

        # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.7
        # //# In discovery mode a AWS KMS MRK Aware Master Key (aws-kms-mrk-aware-
        # //# master-key.md) MUST be returned configured with
        # Note that in the MRK case we ensure the key ID passed along has the discovery region,
        # and in both cases _client(...) will ensure that a client is created that matches the key's region.

        if not arn.resource_id.startswith("mrk"):
            # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.7
            # //# Otherwise if the requested AWS KMS key
            # //# identifier is identified as a multi-Region key (aws-kms-key-
            # //# arn.md#identifying-an-aws-kms-multi-region-key), then AWS Region MUST
            # //# be the region from the AWS KMS key ARN stored in the provider info
            # //# from the encrypted data key.
            # Note that this could return a normal KMSMasterKey and retain the same behavior,
            # however we opt to follow the spec here in order to bias towards consistency between
            # implementations.
            return MRKAwareKMSMasterKey(config=MRKAwareKMSMasterKeyConfig(
                key_id=_key_id,
                client=self._client(_key_id),
                grant_tokens=self.config.grant_tokens))
        else:
            # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key-provider.txt#2.7
            # //# Otherwise if the mode is discovery then
            # //# the AWS Region MUST be the discovery MRK region.
            arn.region = self.config.discovery_region
            new_key_id = arn.to_string()

            return MRKAwareKMSMasterKey(config=MRKAwareKMSMasterKeyConfig(
                key_id=new_key_id,
                client=self._client(new_key_id),
                grant_tokens=self.config.grant_tokens))
コード例 #22
0
    def _new_master_key(self, key_id):
        """Returns a KMSMasterKey for the specified key_id.

        :param bytes key_id: KMS CMK ID
        :returns: KMS Master Key based on key_id
        :rtype: aws_encryption_sdk.key_providers.kms.KMSMasterKey
        :raises InvalidKeyIdError: if key_id is not a valid KMS CMK ID to which this key provider has access
        :raises MasterKeyProviderError: if this MasterKeyProvider is in discovery mode and key_id is not allowed
        """
        _key_id = to_str(key_id)  # KMS client requires str, not bytes

        if self.config.discovery_filter:
            arn = arn_from_str(_key_id)

            if (arn.partition != self.config.discovery_filter.partition
                    or arn.account_id
                    not in self.config.discovery_filter.account_ids):
                raise MasterKeyProviderError(
                    "Key {} not allowed by this Master Key Provider".format(
                        key_id))

        return KMSMasterKey(config=KMSMasterKeyConfig(
            key_id=key_id, client=self._client(_key_id)))
コード例 #23
0
 def test_config_default_client(self):
     """KMSMasterKeys do not require passing a client."""
     test = KMSMasterKeyConfig(key_id=VALUES["arn"])
     arn = arn_from_str(VALUES["arn_str"])
     assert test.client._client_config.region_name == arn.region
コード例 #24
0
    def test_arn_round_trip_alias(self):
        arn_str = "arn:aws:kms:us-east-1:222222222222:alias/aws/service"
        arn = arn_from_str(arn_str)
        arn_str_2 = arn.to_string()

        assert arn_str == arn_str_2
コード例 #25
0
    def test_arn_round_trip_key_id(self):
        arn_str = "arn:aws:kms:us-east-1:222222222222:key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb"
        arn = arn_from_str(arn_str)
        arn_str_2 = arn.to_string()

        assert arn_str == arn_str_2
コード例 #26
0
    def _decrypt_data_key(self,
                          encrypted_data_key,
                          algorithm,
                          encryption_context=None):
        """Decrypts an encrypted data key and returns the plaintext.

        :param data_key: Encrypted data key
        :type data_key: aws_encryption_sdk.structures.EncryptedDataKey
        :type algorithm: `aws_encryption_sdk.identifiers.Algorithm` (not used for KMS)
        :param dict encryption_context: Encryption context to use in decryption
        :returns: Decrypted data key
        :rtype: aws_encryption_sdk.structures.DataKey
        :raises DecryptKeyError: if Master Key is unable to decrypt data key
        """
        # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key.txt#2.9
        # //# Additionally each 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".
        edk_key_id = to_str(encrypted_data_key.key_provider.key_info)
        edk_arn = arn_from_str(edk_key_id)
        if not edk_arn.resource_type == "key":
            error_message = "AWS KMS Provider EDK contains unexpected key_id: {key_id}".format(
                key_id=edk_key_id)
            _LOGGER.exception(error_message)
            raise DecryptKeyError(error_message)

        self._validate_allowed_to_decrypt(edk_key_id)
        kms_params = self._build_decrypt_request(encrypted_data_key,
                                                 encryption_context)
        # Catch any boto3 errors and normalize to expected DecryptKeyError
        try:
            response = self.config.client.decrypt(**kms_params)

            # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key.txt#2.9
            # //# If the call succeeds then the response's "KeyId" MUST be equal to the
            # //# configured AWS KMS key identifier otherwise the function MUST collect
            # //# an error.
            # Note that Python logs but does not collect errors
            returned_key_id = response["KeyId"]
            if returned_key_id != self._key_id:
                error_message = "AWS KMS returned unexpected key_id {returned} (expected {key_id})".format(
                    returned=returned_key_id, key_id=self._key_id)
                _LOGGER.exception(error_message)
                raise DecryptKeyError(error_message)

            # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key.txt#2.9
            # //# The response's "Plaintext"'s length MUST equal the length
            # //# required by the requested algorithm suite otherwise the function MUST
            # //# collect an error.
            # Note that Python logs but does not collect errors
            plaintext = response["Plaintext"]
            if len(plaintext) != algorithm.data_key_len:
                error_message = "Plaintext length ({len1}) does not match algorithm's expected length ({len2})".format(
                    len1=len(plaintext), len2=algorithm.data_key_len)
                raise DecryptKeyError(error_message)

        except (ClientError, KeyError):
            error_message = "Master Key {key_id} unable to decrypt data key".format(
                key_id=self._key_id)
            _LOGGER.exception(error_message)
            raise DecryptKeyError(error_message)
        return DataKey(
            key_provider=self.key_provider,
            data_key=plaintext,
            encrypted_data_key=encrypted_data_key.encrypted_data_key)
コード例 #27
0
def encrypt_decrypt(mrk_arn, mrk_arn_second_region, source_plaintext):
    """Illustrates usage of KMS Multi-Region Keys.

    :param str mrk_arn: Amazon Resource Name (ARN) of the first KMS MRK
    :param str mrk_arn_second_region: Amazon Resource Name (ARN) of a related KMS MRK in a different region
    :param bytes source_plaintext: Data to encrypt
    """
    # Encrypt in the first region

    # Set up an encryption client with an explicit commitment policy. Note that if you do not explicitly choose a
    # commitment policy, REQUIRE_ENCRYPT_REQUIRE_DECRYPT is used by default.
    client = aws_encryption_sdk.EncryptionSDKClient(
        commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT)

    # For this example, set mrk_arn to be a Multi-Region key.
    # Multi-Region keys have a distinctive key ID that begins with 'mrk'.
    # For example: "arn:aws:kms:us-east-1:111122223333:key/mrk-1234abcd12ab34cd56ef1234567890ab".

    # Create a Strict Multi-Region Key Aware Master Key Provider which targets the Multi-Region key ARN.
    kwargs = dict(key_ids=[mrk_arn])
    strict_key_provider = MRKAwareStrictAwsKmsMasterKeyProvider(**kwargs)

    # Encrypt the plaintext using the AWS Encryption SDK. It returns the encrypted message and the header
    ciphertext, _ = client.encrypt(source=source_plaintext,
                                   key_provider=strict_key_provider)

    # Decrypt in a second region

    # For this example, set mrk_arn_second_region to be Multi-Region key related to key_arn.
    # Related multi-Region keys have the same key ID. Their key ARNs differs only in the Region field.
    # For example: "arn:aws:kms:us-west-2:111122223333:key/mrk-1234abcd12ab34cd56ef1234567890ab"

    # Create a Strict Multi-Region Key Aware Master Key Provider which targets the Multi-Region key in the second region
    kwargs = dict(key_ids=[mrk_arn_second_region])
    strict_key_provider_region_2 = MRKAwareStrictAwsKmsMasterKeyProvider(
        **kwargs)

    # Decrypt your ciphertext
    plaintext, _ = client.decrypt(source=ciphertext,
                                  key_provider=strict_key_provider_region_2)

    # Verify that the original message and the decrypted message are the same
    assert source_plaintext == plaintext

    # Decrypt in discovery mode in a second region

    # First determine what region you want to perform discovery in, as well as what
    # accounts and partition you want to allow if using a Discovery Filter.
    # In this example, we just want to use whatever region, account, and partition
    # our second key is in, in order to ensure we can discover it.
    # Note that the ARN itself is never used in the configuration.
    arn = arn_from_str(mrk_arn_second_region)
    discovery_region = arn.region
    filter_accounts = [arn.account_id]
    filter_partition = arn.partition

    # Configure a Discovery Region and optional Discovery Filter
    decrypt_kwargs = dict(
        discovery_filter=DiscoveryFilter(account_ids=filter_accounts,
                                         partition=filter_partition),
        discovery_region=discovery_region,
    )

    # Create an MRK-aware master key provider in discovery mode that targets the second region.
    # This will cause the provider to try to decrypt using this region whenever it encounters an MRK.
    discovery_key_provider = MRKAwareDiscoveryAwsKmsMasterKeyProvider(
        **decrypt_kwargs)

    # Decrypt the encrypted message using the AWS Encryption SDK. It returns the decrypted message and the header.
    plaintext, _ = client.decrypt(source=ciphertext,
                                  key_provider=discovery_key_provider)

    # Verify that the original message and the decrypted message are the same
    assert source_plaintext == plaintext