diff --git a/examples/src/keyrings/aws_kms_discovery_keyring_example.py b/examples/src/keyrings/aws_kms_discovery_keyring_example.py new file mode 100644 index 000000000..24dc111f1 --- /dev/null +++ b/examples/src/keyrings/aws_kms_discovery_keyring_example.py @@ -0,0 +1,206 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +This example sets up the AWS KMS Discovery Keyring + +AWS KMS discovery keyring is an AWS KMS keyring that doesn't specify any wrapping keys. + +The AWS Encryption SDK provides a standard AWS KMS discovery keyring and a discovery keyring +for AWS KMS multi-Region keys. For information about using multi-Region keys with the +AWS Encryption SDK, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/configure.html#config-mrks + +Because it doesn't specify any wrapping keys, a discovery keyring can't encrypt data. +If you use a discovery keyring to encrypt data, alone or in a multi-keyring, the encrypt +operation fails. + +When decrypting, a discovery keyring allows the AWS Encryption SDK to ask AWS KMS to decrypt +any encrypted data key by using the AWS KMS key that encrypted it, regardless of who owns or +has access to that AWS KMS key. The call succeeds only when the caller has kms:Decrypt +permission on the AWS KMS key. + +This example creates a KMS Keyring and then encrypts a custom input EXAMPLE_DATA +with an encryption context. This encrypted ciphertext is then decrypted using the Discovery keyring. +This example also includes some sanity checks for demonstration: +1. Ciphertext and plaintext data are not the same +2. Encryption context is correct in the decrypted message header +3. Decrypted plaintext value matches EXAMPLE_DATA +4. Decryption is only possible if the Discovery Keyring contains the correct AWS Account ID's to + which the KMS key used for encryption belongs +These sanity checks are for demonstration in the example only. You do not need these in your code. + +For more information on how to use KMS Discovery keyrings, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html#kms-keyring-discovery +""" +import sys + +import boto3 +from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders +from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig +from aws_cryptographic_materialproviders.mpl.models import ( + CreateAwsKmsDiscoveryKeyringInput, + CreateAwsKmsKeyringInput, + DiscoveryFilter, +) +from aws_cryptographic_materialproviders.mpl.references import IKeyring +from typing import Dict + +import aws_encryption_sdk +from aws_encryption_sdk import CommitmentPolicy +from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError + +# TODO-MPL: Remove this as part of removing PYTHONPATH hacks. +MODULE_ROOT_DIR = '/'.join(__file__.split("/")[:-1]) + +sys.path.append(MODULE_ROOT_DIR) + +EXAMPLE_DATA: bytes = b"Hello World" + + +def encrypt_and_decrypt_with_keyring( + kms_key_id: str, + aws_account_id: str, + aws_region: str +): + """Demonstrate an encrypt/decrypt cycle using an AWS KMS Discovery Keyring. + + Usage: encrypt_and_decrypt_with_keyring(kms_key_id, aws_account_id) + :param kms_key_id: KMS Key identifier for the KMS key you want to use for creating + the kms_keyring used for encryption + :type kms_key_id: string + :param aws_account_id: AWS Account ID to use in the discovery filter + :type aws_account_id: string + :param aws_region: AWS Region to use for the kms client + :type aws_region: string + + For more information on KMS Key identifiers, see + https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id + """ + # 1. Instantiate the encryption SDK client. + # This builds the client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy, + # which enforces that this client only encrypts using committing algorithm suites and enforces + # that this client will only decrypt encrypted messages that were created with a committing + # algorithm suite. + # This is the default commitment policy if you were to build the client as + # `client = aws_encryption_sdk.EncryptionSDKClient()`. + client = aws_encryption_sdk.EncryptionSDKClient( + commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT + ) + + # 2. Create a boto3 client for KMS. + kms_client = boto3.client('kms', region_name=aws_region) + + # 3. Create encryption context. + # Remember that your encryption context is NOT SECRET. + # For more information, see + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryption_context: Dict[str, str] = { + "encryption": "context", + "is not": "secret", + "but adds": "useful metadata", + "that can help you": "be confident that", + "the data you are handling": "is what you think it is", + } + + # 4. Create the keyring that determines how your data keys are protected. + # Although this example highlights Discovery keyrings, Discovery keyrings cannot + # be used to encrypt, so for encryption we create a KMS keyring without discovery mode. + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( + config=MaterialProvidersConfig() + ) + + kms_keyring_input: CreateAwsKmsKeyringInput = CreateAwsKmsKeyringInput( + kms_key_id=kms_key_id, + kms_client=kms_client + ) + + encrypt_kms_keyring: IKeyring = mat_prov.create_aws_kms_keyring( + input=kms_keyring_input + ) + + # 5. Encrypt the data with the encryptionContext + ciphertext, _ = client.encrypt( + source=EXAMPLE_DATA, + keyring=encrypt_kms_keyring, + encryption_context=encryption_context + ) + + # 6. Demonstrate that the ciphertext and plaintext are different. + # (This is an example for demonstration; you do not need to do this in your own code.) + assert ciphertext != EXAMPLE_DATA, \ + "Ciphertext and plaintext data are the same. Invalid encryption" + + # 7. Now create a Discovery keyring to use for decryption. We'll add a discovery filter + # so that we limit the set of ciphertexts we are willing to decrypt to only ones + # created by KMS keys in our account and partition. + + discovery_keyring_input: CreateAwsKmsDiscoveryKeyringInput = CreateAwsKmsDiscoveryKeyringInput( + kms_client=kms_client, + discovery_filter=DiscoveryFilter( + account_ids=[aws_account_id], + partition="aws" + ) + ) + + discovery_keyring: IKeyring = mat_prov.create_aws_kms_discovery_keyring( + input=discovery_keyring_input + ) + + # 8. Decrypt your encrypted data using the discovery keyring. + # On Decrypt, the header of the encrypted message (ciphertext) will be parsed. + # The header contains the Encrypted Data Keys (EDKs), which, if the EDK + # was encrypted by a KMS Keyring, includes the KMS Key ARN. + # The Discovery Keyring filters these EDKs for + # EDKs encrypted by Single Region OR Multi Region KMS Keys. + # If a Discovery Filter is present, these KMS Keys must belong + # to an AWS Account ID in the discovery filter's AccountIds and + # must be from the discovery filter's partition. + # Finally, KMS is called to decrypt each filtered EDK until an EDK is + # successfully decrypted. The resulting data key is used to decrypt the + # ciphertext's message. + # If all calls to KMS fail, the decryption fails. + plaintext_bytes, dec_header = client.decrypt( + source=ciphertext, + keyring=discovery_keyring + ) + + # 9. Demonstrate that the encryption context is correct in the decrypted message header + # (This is an example for demonstration; you do not need to do this in your own code.) + for k, v in encryption_context.items(): + assert v == dec_header.encryption_context[k], \ + "Encryption context does not match expected values" + + # 10. Demonstrate that the decrypted plaintext is identical to the original plaintext. + # (This is an example for demonstration; you do not need to do this in your own code.) + assert plaintext_bytes == EXAMPLE_DATA + + # 11. Demonstrate that if a discovery keyring (Bob's) doesn't have the correct AWS Account ID's, + # the decrypt will fail with an error message + # Note that this assumes Account ID used here ('888888888888') is different than the one used + # during encryption + discovery_keyring_input_bob: CreateAwsKmsDiscoveryKeyringInput = \ + CreateAwsKmsDiscoveryKeyringInput( + kms_client=kms_client, + discovery_filter=DiscoveryFilter( + account_ids=["888888888888"], + partition="aws" + ) + ) + + discovery_keyring_bob: IKeyring = mat_prov.create_aws_kms_discovery_keyring( + input=discovery_keyring_input_bob + ) + + # Decrypt the ciphertext using Bob's discovery keyring which doesn't contain the required + # Account ID's for the KMS keyring used for encryption. + # This should throw an AWSEncryptionSDKClientError exception + try: + plaintext_bytes, _ = client.decrypt( + source=ciphertext, + keyring=discovery_keyring_bob + ) + + raise AssertionError("Decrypt using discovery keyring with wrong AWS Account ID should" + + "raise AWSEncryptionSDKClientError") + except AWSEncryptionSDKClientError: + pass diff --git a/examples/src/keyrings/aws_kms_discovery_multi_keyring_example.py b/examples/src/keyrings/aws_kms_discovery_multi_keyring_example.py new file mode 100644 index 000000000..0830ecb58 --- /dev/null +++ b/examples/src/keyrings/aws_kms_discovery_multi_keyring_example.py @@ -0,0 +1,173 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +This example sets up the AWS KMS Discovery Multi Keyring and demonstrates decryption +using a Multi-Keyring containing multiple AWS KMS Discovery Keyrings. + +The AWS Encryption SDK provides a standard AWS KMS discovery keyring and a discovery keyring +for AWS KMS multi-Region keys. For information about using multi-Region keys with the +AWS Encryption SDK, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/configure.html#config-mrks + +Because it doesn't specify any wrapping keys, a discovery keyring can't encrypt data. +If you use a discovery keyring to encrypt data, alone or in a multi-keyring, the encrypt +operation fails. + +When decrypting, a discovery keyring allows the AWS Encryption SDK to ask AWS KMS to decrypt +any encrypted data key by using the AWS KMS key that encrypted it, regardless of who owns or +has access to that AWS KMS key. The call succeeds only when the caller has kms:Decrypt +permission on the AWS KMS key. + +This example creates a KMS Keyring and then encrypts a custom input EXAMPLE_DATA +with an encryption context. This encrypted ciphertext is then decrypted using the Discovery Multi +keyring. This example also includes some sanity checks for demonstration: +1. Ciphertext and plaintext data are not the same +2. Encryption context is correct in the decrypted message header +3. Decrypted plaintext value matches EXAMPLE_DATA +These sanity checks are for demonstration in the example only. You do not need these in your code. + +For more information on how to use KMS Discovery keyrings, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html#kms-keyring-discovery +""" +import sys + +import boto3 +from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders +from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig +from aws_cryptographic_materialproviders.mpl.models import ( + CreateAwsKmsDiscoveryMultiKeyringInput, + CreateAwsKmsKeyringInput, + DiscoveryFilter, +) +from aws_cryptographic_materialproviders.mpl.references import IKeyring +from typing import Dict + +import aws_encryption_sdk +from aws_encryption_sdk import CommitmentPolicy + +# TODO-MPL: Remove this as part of removing PYTHONPATH hacks. +MODULE_ROOT_DIR = '/'.join(__file__.split("/")[:-1]) + +sys.path.append(MODULE_ROOT_DIR) + +EXAMPLE_DATA: bytes = b"Hello World" + + +def encrypt_and_decrypt_with_keyring( + kms_key_id: str, + aws_account_id: str, + aws_regions: list[str] +): + """Demonstrate an encrypt/decrypt cycle using an AWS KMS Discovery Multi Keyring. + + Usage: encrypt_and_decrypt_with_keyring(kms_key_id, aws_account_id, aws_regions) + :param kms_key_id: KMS Key identifier for the KMS key you want to use for creating + the kms_keyring used for encryption + :type kms_key_id: string + :param aws_account_id: AWS Account ID to use in the discovery filter + :type aws_account_id: string + :param aws_regions: List of AWS Regions to use for creating the discovery multi keyring + :type aws_regions: list[string] + + For more information on KMS Key identifiers, see + https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id + """ + # 1. Instantiate the encryption SDK client. + # This builds the client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy, + # which enforces that this client only encrypts using committing algorithm suites and enforces + # that this client will only decrypt encrypted messages that were created with a committing + # algorithm suite. + # This is the default commitment policy if you were to build the client as + # `client = aws_encryption_sdk.EncryptionSDKClient()`. + client = aws_encryption_sdk.EncryptionSDKClient( + commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT + ) + + # 2. Create a boto3 client for KMS. + kms_client = boto3.client('kms', region_name="us-west-2") + + # 3. Create encryption context. + # Remember that your encryption context is NOT SECRET. + # For more information, see + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryption_context: Dict[str, str] = { + "encryption": "context", + "is not": "secret", + "but adds": "useful metadata", + "that can help you": "be confident that", + "the data you are handling": "is what you think it is", + } + + # 4. Create the keyring that determines how your data keys are protected. + # Although this example highlights Discovery keyrings, Discovery keyrings cannot + # be used to encrypt, so for encryption we create a KMS keyring without discovery mode. + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( + config=MaterialProvidersConfig() + ) + + kms_keyring_input: CreateAwsKmsKeyringInput = CreateAwsKmsKeyringInput( + kms_key_id=kms_key_id, + kms_client=kms_client + ) + + encrypt_kms_keyring: IKeyring = mat_prov.create_aws_kms_keyring( + input=kms_keyring_input + ) + + # 5. Encrypt the data with the encryptionContext + ciphertext, _ = client.encrypt( + source=EXAMPLE_DATA, + keyring=encrypt_kms_keyring, + encryption_context=encryption_context + ) + + # 6. Demonstrate that the ciphertext and plaintext are different. + # (This is an example for demonstration; you do not need to do this in your own code.) + assert ciphertext != EXAMPLE_DATA, \ + "Ciphertext and plaintext data are the same. Invalid encryption" + + # 7. Now create a Discovery Multi keyring to use for decryption. We'll add a discovery filter + # so that we limit the set of ciphertexts we are willing to decrypt to only ones + # created by KMS keys in our account and partition. + discovery_multi_keyring_input: CreateAwsKmsDiscoveryMultiKeyringInput = \ + CreateAwsKmsDiscoveryMultiKeyringInput( + regions=aws_regions, + discovery_filter=DiscoveryFilter( + account_ids=[aws_account_id], + partition="aws" + ) + ) + + # This is a Multi Keyring composed of Discovery Keyrings. + # There is a keyring for every region in `regions`. + # All the keyrings have the same Discovery Filter. + # Each keyring has its own KMS Client, which is created for the keyring's region. + discovery_multi_keyring: IKeyring = mat_prov.create_aws_kms_discovery_multi_keyring( + input=discovery_multi_keyring_input + ) + + # 8. On Decrypt, the header of the encrypted message (ciphertext) will be parsed. + # The header contains the Encrypted Data Keys (EDKs), which, if the EDK + # was encrypted by a KMS Keyring, includes the KMS Key ARN. + # For each member of the Multi Keyring, every EDK will try to be decrypted until a decryption + # is successful. + # Since every member of the Multi Keyring is a Discovery Keyring: + # Each Keyring will filter the EDKs by the Discovery Filter + # For the filtered EDKs, the keyring will try to decrypt it with the keyring's client. + # All of this is done serially, until a success occurs or all keyrings have + # failed all (filtered) EDKs. + # KMS Discovery Keyrings will attempt to decrypt Multi Region Keys (MRKs) and regular KMS Keys. + plaintext_bytes, dec_header = client.decrypt( + source=ciphertext, + keyring=discovery_multi_keyring + ) + + # 9. Demonstrate that the encryption context is correct in the decrypted message header + # (This is an example for demonstration; you do not need to do this in your own code.) + for k, v in encryption_context.items(): + assert v == dec_header.encryption_context[k], \ + "Encryption context does not match expected values" + + # 10. Demonstrate that the decrypted plaintext is identical to the original plaintext. + # (This is an example for demonstration; you do not need to do this in your own code.) + assert plaintext_bytes == EXAMPLE_DATA diff --git a/examples/src/keyrings/aws_kms_multi_keyring_example.py b/examples/src/keyrings/aws_kms_multi_keyring_example.py new file mode 100644 index 000000000..0e556f5e3 --- /dev/null +++ b/examples/src/keyrings/aws_kms_multi_keyring_example.py @@ -0,0 +1,204 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +This example sets up the AWS KMS Multi Keyring made up of multiple AWS KMS Keyrings. + +A multi-keyring is a keyring that consists of one or more individual keyrings of the +same or a different type. The effect is like using several keyrings in a series. +When you use a multi-keyring to encrypt data, any of the wrapping keys in any of its +keyrings can decrypt that data. + +When you create a multi-keyring to encrypt data, you designate one of the keyrings as +the generator keyring. All other keyrings are known as child keyrings. The generator keyring +generates and encrypts the plaintext data key. Then, all of the wrapping keys in all of the +child keyrings encrypt the same plaintext data key. The multi-keyring returns the plaintext +key and one encrypted data key for each wrapping key in the multi-keyring. If you create a +multi-keyring with no generator keyring, you can use it to decrypt data, but not to encrypt. +If the generator keyring is a KMS keyring, the generator key in the AWS KMS keyring generates +and encrypts the plaintext key. Then, all additional AWS KMS keys in the AWS KMS keyring, +and all wrapping keys in all child keyrings in the multi-keyring, encrypt the same plaintext key. + +When decrypting, the AWS Encryption SDK uses the keyrings to try to decrypt one of the encrypted +data keys. The keyrings are called in the order that they are specified in the multi-keyring. +Processing stops as soon as any key in any keyring can decrypt an encrypted data key. + +This example creates a Multi Keyring and then encrypts a custom input EXAMPLE_DATA +with an encryption context. This example also includes some sanity checks for demonstration: +1. Ciphertext and plaintext data are not the same +2. Decryption of ciphertext is possible using the multi_keyring, + and every one of the keyrings from the multi_keyring separately +3. All decrypted plaintext value match EXAMPLE_DATA +These sanity checks are for demonstration in the example only. You do not need these in your code. + +This example creates a multi_keyring using a KMS keyring as generator keyring and +another KMS keyring as a child keyring. + +For more information on how to use Multi keyrings, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-multi-keyring.html +""" +import sys + +import boto3 +from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders +from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig +from aws_cryptographic_materialproviders.mpl.models import CreateAwsKmsKeyringInput, CreateAwsKmsMultiKeyringInput +from aws_cryptographic_materialproviders.mpl.references import IKeyring +from typing import Dict + +import aws_encryption_sdk +from aws_encryption_sdk import CommitmentPolicy + +# TODO-MPL: Remove this as part of removing PYTHONPATH hacks. +MODULE_ROOT_DIR = '/'.join(__file__.split("/")[:-1]) + +sys.path.append(MODULE_ROOT_DIR) + +EXAMPLE_DATA: bytes = b"Hello World" + + +def encrypt_and_decrypt_with_keyring( + default_region_kms_key_id: str, + second_region_kms_key_id: str, + default_region: str, + second_region: str +): + """Demonstrate an encrypt/decrypt cycle using an AWS KMS Multi keyring. + The multi_keyring is created using a KMS keyring as generator keyring and another KMS keyring + as a child keyring. For this example, `default_region_kms_key_id` is the generator key id + for a KMS key located in your default region, and `second_region_kms_key_id` is the KMS key id + for a KMS Key located in some second region. + + Usage: encrypt_and_decrypt_with_keyring(default_region_kms_key_id, + second_region_kms_key_id, + default_region, + second_region) + :param default_region_kms_key_id: KMS Key identifier for the default region KMS key you want to + use as a generator keyring + :type default_region_kms_key_id: string + :param second_region_kms_key_id: KMS Key identifier for the second region KMS key you want to + use as a child keyring + :type second_region_kms_key_id: string + :param default_region: AWS Region for the default region KMS key + :type default_region: string + :param second_region: AWS Region for the second region KMS key + :type second_region: string + + For more information on KMS Key identifiers, see + https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id + """ + # 1. Instantiate the encryption SDK client. + # This builds the client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy, + # which enforces that this client only encrypts using committing algorithm suites and enforces + # that this client will only decrypt encrypted messages that were created with a committing + # algorithm suite. + # This is the default commitment policy if you were to build the client as + # `client = aws_encryption_sdk.EncryptionSDKClient()`. + client = aws_encryption_sdk.EncryptionSDKClient( + commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT + ) + + # 2. Create encryption context. + # Remember that your encryption context is NOT SECRET. + # For more information, see + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryption_context: Dict[str, str] = { + "encryption": "context", + "is not": "secret", + "but adds": "useful metadata", + "that can help you": "be confident that", + "the data you are handling": "is what you think it is", + } + + # 3. Create an AwsKmsMultiKeyring that protects your data under two different KMS Keys. + # Either KMS Key individually is capable of decrypting data encrypted under this Multi Keyring. + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( + config=MaterialProvidersConfig() + ) + + kms_multi_keyring_input: CreateAwsKmsMultiKeyringInput = CreateAwsKmsMultiKeyringInput( + generator=default_region_kms_key_id, + kms_key_ids=[second_region_kms_key_id] + ) + + kms_multi_keyring: IKeyring = mat_prov.create_aws_kms_multi_keyring( + input=kms_multi_keyring_input + ) + + # 4. Encrypt the data with the encryptionContext + ciphertext, _ = client.encrypt( + source=EXAMPLE_DATA, + keyring=kms_multi_keyring, + encryption_context=encryption_context + ) + + # 5. Demonstrate that the ciphertext and plaintext are different. + # (This is an example for demonstration; you do not need to do this in your own code.) + assert ciphertext != EXAMPLE_DATA, \ + "Ciphertext and plaintext data are the same. Invalid encryption" + + # 6a. Decrypt your encrypted data using the same multi_keyring you used on encrypt. + plaintext_bytes_multi_keyring, _ = client.decrypt( + source=ciphertext, + keyring=kms_multi_keyring + ) + + # 6b. Demonstrate that the decrypted plaintext is identical to the original plaintext. + # (This is an example for demonstration; you do not need to do this in your own code.) + assert plaintext_bytes_multi_keyring == EXAMPLE_DATA + + # Because you used a multi_keyring on Encrypt, you can use either of the two + # kms keyrings individually to decrypt the data. + + # 7. Demonstrate that you can successfully decrypt data using a KMS keyring with just the + # `default_region_kms_key_id` directly. + # (This is an example for demonstration; you do not need to do this in your own code.) + + # 7a. Create a boto3 client for KMS for the default region. + default_region_kms_client = boto3.client('kms', region_name=default_region) + + # 7b. Create KMS keyring + default_region_kms_keyring_input: CreateAwsKmsKeyringInput = CreateAwsKmsKeyringInput( + kms_key_id=default_region_kms_key_id, + kms_client=default_region_kms_client + ) + + default_region_kms_keyring: IKeyring = mat_prov.create_aws_kms_keyring( + input=default_region_kms_keyring_input + ) + + # 7c. Decrypt your encrypted data using the default_region_kms_keyring. + plaintext_bytes_default_region_kms_keyring, _ = client.decrypt( + source=ciphertext, + keyring=default_region_kms_keyring + ) + + # 7d. Demonstrate that the decrypted plaintext is identical to the original plaintext. + # (This is an example for demonstration; you do not need to do this in your own code.) + assert plaintext_bytes_default_region_kms_keyring == EXAMPLE_DATA + + # 8. Demonstrate that you can also successfully decrypt data using a KMS keyring with just the + # `second_region_kms_key_id` directly. + # (This is an example for demonstration; you do not need to do this in your own code.) + + # 8a. Create a boto3 client for KMS for the second region. + second_region_kms_client = boto3.client('kms', region_name=second_region) + + # 8b. Create KMS keyring + second_region_kms_keyring_input: CreateAwsKmsKeyringInput = CreateAwsKmsKeyringInput( + kms_key_id=second_region_kms_key_id, + kms_client=second_region_kms_client + ) + + second_region_kms_keyring: IKeyring = mat_prov.create_aws_kms_keyring( + input=second_region_kms_keyring_input + ) + + # 8c. Decrypt your encrypted data using the second_region_kms_keyring. + plaintext_bytes_second_region_kms_keyring, _ = client.decrypt( + source=ciphertext, + keyring=second_region_kms_keyring + ) + + # 8d. Demonstrate that the decrypted plaintext is identical to the original plaintext. + # (This is an example for demonstration; you do not need to do this in your own code.) + assert plaintext_bytes_second_region_kms_keyring == EXAMPLE_DATA diff --git a/examples/src/keyrings/aws_kms_rsa_keyring_example.py b/examples/src/keyrings/aws_kms_rsa_keyring_example.py new file mode 100644 index 000000000..5c0bbe736 --- /dev/null +++ b/examples/src/keyrings/aws_kms_rsa_keyring_example.py @@ -0,0 +1,125 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +This example sets up the AWS KMS RSA Keyring + +This example creates a KMS RSA Keyring and then encrypts a custom input +EXAMPLE_DATA with an encryption context. This example also includes some sanity checks for +demonstration: +1. Ciphertext and plaintext data are not the same +2. Encryption context is correct in the decrypted message header +3. Decrypted plaintext value matches EXAMPLE_DATA +These sanity checks are for demonstration in the example only. You do not need these in your code. + +# For more information on how to use KMS keyrings, see +# https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html +""" +import sys + +import boto3 +from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders +from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig +from aws_cryptographic_materialproviders.mpl.models import CreateAwsKmsRsaKeyringInput +from aws_cryptographic_materialproviders.mpl.references import IKeyring +from typing import Dict + +import aws_encryption_sdk +from aws_encryption_sdk import CommitmentPolicy +from aws_encryption_sdk.identifiers import AlgorithmSuite + +# TODO-MPL: Remove this as part of removing PYTHONPATH hacks. +MODULE_ROOT_DIR = '/'.join(__file__.split("/")[:-1]) + +sys.path.append(MODULE_ROOT_DIR) + +EXAMPLE_DATA: bytes = b"Hello World" + + +def encrypt_and_decrypt_with_keyring( + kms_rsa_key_id: str, + public_key: str +): + """Demonstrate an encrypt/decrypt cycle using an AWS KMS RSA keyring. + + Usage: encrypt_and_decrypt_with_keyring(kms_rsa_key_id, public_key) + :param kms_rsa_key_id: KMS RSA Key identifier for the KMS RSA key you want to use + :type kms_rsa_key_id: string + :param public_key: public key you want to use + :type public_key: string + + For more information on KMS Key identifiers, see + https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id + """ + # 1. Instantiate the encryption SDK client. + # This builds the client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy, + # which enforces that this client only encrypts using committing algorithm suites and enforces + # that this client will only decrypt encrypted messages that were created with a committing + # algorithm suite. + # This is the default commitment policy if you were to build the client as + # `client = aws_encryption_sdk.EncryptionSDKClient()`. + client = aws_encryption_sdk.EncryptionSDKClient( + commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT + ) + + # 2. Create a boto3 client for KMS. + kms_client = boto3.client('kms', region_name="us-west-2") + + # 3. Create encryption context. + # Remember that your encryption context is NOT SECRET. + # For more information, see + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryption_context: Dict[str, str] = { + "encryption": "context", + "is not": "secret", + "but adds": "useful metadata", + "that can help you": "be confident that", + "the data you are handling": "is what you think it is", + } + + # 4. Create a KMS RSA keyring + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( + config=MaterialProvidersConfig() + ) + + # Create the AWS KMS RSA keyring input + # For more information on the allowed encryption algorithms, please see + # https://docs.aws.amazon.com/kms/latest/developerguide/asymmetric-key-specs.html#key-spec-rsa + keyring_input: CreateAwsKmsRsaKeyringInput = CreateAwsKmsRsaKeyringInput( + public_key=public_key, + kms_key_id=kms_rsa_key_id, + encryption_algorithm="RSAES_OAEP_SHA_256", + kms_client=kms_client + ) + + kms_rsa_keyring: IKeyring = mat_prov.create_aws_kms_rsa_keyring( + input=keyring_input + ) + + # 5. Encrypt the data with the encryptionContext + ciphertext, _ = client.encrypt( + source=EXAMPLE_DATA, + keyring=kms_rsa_keyring, + encryption_context=encryption_context, + algorithm=AlgorithmSuite.AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ) + + # 6. Demonstrate that the ciphertext and plaintext are different. + # (This is an example for demonstration; you do not need to do this in your own code.) + assert ciphertext != EXAMPLE_DATA, \ + "Ciphertext and plaintext data are the same. Invalid encryption" + + # 7. Decrypt your encrypted data using the same keyring you used on encrypt. + plaintext_bytes, dec_header = client.decrypt( + source=ciphertext, + keyring=kms_rsa_keyring + ) + + # 8. Demonstrate that the encryption context is correct in the decrypted message header + # (This is an example for demonstration; you do not need to do this in your own code.) + for k, v in encryption_context.items(): + assert v == dec_header.encryption_context[k], \ + "Encryption context does not match expected values" + + # 9. Demonstrate that the decrypted plaintext is identical to the original plaintext. + # (This is an example for demonstration; you do not need to do this in your own code.) + assert plaintext_bytes == EXAMPLE_DATA diff --git a/examples/src/keyrings/hierarchical_keyring.py b/examples/src/keyrings/hierarchical_keyring.py index 32a6cbf8b..5642a0b71 100644 --- a/examples/src/keyrings/hierarchical_keyring.py +++ b/examples/src/keyrings/hierarchical_keyring.py @@ -154,7 +154,7 @@ def encrypt_and_decrypt_with_keyring( "the data you are handling": "is what you think it is", } - # 8. Encrypt the data for encryptionContextA & encryptionContextB + # 8. Encrypt the data with encryptionContextA & encryptionContextB ciphertext_A, _ = client.encrypt( source=EXAMPLE_DATA, keyring=hierarchical_keyring, diff --git a/examples/src/keyrings/multi_keyring_example.py b/examples/src/keyrings/multi_keyring_example.py new file mode 100644 index 000000000..58e4839ac --- /dev/null +++ b/examples/src/keyrings/multi_keyring_example.py @@ -0,0 +1,209 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +This example sets up the Multi Keyring + +A multi-keyring is a keyring that consists of one or more individual keyrings of the +same or a different type. The effect is like using several keyrings in a series. +When you use a multi-keyring to encrypt data, any of the wrapping keys in any of its +keyrings can decrypt that data. + +When you create a multi-keyring to encrypt data, you designate one of the keyrings as +the generator keyring. All other keyrings are known as child keyrings. The generator keyring +generates and encrypts the plaintext data key. Then, all of the wrapping keys in all of the +child keyrings encrypt the same plaintext data key. The multi-keyring returns the plaintext +key and one encrypted data key for each wrapping key in the multi-keyring. If you create a +multi-keyring with no generator keyring, you can use it to decrypt data, but not to encrypt. +If the generator keyring is a KMS keyring, the generator key in the AWS KMS keyring generates +and encrypts the plaintext key. Then, all additional AWS KMS keys in the AWS KMS keyring, +and all wrapping keys in all child keyrings in the multi-keyring, encrypt the same plaintext key. + +When decrypting, the AWS Encryption SDK uses the keyrings to try to decrypt one of the encrypted +data keys. The keyrings are called in the order that they are specified in the multi-keyring. +Processing stops as soon as any key in any keyring can decrypt an encrypted data key. + +This example creates a Multi Keyring and then encrypts a custom input EXAMPLE_DATA +with an encryption context. This example also includes some sanity checks for demonstration: +1. Ciphertext and plaintext data are not the same +2. Decryption of ciphertext is possible using the multi_keyring, +and every one of the keyrings from the multi_keyring separately +3. All decrypted plaintext value match EXAMPLE_DATA +These sanity checks are for demonstration in the example only. You do not need these in your code. + +This example creates a multi_keyring using a KMS keyring as generator keyring and a raw AES keyring +as a child keyring. You can use different combinations of keyrings in the multi_keyring. + +For more information on how to use Multi keyrings, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-multi-keyring.html +""" +import secrets +import sys + +import boto3 +from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders +from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig +from aws_cryptographic_materialproviders.mpl.models import ( + AesWrappingAlg, + CreateAwsKmsKeyringInput, + CreateMultiKeyringInput, + CreateRawAesKeyringInput, +) +from aws_cryptographic_materialproviders.mpl.references import IKeyring +from typing import Dict + +import aws_encryption_sdk +from aws_encryption_sdk import CommitmentPolicy + +# TODO-MPL: Remove this as part of removing PYTHONPATH hacks. +MODULE_ROOT_DIR = '/'.join(__file__.split("/")[:-1]) + +sys.path.append(MODULE_ROOT_DIR) + +EXAMPLE_DATA: bytes = b"Hello World" + + +def encrypt_and_decrypt_with_keyring( + kms_key_id: str +): + """Demonstrate an encrypt/decrypt cycle using a Multi keyring. + The multi_keyring is created using a KMS keyring as generator keyring and a raw AES keyring + as a child keyring. Therefore, we take a kms_key_id as input + + Usage: encrypt_and_decrypt_with_keyring(kms_key_id) + :param kms_key_id: KMS Key identifier for the KMS key you want to use for encryption and + decryption of your data keys in the kms_keyring, that is in-turn used in the multi_keyring + :type kms_key_id: string + + For more information on KMS Key identifiers, see + https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id + """ + # 1. Instantiate the encryption SDK client. + # This builds the client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy, + # which enforces that this client only encrypts using committing algorithm suites and enforces + # that this client will only decrypt encrypted messages that were created with a committing + # algorithm suite. + # This is the default commitment policy if you were to build the client as + # `client = aws_encryption_sdk.EncryptionSDKClient()`. + client = aws_encryption_sdk.EncryptionSDKClient( + commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT + ) + + # 2. Create a boto3 client for KMS. + kms_client = boto3.client('kms', region_name="us-west-2") + + # 3. Create encryption context. + # Remember that your encryption context is NOT SECRET. + # For more information, see + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryption_context: Dict[str, str] = { + "encryption": "context", + "is not": "secret", + "but adds": "useful metadata", + "that can help you": "be confident that", + "the data you are handling": "is what you think it is", + } + + # 4. Initialize the material providers library + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( + config=MaterialProvidersConfig() + ) + + # 5. Create a KMS keyring + kms_keyring_input: CreateAwsKmsKeyringInput = CreateAwsKmsKeyringInput( + kms_key_id=kms_key_id, + kms_client=kms_client + ) + + kms_keyring: IKeyring = mat_prov.create_aws_kms_keyring( + input=kms_keyring_input + ) + + # 6. Create a raw AES keyring to additionally encrypt under as child_keyring + + # The key namespace and key name are defined by you. + # and are used by the Raw AES keyring to determine + # whether it should attempt to decrypt an encrypted data key. + # For more information, see + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-aes-keyring.html + key_name_space = "Some managed raw keys" + key_name = "My 256-bit AES wrapping key" + + # Generate a 256-bit AES key to use with your raw AES keyring. + # Here, the input to secrets.token_bytes() = 32 bytes = 256 bits + static_key = secrets.token_bytes(32) + + raw_aes_keyring_input: CreateRawAesKeyringInput = CreateRawAesKeyringInput( + key_namespace=key_name_space, + key_name=key_name, + wrapping_key=static_key, + wrapping_alg=AesWrappingAlg.ALG_AES256_GCM_IV12_TAG16 + ) + + raw_aes_keyring: IKeyring = mat_prov.create_raw_aes_keyring( + input=raw_aes_keyring_input + ) + + # 7. Create a multi_keyring that consists of the previously created keyrings. + # When using this multi_keyring to encrypt data, either `kms_keyring` or + # `raw_aes_keyring` (or a multi_keyring containing either) may be used to decrypt the data. + multi_keyring_input: CreateMultiKeyringInput = CreateMultiKeyringInput( + generator=kms_keyring, + child_keyrings=[raw_aes_keyring] + ) + + multi_keyring: IKeyring = mat_prov.create_multi_keyring( + input=multi_keyring_input + ) + + # 8. Encrypt the data with the encryptionContext + ciphertext, _ = client.encrypt( + source=EXAMPLE_DATA, + keyring=multi_keyring, + encryption_context=encryption_context + ) + + # 9. Demonstrate that the ciphertext and plaintext are different. + # (This is an example for demonstration; you do not need to do this in your own code.) + assert ciphertext != EXAMPLE_DATA, \ + "Ciphertext and plaintext data are the same. Invalid encryption" + + # 10a. Decrypt your encrypted data using the same multi_keyring you used on encrypt. + plaintext_bytes_multi_keyring, _ = client.decrypt( + source=ciphertext, + keyring=multi_keyring + ) + + # 10b. Demonstrate that the decrypted plaintext is identical to the original plaintext. + # (This is an example for demonstration; you do not need to do this in your own code.) + assert plaintext_bytes_multi_keyring == EXAMPLE_DATA + + # Because you used a multi_keyring on Encrypt, you can use either the + # `kms_keyring` or `raw_aes_keyring` individually to decrypt the data. + + # 11. Demonstrate that you can successfully decrypt data using just the `kms_keyring` + # directly. + # (This is an example for demonstration; you do not need to do this in your own code.) + + # 11a. Decrypt your encrypted data using the kms_keyring. + plaintext_bytes_kms_keyring, _ = client.decrypt( + source=ciphertext, + keyring=kms_keyring + ) + + # 11b. Demonstrate that the decrypted plaintext is identical to the original plaintext. + # (This is an example for demonstration; you do not need to do this in your own code.) + assert plaintext_bytes_kms_keyring == EXAMPLE_DATA + + # 12. Demonstrate that you can also successfully decrypt data using the `raw_aes_keyring` + # directly. + # (This is an example for demonstration; you do not need to do this in your own code.) + + # 12a. Decrypt your encrypted data using the raw_aes_keyring. + plaintext_bytes_raw_aes_keyring, _ = client.decrypt( + source=ciphertext, + keyring=raw_aes_keyring + ) + + # 12b. Demonstrate that the decrypted plaintext is identical to the original plaintext. + # (This is an example for demonstration; you do not need to do this in your own code.) + assert plaintext_bytes_raw_aes_keyring == EXAMPLE_DATA diff --git a/examples/test/keyrings/test_i_aws_kms_discovery_keyring_example.py b/examples/test/keyrings/test_i_aws_kms_discovery_keyring_example.py new file mode 100644 index 000000000..af3c2a3a3 --- /dev/null +++ b/examples/test/keyrings/test_i_aws_kms_discovery_keyring_example.py @@ -0,0 +1,16 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Test suite for the AWS KMS Discovery keyring example.""" +import pytest + +from ...src.keyrings.aws_kms_discovery_keyring_example import encrypt_and_decrypt_with_keyring + +pytestmark = [pytest.mark.examples] + + +def test_encrypt_and_decrypt_with_keyring(): + """Test function for encrypt and decrypt using the AWS KMS Discovery Keyring example.""" + kms_key_id = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f" + aws_account_id = "658956600833" + aws_region = "us-west-2" + encrypt_and_decrypt_with_keyring(kms_key_id, aws_account_id, aws_region) diff --git a/examples/test/keyrings/test_i_aws_kms_discovery_multi_keyring_example.py b/examples/test/keyrings/test_i_aws_kms_discovery_multi_keyring_example.py new file mode 100644 index 000000000..eff2a1a3b --- /dev/null +++ b/examples/test/keyrings/test_i_aws_kms_discovery_multi_keyring_example.py @@ -0,0 +1,16 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Test suite for the AWS KMS Discovery Multi keyring example.""" +import pytest + +from ...src.keyrings.aws_kms_discovery_multi_keyring_example import encrypt_and_decrypt_with_keyring + +pytestmark = [pytest.mark.examples] + + +def test_encrypt_and_decrypt_with_keyring(): + """Test function for encrypt and decrypt using the AWS KMS Discovery Multi Keyring example.""" + kms_key_id = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f" + aws_account_id = "658956600833" + aws_regions = ["us-east-1", "us-west-2"] + encrypt_and_decrypt_with_keyring(kms_key_id, aws_account_id, aws_regions) diff --git a/examples/test/keyrings/test_i_aws_kms_multi_keyring_example.py b/examples/test/keyrings/test_i_aws_kms_multi_keyring_example.py new file mode 100644 index 000000000..10f991754 --- /dev/null +++ b/examples/test/keyrings/test_i_aws_kms_multi_keyring_example.py @@ -0,0 +1,22 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Test suite for the AWS KMS multi keyring example.""" +import pytest + +from ...src.keyrings.aws_kms_multi_keyring_example import encrypt_and_decrypt_with_keyring + +pytestmark = [pytest.mark.examples] + + +def test_encrypt_and_decrypt_with_keyring(): + """Test function for encrypt and decrypt using the AWS KMS Multi Keyring example.""" + default_region_kms_key_id = \ + "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f" + second_region_kms_key_id = \ + "arn:aws:kms:eu-central-1:658956600833:key/75414c93-5285-4b57-99c9-30c1cf0a22c2" + default_region = "us-west-2" + second_region = "eu-central-1" + encrypt_and_decrypt_with_keyring(default_region_kms_key_id, + second_region_kms_key_id, + default_region, + second_region) diff --git a/examples/test/keyrings/test_i_aws_kms_rsa_keyring_example.py b/examples/test/keyrings/test_i_aws_kms_rsa_keyring_example.py new file mode 100644 index 000000000..4d953947a --- /dev/null +++ b/examples/test/keyrings/test_i_aws_kms_rsa_keyring_example.py @@ -0,0 +1,25 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Test suite for the AWS KMS RSA keyring example.""" +import pytest + +from ...src.keyrings.aws_kms_rsa_keyring_example import encrypt_and_decrypt_with_keyring + +pytestmark = [pytest.mark.examples] + + +def test_encrypt_and_decrypt_with_keyring(): + """Test function for encrypt and decrypt using the AWS KMS RSA Keyring example.""" + kms_rsa_key_id = "arn:aws:kms:us-west-2:370957321024:key/mrk-63d386cb70614ea59b32ad65c9315297" + + # THIS IS A PUBLIC RESOURCE AND SHOULD NOT BE USED IN A PRODUCTION ENVIRONMENT + public_key = bytes("-----BEGIN PUBLIC KEY-----\n" + + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA27Uc/fBaMVhxCE/SpCMQ" + + "oSBRSzQJw+o2hBaA+FiPGtiJ/aPy7sn18aCkelaSj4kwoC79b/arNHlkjc7OJFsN" + + "/GoFKgNvaiY4lOeJqEiWQGSSgHtsJLdbO2u4OOSxh8qIRAMKbMgQDVX4FR/PLKeK" + + "fc2aCDvcNSpAM++8NlNmv7+xQBJydr5ce91eISbHkFRkK3/bAM+1iddupoRw4Wo2" + + "r3avzrg5xBHmzR7u1FTab22Op3Hgb2dBLZH43wNKAceVwKqKA8UNAxashFON7xK9" + + "yy4kfOL0Z/nhxRKe4jRZ/5v508qIzgzCksYy7Y3QbMejAtiYnr7s5/d5KWw0swou" + + "twIDAQAB" + + "\n-----END PUBLIC KEY-----", 'utf-8') + encrypt_and_decrypt_with_keyring(kms_rsa_key_id, public_key) diff --git a/examples/test/keyrings/test_i_multi_keyring_example.py b/examples/test/keyrings/test_i_multi_keyring_example.py new file mode 100644 index 000000000..ad12c0c9c --- /dev/null +++ b/examples/test/keyrings/test_i_multi_keyring_example.py @@ -0,0 +1,14 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Test suite for the multi keyring example.""" +import pytest + +from ...src.keyrings.multi_keyring_example import encrypt_and_decrypt_with_keyring + +pytestmark = [pytest.mark.examples] + + +def test_encrypt_and_decrypt_with_keyring(): + """Test function for encrypt and decrypt using the Multi Keyring example.""" + kms_key_id = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f" + encrypt_and_decrypt_with_keyring(kms_key_id) diff --git a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py index 1f2102757..86ff37dd2 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py @@ -78,18 +78,24 @@ def get_encryption_materials( def _native_to_mpl_get_encryption_materials( request: EncryptionMaterialsRequest ) -> 'MPL_GetEncryptionMaterialsInput': - commitment_policy = CryptoMaterialsManagerFromMPL._native_to_mpl_commmitment_policy( - request.commitment_policy - ) - output: MPL_GetEncryptionMaterialsInput = MPL_GetEncryptionMaterialsInput( - encryption_context=request.encryption_context, - commitment_policy=commitment_policy, - max_plaintext_length=request.plaintext_length, - ) - return output + output_kwargs = { + "encryption_context": request.encryption_context, + "max_plaintext_length": request.plaintext_length, + "commitment_policy": CryptoMaterialsManagerFromMPL._native_to_mpl_commitment_policy( + request.commitment_policy + ) + } + + if request.algorithm is not None: + output_kwargs["algorithm_suite_id"] = \ + CryptoMaterialsManagerFromMPL._native_algorithm_id_to_mpl_algorithm_id( + request.algorithm.algorithm_id + ) + + return MPL_GetEncryptionMaterialsInput(**output_kwargs) @staticmethod - def _native_to_mpl_commmitment_policy( + def _native_to_mpl_commitment_policy( native_commitment_policy: CommitmentPolicy ) -> 'MPL_CommitmentPolicyESDK': if native_commitment_policy == CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT: @@ -138,7 +144,7 @@ def _create_mpl_decrypt_materials_input_from_request( algorithm_suite_id=CryptoMaterialsManagerFromMPL._native_algorithm_id_to_mpl_algorithm_id( request.algorithm.algorithm_id ), - commitment_policy=CryptoMaterialsManagerFromMPL._native_to_mpl_commmitment_policy( + commitment_policy=CryptoMaterialsManagerFromMPL._native_to_mpl_commitment_policy( request.commitment_policy ), encrypted_data_keys=list_edks, diff --git a/test/mpl/unit/test_material_managers_mpl_cmm.py b/test/mpl/unit/test_material_managers_mpl_cmm.py index 0551e8f30..603446550 100644 --- a/test/mpl/unit/test_material_managers_mpl_cmm.py +++ b/test/mpl/unit/test_material_managers_mpl_cmm.py @@ -97,10 +97,17 @@ def test_GIVEN_valid_request_WHEN_get_encryption_materials_THEN_return_Encryptio @patch("aws_encryption_sdk.materials_managers.mpl.cmm.CryptoMaterialsManagerFromMPL" - "._native_to_mpl_commmitment_policy") + "._native_algorithm_id_to_mpl_algorithm_id") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.CryptoMaterialsManagerFromMPL" + "._native_to_mpl_commitment_policy") def test_GIVEN_mpl_cmm_raises_MPLException_WHEN_get_encryption_materials_THEN_raise_ESDKException( - _ + _, + mock_mpl_algorithm_id, ): + # Given: _native_algorithm_id_to_mpl_algorithm_id returns a valid MPL algorithm ID + mock_algorithm_id = "0x1234" # Some fake algorithm ID that fits the format + mock_mpl_algorithm_id.return_value = mock_algorithm_id + # Then: Raises AWSEncryptionSDKClientError with pytest.raises(AWSEncryptionSDKClientError): # Given: mpl_cmm.get_encryption_materials raises MPL exception @@ -112,10 +119,17 @@ def test_GIVEN_mpl_cmm_raises_MPLException_WHEN_get_encryption_materials_THEN_ra @patch("aws_encryption_sdk.materials_managers.mpl.cmm.CryptoMaterialsManagerFromMPL" - "._native_to_mpl_commmitment_policy") + "._native_algorithm_id_to_mpl_algorithm_id") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.CryptoMaterialsManagerFromMPL" + "._native_to_mpl_commitment_policy") def test_GIVEN_valid_mpl_commitment_policy_WHEN_native_to_mpl_get_encryption_materials_THEN_returns_MPL_GetEncryptionMaterialsInput( # noqa: E501 - mock_mpl_commitment_policy + mock_mpl_commitment_policy, + mock_mpl_algorithm_id, ): + # Given: _native_algorithm_id_to_mpl_algorithm_id returns a valid MPL algorithm ID + mock_algorithm_id = "0x1234" # Some fake algorithm ID that fits the format + mock_mpl_algorithm_id.return_value = mock_algorithm_id + # Given: commitment policy is some MPL ESDK commitment policy mock_commitment_policy = MagicMock(__class__=MPL_CommitmentPolicyESDK) mock_mpl_commitment_policy.return_value = mock_commitment_policy @@ -132,50 +146,50 @@ def test_GIVEN_valid_mpl_commitment_policy_WHEN_native_to_mpl_get_encryption_mat assert output.max_plaintext_length == mock_encryption_materials_request.plaintext_length -def test_GIVEN_CommitmentPolicy_FORBID_ENCRYPT_ALLOW_DECRYPT_WHEN_native_to_mpl_commmitment_policy_THEN_returns_MPL_CommitmentPolicyESDK_FORBID_ENCRYPT_ALLOW_DECRYPT(): # noqa: E501 +def test_GIVEN_CommitmentPolicy_FORBID_ENCRYPT_ALLOW_DECRYPT_WHEN_native_to_mpl_commitment_policy_THEN_returns_MPL_CommitmentPolicyESDK_FORBID_ENCRYPT_ALLOW_DECRYPT(): # noqa: E501 # Given: native FORBID_ENCRYPT_ALLOW_DECRYPT native_commitment_policy = CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT - # When: _native_to_mpl_commmitment_policy - output = CryptoMaterialsManagerFromMPL._native_to_mpl_commmitment_policy(native_commitment_policy) + # When: _native_to_mpl_commitment_policy + output = CryptoMaterialsManagerFromMPL._native_to_mpl_commitment_policy(native_commitment_policy) # Then: Returns MPL FORBID_ENCRYPT_ALLOW_DECRYPT assert isinstance(output, MPL_CommitmentPolicyESDK) assert output.value == "FORBID_ENCRYPT_ALLOW_DECRYPT" -def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_ALLOW_DECRYPT_WHEN_native_to_mpl_commmitment_policy_THEN_returns_MPL_CommitmentPolicyESDK_REQUIRE_ENCRYPT_ALLOW_DECRYPT(): # noqa: E501 +def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_ALLOW_DECRYPT_WHEN_native_to_mpl_commitment_policy_THEN_returns_MPL_CommitmentPolicyESDK_REQUIRE_ENCRYPT_ALLOW_DECRYPT(): # noqa: E501 # Given: native REQUIRE_ENCRYPT_ALLOW_DECRYPT native_commitment_policy = CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT - # When: _native_to_mpl_commmitment_policy - output = CryptoMaterialsManagerFromMPL._native_to_mpl_commmitment_policy(native_commitment_policy) + # When: _native_to_mpl_commitment_policy + output = CryptoMaterialsManagerFromMPL._native_to_mpl_commitment_policy(native_commitment_policy) # Then: Returns MPL REQUIRE_ENCRYPT_ALLOW_DECRYPT assert isinstance(output, MPL_CommitmentPolicyESDK) assert output.value == "REQUIRE_ENCRYPT_ALLOW_DECRYPT" -def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_REQUIRE_DECRYPT_WHEN_native_to_mpl_commmitment_policy_THEN_returns_MPL_CommitmentPolicyESDK_REQUIRE_ENCRYPT_REQUIRE_DECRYPT(): # noqa: E501 +def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_REQUIRE_DECRYPT_WHEN_native_to_mpl_commitment_policy_THEN_returns_MPL_CommitmentPolicyESDK_REQUIRE_ENCRYPT_REQUIRE_DECRYPT(): # noqa: E501 # Given: native REQUIRE_ENCRYPT_REQUIRE_DECRYPT native_commitment_policy = CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT - # When: _native_to_mpl_commmitment_policy - output = CryptoMaterialsManagerFromMPL._native_to_mpl_commmitment_policy(native_commitment_policy) + # When: _native_to_mpl_commitment_policy + output = CryptoMaterialsManagerFromMPL._native_to_mpl_commitment_policy(native_commitment_policy) # Then: Returns MPL REQUIRE_ENCRYPT_REQUIRE_DECRYPT assert isinstance(output, MPL_CommitmentPolicyESDK) assert output.value == "REQUIRE_ENCRYPT_REQUIRE_DECRYPT" -def test_GIVEN_CommitmentPolicy_unrecognized_WHEN_native_to_mpl_commmitment_policy_THEN_raise_ValueError(): +def test_GIVEN_CommitmentPolicy_unrecognized_WHEN_native_to_mpl_commitment_policy_THEN_raise_ValueError(): # Given: invalid native commitment policy native_commitment_policy = "not a commitment policy" # Then: Raises ValueError with pytest.raises(ValueError): - # When: _native_to_mpl_commmitment_policy - CryptoMaterialsManagerFromMPL._native_to_mpl_commmitment_policy(native_commitment_policy) + # When: _native_to_mpl_commitment_policy + CryptoMaterialsManagerFromMPL._native_to_mpl_commitment_policy(native_commitment_policy) @patch.object(mock_mpl_cmm, "decrypt_materials") @@ -239,7 +253,7 @@ def test_GIVEN_valid_native_algorithm_id_WHEN_native_algorithm_id_to_mpl_algorit @patch("aws_encryption_sdk.materials_managers.mpl.cmm.CryptoMaterialsManagerFromMPL" "._native_algorithm_id_to_mpl_algorithm_id") @patch("aws_encryption_sdk.materials_managers.mpl.cmm.CryptoMaterialsManagerFromMPL" - "._native_to_mpl_commmitment_policy") + "._native_to_mpl_commitment_policy") def test_GIVEN_valid_request_WHEN_create_mpl_decrypt_materials_input_from_request_THEN_returns_MPL_MPL_DecryptMaterialsInput( # noqa: E501 mock_mpl_commitment_policy, mock_mpl_algorithm_id, @@ -248,7 +262,7 @@ def test_GIVEN_valid_request_WHEN_create_mpl_decrypt_materials_input_from_reques mock_algorithm_id = "0x1234" # Some fake algorithm ID that fits the format mock_mpl_algorithm_id.return_value = mock_algorithm_id - # Given: _native_to_mpl_commmitment_policy returns some MPL commitment policy + # Given: _native_to_mpl_commitment_policy returns some MPL commitment policy mock_commitment_policy = MagicMock(__class__=MPL_CommitmentPolicyESDK) mock_mpl_commitment_policy.return_value = mock_commitment_policy