From 7360edd46964ae0a0d7127a1cf51bd180973af33 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 31 Jan 2024 15:18:21 -0800 Subject: [PATCH 001/184] passing hierarchy keyring example --- examples/src/basic_encryption.py | 98 +++++++++++++++++-- requirements.txt | 2 +- setup.py | 8 ++ .../internal/crypto/authentication.py | 8 +- src/aws_encryption_sdk/streaming_client.py | 83 +++++++++++++--- 5 files changed, 179 insertions(+), 20 deletions(-) diff --git a/examples/src/basic_encryption.py b/examples/src/basic_encryption.py index cfe8ac791..f48f7e4a1 100644 --- a/examples/src/basic_encryption.py +++ b/examples/src/basic_encryption.py @@ -13,7 +13,40 @@ """Example showing basic encryption and decryption of a value already in memory.""" import aws_encryption_sdk from aws_encryption_sdk import CommitmentPolicy +import aws_cryptographic_materialproviders +import boto3 +from aws_encryption_sdk.cmm_handler import (CMMHandler) + +import sys + +module_root_dir = '/'.join(__file__.split("/")[:-1]) + +sys.path.append(module_root_dir) + +import aws_cryptographic_materialproviders + +from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.client import AwsCryptographicMaterialProviders +from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.config import MaterialProvidersConfig +from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.models import ( + CreateAwsKmsHierarchicalKeyringInput, + CacheTypeDefault, + DefaultCache, + GetBranchKeyIdInput, + GetBranchKeyIdOutput, + CreateDefaultCryptographicMaterialsManagerInput, +) +from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.references import ( + IKeyring, + IBranchKeyIdSupplier, +) + +from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_keystore.client import KeyStore +from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_keystore.config import KeyStoreConfig +from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_keystore.models import ( + CreateKeyInput, + KMSConfigurationKmsKeyArn, +) def cycle_string(key_arn, source_plaintext, botocore_session=None): """Encrypts and then decrypts a string under a KMS customer master key (CMK). @@ -25,23 +58,72 @@ def cycle_string(key_arn, source_plaintext, botocore_session=None): """ # 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) + client = aws_encryption_sdk.EncryptionSDKClient() # Create a KMS master key provider. Note that because we are planning on decrypting using this same provider, # we MUST provide the ARN of the KMS Key. If we provide a raw key id or a key alias, decryption will fail. kms_kwargs = dict(key_ids=[key_arn]) if botocore_session is not None: kms_kwargs["botocore_session"] = botocore_session - master_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(**kms_kwargs) + # master_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(**kms_kwargs) + + ##### + + + key_store_table_name="KeyStoreDdbTable" + logical_key_store_name="KeyStoreDdbTable" + keystore_kms_key_id="arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126" + + ddb_client = boto3.client('dynamodb') + kms_client = boto3.client('kms') + + keystore: KeyStore = KeyStore( + config=KeyStoreConfig( + ddb_client=ddb_client, + ddb_table_name=key_store_table_name, + logical_key_store_name=logical_key_store_name, + kms_client=kms_client, + kms_configuration=KMSConfigurationKmsKeyArn(value=keystore_kms_key_id), + ) + ) + + new_branch_key_id: str = keystore.create_key(input=CreateKeyInput()).branch_key_identifier + print(f"DEBUG: {new_branch_key_id=}") + + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( + config=MaterialProvidersConfig() + ) + + keyring_input: CreateAwsKmsHierarchicalKeyringInput = CreateAwsKmsHierarchicalKeyringInput( + key_store=keystore, + branch_key_id=new_branch_key_id, + ttl_seconds=600, + cache=CacheTypeDefault(value=DefaultCache(entry_capacity=100)), + ) + + hierarchical_keyring: IKeyring = mat_prov.create_aws_kms_hierarchical_keyring( + input=keyring_input + ) + # This is as far as we can go in the linked Java example without the ESDK. + # We can't use this keyring until it's integrated with the ESDK :( + # Peek at it with print statement for now + print(f"DEBUG: {hierarchical_keyring=}") + + ##### + + cmm = mat_prov.create_default_cryptographic_materials_manager(CreateDefaultCryptographicMaterialsManagerInput(keyring=hierarchical_keyring)) + + cmm_handler: CMMHandler = CMMHandler(cmm) # Encrypt the plaintext source data - ciphertext, encryptor_header = client.encrypt(source=source_plaintext, key_provider=master_key_provider) + ciphertext, encryptor_header = client.encrypt(source=source_plaintext, materials_manager=cmm_handler) # Decrypt the ciphertext - cycled_plaintext, decrypted_header = client.decrypt(source=ciphertext, key_provider=master_key_provider) + cycled_plaintext, decrypted_header = client.decrypt(source=ciphertext, materials_manager=cmm_handler) + cycled_plaintext_str = str(cycled_plaintext, encoding="ascii") # Verify that the "cycled" (encrypted, then decrypted) plaintext is identical to the source plaintext - assert cycled_plaintext == source_plaintext + assert cycled_plaintext_str == source_plaintext # Verify that the encryption context used in the decrypt operation includes all key pairs from # the encrypt operation. (The SDK can add pairs, so don't require an exact match.) @@ -49,5 +131,9 @@ def cycle_string(key_arn, source_plaintext, botocore_session=None): # In production, always use a meaningful encryption context. In this sample, we omit the # encryption context (no key pairs). assert all( - pair in decrypted_header.encryption_context.items() for pair in encryptor_header.encryption_context.items() + (str(k, encoding="ascii"), str(v, encoding="ascii")) in decrypted_header.encryption_context.items() for (k, v) in encryptor_header.encryption_context.items() ) + +# hack in a test +import botocore +cycle_string("arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f", "abcdefg", botocore_session=botocore.session.Session()) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 2f4323845..13466216c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ boto3>=1.10.0 cryptography>=3.4.0 attrs>=17.4.0 -wrapt>=1.10.11 +wrapt>=1.16.0 diff --git a/setup.py b/setup.py index 7cc111bac..cafc16979 100644 --- a/setup.py +++ b/setup.py @@ -39,6 +39,11 @@ def get_requirements(): keywords="aws-encryption-sdk aws kms encryption", license="Apache License 2.0", install_requires=get_requirements(), + # TODO: Point at main once Python is merged into main. + # PyPI will not accept a package that declares dependencies using direct URLs. + extras_require={ + "MPL": ["aws-cryptographic-material-providers @ git+https://github.com/aws/aws-cryptographic-material-providers-library.git@lucmcdon/python-mpl#subdirectory=AwsCryptographicMaterialProviders/runtimes/python"], + }, classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", @@ -49,6 +54,9 @@ def get_requirements(): "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Security", "Topic :: Security :: Cryptography", diff --git a/src/aws_encryption_sdk/internal/crypto/authentication.py b/src/aws_encryption_sdk/internal/crypto/authentication.py index f90ac77e0..ad5cf1b2a 100644 --- a/src/aws_encryption_sdk/internal/crypto/authentication.py +++ b/src/aws_encryption_sdk/internal/crypto/authentication.py @@ -76,7 +76,8 @@ def from_key_bytes(cls, algorithm, key_bytes): :param bytes key_bytes: Raw signing key :rtype: aws_encryption_sdk.internal.crypto.Signer """ - key = serialization.load_der_private_key(data=key_bytes, password=None, backend=default_backend()) + # key = serialization.load_der_private_key(data=key_bytes, password=None, backend=default_backend()) + key = serialization.load_pem_private_key(data=key_bytes, password=None, backend=default_backend()) return cls(algorithm, key) def key_bytes(self): @@ -140,6 +141,7 @@ def from_encoded_point(cls, algorithm, encoded_point): :returns: Instance of Verifier generated from encoded point :rtype: aws_encryption_sdk.internal.crypto.Verifier """ + print(f"from_encoded_point {encoded_point=}") return cls( algorithm=algorithm, key=_ecc_public_numbers_from_compressed_point( @@ -157,8 +159,10 @@ def from_key_bytes(cls, algorithm, key_bytes): :returns: Instance of Verifier generated from encoded point :rtype: aws_encryption_sdk.internal.crypto.Verifier """ + print(f"{algorithm=}") + print(f"{key_bytes=}") return cls( - algorithm=algorithm, key=serialization.load_der_public_key(data=key_bytes, backend=default_backend()) + algorithm=algorithm, key=serialization.load_pem_public_key(data=key_bytes, backend=default_backend()) ) def key_bytes(self): diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 1119cb740..afed52e0f 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -66,6 +66,21 @@ from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager from aws_encryption_sdk.structures import MessageHeader +try: + import aws_cryptographic_materialproviders + from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.client import AwsCryptographicMaterialProviders + from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.config import MaterialProvidersConfig + from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.models import ( + CreateDefaultCryptographicMaterialsManagerInput + ) + from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.references import ( + IKeyring, + ) + from aws_encryption_sdk.cmm_handler import CMMHandler + + _has_mpl = True +except ImportError as e: + _has_mpl = False _LOGGER = logging.getLogger(__name__) @@ -113,6 +128,10 @@ class _ClientConfig(object): # pylint: disable=too-many-instance-attributes key_provider = attr.ib( hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(MasterKeyProvider)) ) + if _has_mpl: + keyring = attr.ib( + hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(IKeyring)) + ) source_length = attr.ib( hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(six.integer_types)) ) @@ -122,13 +141,38 @@ class _ClientConfig(object): # pylint: disable=too-many-instance-attributes def __attrs_post_init__(self): """Normalize inputs to crypto material manager.""" - both_cmm_and_mkp_defined = self.materials_manager is not None and self.key_provider is not None - neither_cmm_nor_mkp_defined = self.materials_manager is None and self.key_provider is None + if _has_mpl: + all_cmm_and_mkp_and_keyring_defined = all([ + self.materials_manager is not None, + self.key_provider is not None, + self.keyring is not None, + ]) + none_cmm_nor_mkp_nor_keyring_defined = all([ + self.materials_manager is None, + self.key_provider is None, + self.keyring is None, + ]) + + if all_cmm_and_mkp_and_keyring_defined or none_cmm_nor_mkp_nor_keyring_defined: + raise TypeError("Exactly one of keyring, materials_manager, or key_provider must be provided") + if self.materials_manager is None: + if self.key_provider is not None: + self.materials_manager = DefaultCryptoMaterialsManager(master_key_provider=self.key_provider) + elif self.keyring is not None: + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( + config=MaterialProvidersConfig() + ) + cmm = mat_prov.create_default_cryptographic_materials_manager(CreateDefaultCryptographicMaterialsManagerInput(keyring=self.keyring)) + cmm_handler: CryptoMaterialsManager = CMMHandler(cmm) + self.materials_manager = cmm_handler + elif not _has_mpl: + both_cmm_and_mkp_defined = self.materials_manager is not None and self.key_provider is not None + neither_cmm_nor_mkp_defined = self.materials_manager is None and self.key_provider is None - if both_cmm_and_mkp_defined or neither_cmm_nor_mkp_defined: - raise TypeError("Exactly one of materials_manager or key_provider must be provided") - if self.materials_manager is None: - self.materials_manager = DefaultCryptoMaterialsManager(master_key_provider=self.key_provider) + if both_cmm_and_mkp_defined or neither_cmm_nor_mkp_defined: + raise TypeError("Exactly one of materials_manager or key_provider must be provided") + if self.materials_manager is None: + self.materials_manager = DefaultCryptoMaterialsManager(master_key_provider=self.key_provider) class _EncryptionStream(io.IOBase): @@ -343,6 +387,8 @@ class EncryptorConfig(_ClientConfig): :param key_provider: `MasterKeyProvider` from which to obtain data keys for encryption (either `materials_manager` or `key_provider` required) :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider + :param keyring: `IKeyring` TODO-MPL content + :type keyring: TODO-MPL :param int source_length: Length of source data (optional) .. note:: @@ -394,6 +440,8 @@ class StreamEncryptor(_EncryptionStream): # pylint: disable=too-many-instance-a :param key_provider: `MasterKeyProvider` from which to obtain data keys for encryption (either `materials_manager` or `key_provider` required) :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider + :param keyring: `IKeyring` TODO-MPL content + :type keyring: TODO-MPL :param int source_length: Length of source data (optional) .. note:: @@ -729,11 +777,13 @@ class DecryptorConfig(_ClientConfig): :param source: Source data to encrypt or decrypt :type source: str, bytes, io.IOBase, or file :param materials_manager: `CryptoMaterialsManager` from which to obtain cryptographic materials - (either `materials_manager` or `key_provider` required) + (either `keyring`, `materials_manager` or `key_provider` required) :type materials_manager: aws_encryption_sdk.materials_managers.base.CryptoMaterialsManager :param key_provider: `MasterKeyProvider` from which to obtain data keys for decryption - (either `materials_manager` or `key_provider` required) + (either `keyring`, `materials_manager` or `key_provider` required) :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider + :param keyring: `IKeyring` TODO-MPL content + :type keyring: TODO-MPL :param int source_length: Length of source data (optional) .. note:: @@ -770,6 +820,8 @@ class StreamDecryptor(_EncryptionStream): # pylint: disable=too-many-instance-a :param key_provider: `MasterKeyProvider` from which to obtain data keys for decryption (either `materials_manager` or `key_provider` required) :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider + :param keyring: `IKeyring` TODO-MPL content + :type keyring: TODO-MPL :param int source_length: Length of source data (optional) .. note:: @@ -831,9 +883,18 @@ def _read_header(self): if decryption_materials.verification_key is None: self.verifier = None else: - self.verifier = Verifier.from_key_bytes( - algorithm=header.algorithm, key_bytes=decryption_materials.verification_key - ) + # MPL verification key is NOT key bytes, it is bytes of the compressed point + # TODO-MPL: clean this up, least-privilege violation + import base64 + if hasattr(self.config.materials_manager, "mpl_cmm"): + self.verifier = Verifier.from_encoded_point( + algorithm=header.algorithm, + encoded_point=base64.b64encode(decryption_materials.verification_key) + ) + else: + self.verifier = Verifier.from_key_bytes( + algorithm=header.algorithm, key_bytes=decryption_materials.verification_key + ) if self.verifier is not None: self.verifier.update(raw_header) From 53c46ece22fec60a2d4d653a4720f66fe706ccca Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 2 Feb 2024 09:19:14 -0800 Subject: [PATCH 002/184] cleanup --- examples/src/basic_encryption.py | 98 +--------------- requirements.txt | 2 +- setup.py | 1 - .../internal/crypto/authentication.py | 3 - src/aws_encryption_sdk/streaming_client.py | 105 +++++++++++------- 5 files changed, 74 insertions(+), 135 deletions(-) diff --git a/examples/src/basic_encryption.py b/examples/src/basic_encryption.py index f48f7e4a1..cfe8ac791 100644 --- a/examples/src/basic_encryption.py +++ b/examples/src/basic_encryption.py @@ -13,40 +13,7 @@ """Example showing basic encryption and decryption of a value already in memory.""" import aws_encryption_sdk from aws_encryption_sdk import CommitmentPolicy -import aws_cryptographic_materialproviders -import boto3 -from aws_encryption_sdk.cmm_handler import (CMMHandler) - -import sys - -module_root_dir = '/'.join(__file__.split("/")[:-1]) - -sys.path.append(module_root_dir) - -import aws_cryptographic_materialproviders - -from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.client import AwsCryptographicMaterialProviders -from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.config import MaterialProvidersConfig -from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.models import ( - CreateAwsKmsHierarchicalKeyringInput, - CacheTypeDefault, - DefaultCache, - GetBranchKeyIdInput, - GetBranchKeyIdOutput, - CreateDefaultCryptographicMaterialsManagerInput, -) -from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.references import ( - IKeyring, - IBranchKeyIdSupplier, -) - -from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_keystore.client import KeyStore -from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_keystore.config import KeyStoreConfig -from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_keystore.models import ( - CreateKeyInput, - KMSConfigurationKmsKeyArn, -) def cycle_string(key_arn, source_plaintext, botocore_session=None): """Encrypts and then decrypts a string under a KMS customer master key (CMK). @@ -58,72 +25,23 @@ def cycle_string(key_arn, source_plaintext, botocore_session=None): """ # 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() + client = aws_encryption_sdk.EncryptionSDKClient(commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT) # Create a KMS master key provider. Note that because we are planning on decrypting using this same provider, # we MUST provide the ARN of the KMS Key. If we provide a raw key id or a key alias, decryption will fail. kms_kwargs = dict(key_ids=[key_arn]) if botocore_session is not None: kms_kwargs["botocore_session"] = botocore_session - # master_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(**kms_kwargs) - - ##### - - - key_store_table_name="KeyStoreDdbTable" - logical_key_store_name="KeyStoreDdbTable" - keystore_kms_key_id="arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126" - - ddb_client = boto3.client('dynamodb') - kms_client = boto3.client('kms') - - keystore: KeyStore = KeyStore( - config=KeyStoreConfig( - ddb_client=ddb_client, - ddb_table_name=key_store_table_name, - logical_key_store_name=logical_key_store_name, - kms_client=kms_client, - kms_configuration=KMSConfigurationKmsKeyArn(value=keystore_kms_key_id), - ) - ) - - new_branch_key_id: str = keystore.create_key(input=CreateKeyInput()).branch_key_identifier - print(f"DEBUG: {new_branch_key_id=}") - - mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( - config=MaterialProvidersConfig() - ) - - keyring_input: CreateAwsKmsHierarchicalKeyringInput = CreateAwsKmsHierarchicalKeyringInput( - key_store=keystore, - branch_key_id=new_branch_key_id, - ttl_seconds=600, - cache=CacheTypeDefault(value=DefaultCache(entry_capacity=100)), - ) - - hierarchical_keyring: IKeyring = mat_prov.create_aws_kms_hierarchical_keyring( - input=keyring_input - ) - # This is as far as we can go in the linked Java example without the ESDK. - # We can't use this keyring until it's integrated with the ESDK :( - # Peek at it with print statement for now - print(f"DEBUG: {hierarchical_keyring=}") - - ##### - - cmm = mat_prov.create_default_cryptographic_materials_manager(CreateDefaultCryptographicMaterialsManagerInput(keyring=hierarchical_keyring)) - - cmm_handler: CMMHandler = CMMHandler(cmm) + master_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(**kms_kwargs) # Encrypt the plaintext source data - ciphertext, encryptor_header = client.encrypt(source=source_plaintext, materials_manager=cmm_handler) + ciphertext, encryptor_header = client.encrypt(source=source_plaintext, key_provider=master_key_provider) # Decrypt the ciphertext - cycled_plaintext, decrypted_header = client.decrypt(source=ciphertext, materials_manager=cmm_handler) - cycled_plaintext_str = str(cycled_plaintext, encoding="ascii") + cycled_plaintext, decrypted_header = client.decrypt(source=ciphertext, key_provider=master_key_provider) # Verify that the "cycled" (encrypted, then decrypted) plaintext is identical to the source plaintext - assert cycled_plaintext_str == source_plaintext + assert cycled_plaintext == source_plaintext # Verify that the encryption context used in the decrypt operation includes all key pairs from # the encrypt operation. (The SDK can add pairs, so don't require an exact match.) @@ -131,9 +49,5 @@ def cycle_string(key_arn, source_plaintext, botocore_session=None): # In production, always use a meaningful encryption context. In this sample, we omit the # encryption context (no key pairs). assert all( - (str(k, encoding="ascii"), str(v, encoding="ascii")) in decrypted_header.encryption_context.items() for (k, v) in encryptor_header.encryption_context.items() + pair in decrypted_header.encryption_context.items() for pair in encryptor_header.encryption_context.items() ) - -# hack in a test -import botocore -cycle_string("arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f", "abcdefg", botocore_session=botocore.session.Session()) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 13466216c..2f4323845 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ boto3>=1.10.0 cryptography>=3.4.0 attrs>=17.4.0 -wrapt>=1.16.0 +wrapt>=1.10.11 diff --git a/setup.py b/setup.py index cafc16979..8ffd74015 100644 --- a/setup.py +++ b/setup.py @@ -56,7 +56,6 @@ def get_requirements(): "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Security", "Topic :: Security :: Cryptography", diff --git a/src/aws_encryption_sdk/internal/crypto/authentication.py b/src/aws_encryption_sdk/internal/crypto/authentication.py index ad5cf1b2a..b9692eb16 100644 --- a/src/aws_encryption_sdk/internal/crypto/authentication.py +++ b/src/aws_encryption_sdk/internal/crypto/authentication.py @@ -141,7 +141,6 @@ def from_encoded_point(cls, algorithm, encoded_point): :returns: Instance of Verifier generated from encoded point :rtype: aws_encryption_sdk.internal.crypto.Verifier """ - print(f"from_encoded_point {encoded_point=}") return cls( algorithm=algorithm, key=_ecc_public_numbers_from_compressed_point( @@ -159,8 +158,6 @@ def from_key_bytes(cls, algorithm, key_bytes): :returns: Instance of Verifier generated from encoded point :rtype: aws_encryption_sdk.internal.crypto.Verifier """ - print(f"{algorithm=}") - print(f"{key_bytes=}") return cls( algorithm=algorithm, key=serialization.load_pem_public_key(data=key_bytes, backend=default_backend()) ) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index afed52e0f..176b92334 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -139,40 +139,61 @@ class _ClientConfig(object): # pylint: disable=too-many-instance-attributes hash=True, default=LINE_LENGTH, validator=attr.validators.instance_of(six.integer_types) ) # DEPRECATED: Value is no longer configurable here. Parameter left here to avoid breaking consumers. + def _has_mpl_attrs_post_init(self): + + def _exactly_one_arg_is_not_None(*args): + ''' + Private helper function. + Returns `True` if exactly one item in the list is not `None`. + Returns `False` otherwise. + ''' + # Have not found any `not None` + found_one = False + for arg in args: + if arg is not None: + if found_one == False: + # Have not already found a `not None`, found a `not None` => only one `not None` (so far) + found_one = True + else: + # Already found a `not None`, found another `not None` => not exactly one `not None` + return False + return found_one + + if not _exactly_one_arg_is_not_None(self.materials_manager, self.key_provider, self.keyring): + raise TypeError("Exactly one of keyring, materials_manager, or key_provider must be provided") + if self.materials_manager is None: + if self.key_provider is not None: + # No CMM, provided (legacy) native `key_provider` => create (legacy) native DefaultCryptoMaterialsManager + self.materials_manager = DefaultCryptoMaterialsManager(master_key_provider=self.key_provider) + elif self.keyring is not None: + # No CMM, provided MPL keyring => create MPL's DefaultCryptographicMaterialsManager + try: + assert isinstance(self.keyring, IKeyring) + except AssertionError as e: + raise ValueError(f"Argument provided to keyring MUST be a {IKeyring}. Found {keyring.__class__.__name__=}") + + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( + config=MaterialProvidersConfig() + ) + cmm = mat_prov.create_default_cryptographic_materials_manager(CreateDefaultCryptographicMaterialsManagerInput(keyring=self.keyring)) + cmm_handler: CryptoMaterialsManager = CMMHandler(cmm) + self.materials_manager = cmm_handler + + def _no_mpl_attrs_post_init(self): + both_cmm_and_mkp_defined = self.materials_manager is not None and self.key_provider is not None + neither_cmm_nor_mkp_defined = self.materials_manager is None and self.key_provider is None + + if both_cmm_and_mkp_defined or neither_cmm_nor_mkp_defined: + raise TypeError("Exactly one of materials_manager or key_provider must be provided") + if self.materials_manager is None: + self.materials_manager = DefaultCryptoMaterialsManager(master_key_provider=self.key_provider) + def __attrs_post_init__(self): """Normalize inputs to crypto material manager.""" if _has_mpl: - all_cmm_and_mkp_and_keyring_defined = all([ - self.materials_manager is not None, - self.key_provider is not None, - self.keyring is not None, - ]) - none_cmm_nor_mkp_nor_keyring_defined = all([ - self.materials_manager is None, - self.key_provider is None, - self.keyring is None, - ]) - - if all_cmm_and_mkp_and_keyring_defined or none_cmm_nor_mkp_nor_keyring_defined: - raise TypeError("Exactly one of keyring, materials_manager, or key_provider must be provided") - if self.materials_manager is None: - if self.key_provider is not None: - self.materials_manager = DefaultCryptoMaterialsManager(master_key_provider=self.key_provider) - elif self.keyring is not None: - mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( - config=MaterialProvidersConfig() - ) - cmm = mat_prov.create_default_cryptographic_materials_manager(CreateDefaultCryptographicMaterialsManagerInput(keyring=self.keyring)) - cmm_handler: CryptoMaterialsManager = CMMHandler(cmm) - self.materials_manager = cmm_handler + self._has_mpl_attrs_post_init() elif not _has_mpl: - both_cmm_and_mkp_defined = self.materials_manager is not None and self.key_provider is not None - neither_cmm_nor_mkp_defined = self.materials_manager is None and self.key_provider is None - - if both_cmm_and_mkp_defined or neither_cmm_nor_mkp_defined: - raise TypeError("Exactly one of materials_manager or key_provider must be provided") - if self.materials_manager is None: - self.materials_manager = DefaultCryptoMaterialsManager(master_key_provider=self.key_provider) + self._no_mpl_attrs_post_init() class _EncryptionStream(io.IOBase): @@ -387,8 +408,10 @@ class EncryptorConfig(_ClientConfig): :param key_provider: `MasterKeyProvider` from which to obtain data keys for encryption (either `materials_manager` or `key_provider` required) :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider - :param keyring: `IKeyring` TODO-MPL content - :type keyring: TODO-MPL + :param keyring: `IKeyring` from the aws_cryptographic_materialproviders library + which handles encryption and decryption + :type keyring: + aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.references.IKeyring :param int source_length: Length of source data (optional) .. note:: @@ -440,8 +463,10 @@ class StreamEncryptor(_EncryptionStream): # pylint: disable=too-many-instance-a :param key_provider: `MasterKeyProvider` from which to obtain data keys for encryption (either `materials_manager` or `key_provider` required) :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider - :param keyring: `IKeyring` TODO-MPL content - :type keyring: TODO-MPL + :param keyring: `IKeyring` from the aws_cryptographic_materialproviders library + which handles encryption and decryption + :type keyring: + aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.references.IKeyring :param int source_length: Length of source data (optional) .. note:: @@ -782,8 +807,10 @@ class DecryptorConfig(_ClientConfig): :param key_provider: `MasterKeyProvider` from which to obtain data keys for decryption (either `keyring`, `materials_manager` or `key_provider` required) :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider - :param keyring: `IKeyring` TODO-MPL content - :type keyring: TODO-MPL + :param keyring: `IKeyring` from the aws_cryptographic_materialproviders library + which handles encryption and decryption + :type keyring: + aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.references.IKeyring :param int source_length: Length of source data (optional) .. note:: @@ -820,8 +847,10 @@ class StreamDecryptor(_EncryptionStream): # pylint: disable=too-many-instance-a :param key_provider: `MasterKeyProvider` from which to obtain data keys for decryption (either `materials_manager` or `key_provider` required) :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider - :param keyring: `IKeyring` TODO-MPL content - :type keyring: TODO-MPL + :param keyring: `IKeyring` from the aws_cryptographic_materialproviders library + which handles encryption and decryption + :type keyring: + aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.references.IKeyring :param int source_length: Length of source data (optional) .. note:: From 3f5a503ab7f866c7750cabdc79dbf7f8f75a34e6 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 2 Feb 2024 09:21:46 -0800 Subject: [PATCH 003/184] add --- setup.py | 3 +- src/aws_encryption_sdk/cmm_handler.py | 138 ++++++++++++++++ src/aws_encryption_sdk/materials_handlers.py | 165 +++++++++++++++++++ 3 files changed, 304 insertions(+), 2 deletions(-) create mode 100644 src/aws_encryption_sdk/cmm_handler.py create mode 100644 src/aws_encryption_sdk/materials_handlers.py diff --git a/setup.py b/setup.py index 8ffd74015..c4c277096 100644 --- a/setup.py +++ b/setup.py @@ -39,8 +39,7 @@ def get_requirements(): keywords="aws-encryption-sdk aws kms encryption", license="Apache License 2.0", install_requires=get_requirements(), - # TODO: Point at main once Python is merged into main. - # PyPI will not accept a package that declares dependencies using direct URLs. + # TODO: Point at MPL main branch once Python MPL is merged into main. extras_require={ "MPL": ["aws-cryptographic-material-providers @ git+https://github.com/aws/aws-cryptographic-material-providers-library.git@lucmcdon/python-mpl#subdirectory=AwsCryptographicMaterialProviders/runtimes/python"], }, diff --git a/src/aws_encryption_sdk/cmm_handler.py b/src/aws_encryption_sdk/cmm_handler.py new file mode 100644 index 000000000..82aad2248 --- /dev/null +++ b/src/aws_encryption_sdk/cmm_handler.py @@ -0,0 +1,138 @@ +# These dependencies are only loaded if you install the MPL. +try: + from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.references import ( + ICryptographicMaterialsManager, + ) + from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.models import ( + GetEncryptionMaterialsInput, + GetEncryptionMaterialsOutput, + DecryptMaterialsInput, + DecryptMaterialsOutput, + EncryptedDataKey as MPL_EncryptedDataKey, + CommitmentPolicyESDK, + AlgorithmSuiteIdESDK, + ) +except ImportError as e: + print(f"WARNING: MPL import failed with {e=}") + +from aws_encryption_sdk.materials_managers import ( + DecryptionMaterialsRequest, + EncryptionMaterialsRequest, +) +from aws_encryption_sdk.materials_managers.base import ( + CryptoMaterialsManager, +) +from aws_encryption_sdk.materials_handlers import ( + EncryptionMaterialsHandler, + DecryptionMaterialsHandler, +) +from aws_encryption_sdk.structures import ( + EncryptedDataKey as Native_EncryptedDataKey, +) +from aws_encryption_sdk.identifiers import ( + Algorithm, + AlgorithmSuite, + CommitmentPolicy, +) + +# TODO-MPL Should this implement interface..? seems like yes since it implements all of interface methods +class CMMHandler(CryptoMaterialsManager): + native_cmm: CryptoMaterialsManager + mpl_cmm: 'ICryptographicMaterialsManager' + + def __init__( + self, + cmm: 'CryptoMaterialsManager | ICryptographicMaterialsManager' + ): + if isinstance(cmm, CryptoMaterialsManager): + self.native_cmm = cmm + elif isinstance(cmm, ICryptographicMaterialsManager): + self.mpl_cmm = cmm + else: + raise ValueError(f"Invalid CMM passed to CMMHander: {cmm=}") + + def get_encryption_materials( + self, + request: EncryptionMaterialsRequest + ) -> EncryptionMaterialsHandler: + ''' + Returns an EncryptionMaterialsHandler based on the configured CMM. + ''' + if (hasattr(self, "native_cmm") and not hasattr(self, "mpl_cmm")): + return EncryptionMaterialsHandler(self.native_cmm.get_encryption_materials(request)) + else: + input: GetEncryptionMaterialsInput = CMMHandler._create_mpl_get_encryption_materials_input_from_request(request) + print(f"get_encryption_materials {input=}") + output: GetEncryptionMaterialsOutput = self.mpl_cmm.get_encryption_materials(input) + print(f"get_encryption_materials {output=}") + return EncryptionMaterialsHandler(output.encryption_materials) + + @staticmethod + def _create_mpl_get_encryption_materials_input_from_request( + request: EncryptionMaterialsRequest + ) -> 'GetEncryptionMaterialsInput': + print(f"_create_mpl_get_encryption_materials_input_from_request {request=}") + print(f"{CMMHandler._map_native_commitment_policy_to_mpl_commitment_policy(request.commitment_policy)=}") + print(f"_create_mpl_get_encryption_materials_input_from_request {request.encryption_context=}") + output: GetEncryptionMaterialsInput = GetEncryptionMaterialsInput( + encryption_context=request.encryption_context, + commitment_policy=CMMHandler._map_native_commitment_policy_to_mpl_commitment_policy(request.commitment_policy), + # TODO double check this + # optional... maybe this needs to be kwargs?? + # algorithm_suite_id=request.algorithm.algorithm_id, + max_plaintext_length=request.plaintext_length, + ) + print(f"_create_mpl_get_encryption_materials_input_from_request {output=}") + return output + + @staticmethod + def _map_native_commitment_policy_to_mpl_commitment_policy( + native_commitment_policy: CommitmentPolicy + ) -> CommitmentPolicyESDK: + if native_commitment_policy == CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT: + return CommitmentPolicyESDK(value="FORBID_ENCRYPT_ALLOW_DECRYPT") + elif native_commitment_policy == CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT: + return CommitmentPolicyESDK(value="REQUIRE_ENCRYPT_ALLOW_DECRYPT") + elif native_commitment_policy == CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT: + return CommitmentPolicyESDK(value="REQUIRE_ENCRYPT_REQUIRE_DECRYPT") + else: + raise ValueError(f"Invalid {native_commitment_policy=}") + + def decrypt_materials( + self, + request: DecryptionMaterialsRequest + ) -> DecryptionMaterialsHandler: + ''' + Returns a DecryptionMaterialsHandler based on the configured CMM. + ''' + print(f"decrypt_materials {request=}") + if (hasattr(self, "native_cmm") and not hasattr(self, "mpl_cmm")): + return DecryptionMaterialsHandler(self.native_cmm.decrypt_materials(request)) + else: + input: 'DecryptMaterialsInput' = CMMHandler._create_mpl_decrypt_materials_input_from_request(request) + output: 'DecryptMaterialsOutput' = self.mpl_cmm.decrypt_materials(input) + print(f"decrypt_materials {output.decryption_materials.verification_key=}") + return DecryptionMaterialsHandler(output.decryption_materials) + + @staticmethod + def _native_algorithm_id_to_mpl_algorithm_id(native_algorithm_id: str) -> AlgorithmSuiteIdESDK: + # MPL algorithm suite ID = hexstr(native_algorithm_id) padded to 4 digits post-`x`. + return AlgorithmSuiteIdESDK(f"{native_algorithm_id:#0{6}x}") + + @staticmethod + def _create_mpl_decrypt_materials_input_from_request( + request: DecryptionMaterialsRequest + ) -> 'DecryptMaterialsInput': + key_blob_list: list[Native_EncryptedDataKey] = request.encrypted_data_keys + list_edks = [MPL_EncryptedDataKey( + key_provider_id=key_blob.key_provider.provider_id, + key_provider_info=key_blob.key_provider.key_info, + ciphertext=key_blob.encrypted_data_key, + ) for key_blob in key_blob_list] + output: DecryptMaterialsInput = DecryptMaterialsInput( + algorithm_suite_id=CMMHandler._native_algorithm_id_to_mpl_algorithm_id(request.algorithm.algorithm_id), + commitment_policy=CMMHandler._map_native_commitment_policy_to_mpl_commitment_policy(request.commitment_policy), + encrypted_data_keys=list_edks, + encryption_context=request.encryption_context, + ) + return output diff --git a/src/aws_encryption_sdk/materials_handlers.py b/src/aws_encryption_sdk/materials_handlers.py new file mode 100644 index 000000000..bf3073ad3 --- /dev/null +++ b/src/aws_encryption_sdk/materials_handlers.py @@ -0,0 +1,165 @@ +# These dependencies are only loaded if you install the MPL. +try: + from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.models import ( + DecryptionMaterials as MPL_DecryptionMaterials, + EncryptionMaterials as MPL_EncryptionMaterials, + EncryptedDataKey as MPL_EncryptedDataKey, + ) +except ImportError as e: + pass + +from aws_encryption_sdk.materials_managers import ( + DecryptionMaterials as Native_DecryptionMaterials, + EncryptionMaterials as Native_EncryptionMaterials, +) +from aws_encryption_sdk.identifiers import ( + Algorithm, + AlgorithmSuite, +) +from aws_encryption_sdk.structures import ( + DataKey, + EncryptedDataKey as Native_EncryptedDataKey, + MasterKeyInfo, +) +from aws_encryption_sdk.internal.crypto.authentication import ( + Signer +) + +class EncryptionMaterialsHandler: + native_materials: Native_EncryptionMaterials + mpl_materials: 'MPL_EncryptionMaterials' + + @staticmethod + def _mpl_algorithm_id_to_native_algorithm_id(mpl_algorithm_id: str): + # MPL algorithm suite ID == "ALG_" + native algorithm suite ID. + return int(mpl_algorithm_id, 16) + + def __init__( + self, + materials: 'Native_EncryptionMaterials | MPL_EncryptionMaterials' + ): + if isinstance(materials, Native_EncryptionMaterials): + self.native_materials = materials + elif isinstance(materials, MPL_EncryptionMaterials): + self.mpl_materials = materials + else: + raise ValueError(f"Invalid EncryptionMaterials passed to EncryptionMaterialsHandler: {materials=}") + @property + def algorithm(self) -> Algorithm: + if hasattr(self, "native_materials"): + return self.native_materials.algorithm + else: + print(f"algorithm {self.mpl_materials.algorithm_suite.id.value=}") + return AlgorithmSuite.get_by_id( + EncryptionMaterialsHandler._mpl_algorithm_id_to_native_algorithm_id( + self.mpl_materials.algorithm_suite.id.value + ) + ) + + @property + def encryption_context(self) -> dict[str, str]: + if hasattr(self, "native_materials"): + return self.native_materials.encryption_context + else: + return self.mpl_materials.encryption_context + + @property + def encrypted_data_keys(self) -> list[Native_EncryptedDataKey]: + if hasattr(self, "native_materials"): + return self.native_materials.encrypted_data_keys + else: + mpl_edk_list: list[MPL_EncryptedDataKey] = self.mpl_materials.encrypted_data_keys + key_blob_list: set[Native_EncryptedDataKey] = {Native_EncryptedDataKey( + key_provider=MasterKeyInfo( + provider_id=mpl_edk.key_provider_id, + key_info=mpl_edk.key_provider_info, + ), + encrypted_data_key=mpl_edk.ciphertext, + ) for mpl_edk in mpl_edk_list} + return key_blob_list + + @property + def data_encryption_key(self) -> DataKey: + if hasattr(self, "native_materials"): + return self.native_materials.data_encryption_key + else: + # TODO-MPL This impl is probably wrong + mpl_dek = self.mpl_materials.plaintext_data_key + return DataKey( + # key_provider=None, # No MasterKeyInfo object for plaintext data key + key_provider=MasterKeyInfo( + provider_id="", + key_info=b'' + ), + data_key=mpl_dek, + encrypted_data_key=b'', # No encrypted DEK + ) + + @property + def signing_key(self) -> bytes: + if hasattr(self, "native_materials"): + return self.native_materials.signing_key + else: + print(f"sign {self.mpl_materials.signing_key=}") + return self.mpl_materials.signing_key + # if self.mpl_materials.signing_key is None: + # return Signer.from_key_bytes( + # algorithm=AlgorithmSuite.get_by_id(self.mpl_materials.algorithm_suite.id.value), + # bytes=self.mpl_materials.signing_key + # ) + + def get_required_encryption_context_keys(self) -> list[str]: + if hasattr(self, "native_materials"): + return [] + else: + return self.mpl_materials.required_encryption_context_keys + +class DecryptionMaterialsHandler: + native_materials: Native_DecryptionMaterials + mpl_materials: 'MPL_DecryptionMaterials' + + def __init__( + self, + materials: 'Native_DecryptionMaterials | MPL_DecryptionMaterials' + ): + if isinstance(materials, Native_DecryptionMaterials): + self.native_materials = materials + elif isinstance(materials, MPL_DecryptionMaterials): + self.mpl_materials = materials + else: + raise ValueError(f"Invalid DecryptionMaterials passed to DecryptionMaterialsHandler: {materials=}") + + def get_encryption_context(self) -> dict[str, str]: + if hasattr(self, "native_materials"): + return {} # TODO-MPL This impl is probably wrong + else: + return self.mpl_materials.encryption_context + + @property + def data_key(self) -> DataKey: + if hasattr(self, "native_materials"): + return self.native_materials.data_key + else: + # TODO-MPL This impl is probably wrong + return DataKey( + key_provider=MasterKeyInfo( + provider_id="", + key_info=b'' + ), + data_key=self.mpl_materials.plaintext_data_key, + encrypted_data_key=b'', + ) + + @property + def verification_key(self) -> bytes: + if hasattr(self, "native_materials"): + return self.native_materials.verification_key + else: + print(f"ver {self.mpl_materials.verification_key=}") + return self.mpl_materials.verification_key + + def get_required_encryption_context_keys(self) -> list[str]: + if hasattr(self, "native_materials"): + return [] + else: + return self.mpl_materials.required_encryption_context_keys \ No newline at end of file From 16cf5c1f38ae1aaa0e7e5f68fc58cd0c6f3532b2 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 2 Feb 2024 14:41:53 -0800 Subject: [PATCH 004/184] changes, cleanup: --- examples/src/basic_encryption.py | 5 + examples/src/keyrings/hierarchical_keyring.py | 248 ++++++++++++++++++ examples/src/keyrings/module_.py | 0 examples/src/module_.py | 0 src/aws_encryption_sdk/cmm_handler.py | 55 ++-- src/aws_encryption_sdk/materials_handlers.py | 56 ++-- src/aws_encryption_sdk/streaming_client.py | 18 +- .../test_streaming_client_stream_decryptor.py | 2 +- 8 files changed, 314 insertions(+), 70 deletions(-) create mode 100644 examples/src/keyrings/hierarchical_keyring.py create mode 100644 examples/src/keyrings/module_.py create mode 100644 examples/src/module_.py diff --git a/examples/src/basic_encryption.py b/examples/src/basic_encryption.py index cfe8ac791..7b729feab 100644 --- a/examples/src/basic_encryption.py +++ b/examples/src/basic_encryption.py @@ -51,3 +51,8 @@ def cycle_string(key_arn, source_plaintext, botocore_session=None): assert all( pair in decrypted_header.encryption_context.items() for pair in encryptor_header.encryption_context.items() ) + +cycle_string( + "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f", + "abcdefg", +) \ No newline at end of file diff --git a/examples/src/keyrings/hierarchical_keyring.py b/examples/src/keyrings/hierarchical_keyring.py new file mode 100644 index 000000000..e8f662b73 --- /dev/null +++ b/examples/src/keyrings/hierarchical_keyring.py @@ -0,0 +1,248 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + + + +"""Example showing basic encryption and decryption of a value already in memory.""" +import aws_encryption_sdk +from aws_encryption_sdk import CommitmentPolicy +import boto3 + +import sys + +from aws_encryption_sdk.exceptions import ( + AWSEncryptionSDKClientError, + SerializationError, +) + +module_root_dir = '/'.join(__file__.split("/")[:-1]) + +sys.path.append(module_root_dir) + +import aws_cryptographic_materialproviders + +from aws_cryptographic_materialproviders.mpl.client import AwsCryptographicMaterialProviders +from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig +from aws_cryptographic_materialproviders.mpl.models import ( + CreateAwsKmsHierarchicalKeyringInput, + CacheTypeDefault, + DefaultCache, + GetBranchKeyIdInput, + GetBranchKeyIdOutput, +) +from aws_cryptographic_materialproviders.mpl.references import ( + IKeyring, + IBranchKeyIdSupplier, +) + +from aws_cryptographic_materialproviders.keystore.client import KeyStore +from aws_cryptographic_materialproviders.keystore.config import KeyStoreConfig +from aws_cryptographic_materialproviders.keystore.models import ( + CreateKeyInput, + KMSConfigurationKmsKeyArn, +) + +EXAMPLE_DATA: bytes = b"Hello World" + +def encrypt_and_decrypt_with_keyring( + key_store_table_name: str, + logical_key_store_name: str, + kms_key_id: str + ): + + # 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 boto3 clients for DynamoDB and KMS. + ddb_client = boto3.client('dynamodb') + kms_client = boto3.client('kms') + + # 3. Configure your KeyStore resource. + # This SHOULD be the same configuration that you used + # to initially create and populate your KeyStore. + keystore: KeyStore = KeyStore( + config=KeyStoreConfig( + ddb_client=ddb_client, + ddb_table_name=key_store_table_name, + logical_key_store_name=logical_key_store_name, + kms_client=kms_client, + kms_configuration=KMSConfigurationKmsKeyArn( + value=kms_key_id + ), + ) + ) + + # 4. Call CreateKey to create two new active branch keys + branch_key_id_A: str = keystore.create_key(input=CreateKeyInput()).branch_key_identifier + branch_key_id_B: str = keystore.create_key(input=CreateKeyInput()).branch_key_identifier + + class ExampleBranchKeyIdSupplier(IBranchKeyIdSupplier): + branch_key_id_for_tenant_A: str + branch_key_id_for_tenant_B: str + + def __init__(self, tenant_1_id, tenant_2_id): + self.branch_key_id_for_tenant_A = tenant_1_id + self.branch_key_id_for_tenant_B = tenant_2_id + + def get_branch_key_id( + self, + input: GetBranchKeyIdInput + ) -> GetBranchKeyIdOutput: + encryption_context: dict[str, str] = input.encryption_context + + if b"tenant" not in encryption_context: + raise ValueError("EncryptionContext invalid, does not contain expected tenant key value pair.") + + tenant_key_id: str = encryption_context.get(b"tenant") + branch_key_id: str + + if tenant_key_id == b"TenantA": + branch_key_id = self.branch_key_id_for_tenant_A + elif tenant_key_id == b"TenantB": + branch_key_id = self.branch_key_id_for_tenant_B + else: + raise ValueError(f"Item does not contain valid tenant ID: {tenant_key_id=}") + + return GetBranchKeyIdOutput(branch_key_id=branch_key_id) + + # 5. Create a branch key supplier that maps the branch key id to a more readable format + branch_key_id_supplier: IBranchKeyIdSupplier = ExampleBranchKeyIdSupplier( + tenant_1_id=branch_key_id_A, + tenant_2_id=branch_key_id_B, + ) + + # 6. Create the Hierarchical Keyring. + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( + config=MaterialProvidersConfig() + ) + + keyring_input: CreateAwsKmsHierarchicalKeyringInput = CreateAwsKmsHierarchicalKeyringInput( + key_store=keystore, + branch_key_id_supplier=branch_key_id_supplier, + ttl_seconds=600, + cache=CacheTypeDefault( + value=DefaultCache( + entry_capacity=100 + ) + ), + ) + + hierarchical_keyring: IKeyring = mat_prov.create_aws_kms_hierarchical_keyring( + input=keyring_input + ) + + # The Branch Key Id supplier uses the encryption context to determine which branch key id will + # be used to encrypt data. + # Create encryption context for TenantA + encryption_context_A: dict[str, str] = { + "tenant": "TenantA", + "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", + } + + # Create encryption context for TenantB + encryption_context_B: dict[str, str] = { + "tenant": "TenantB", + "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", + } + + # Encrypt the data for encryptionContextA & encryptionContextB + ciphertext_A, _ = client.encrypt( + source=EXAMPLE_DATA, + keyring=hierarchical_keyring, + encryption_context=encryption_context_A + ) + ciphertext_B, _ = client.encrypt( + source=EXAMPLE_DATA, + keyring=hierarchical_keyring, + encryption_context=encryption_context_B + ) + + # To attest that TenantKeyB cannot decrypt a message written by TenantKeyA + # let's construct more restrictive hierarchical keyrings. + keyring_input_A: CreateAwsKmsHierarchicalKeyringInput = CreateAwsKmsHierarchicalKeyringInput( + key_store=keystore, + branch_key_id=branch_key_id_A, + ttl_seconds=600, + cache=CacheTypeDefault( + value=DefaultCache( + entry_capacity=100 + ) + ), + ) + + hierarchical_keyring_A: IKeyring = mat_prov.create_aws_kms_hierarchical_keyring( + input=keyring_input_A + ) + + keyring_input_B: CreateAwsKmsHierarchicalKeyringInput = CreateAwsKmsHierarchicalKeyringInput( + key_store=keystore, + branch_key_id=branch_key_id_B, + ttl_seconds=600, + cache=CacheTypeDefault( + value=DefaultCache( + entry_capacity=100 + ) + ), + ) + + hierarchical_keyring_B: IKeyring = mat_prov.create_aws_kms_hierarchical_keyring( + input=keyring_input_B + ) + + # TODO: Run the decrypt, get expected exception type + # This should fail + try: + client.decrypt( + source=ciphertext_A, + keyring=hierarchical_keyring_B + ) + except AWSEncryptionSDKClientError: + pass + + # # This should fail + try: + client.decrypt( + source=ciphertext_B, + keyring=hierarchical_keyring_A + ) + except AWSEncryptionSDKClientError: + pass + + # These should succeed + plaintext_bytes_A, _ = client.decrypt( + source=ciphertext_A, + keyring=hierarchical_keyring_A + ) + assert plaintext_bytes_A == EXAMPLE_DATA + plaintext_bytes_B, _ = client.decrypt( + source=ciphertext_B, + keyring=hierarchical_keyring_B + ) + assert plaintext_bytes_B == EXAMPLE_DATA + +# Also, a thread-safe example ig + +# hack in a test +import botocore +encrypt_and_decrypt_with_keyring( + "KeyStoreDdbTable", + "KeyStoreDdbTable", + "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126" +) \ No newline at end of file diff --git a/examples/src/keyrings/module_.py b/examples/src/keyrings/module_.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/src/module_.py b/examples/src/module_.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/aws_encryption_sdk/cmm_handler.py b/src/aws_encryption_sdk/cmm_handler.py index 82aad2248..d634dd571 100644 --- a/src/aws_encryption_sdk/cmm_handler.py +++ b/src/aws_encryption_sdk/cmm_handler.py @@ -1,9 +1,12 @@ # These dependencies are only loaded if you install the MPL. try: - from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.references import ( + from aws_cryptographic_materialproviders.mpl.errors import ( + AwsCryptographicMaterialProvidersException + ) + from aws_cryptographic_materialproviders.mpl.references import ( ICryptographicMaterialsManager, ) - from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.models import ( + from aws_cryptographic_materialproviders.mpl.models import ( GetEncryptionMaterialsInput, GetEncryptionMaterialsOutput, DecryptMaterialsInput, @@ -13,8 +16,11 @@ AlgorithmSuiteIdESDK, ) except ImportError as e: - print(f"WARNING: MPL import failed with {e=}") + pass +from aws_encryption_sdk.exceptions import ( + AWSEncryptionSDKClientError, +) from aws_encryption_sdk.materials_managers import ( DecryptionMaterialsRequest, EncryptionMaterialsRequest, @@ -30,8 +36,6 @@ EncryptedDataKey as Native_EncryptedDataKey, ) from aws_encryption_sdk.identifiers import ( - Algorithm, - AlgorithmSuite, CommitmentPolicy, ) @@ -40,6 +44,9 @@ class CMMHandler(CryptoMaterialsManager): native_cmm: CryptoMaterialsManager mpl_cmm: 'ICryptographicMaterialsManager' + def _is_using_native_cmm(self): + return hasattr(self, "native_cmm") and not hasattr(self, "mpl_cmm") + def __init__( self, cmm: 'CryptoMaterialsManager | ICryptographicMaterialsManager' @@ -56,24 +63,24 @@ def get_encryption_materials( request: EncryptionMaterialsRequest ) -> EncryptionMaterialsHandler: ''' - Returns an EncryptionMaterialsHandler based on the configured CMM. + Returns an EncryptionMaterialsHandler for the configured CMM. ''' - if (hasattr(self, "native_cmm") and not hasattr(self, "mpl_cmm")): + if (self._is_using_native_cmm()): return EncryptionMaterialsHandler(self.native_cmm.get_encryption_materials(request)) else: - input: GetEncryptionMaterialsInput = CMMHandler._create_mpl_get_encryption_materials_input_from_request(request) - print(f"get_encryption_materials {input=}") - output: GetEncryptionMaterialsOutput = self.mpl_cmm.get_encryption_materials(input) - print(f"get_encryption_materials {output=}") - return EncryptionMaterialsHandler(output.encryption_materials) + try: + input: GetEncryptionMaterialsInput = CMMHandler._create_mpl_get_encryption_materials_input_from_request(request) + output: GetEncryptionMaterialsOutput = self.mpl_cmm.get_encryption_materials(input) + return EncryptionMaterialsHandler(output.encryption_materials) + except AwsCryptographicMaterialProvidersException as e: + # Wrap MPL error into the ESDK error type + # so customers only have to catch ESDK error types. + raise AWSEncryptionSDKClientError(e) @staticmethod def _create_mpl_get_encryption_materials_input_from_request( request: EncryptionMaterialsRequest ) -> 'GetEncryptionMaterialsInput': - print(f"_create_mpl_get_encryption_materials_input_from_request {request=}") - print(f"{CMMHandler._map_native_commitment_policy_to_mpl_commitment_policy(request.commitment_policy)=}") - print(f"_create_mpl_get_encryption_materials_input_from_request {request.encryption_context=}") output: GetEncryptionMaterialsInput = GetEncryptionMaterialsInput( encryption_context=request.encryption_context, commitment_policy=CMMHandler._map_native_commitment_policy_to_mpl_commitment_policy(request.commitment_policy), @@ -82,7 +89,6 @@ def _create_mpl_get_encryption_materials_input_from_request( # algorithm_suite_id=request.algorithm.algorithm_id, max_plaintext_length=request.plaintext_length, ) - print(f"_create_mpl_get_encryption_materials_input_from_request {output=}") return output @staticmethod @@ -103,16 +109,19 @@ def decrypt_materials( request: DecryptionMaterialsRequest ) -> DecryptionMaterialsHandler: ''' - Returns a DecryptionMaterialsHandler based on the configured CMM. + Returns a DecryptionMaterialsHandler for the configured CMM. ''' - print(f"decrypt_materials {request=}") - if (hasattr(self, "native_cmm") and not hasattr(self, "mpl_cmm")): + if (self._is_using_native_cmm()): return DecryptionMaterialsHandler(self.native_cmm.decrypt_materials(request)) else: - input: 'DecryptMaterialsInput' = CMMHandler._create_mpl_decrypt_materials_input_from_request(request) - output: 'DecryptMaterialsOutput' = self.mpl_cmm.decrypt_materials(input) - print(f"decrypt_materials {output.decryption_materials.verification_key=}") - return DecryptionMaterialsHandler(output.decryption_materials) + try: + input: 'DecryptMaterialsInput' = CMMHandler._create_mpl_decrypt_materials_input_from_request(request) + output: 'DecryptMaterialsOutput' = self.mpl_cmm.decrypt_materials(input) + return DecryptionMaterialsHandler(output.decryption_materials) + except AwsCryptographicMaterialProvidersException as e: + # Wrap MPL error into the ESDK error type + # so customers only have to catch ESDK error types. + raise AWSEncryptionSDKClientError(e) @staticmethod def _native_algorithm_id_to_mpl_algorithm_id(native_algorithm_id: str) -> AlgorithmSuiteIdESDK: diff --git a/src/aws_encryption_sdk/materials_handlers.py b/src/aws_encryption_sdk/materials_handlers.py index bf3073ad3..1f34eba03 100644 --- a/src/aws_encryption_sdk/materials_handlers.py +++ b/src/aws_encryption_sdk/materials_handlers.py @@ -1,6 +1,6 @@ # These dependencies are only loaded if you install the MPL. try: - from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.models import ( + from aws_cryptographic_materialproviders.mpl.models import ( DecryptionMaterials as MPL_DecryptionMaterials, EncryptionMaterials as MPL_EncryptionMaterials, EncryptedDataKey as MPL_EncryptedDataKey, @@ -25,15 +25,19 @@ Signer ) +def _mpl_algorithm_id_to_native_algorithm_id(mpl_algorithm_id: str): + # MPL algorithm suite ID == hex(native algorithm suite ID) + return int(mpl_algorithm_id, 16) + class EncryptionMaterialsHandler: + ''' + In instances where encryption materials may be provided by either + the native `aws_encryption_sdk.materials_managers.Native_EncryptionMaterials` + or the MPL's `aws_cryptographic_materialproviders.mpl.models` + ''' native_materials: Native_EncryptionMaterials mpl_materials: 'MPL_EncryptionMaterials' - @staticmethod - def _mpl_algorithm_id_to_native_algorithm_id(mpl_algorithm_id: str): - # MPL algorithm suite ID == "ALG_" + native algorithm suite ID. - return int(mpl_algorithm_id, 16) - def __init__( self, materials: 'Native_EncryptionMaterials | MPL_EncryptionMaterials' @@ -49,9 +53,8 @@ def algorithm(self) -> Algorithm: if hasattr(self, "native_materials"): return self.native_materials.algorithm else: - print(f"algorithm {self.mpl_materials.algorithm_suite.id.value=}") return AlgorithmSuite.get_by_id( - EncryptionMaterialsHandler._mpl_algorithm_id_to_native_algorithm_id( + _mpl_algorithm_id_to_native_algorithm_id( self.mpl_materials.algorithm_suite.id.value ) ) @@ -83,10 +86,12 @@ def data_encryption_key(self) -> DataKey: if hasattr(self, "native_materials"): return self.native_materials.data_encryption_key else: - # TODO-MPL This impl is probably wrong + # TODO-MPL This impl is probably wrong, but works for for now + # If this works for all features, great! Remove this comment before launch. + # Otherwise, fix the implementation. mpl_dek = self.mpl_materials.plaintext_data_key return DataKey( - # key_provider=None, # No MasterKeyInfo object for plaintext data key + # key_provider is unused, but the return type is DataKey key_provider=MasterKeyInfo( provider_id="", key_info=b'' @@ -100,20 +105,8 @@ def signing_key(self) -> bytes: if hasattr(self, "native_materials"): return self.native_materials.signing_key else: - print(f"sign {self.mpl_materials.signing_key=}") return self.mpl_materials.signing_key - # if self.mpl_materials.signing_key is None: - # return Signer.from_key_bytes( - # algorithm=AlgorithmSuite.get_by_id(self.mpl_materials.algorithm_suite.id.value), - # bytes=self.mpl_materials.signing_key - # ) - def get_required_encryption_context_keys(self) -> list[str]: - if hasattr(self, "native_materials"): - return [] - else: - return self.mpl_materials.required_encryption_context_keys - class DecryptionMaterialsHandler: native_materials: Native_DecryptionMaterials mpl_materials: 'MPL_DecryptionMaterials' @@ -128,19 +121,15 @@ def __init__( self.mpl_materials = materials else: raise ValueError(f"Invalid DecryptionMaterials passed to DecryptionMaterialsHandler: {materials=}") - - def get_encryption_context(self) -> dict[str, str]: - if hasattr(self, "native_materials"): - return {} # TODO-MPL This impl is probably wrong - else: - return self.mpl_materials.encryption_context @property def data_key(self) -> DataKey: if hasattr(self, "native_materials"): return self.native_materials.data_key else: - # TODO-MPL This impl is probably wrong + # TODO-MPL This impl is probably wrong, but works for for now + # If this works for all features, great! Remove this comment before launch. + # Otherwise, fix the implementation. return DataKey( key_provider=MasterKeyInfo( provider_id="", @@ -155,11 +144,4 @@ def verification_key(self) -> bytes: if hasattr(self, "native_materials"): return self.native_materials.verification_key else: - print(f"ver {self.mpl_materials.verification_key=}") - return self.mpl_materials.verification_key - - def get_required_encryption_context_keys(self) -> list[str]: - if hasattr(self, "native_materials"): - return [] - else: - return self.mpl_materials.required_encryption_context_keys \ No newline at end of file + return self.mpl_materials.verification_key \ No newline at end of file diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 176b92334..e6cf00635 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -68,12 +68,12 @@ from aws_encryption_sdk.structures import MessageHeader try: import aws_cryptographic_materialproviders - from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.client import AwsCryptographicMaterialProviders - from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.config import MaterialProvidersConfig - from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.models import ( + from aws_cryptographic_materialproviders.mpl.client import AwsCryptographicMaterialProviders + from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig + from aws_cryptographic_materialproviders.mpl.models import ( CreateDefaultCryptographicMaterialsManagerInput ) - from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.references import ( + from aws_cryptographic_materialproviders.mpl.references import ( IKeyring, ) from aws_encryption_sdk.cmm_handler import CMMHandler @@ -411,7 +411,7 @@ class EncryptorConfig(_ClientConfig): :param keyring: `IKeyring` from the aws_cryptographic_materialproviders library which handles encryption and decryption :type keyring: - aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.references.IKeyring + aws_cryptographic_materialproviders.mpl.references.IKeyring :param int source_length: Length of source data (optional) .. note:: @@ -466,7 +466,7 @@ class StreamEncryptor(_EncryptionStream): # pylint: disable=too-many-instance-a :param keyring: `IKeyring` from the aws_cryptographic_materialproviders library which handles encryption and decryption :type keyring: - aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.references.IKeyring + aws_cryptographic_materialproviders.mpl.references.IKeyring :param int source_length: Length of source data (optional) .. note:: @@ -810,7 +810,7 @@ class DecryptorConfig(_ClientConfig): :param keyring: `IKeyring` from the aws_cryptographic_materialproviders library which handles encryption and decryption :type keyring: - aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.references.IKeyring + aws_cryptographic_materialproviders.mpl.references.IKeyring :param int source_length: Length of source data (optional) .. note:: @@ -850,7 +850,7 @@ class StreamDecryptor(_EncryptionStream): # pylint: disable=too-many-instance-a :param keyring: `IKeyring` from the aws_cryptographic_materialproviders library which handles encryption and decryption :type keyring: - aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.references.IKeyring + aws_cryptographic_materialproviders.mpl.references.IKeyring :param int source_length: Length of source data (optional) .. note:: @@ -1082,7 +1082,7 @@ def close(self): """Closes out the stream.""" _LOGGER.debug("Closing stream") if not hasattr(self, "footer"): - raise SerializationError("Footer not read") + raise SerializationError("Footer not read, message may be corrupted or data key may be incorrect") super(StreamDecryptor, self).close() diff --git a/test/unit/test_streaming_client_stream_decryptor.py b/test/unit/test_streaming_client_stream_decryptor.py index 157755094..94b22b092 100644 --- a/test/unit/test_streaming_client_stream_decryptor.py +++ b/test/unit/test_streaming_client_stream_decryptor.py @@ -767,4 +767,4 @@ def test_close_no_footer(self, mock_close): ) with pytest.raises(SerializationError) as excinfo: test_decryptor.close() - excinfo.match("Footer not read") + excinfo.match("Footer not read, message may be corrupted or data key may be incorrect") From 5b5aa07af75e37d2ad42e7ba850345097d55fcc0 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 2 Feb 2024 15:04:57 -0800 Subject: [PATCH 005/184] changes, cleanup --- src/aws_encryption_sdk/streaming_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index e6cf00635..55be3b917 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -170,7 +170,7 @@ def _exactly_one_arg_is_not_None(*args): try: assert isinstance(self.keyring, IKeyring) except AssertionError as e: - raise ValueError(f"Argument provided to keyring MUST be a {IKeyring}. Found {keyring.__class__.__name__=}") + raise ValueError(f"Argument provided to keyring MUST be a {IKeyring}. Found {self.keyring.__class__.__name__=}") mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( config=MaterialProvidersConfig() From 03e19caff1eaa264873fab500beb8e62c890b583 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 2 Feb 2024 15:19:08 -0800 Subject: [PATCH 006/184] flake8 --- src/aws_encryption_sdk/cmm_handler.py | 31 ++++++---- src/aws_encryption_sdk/materials_handlers.py | 27 +++++---- src/aws_encryption_sdk/streaming_client.py | 62 +++++++++++--------- 3 files changed, 68 insertions(+), 52 deletions(-) diff --git a/src/aws_encryption_sdk/cmm_handler.py b/src/aws_encryption_sdk/cmm_handler.py index d634dd571..f7f95b0c9 100644 --- a/src/aws_encryption_sdk/cmm_handler.py +++ b/src/aws_encryption_sdk/cmm_handler.py @@ -15,7 +15,7 @@ CommitmentPolicyESDK, AlgorithmSuiteIdESDK, ) -except ImportError as e: +except ImportError: pass from aws_encryption_sdk.exceptions import ( @@ -39,6 +39,7 @@ CommitmentPolicy, ) + # TODO-MPL Should this implement interface..? seems like yes since it implements all of interface methods class CMMHandler(CryptoMaterialsManager): native_cmm: CryptoMaterialsManager @@ -57,7 +58,7 @@ def __init__( self.mpl_cmm = cmm else: raise ValueError(f"Invalid CMM passed to CMMHander: {cmm=}") - + def get_encryption_materials( self, request: EncryptionMaterialsRequest @@ -69,28 +70,32 @@ def get_encryption_materials( return EncryptionMaterialsHandler(self.native_cmm.get_encryption_materials(request)) else: try: - input: GetEncryptionMaterialsInput = CMMHandler._create_mpl_get_encryption_materials_input_from_request(request) + input: GetEncryptionMaterialsInput = CMMHandler._create_mpl_get_encryption_materials_input_from_request( + request + ) output: GetEncryptionMaterialsOutput = self.mpl_cmm.get_encryption_materials(input) return EncryptionMaterialsHandler(output.encryption_materials) except AwsCryptographicMaterialProvidersException as e: # Wrap MPL error into the ESDK error type # so customers only have to catch ESDK error types. raise AWSEncryptionSDKClientError(e) - + @staticmethod def _create_mpl_get_encryption_materials_input_from_request( request: EncryptionMaterialsRequest ) -> 'GetEncryptionMaterialsInput': output: GetEncryptionMaterialsInput = GetEncryptionMaterialsInput( encryption_context=request.encryption_context, - commitment_policy=CMMHandler._map_native_commitment_policy_to_mpl_commitment_policy(request.commitment_policy), + commitment_policy=CMMHandler._map_native_commitment_policy_to_mpl_commitment_policy( + request.commitment_policy + ), # TODO double check this # optional... maybe this needs to be kwargs?? # algorithm_suite_id=request.algorithm.algorithm_id, max_plaintext_length=request.plaintext_length, ) return output - + @staticmethod def _map_native_commitment_policy_to_mpl_commitment_policy( native_commitment_policy: CommitmentPolicy @@ -103,7 +108,7 @@ def _map_native_commitment_policy_to_mpl_commitment_policy( return CommitmentPolicyESDK(value="REQUIRE_ENCRYPT_REQUIRE_DECRYPT") else: raise ValueError(f"Invalid {native_commitment_policy=}") - + def decrypt_materials( self, request: DecryptionMaterialsRequest @@ -122,12 +127,12 @@ def decrypt_materials( # Wrap MPL error into the ESDK error type # so customers only have to catch ESDK error types. raise AWSEncryptionSDKClientError(e) - + @staticmethod def _native_algorithm_id_to_mpl_algorithm_id(native_algorithm_id: str) -> AlgorithmSuiteIdESDK: # MPL algorithm suite ID = hexstr(native_algorithm_id) padded to 4 digits post-`x`. return AlgorithmSuiteIdESDK(f"{native_algorithm_id:#0{6}x}") - + @staticmethod def _create_mpl_decrypt_materials_input_from_request( request: DecryptionMaterialsRequest @@ -139,8 +144,12 @@ def _create_mpl_decrypt_materials_input_from_request( ciphertext=key_blob.encrypted_data_key, ) for key_blob in key_blob_list] output: DecryptMaterialsInput = DecryptMaterialsInput( - algorithm_suite_id=CMMHandler._native_algorithm_id_to_mpl_algorithm_id(request.algorithm.algorithm_id), - commitment_policy=CMMHandler._map_native_commitment_policy_to_mpl_commitment_policy(request.commitment_policy), + algorithm_suite_id=CMMHandler._native_algorithm_id_to_mpl_algorithm_id( + request.algorithm.algorithm_id + ), + commitment_policy=CMMHandler._map_native_commitment_policy_to_mpl_commitment_policy( + request.commitment_policy + ), encrypted_data_keys=list_edks, encryption_context=request.encryption_context, ) diff --git a/src/aws_encryption_sdk/materials_handlers.py b/src/aws_encryption_sdk/materials_handlers.py index 1f34eba03..a03138e78 100644 --- a/src/aws_encryption_sdk/materials_handlers.py +++ b/src/aws_encryption_sdk/materials_handlers.py @@ -5,7 +5,7 @@ EncryptionMaterials as MPL_EncryptionMaterials, EncryptedDataKey as MPL_EncryptedDataKey, ) -except ImportError as e: +except ImportError: pass from aws_encryption_sdk.materials_managers import ( @@ -21,14 +21,13 @@ EncryptedDataKey as Native_EncryptedDataKey, MasterKeyInfo, ) -from aws_encryption_sdk.internal.crypto.authentication import ( - Signer -) + def _mpl_algorithm_id_to_native_algorithm_id(mpl_algorithm_id: str): # MPL algorithm suite ID == hex(native algorithm suite ID) return int(mpl_algorithm_id, 16) + class EncryptionMaterialsHandler: ''' In instances where encryption materials may be provided by either @@ -48,6 +47,7 @@ def __init__( self.mpl_materials = materials else: raise ValueError(f"Invalid EncryptionMaterials passed to EncryptionMaterialsHandler: {materials=}") + @property def algorithm(self) -> Algorithm: if hasattr(self, "native_materials"): @@ -58,14 +58,14 @@ def algorithm(self) -> Algorithm: self.mpl_materials.algorithm_suite.id.value ) ) - + @property def encryption_context(self) -> dict[str, str]: if hasattr(self, "native_materials"): return self.native_materials.encryption_context else: return self.mpl_materials.encryption_context - + @property def encrypted_data_keys(self) -> list[Native_EncryptedDataKey]: if hasattr(self, "native_materials"): @@ -80,7 +80,7 @@ def encrypted_data_keys(self) -> list[Native_EncryptedDataKey]: encrypted_data_key=mpl_edk.ciphertext, ) for mpl_edk in mpl_edk_list} return key_blob_list - + @property def data_encryption_key(self) -> DataKey: if hasattr(self, "native_materials"): @@ -97,16 +97,17 @@ def data_encryption_key(self) -> DataKey: key_info=b'' ), data_key=mpl_dek, - encrypted_data_key=b'', # No encrypted DEK + encrypted_data_key=b'', # No encrypted DEK ) - + @property def signing_key(self) -> bytes: if hasattr(self, "native_materials"): return self.native_materials.signing_key else: return self.mpl_materials.signing_key - + + class DecryptionMaterialsHandler: native_materials: Native_DecryptionMaterials mpl_materials: 'MPL_DecryptionMaterials' @@ -121,7 +122,7 @@ def __init__( self.mpl_materials = materials else: raise ValueError(f"Invalid DecryptionMaterials passed to DecryptionMaterialsHandler: {materials=}") - + @property def data_key(self) -> DataKey: if hasattr(self, "native_materials"): @@ -138,10 +139,10 @@ def data_key(self) -> DataKey: data_key=self.mpl_materials.plaintext_data_key, encrypted_data_key=b'', ) - + @property def verification_key(self) -> bytes: if hasattr(self, "native_materials"): return self.native_materials.verification_key else: - return self.mpl_materials.verification_key \ No newline at end of file + return self.mpl_materials.verification_key diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 55be3b917..661b3fa21 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -67,7 +67,6 @@ from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager from aws_encryption_sdk.structures import MessageHeader try: - import aws_cryptographic_materialproviders from aws_cryptographic_materialproviders.mpl.client import AwsCryptographicMaterialProviders from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig from aws_cryptographic_materialproviders.mpl.models import ( @@ -77,14 +76,33 @@ IKeyring, ) from aws_encryption_sdk.cmm_handler import CMMHandler - + _has_mpl = True -except ImportError as e: +except ImportError: _has_mpl = False _LOGGER = logging.getLogger(__name__) +def _exactly_one_arg_is_not_None(*args): + ''' + Private helper function. + Returns `True` if exactly one item in the list is not `None`. + Returns `False` otherwise. + ''' + # Have not found any `not None` + found_one = False + for arg in args: + if arg is not None: + if found_one is False: + # Have not already found a `not None`, found a `not None` => only one `not None` (so far) + found_one = True + else: + # Already found a `not None`, found another `not None` => not exactly one `not None` + return False + return found_one + + @attr.s(hash=True) # pylint: disable=too-many-instance-attributes @six.add_metaclass(abc.ABCMeta) class _ClientConfig(object): # pylint: disable=too-many-instance-attributes @@ -140,42 +158,30 @@ class _ClientConfig(object): # pylint: disable=too-many-instance-attributes ) # DEPRECATED: Value is no longer configurable here. Parameter left here to avoid breaking consumers. def _has_mpl_attrs_post_init(self): - - def _exactly_one_arg_is_not_None(*args): - ''' - Private helper function. - Returns `True` if exactly one item in the list is not `None`. - Returns `False` otherwise. - ''' - # Have not found any `not None` - found_one = False - for arg in args: - if arg is not None: - if found_one == False: - # Have not already found a `not None`, found a `not None` => only one `not None` (so far) - found_one = True - else: - # Already found a `not None`, found another `not None` => not exactly one `not None` - return False - return found_one - if not _exactly_one_arg_is_not_None(self.materials_manager, self.key_provider, self.keyring): raise TypeError("Exactly one of keyring, materials_manager, or key_provider must be provided") if self.materials_manager is None: if self.key_provider is not None: - # No CMM, provided (legacy) native `key_provider` => create (legacy) native DefaultCryptoMaterialsManager - self.materials_manager = DefaultCryptoMaterialsManager(master_key_provider=self.key_provider) + # No CMM, provided legacy native `key_provider` => create legacy native DefaultCryptoMaterialsManager + self.materials_manager = DefaultCryptoMaterialsManager( + master_key_provider=self.key_provider + ) elif self.keyring is not None: # No CMM, provided MPL keyring => create MPL's DefaultCryptographicMaterialsManager try: assert isinstance(self.keyring, IKeyring) - except AssertionError as e: - raise ValueError(f"Argument provided to keyring MUST be a {IKeyring}. Found {self.keyring.__class__.__name__=}") - + except AssertionError: + raise ValueError(f"Argument provided to keyring MUST be a {IKeyring}. \ + Found {self.keyring.__class__.__name__=}") + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( config=MaterialProvidersConfig() ) - cmm = mat_prov.create_default_cryptographic_materials_manager(CreateDefaultCryptographicMaterialsManagerInput(keyring=self.keyring)) + cmm = mat_prov.create_default_cryptographic_materials_manager( + CreateDefaultCryptographicMaterialsManagerInput( + keyring=self.keyring + ) + ) cmm_handler: CryptoMaterialsManager = CMMHandler(cmm) self.materials_manager = cmm_handler From b5d33275a462e9311b08359a722496d0737a81be Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 2 Feb 2024 15:41:21 -0800 Subject: [PATCH 007/184] flake8 --- src/aws_encryption_sdk/cmm_handler.py | 25 +++++++-- src/aws_encryption_sdk/materials_handlers.py | 56 ++++++++++++++++++-- 2 files changed, 72 insertions(+), 9 deletions(-) diff --git a/src/aws_encryption_sdk/cmm_handler.py b/src/aws_encryption_sdk/cmm_handler.py index f7f95b0c9..bb60a4fa1 100644 --- a/src/aws_encryption_sdk/cmm_handler.py +++ b/src/aws_encryption_sdk/cmm_handler.py @@ -1,3 +1,5 @@ +"""Retrieves encryption/decryption materials from an underlying materials provider.""" + # These dependencies are only loaded if you install the MPL. try: from aws_cryptographic_materialproviders.mpl.errors import ( @@ -42,6 +44,15 @@ # TODO-MPL Should this implement interface..? seems like yes since it implements all of interface methods class CMMHandler(CryptoMaterialsManager): + """ + In instances where encryption materials may be provided by either + an implementation of the native + `aws_encryption_sdk.materials_managers.base.CryptoMaterialsManager` + or an implementation of the MPL's + `aws_cryptographic_materialproviders.mpl.references.ICryptographicMaterialsManager`, + this provides the correct materials based on the underlying materials manager. + """ + native_cmm: CryptoMaterialsManager mpl_cmm: 'ICryptographicMaterialsManager' @@ -57,15 +68,17 @@ def __init__( elif isinstance(cmm, ICryptographicMaterialsManager): self.mpl_cmm = cmm else: - raise ValueError(f"Invalid CMM passed to CMMHander: {cmm=}") + raise ValueError(f"Invalid CMM passed to CMMHandler: {cmm=}") def get_encryption_materials( self, request: EncryptionMaterialsRequest ) -> EncryptionMaterialsHandler: - ''' + """ Returns an EncryptionMaterialsHandler for the configured CMM. - ''' + :param request: Request for encryption materials + """ + if (self._is_using_native_cmm()): return EncryptionMaterialsHandler(self.native_cmm.get_encryption_materials(request)) else: @@ -113,9 +126,11 @@ def decrypt_materials( self, request: DecryptionMaterialsRequest ) -> DecryptionMaterialsHandler: - ''' + """ Returns a DecryptionMaterialsHandler for the configured CMM. - ''' + :param request: Request for decryption materials + """ + if (self._is_using_native_cmm()): return DecryptionMaterialsHandler(self.native_cmm.decrypt_materials(request)) else: diff --git a/src/aws_encryption_sdk/materials_handlers.py b/src/aws_encryption_sdk/materials_handlers.py index a03138e78..180dec3bb 100644 --- a/src/aws_encryption_sdk/materials_handlers.py +++ b/src/aws_encryption_sdk/materials_handlers.py @@ -1,3 +1,4 @@ +"""Provides encryption/decryption materials from an underlying materials provider.""" # These dependencies are only loaded if you install the MPL. try: from aws_cryptographic_materialproviders.mpl.models import ( @@ -29,11 +30,13 @@ def _mpl_algorithm_id_to_native_algorithm_id(mpl_algorithm_id: str): class EncryptionMaterialsHandler: - ''' + """ In instances where encryption materials may be provided by either - the native `aws_encryption_sdk.materials_managers.Native_EncryptionMaterials` - or the MPL's `aws_cryptographic_materialproviders.mpl.models` - ''' + the native `aws_encryption_sdk.materials_managers.EncryptionMaterials` + or the MPL's `aws_cryptographic_materialproviders.mpl.models.EncryptionMaterials`, + this provides the correct materials based on the configured materials provider. + """ + native_materials: Native_EncryptionMaterials mpl_materials: 'MPL_EncryptionMaterials' @@ -41,6 +44,11 @@ def __init__( self, materials: 'Native_EncryptionMaterials | MPL_EncryptionMaterials' ): + """ + Create EncryptionMaterialsHandler. + :param materials: Underlying encryption materials + """ + if isinstance(materials, Native_EncryptionMaterials): self.native_materials = materials elif isinstance(materials, MPL_EncryptionMaterials): @@ -50,6 +58,10 @@ def __init__( @property def algorithm(self) -> Algorithm: + """ + Materials' native Algorithm. + """ + if hasattr(self, "native_materials"): return self.native_materials.algorithm else: @@ -61,6 +73,10 @@ def algorithm(self) -> Algorithm: @property def encryption_context(self) -> dict[str, str]: + """ + Materials' encryption context. + """ + if hasattr(self, "native_materials"): return self.native_materials.encryption_context else: @@ -68,6 +84,10 @@ def encryption_context(self) -> dict[str, str]: @property def encrypted_data_keys(self) -> list[Native_EncryptedDataKey]: + """ + Materials' encrypted data keys. + """ + if hasattr(self, "native_materials"): return self.native_materials.encrypted_data_keys else: @@ -83,6 +103,10 @@ def encrypted_data_keys(self) -> list[Native_EncryptedDataKey]: @property def data_encryption_key(self) -> DataKey: + """ + Materials' data encryption key. + """ + if hasattr(self, "native_materials"): return self.native_materials.data_encryption_key else: @@ -102,6 +126,10 @@ def data_encryption_key(self) -> DataKey: @property def signing_key(self) -> bytes: + """ + Materials' signing key. + """ + if hasattr(self, "native_materials"): return self.native_materials.signing_key else: @@ -109,6 +137,13 @@ def signing_key(self) -> bytes: class DecryptionMaterialsHandler: + """ + In instances where decryption materials may be provided by either + the native `aws_encryption_sdk.materials_managers.DecryptionMaterials` + or the MPL's `aws_cryptographic_materialproviders.mpl.models.DecryptionMaterials`, + this provides the correct materials based on the configured materials provider. + """ + native_materials: Native_DecryptionMaterials mpl_materials: 'MPL_DecryptionMaterials' @@ -116,6 +151,11 @@ def __init__( self, materials: 'Native_DecryptionMaterials | MPL_DecryptionMaterials' ): + """ + Create DecryptionMaterialsHandler. + :param materials: Underlying decryption materials + """ + if isinstance(materials, Native_DecryptionMaterials): self.native_materials = materials elif isinstance(materials, MPL_DecryptionMaterials): @@ -125,6 +165,10 @@ def __init__( @property def data_key(self) -> DataKey: + """ + Materials' data key. + """ + if hasattr(self, "native_materials"): return self.native_materials.data_key else: @@ -142,6 +186,10 @@ def data_key(self) -> DataKey: @property def verification_key(self) -> bytes: + """ + Materials' verification key. + """ + if hasattr(self, "native_materials"): return self.native_materials.verification_key else: From b13cd191a8874dc0091e844c4cb0789d81591764 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 2 Feb 2024 15:45:55 -0800 Subject: [PATCH 008/184] flake8 --- src/aws_encryption_sdk/cmm_handler.py | 7 ++-- src/aws_encryption_sdk/materials_handlers.py | 37 ++++---------------- src/aws_encryption_sdk/streaming_client.py | 4 +-- 3 files changed, 14 insertions(+), 34 deletions(-) diff --git a/src/aws_encryption_sdk/cmm_handler.py b/src/aws_encryption_sdk/cmm_handler.py index bb60a4fa1..17d59792a 100644 --- a/src/aws_encryption_sdk/cmm_handler.py +++ b/src/aws_encryption_sdk/cmm_handler.py @@ -63,6 +63,11 @@ def __init__( self, cmm: 'CryptoMaterialsManager | ICryptographicMaterialsManager' ): + """ + Create DecryptionMaterialsHandler. + :param cmm: Underlying cryptographic materials manager + """ + if isinstance(cmm, CryptoMaterialsManager): self.native_cmm = cmm elif isinstance(cmm, ICryptographicMaterialsManager): @@ -78,7 +83,6 @@ def get_encryption_materials( Returns an EncryptionMaterialsHandler for the configured CMM. :param request: Request for encryption materials """ - if (self._is_using_native_cmm()): return EncryptionMaterialsHandler(self.native_cmm.get_encryption_materials(request)) else: @@ -130,7 +134,6 @@ def decrypt_materials( Returns a DecryptionMaterialsHandler for the configured CMM. :param request: Request for decryption materials """ - if (self._is_using_native_cmm()): return DecryptionMaterialsHandler(self.native_cmm.decrypt_materials(request)) else: diff --git a/src/aws_encryption_sdk/materials_handlers.py b/src/aws_encryption_sdk/materials_handlers.py index 180dec3bb..d54e4517b 100644 --- a/src/aws_encryption_sdk/materials_handlers.py +++ b/src/aws_encryption_sdk/materials_handlers.py @@ -48,7 +48,6 @@ def __init__( Create EncryptionMaterialsHandler. :param materials: Underlying encryption materials """ - if isinstance(materials, Native_EncryptionMaterials): self.native_materials = materials elif isinstance(materials, MPL_EncryptionMaterials): @@ -58,10 +57,7 @@ def __init__( @property def algorithm(self) -> Algorithm: - """ - Materials' native Algorithm. - """ - + """Materials' native Algorithm.""" if hasattr(self, "native_materials"): return self.native_materials.algorithm else: @@ -73,10 +69,7 @@ def algorithm(self) -> Algorithm: @property def encryption_context(self) -> dict[str, str]: - """ - Materials' encryption context. - """ - + """Materials' encryption context.""" if hasattr(self, "native_materials"): return self.native_materials.encryption_context else: @@ -84,10 +77,7 @@ def encryption_context(self) -> dict[str, str]: @property def encrypted_data_keys(self) -> list[Native_EncryptedDataKey]: - """ - Materials' encrypted data keys. - """ - + """Materials' encrypted data keys.""" if hasattr(self, "native_materials"): return self.native_materials.encrypted_data_keys else: @@ -103,10 +93,7 @@ def encrypted_data_keys(self) -> list[Native_EncryptedDataKey]: @property def data_encryption_key(self) -> DataKey: - """ - Materials' data encryption key. - """ - + """Materials' data encryption key.""" if hasattr(self, "native_materials"): return self.native_materials.data_encryption_key else: @@ -126,10 +113,7 @@ def data_encryption_key(self) -> DataKey: @property def signing_key(self) -> bytes: - """ - Materials' signing key. - """ - + """Materials' signing key.""" if hasattr(self, "native_materials"): return self.native_materials.signing_key else: @@ -155,7 +139,6 @@ def __init__( Create DecryptionMaterialsHandler. :param materials: Underlying decryption materials """ - if isinstance(materials, Native_DecryptionMaterials): self.native_materials = materials elif isinstance(materials, MPL_DecryptionMaterials): @@ -165,10 +148,7 @@ def __init__( @property def data_key(self) -> DataKey: - """ - Materials' data key. - """ - + """Materials' data key.""" if hasattr(self, "native_materials"): return self.native_materials.data_key else: @@ -186,10 +166,7 @@ def data_key(self) -> DataKey: @property def verification_key(self) -> bytes: - """ - Materials' verification key. - """ - + """Materials' verification key.""" if hasattr(self, "native_materials"): return self.native_materials.verification_key else: diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 661b3fa21..ec19b6dd5 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -85,11 +85,11 @@ def _exactly_one_arg_is_not_None(*args): - ''' + """ Private helper function. Returns `True` if exactly one item in the list is not `None`. Returns `False` otherwise. - ''' + """ # Have not found any `not None` found_one = False for arg in args: From 51065cb186bb3e649117a3a9871b2b5943066a52 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 2 Feb 2024 15:48:34 -0800 Subject: [PATCH 009/184] flake8 --- setup.py | 4 +++- src/aws_encryption_sdk/cmm_handler.py | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index c4c277096..353781800 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,9 @@ def get_requirements(): install_requires=get_requirements(), # TODO: Point at MPL main branch once Python MPL is merged into main. extras_require={ - "MPL": ["aws-cryptographic-material-providers @ git+https://github.com/aws/aws-cryptographic-material-providers-library.git@lucmcdon/python-mpl#subdirectory=AwsCryptographicMaterialProviders/runtimes/python"], + "MPL": ["aws-cryptographic-material-providers @\ + git+https://github.com/aws/aws-cryptographic-material-providers-library.git@\ + lucmcdon/python-mpl#subdirectory=AwsCryptographicMaterialProviders/runtimes/python"], }, classifiers=[ "Development Status :: 5 - Production/Stable", diff --git a/src/aws_encryption_sdk/cmm_handler.py b/src/aws_encryption_sdk/cmm_handler.py index 17d59792a..887d9d79e 100644 --- a/src/aws_encryption_sdk/cmm_handler.py +++ b/src/aws_encryption_sdk/cmm_handler.py @@ -67,7 +67,6 @@ def __init__( Create DecryptionMaterialsHandler. :param cmm: Underlying cryptographic materials manager """ - if isinstance(cmm, CryptoMaterialsManager): self.native_cmm = cmm elif isinstance(cmm, ICryptographicMaterialsManager): From fc4d254d7f7601d86fc9954ed69d0817869a43e2 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 2 Feb 2024 15:51:15 -0800 Subject: [PATCH 010/184] flake8 --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 353781800..4cd8027cd 100644 --- a/setup.py +++ b/setup.py @@ -41,9 +41,9 @@ def get_requirements(): install_requires=get_requirements(), # TODO: Point at MPL main branch once Python MPL is merged into main. extras_require={ - "MPL": ["aws-cryptographic-material-providers @\ - git+https://github.com/aws/aws-cryptographic-material-providers-library.git@\ - lucmcdon/python-mpl#subdirectory=AwsCryptographicMaterialProviders/runtimes/python"], + "MPL": ["aws-cryptographic-material-providers @" \ + "git+https://github.com/aws/aws-cryptographic-material-providers-library.git@" \ + "lucmcdon/python-mpl#subdirectory=AwsCryptographicMaterialProviders/runtimes/python"], }, classifiers=[ "Development Status :: 5 - Production/Stable", From a8e52d310c777905326fc98cbf667217116de303 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Mon, 5 Feb 2024 13:23:21 -0800 Subject: [PATCH 011/184] fix pem/der --- .../internal/crypto/authentication.py | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/aws_encryption_sdk/internal/crypto/authentication.py b/src/aws_encryption_sdk/internal/crypto/authentication.py index b9692eb16..8c1b9af31 100644 --- a/src/aws_encryption_sdk/internal/crypto/authentication.py +++ b/src/aws_encryption_sdk/internal/crypto/authentication.py @@ -68,25 +68,31 @@ class Signer(_PrehashingAuthenticator): """ @classmethod - def from_key_bytes(cls, algorithm, key_bytes): + def from_key_bytes(cls, algorithm, key_bytes, encoding=serialization.Encoding.DER): """Builds a `Signer` from an algorithm suite and a raw signing key. :param algorithm: Algorithm on which to base signer :type algorithm: aws_encryption_sdk.identifiers.Algorithm :param bytes key_bytes: Raw signing key + :param encoding: Encoding used for key bytes + :type encoding: cryptography.hazmat.primitives.serialization.encoding :rtype: aws_encryption_sdk.internal.crypto.Signer """ - # key = serialization.load_der_private_key(data=key_bytes, password=None, backend=default_backend()) - key = serialization.load_pem_private_key(data=key_bytes, password=None, backend=default_backend()) + if encoding == serialization.Encoding.DER: + key = serialization.load_der_private_key(data=key_bytes, password=None, backend=default_backend()) + elif encoding == serialization.Encoding.PEM: + key = serialization.load_pem_private_key(data=key_bytes, password=None, backend=default_backend()) + else: + raise ValueError("Unsupported signing key encoding: {}".format(encoding)) return cls(algorithm, key) - def key_bytes(self): + def key_bytes(self, encoding=serialization.Encoding.DER): """Returns the raw signing key. :rtype: bytes """ return self.key.private_bytes( - encoding=serialization.Encoding.DER, + encoding=encoding, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption(), ) @@ -149,19 +155,27 @@ def from_encoded_point(cls, algorithm, encoded_point): ) @classmethod - def from_key_bytes(cls, algorithm, key_bytes): + def from_key_bytes(cls, algorithm, key_bytes, encoding=serialization.Encoding.DER): """Creates a `Verifier` object based on the supplied algorithm and raw verification key. :param algorithm: Algorithm on which to base verifier :type algorithm: aws_encryption_sdk.identifiers.Algorithm :param bytes encoded_point: Raw verification key + :param encoding: Encoding used for key bytes + :type encoding: cryptography.hazmat.primitives.serialization.encoding :returns: Instance of Verifier generated from encoded point :rtype: aws_encryption_sdk.internal.crypto.Verifier """ + if encoding == serialization.Encoding.DER: + key = serialization.load_der_private_key(data=key_bytes, password=None, backend=default_backend()) + elif encoding == serialization.Encoding.PEM: + key = serialization.load_pem_private_key(data=key_bytes, password=None, backend=default_backend()) + else: + raise ValueError("Unsupported verification key encoding: {}".format(encoding)) return cls( algorithm=algorithm, key=serialization.load_pem_public_key(data=key_bytes, backend=default_backend()) ) - + def key_bytes(self): """Returns the raw verification key. From 6f5504741a4797ee1cce5986a5ba48718759b810 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Mon, 5 Feb 2024 13:30:49 -0800 Subject: [PATCH 012/184] fix pem/der --- src/aws_encryption_sdk/streaming_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index ec19b6dd5..f6b3529a9 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -172,7 +172,7 @@ def _has_mpl_attrs_post_init(self): assert isinstance(self.keyring, IKeyring) except AssertionError: raise ValueError(f"Argument provided to keyring MUST be a {IKeyring}. \ - Found {self.keyring.__class__.__name__=}") + Found {self.keyring.__class__.__name__}") mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( config=MaterialProvidersConfig() From 1b1b4e4bc61d7c74a3815d225e992c1cb7bce135 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Mon, 5 Feb 2024 16:11:08 -0800 Subject: [PATCH 013/184] debug --- src/aws_encryption_sdk/internal/crypto/authentication.py | 2 +- src/aws_encryption_sdk/streaming_client.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/aws_encryption_sdk/internal/crypto/authentication.py b/src/aws_encryption_sdk/internal/crypto/authentication.py index 8c1b9af31..5e8dcd10c 100644 --- a/src/aws_encryption_sdk/internal/crypto/authentication.py +++ b/src/aws_encryption_sdk/internal/crypto/authentication.py @@ -173,7 +173,7 @@ def from_key_bytes(cls, algorithm, key_bytes, encoding=serialization.Encoding.DE else: raise ValueError("Unsupported verification key encoding: {}".format(encoding)) return cls( - algorithm=algorithm, key=serialization.load_pem_public_key(data=key_bytes, backend=default_backend()) + algorithm=algorithm, key=key ) def key_bytes(self): diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index f6b3529a9..83e4a9ecd 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -165,12 +165,10 @@ def _has_mpl_attrs_post_init(self): # No CMM, provided legacy native `key_provider` => create legacy native DefaultCryptoMaterialsManager self.materials_manager = DefaultCryptoMaterialsManager( master_key_provider=self.key_provider - ) + ) elif self.keyring is not None: # No CMM, provided MPL keyring => create MPL's DefaultCryptographicMaterialsManager - try: - assert isinstance(self.keyring, IKeyring) - except AssertionError: + if not isinstance(self.keyring, IKeyring): raise ValueError(f"Argument provided to keyring MUST be a {IKeyring}. \ Found {self.keyring.__class__.__name__}") From 38a4cc9c6808d5da025822634e8ec9bb0b9f960f Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Mon, 5 Feb 2024 16:14:15 -0800 Subject: [PATCH 014/184] debug --- src/aws_encryption_sdk/internal/crypto/authentication.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aws_encryption_sdk/internal/crypto/authentication.py b/src/aws_encryption_sdk/internal/crypto/authentication.py index 5e8dcd10c..80469ff1f 100644 --- a/src/aws_encryption_sdk/internal/crypto/authentication.py +++ b/src/aws_encryption_sdk/internal/crypto/authentication.py @@ -167,9 +167,9 @@ def from_key_bytes(cls, algorithm, key_bytes, encoding=serialization.Encoding.DE :rtype: aws_encryption_sdk.internal.crypto.Verifier """ if encoding == serialization.Encoding.DER: - key = serialization.load_der_private_key(data=key_bytes, password=None, backend=default_backend()) + key = serialization.load_der_private_key(data=key_bytes, backend=default_backend()) elif encoding == serialization.Encoding.PEM: - key = serialization.load_pem_private_key(data=key_bytes, password=None, backend=default_backend()) + key = serialization.load_pem_private_key(data=key_bytes, backend=default_backend()) else: raise ValueError("Unsupported verification key encoding: {}".format(encoding)) return cls( From 0cd0e2301fc7133e8023a825f128bc457af311aa Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 6 Feb 2024 15:47:48 -0800 Subject: [PATCH 015/184] fix --- .../internal/crypto/authentication.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/aws_encryption_sdk/internal/crypto/authentication.py b/src/aws_encryption_sdk/internal/crypto/authentication.py index 80469ff1f..88d21a2ef 100644 --- a/src/aws_encryption_sdk/internal/crypto/authentication.py +++ b/src/aws_encryption_sdk/internal/crypto/authentication.py @@ -78,12 +78,7 @@ def from_key_bytes(cls, algorithm, key_bytes, encoding=serialization.Encoding.DE :type encoding: cryptography.hazmat.primitives.serialization.encoding :rtype: aws_encryption_sdk.internal.crypto.Signer """ - if encoding == serialization.Encoding.DER: - key = serialization.load_der_private_key(data=key_bytes, password=None, backend=default_backend()) - elif encoding == serialization.Encoding.PEM: - key = serialization.load_pem_private_key(data=key_bytes, password=None, backend=default_backend()) - else: - raise ValueError("Unsupported signing key encoding: {}".format(encoding)) + key = serialization.load_der_private_key(data=key_bytes, password=None, backend=default_backend()) return cls(algorithm, key) def key_bytes(self, encoding=serialization.Encoding.DER): @@ -166,14 +161,8 @@ def from_key_bytes(cls, algorithm, key_bytes, encoding=serialization.Encoding.DE :returns: Instance of Verifier generated from encoded point :rtype: aws_encryption_sdk.internal.crypto.Verifier """ - if encoding == serialization.Encoding.DER: - key = serialization.load_der_private_key(data=key_bytes, backend=default_backend()) - elif encoding == serialization.Encoding.PEM: - key = serialization.load_pem_private_key(data=key_bytes, backend=default_backend()) - else: - raise ValueError("Unsupported verification key encoding: {}".format(encoding)) return cls( - algorithm=algorithm, key=key + algorithm=algorithm, key=serialization.load_der_public_key(data=key_bytes, backend=default_backend()) ) def key_bytes(self): From 44826a2568fd3fa86d3031d11837c707d17850e0 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 6 Feb 2024 16:09:14 -0800 Subject: [PATCH 016/184] fix --- .../internal/crypto/authentication.py | 14 +++++--------- src/aws_encryption_sdk/streaming_client.py | 3 ++- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/aws_encryption_sdk/internal/crypto/authentication.py b/src/aws_encryption_sdk/internal/crypto/authentication.py index 88d21a2ef..f90ac77e0 100644 --- a/src/aws_encryption_sdk/internal/crypto/authentication.py +++ b/src/aws_encryption_sdk/internal/crypto/authentication.py @@ -68,26 +68,24 @@ class Signer(_PrehashingAuthenticator): """ @classmethod - def from_key_bytes(cls, algorithm, key_bytes, encoding=serialization.Encoding.DER): + def from_key_bytes(cls, algorithm, key_bytes): """Builds a `Signer` from an algorithm suite and a raw signing key. :param algorithm: Algorithm on which to base signer :type algorithm: aws_encryption_sdk.identifiers.Algorithm :param bytes key_bytes: Raw signing key - :param encoding: Encoding used for key bytes - :type encoding: cryptography.hazmat.primitives.serialization.encoding :rtype: aws_encryption_sdk.internal.crypto.Signer """ key = serialization.load_der_private_key(data=key_bytes, password=None, backend=default_backend()) return cls(algorithm, key) - def key_bytes(self, encoding=serialization.Encoding.DER): + def key_bytes(self): """Returns the raw signing key. :rtype: bytes """ return self.key.private_bytes( - encoding=encoding, + encoding=serialization.Encoding.DER, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption(), ) @@ -150,21 +148,19 @@ def from_encoded_point(cls, algorithm, encoded_point): ) @classmethod - def from_key_bytes(cls, algorithm, key_bytes, encoding=serialization.Encoding.DER): + def from_key_bytes(cls, algorithm, key_bytes): """Creates a `Verifier` object based on the supplied algorithm and raw verification key. :param algorithm: Algorithm on which to base verifier :type algorithm: aws_encryption_sdk.identifiers.Algorithm :param bytes encoded_point: Raw verification key - :param encoding: Encoding used for key bytes - :type encoding: cryptography.hazmat.primitives.serialization.encoding :returns: Instance of Verifier generated from encoded point :rtype: aws_encryption_sdk.internal.crypto.Verifier """ return cls( algorithm=algorithm, key=serialization.load_der_public_key(data=key_bytes, backend=default_backend()) ) - + def key_bytes(self): """Returns the raw verification key. diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 83e4a9ecd..582472025 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -919,7 +919,8 @@ def _read_header(self): # MPL verification key is NOT key bytes, it is bytes of the compressed point # TODO-MPL: clean this up, least-privilege violation import base64 - if hasattr(self.config.materials_manager, "mpl_cmm"): + if (isinstance(self.config.materials_manager, CMMHandler) + and hasattr(self.config.materials_manager, "mpl_cmm")): self.verifier = Verifier.from_encoded_point( algorithm=header.algorithm, encoded_point=base64.b64encode(decryption_materials.verification_key) From 02e9f843826506597ca03a3139da1b58a88da2f6 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 6 Feb 2024 16:27:51 -0800 Subject: [PATCH 017/184] fix --- src/aws_encryption_sdk/cmm_handler.py | 13 ++++++++----- src/aws_encryption_sdk/materials_handlers.py | 12 +++++++----- src/aws_encryption_sdk/streaming_client.py | 2 +- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/aws_encryption_sdk/cmm_handler.py b/src/aws_encryption_sdk/cmm_handler.py index 887d9d79e..fa1786837 100644 --- a/src/aws_encryption_sdk/cmm_handler.py +++ b/src/aws_encryption_sdk/cmm_handler.py @@ -17,9 +17,12 @@ CommitmentPolicyESDK, AlgorithmSuiteIdESDK, ) + except ImportError: pass +from typing import List + from aws_encryption_sdk.exceptions import ( AWSEncryptionSDKClientError, ) @@ -72,7 +75,7 @@ def __init__( elif isinstance(cmm, ICryptographicMaterialsManager): self.mpl_cmm = cmm else: - raise ValueError(f"Invalid CMM passed to CMMHandler: {cmm=}") + raise ValueError(f"Invalid CMM passed to CMMHandler. cmm: {cmm}") def get_encryption_materials( self, @@ -115,7 +118,7 @@ def _create_mpl_get_encryption_materials_input_from_request( @staticmethod def _map_native_commitment_policy_to_mpl_commitment_policy( native_commitment_policy: CommitmentPolicy - ) -> CommitmentPolicyESDK: + ) -> 'CommitmentPolicyESDK': if native_commitment_policy == CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT: return CommitmentPolicyESDK(value="FORBID_ENCRYPT_ALLOW_DECRYPT") elif native_commitment_policy == CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT: @@ -123,7 +126,7 @@ def _map_native_commitment_policy_to_mpl_commitment_policy( elif native_commitment_policy == CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT: return CommitmentPolicyESDK(value="REQUIRE_ENCRYPT_REQUIRE_DECRYPT") else: - raise ValueError(f"Invalid {native_commitment_policy=}") + raise ValueError(f"Invalid native_commitment_policy: {native_commitment_policy}") def decrypt_materials( self, @@ -146,7 +149,7 @@ def decrypt_materials( raise AWSEncryptionSDKClientError(e) @staticmethod - def _native_algorithm_id_to_mpl_algorithm_id(native_algorithm_id: str) -> AlgorithmSuiteIdESDK: + def _native_algorithm_id_to_mpl_algorithm_id(native_algorithm_id: str) -> 'AlgorithmSuiteIdESDK': # MPL algorithm suite ID = hexstr(native_algorithm_id) padded to 4 digits post-`x`. return AlgorithmSuiteIdESDK(f"{native_algorithm_id:#0{6}x}") @@ -154,7 +157,7 @@ def _native_algorithm_id_to_mpl_algorithm_id(native_algorithm_id: str) -> Algori def _create_mpl_decrypt_materials_input_from_request( request: DecryptionMaterialsRequest ) -> 'DecryptMaterialsInput': - key_blob_list: list[Native_EncryptedDataKey] = request.encrypted_data_keys + key_blob_list: List[Native_EncryptedDataKey] = request.encrypted_data_keys list_edks = [MPL_EncryptedDataKey( key_provider_id=key_blob.key_provider.provider_id, key_provider_info=key_blob.key_provider.key_info, diff --git a/src/aws_encryption_sdk/materials_handlers.py b/src/aws_encryption_sdk/materials_handlers.py index d54e4517b..970963e10 100644 --- a/src/aws_encryption_sdk/materials_handlers.py +++ b/src/aws_encryption_sdk/materials_handlers.py @@ -9,6 +9,8 @@ except ImportError: pass +from typing import Dict, List + from aws_encryption_sdk.materials_managers import ( DecryptionMaterials as Native_DecryptionMaterials, EncryptionMaterials as Native_EncryptionMaterials, @@ -53,7 +55,7 @@ def __init__( elif isinstance(materials, MPL_EncryptionMaterials): self.mpl_materials = materials else: - raise ValueError(f"Invalid EncryptionMaterials passed to EncryptionMaterialsHandler: {materials=}") + raise ValueError(f"Invalid EncryptionMaterials passed to EncryptionMaterialsHandler. materials: {materials}") @property def algorithm(self) -> Algorithm: @@ -68,7 +70,7 @@ def algorithm(self) -> Algorithm: ) @property - def encryption_context(self) -> dict[str, str]: + def encryption_context(self) -> Dict[str, str]: """Materials' encryption context.""" if hasattr(self, "native_materials"): return self.native_materials.encryption_context @@ -76,12 +78,12 @@ def encryption_context(self) -> dict[str, str]: return self.mpl_materials.encryption_context @property - def encrypted_data_keys(self) -> list[Native_EncryptedDataKey]: + def encrypted_data_keys(self) -> List[Native_EncryptedDataKey]: """Materials' encrypted data keys.""" if hasattr(self, "native_materials"): return self.native_materials.encrypted_data_keys else: - mpl_edk_list: list[MPL_EncryptedDataKey] = self.mpl_materials.encrypted_data_keys + mpl_edk_list: List[MPL_EncryptedDataKey] = self.mpl_materials.encrypted_data_keys key_blob_list: set[Native_EncryptedDataKey] = {Native_EncryptedDataKey( key_provider=MasterKeyInfo( provider_id=mpl_edk.key_provider_id, @@ -144,7 +146,7 @@ def __init__( elif isinstance(materials, MPL_DecryptionMaterials): self.mpl_materials = materials else: - raise ValueError(f"Invalid DecryptionMaterials passed to DecryptionMaterialsHandler: {materials=}") + raise ValueError(f"Invalid DecryptionMaterials passed to DecryptionMaterialsHandler. materials: {materials}") @property def data_key(self) -> DataKey: diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 582472025..6a2dc1d27 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -66,6 +66,7 @@ from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager from aws_encryption_sdk.structures import MessageHeader +from aws_encryption_sdk.cmm_handler import CMMHandler try: from aws_cryptographic_materialproviders.mpl.client import AwsCryptographicMaterialProviders from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig @@ -75,7 +76,6 @@ from aws_cryptographic_materialproviders.mpl.references import ( IKeyring, ) - from aws_encryption_sdk.cmm_handler import CMMHandler _has_mpl = True except ImportError: From a3babfd2ef936cf4595bdaab041fc16e7a005868 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 6 Feb 2024 17:55:40 -0800 Subject: [PATCH 018/184] linter --- src/aws_encryption_sdk/cmm_handler.py | 36 +++++++++----------- src/aws_encryption_sdk/materials_handlers.py | 10 +++--- src/aws_encryption_sdk/streaming_client.py | 18 +++++----- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/aws_encryption_sdk/cmm_handler.py b/src/aws_encryption_sdk/cmm_handler.py index fa1786837..20298f801 100644 --- a/src/aws_encryption_sdk/cmm_handler.py +++ b/src/aws_encryption_sdk/cmm_handler.py @@ -85,38 +85,35 @@ def get_encryption_materials( Returns an EncryptionMaterialsHandler for the configured CMM. :param request: Request for encryption materials """ - if (self._is_using_native_cmm()): + if self._is_using_native_cmm(): return EncryptionMaterialsHandler(self.native_cmm.get_encryption_materials(request)) else: try: - input: GetEncryptionMaterialsInput = CMMHandler._create_mpl_get_encryption_materials_input_from_request( + mpl_input: GetEncryptionMaterialsInput = CMMHandler._native_to_mpl_get_encryption_materials( request ) - output: GetEncryptionMaterialsOutput = self.mpl_cmm.get_encryption_materials(input) - return EncryptionMaterialsHandler(output.encryption_materials) - except AwsCryptographicMaterialProvidersException as e: + mpl_output: GetEncryptionMaterialsOutput = self.mpl_cmm.get_encryption_materials(mpl_input) + return EncryptionMaterialsHandler(mpl_output.encryption_materials) + except AwsCryptographicMaterialProvidersException as mpl_exception: # Wrap MPL error into the ESDK error type # so customers only have to catch ESDK error types. - raise AWSEncryptionSDKClientError(e) + raise AWSEncryptionSDKClientError(mpl_exception) @staticmethod - def _create_mpl_get_encryption_materials_input_from_request( + def _native_to_mpl_get_encryption_materials( request: EncryptionMaterialsRequest ) -> 'GetEncryptionMaterialsInput': output: GetEncryptionMaterialsInput = GetEncryptionMaterialsInput( encryption_context=request.encryption_context, - commitment_policy=CMMHandler._map_native_commitment_policy_to_mpl_commitment_policy( + commitment_policy=CMMHandler._native_to_mpl_commmitment_policy( request.commitment_policy ), - # TODO double check this - # optional... maybe this needs to be kwargs?? - # algorithm_suite_id=request.algorithm.algorithm_id, max_plaintext_length=request.plaintext_length, ) return output @staticmethod - def _map_native_commitment_policy_to_mpl_commitment_policy( + def _native_to_mpl_commmitment_policy( native_commitment_policy: CommitmentPolicy ) -> 'CommitmentPolicyESDK': if native_commitment_policy == CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT: @@ -136,17 +133,18 @@ def decrypt_materials( Returns a DecryptionMaterialsHandler for the configured CMM. :param request: Request for decryption materials """ - if (self._is_using_native_cmm()): + if self._is_using_native_cmm(): return DecryptionMaterialsHandler(self.native_cmm.decrypt_materials(request)) else: try: - input: 'DecryptMaterialsInput' = CMMHandler._create_mpl_decrypt_materials_input_from_request(request) - output: 'DecryptMaterialsOutput' = self.mpl_cmm.decrypt_materials(input) - return DecryptionMaterialsHandler(output.decryption_materials) - except AwsCryptographicMaterialProvidersException as e: + mpl_input: 'DecryptMaterialsInput' = \ + CMMHandler._create_mpl_decrypt_materials_input_from_request(request) + mpl_output: 'DecryptMaterialsOutput' = self.mpl_cmm.decrypt_materials(mpl_input) + return DecryptionMaterialsHandler(mpl_output.decryption_materials) + except AwsCryptographicMaterialProvidersException as mpl_exception: # Wrap MPL error into the ESDK error type # so customers only have to catch ESDK error types. - raise AWSEncryptionSDKClientError(e) + raise AWSEncryptionSDKClientError(mpl_exception) @staticmethod def _native_algorithm_id_to_mpl_algorithm_id(native_algorithm_id: str) -> 'AlgorithmSuiteIdESDK': @@ -167,7 +165,7 @@ def _create_mpl_decrypt_materials_input_from_request( algorithm_suite_id=CMMHandler._native_algorithm_id_to_mpl_algorithm_id( request.algorithm.algorithm_id ), - commitment_policy=CMMHandler._map_native_commitment_policy_to_mpl_commitment_policy( + commitment_policy=CMMHandler._native_to_mpl_commmitment_policy( request.commitment_policy ), encrypted_data_keys=list_edks, diff --git a/src/aws_encryption_sdk/materials_handlers.py b/src/aws_encryption_sdk/materials_handlers.py index 970963e10..00d67ed71 100644 --- a/src/aws_encryption_sdk/materials_handlers.py +++ b/src/aws_encryption_sdk/materials_handlers.py @@ -9,7 +9,7 @@ except ImportError: pass -from typing import Dict, List +from typing import Dict, List, Set from aws_encryption_sdk.materials_managers import ( DecryptionMaterials as Native_DecryptionMaterials, @@ -55,7 +55,8 @@ def __init__( elif isinstance(materials, MPL_EncryptionMaterials): self.mpl_materials = materials else: - raise ValueError(f"Invalid EncryptionMaterials passed to EncryptionMaterialsHandler. materials: {materials}") + raise ValueError(f"Invalid EncryptionMaterials passed to EncryptionMaterialsHandler.\ + materials: {materials}") @property def algorithm(self) -> Algorithm: @@ -84,7 +85,7 @@ def encrypted_data_keys(self) -> List[Native_EncryptedDataKey]: return self.native_materials.encrypted_data_keys else: mpl_edk_list: List[MPL_EncryptedDataKey] = self.mpl_materials.encrypted_data_keys - key_blob_list: set[Native_EncryptedDataKey] = {Native_EncryptedDataKey( + key_blob_list: Set[Native_EncryptedDataKey] = {Native_EncryptedDataKey( key_provider=MasterKeyInfo( provider_id=mpl_edk.key_provider_id, key_info=mpl_edk.key_provider_info, @@ -146,7 +147,8 @@ def __init__( elif isinstance(materials, MPL_DecryptionMaterials): self.mpl_materials = materials else: - raise ValueError(f"Invalid DecryptionMaterials passed to DecryptionMaterialsHandler. materials: {materials}") + raise ValueError(f"Invalid DecryptionMaterials passed to DecryptionMaterialsHandler.\ + materials: {materials}") @property def data_key(self) -> DataKey: diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 6a2dc1d27..6b977e6e4 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -18,6 +18,7 @@ import io import logging import math +import base64 import attr import six @@ -77,14 +78,14 @@ IKeyring, ) - _has_mpl = True + HAS_MPL = True except ImportError: - _has_mpl = False + HAS_MPL = False _LOGGER = logging.getLogger(__name__) -def _exactly_one_arg_is_not_None(*args): +def _exactly_one_arg_is_not_none(*args): """ Private helper function. Returns `True` if exactly one item in the list is not `None`. @@ -146,7 +147,7 @@ class _ClientConfig(object): # pylint: disable=too-many-instance-attributes key_provider = attr.ib( hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(MasterKeyProvider)) ) - if _has_mpl: + if HAS_MPL: keyring = attr.ib( hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(IKeyring)) ) @@ -158,14 +159,14 @@ class _ClientConfig(object): # pylint: disable=too-many-instance-attributes ) # DEPRECATED: Value is no longer configurable here. Parameter left here to avoid breaking consumers. def _has_mpl_attrs_post_init(self): - if not _exactly_one_arg_is_not_None(self.materials_manager, self.key_provider, self.keyring): + if not _exactly_one_arg_is_not_none(self.materials_manager, self.key_provider, self.keyring): raise TypeError("Exactly one of keyring, materials_manager, or key_provider must be provided") if self.materials_manager is None: if self.key_provider is not None: # No CMM, provided legacy native `key_provider` => create legacy native DefaultCryptoMaterialsManager self.materials_manager = DefaultCryptoMaterialsManager( master_key_provider=self.key_provider - ) + ) elif self.keyring is not None: # No CMM, provided MPL keyring => create MPL's DefaultCryptographicMaterialsManager if not isinstance(self.keyring, IKeyring): @@ -194,9 +195,9 @@ def _no_mpl_attrs_post_init(self): def __attrs_post_init__(self): """Normalize inputs to crypto material manager.""" - if _has_mpl: + if HAS_MPL: self._has_mpl_attrs_post_init() - elif not _has_mpl: + elif not HAS_MPL: self._no_mpl_attrs_post_init() @@ -918,7 +919,6 @@ def _read_header(self): else: # MPL verification key is NOT key bytes, it is bytes of the compressed point # TODO-MPL: clean this up, least-privilege violation - import base64 if (isinstance(self.config.materials_manager, CMMHandler) and hasattr(self.config.materials_manager, "mpl_cmm")): self.verifier = Verifier.from_encoded_point( From d2c974afc11a0f11a3ab32ad44e17061d640c5d1 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 6 Feb 2024 17:59:21 -0800 Subject: [PATCH 019/184] linter --- setup.py | 1 + src/aws_encryption_sdk/cmm_handler.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/setup.py b/setup.py index 4cd8027cd..084edc09a 100644 --- a/setup.py +++ b/setup.py @@ -39,6 +39,7 @@ def get_requirements(): keywords="aws-encryption-sdk aws kms encryption", license="Apache License 2.0", install_requires=get_requirements(), + # pylint: disable=fixme # TODO: Point at MPL main branch once Python MPL is merged into main. extras_require={ "MPL": ["aws-cryptographic-material-providers @" \ diff --git a/src/aws_encryption_sdk/cmm_handler.py b/src/aws_encryption_sdk/cmm_handler.py index 20298f801..2479038a1 100644 --- a/src/aws_encryption_sdk/cmm_handler.py +++ b/src/aws_encryption_sdk/cmm_handler.py @@ -2,6 +2,8 @@ # These dependencies are only loaded if you install the MPL. try: + # pylint seems to struggle with this condition import + # pylint: disable=unused-import from aws_cryptographic_materialproviders.mpl.errors import ( AwsCryptographicMaterialProvidersException ) From 55b24a83580f880278d0689f57fbfe1bdca285f6 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 09:44:02 -0800 Subject: [PATCH 020/184] isort --- examples/src/basic_encryption.py | 5 --- examples/src/keyrings/hierarchical_keyring.py | 32 +++++--------- src/aws_encryption_sdk/cmm_handler.py | 44 ++++++------------- src/aws_encryption_sdk/materials_handlers.py | 13 ++---- src/aws_encryption_sdk/streaming_client.py | 13 +++--- 5 files changed, 32 insertions(+), 75 deletions(-) diff --git a/examples/src/basic_encryption.py b/examples/src/basic_encryption.py index 7b729feab..cfe8ac791 100644 --- a/examples/src/basic_encryption.py +++ b/examples/src/basic_encryption.py @@ -51,8 +51,3 @@ def cycle_string(key_arn, source_plaintext, botocore_session=None): assert all( pair in decrypted_header.encryption_context.items() for pair in encryptor_header.encryption_context.items() ) - -cycle_string( - "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f", - "abcdefg", -) \ No newline at end of file diff --git a/examples/src/keyrings/hierarchical_keyring.py b/examples/src/keyrings/hierarchical_keyring.py index e8f662b73..20647bed6 100644 --- a/examples/src/keyrings/hierarchical_keyring.py +++ b/examples/src/keyrings/hierarchical_keyring.py @@ -4,43 +4,32 @@ """Example showing basic encryption and decryption of a value already in memory.""" -import aws_encryption_sdk -from aws_encryption_sdk import CommitmentPolicy -import boto3 - import sys -from aws_encryption_sdk.exceptions import ( - AWSEncryptionSDKClientError, - SerializationError, -) +import boto3 + +import aws_encryption_sdk +from aws_encryption_sdk import CommitmentPolicy +from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError, SerializationError module_root_dir = '/'.join(__file__.split("/")[:-1]) sys.path.append(module_root_dir) import aws_cryptographic_materialproviders - +from aws_cryptographic_materialproviders.keystore.client import KeyStore +from aws_cryptographic_materialproviders.keystore.config import KeyStoreConfig +from aws_cryptographic_materialproviders.keystore.models import CreateKeyInput, KMSConfigurationKmsKeyArn from aws_cryptographic_materialproviders.mpl.client import AwsCryptographicMaterialProviders from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig from aws_cryptographic_materialproviders.mpl.models import ( - CreateAwsKmsHierarchicalKeyringInput, CacheTypeDefault, + CreateAwsKmsHierarchicalKeyringInput, DefaultCache, GetBranchKeyIdInput, GetBranchKeyIdOutput, ) -from aws_cryptographic_materialproviders.mpl.references import ( - IKeyring, - IBranchKeyIdSupplier, -) - -from aws_cryptographic_materialproviders.keystore.client import KeyStore -from aws_cryptographic_materialproviders.keystore.config import KeyStoreConfig -from aws_cryptographic_materialproviders.keystore.models import ( - CreateKeyInput, - KMSConfigurationKmsKeyArn, -) +from aws_cryptographic_materialproviders.mpl.references import IBranchKeyIdSupplier, IKeyring EXAMPLE_DATA: bytes = b"Hello World" @@ -241,6 +230,7 @@ def get_branch_key_id( # hack in a test import botocore + encrypt_and_decrypt_with_keyring( "KeyStoreDdbTable", "KeyStoreDdbTable", diff --git a/src/aws_encryption_sdk/cmm_handler.py b/src/aws_encryption_sdk/cmm_handler.py index 2479038a1..5bac15b87 100644 --- a/src/aws_encryption_sdk/cmm_handler.py +++ b/src/aws_encryption_sdk/cmm_handler.py @@ -2,49 +2,31 @@ # These dependencies are only loaded if you install the MPL. try: - # pylint seems to struggle with this condition import + # pylint seems to struggle with this conditional import # pylint: disable=unused-import - from aws_cryptographic_materialproviders.mpl.errors import ( - AwsCryptographicMaterialProvidersException - ) - from aws_cryptographic_materialproviders.mpl.references import ( - ICryptographicMaterialsManager, - ) + from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException from aws_cryptographic_materialproviders.mpl.models import ( - GetEncryptionMaterialsInput, - GetEncryptionMaterialsOutput, + AlgorithmSuiteIdESDK, + CommitmentPolicyESDK, DecryptMaterialsInput, DecryptMaterialsOutput, EncryptedDataKey as MPL_EncryptedDataKey, - CommitmentPolicyESDK, - AlgorithmSuiteIdESDK, + GetEncryptionMaterialsInput, + GetEncryptionMaterialsOutput, ) + from aws_cryptographic_materialproviders.mpl.references import ICryptographicMaterialsManager except ImportError: pass from typing import List -from aws_encryption_sdk.exceptions import ( - AWSEncryptionSDKClientError, -) -from aws_encryption_sdk.materials_managers import ( - DecryptionMaterialsRequest, - EncryptionMaterialsRequest, -) -from aws_encryption_sdk.materials_managers.base import ( - CryptoMaterialsManager, -) -from aws_encryption_sdk.materials_handlers import ( - EncryptionMaterialsHandler, - DecryptionMaterialsHandler, -) -from aws_encryption_sdk.structures import ( - EncryptedDataKey as Native_EncryptedDataKey, -) -from aws_encryption_sdk.identifiers import ( - CommitmentPolicy, -) +from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError +from aws_encryption_sdk.identifiers import CommitmentPolicy +from aws_encryption_sdk.materials_handlers import DecryptionMaterialsHandler, EncryptionMaterialsHandler +from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest +from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager +from aws_encryption_sdk.structures import EncryptedDataKey as Native_EncryptedDataKey # TODO-MPL Should this implement interface..? seems like yes since it implements all of interface methods diff --git a/src/aws_encryption_sdk/materials_handlers.py b/src/aws_encryption_sdk/materials_handlers.py index 00d67ed71..57f54144e 100644 --- a/src/aws_encryption_sdk/materials_handlers.py +++ b/src/aws_encryption_sdk/materials_handlers.py @@ -3,27 +3,20 @@ try: from aws_cryptographic_materialproviders.mpl.models import ( DecryptionMaterials as MPL_DecryptionMaterials, - EncryptionMaterials as MPL_EncryptionMaterials, EncryptedDataKey as MPL_EncryptedDataKey, + EncryptionMaterials as MPL_EncryptionMaterials, ) except ImportError: pass from typing import Dict, List, Set +from aws_encryption_sdk.identifiers import Algorithm, AlgorithmSuite from aws_encryption_sdk.materials_managers import ( DecryptionMaterials as Native_DecryptionMaterials, EncryptionMaterials as Native_EncryptionMaterials, ) -from aws_encryption_sdk.identifiers import ( - Algorithm, - AlgorithmSuite, -) -from aws_encryption_sdk.structures import ( - DataKey, - EncryptedDataKey as Native_EncryptedDataKey, - MasterKeyInfo, -) +from aws_encryption_sdk.structures import DataKey, EncryptedDataKey as Native_EncryptedDataKey, MasterKeyInfo def _mpl_algorithm_id_to_native_algorithm_id(mpl_algorithm_id: str): diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 6b977e6e4..afe9987ff 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -14,16 +14,17 @@ from __future__ import division import abc +import base64 import hmac import io import logging import math -import base64 import attr import six import aws_encryption_sdk.internal.utils +from aws_encryption_sdk.cmm_handler import CMMHandler from aws_encryption_sdk.exceptions import ( ActionNotAllowedError, AWSEncryptionSDKClientError, @@ -67,16 +68,12 @@ from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager from aws_encryption_sdk.structures import MessageHeader -from aws_encryption_sdk.cmm_handler import CMMHandler + try: from aws_cryptographic_materialproviders.mpl.client import AwsCryptographicMaterialProviders from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig - from aws_cryptographic_materialproviders.mpl.models import ( - CreateDefaultCryptographicMaterialsManagerInput - ) - from aws_cryptographic_materialproviders.mpl.references import ( - IKeyring, - ) + from aws_cryptographic_materialproviders.mpl.models import CreateDefaultCryptographicMaterialsManagerInput + from aws_cryptographic_materialproviders.mpl.references import IKeyring HAS_MPL = True except ImportError: From 7e5fa4837f252f8b038efc51567d04f90d6510d8 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 09:54:14 -0800 Subject: [PATCH 021/184] flake8 examples --- examples/src/keyrings/hierarchical_keyring.py | 34 ++++++------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/examples/src/keyrings/hierarchical_keyring.py b/examples/src/keyrings/hierarchical_keyring.py index 20647bed6..81d02f786 100644 --- a/examples/src/keyrings/hierarchical_keyring.py +++ b/examples/src/keyrings/hierarchical_keyring.py @@ -1,8 +1,5 @@ # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 - - - """Example showing basic encryption and decryption of a value already in memory.""" import sys @@ -10,13 +7,8 @@ import aws_encryption_sdk from aws_encryption_sdk import CommitmentPolicy -from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError, SerializationError - -module_root_dir = '/'.join(__file__.split("/")[:-1]) - -sys.path.append(module_root_dir) +from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError -import aws_cryptographic_materialproviders from aws_cryptographic_materialproviders.keystore.client import KeyStore from aws_cryptographic_materialproviders.keystore.config import KeyStoreConfig from aws_cryptographic_materialproviders.keystore.models import CreateKeyInput, KMSConfigurationKmsKeyArn @@ -31,13 +23,18 @@ ) from aws_cryptographic_materialproviders.mpl.references import IBranchKeyIdSupplier, IKeyring +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( - key_store_table_name: str, - logical_key_store_name: str, - kms_key_id: str - ): + key_store_table_name: str, + logical_key_store_name: str, + kms_key_id: str +): # 1. Instantiate the encryption SDK client. # This builds the client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy, @@ -90,7 +87,7 @@ def get_branch_key_id( if b"tenant" not in encryption_context: raise ValueError("EncryptionContext invalid, does not contain expected tenant key value pair.") - + tenant_key_id: str = encryption_context.get(b"tenant") branch_key_id: str @@ -227,12 +224,3 @@ def get_branch_key_id( assert plaintext_bytes_B == EXAMPLE_DATA # Also, a thread-safe example ig - -# hack in a test -import botocore - -encrypt_and_decrypt_with_keyring( - "KeyStoreDdbTable", - "KeyStoreDdbTable", - "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126" -) \ No newline at end of file From 055deabd332af255f40df7fb52b80063df268f06 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 09:58:05 -0800 Subject: [PATCH 022/184] isort + flake8 --- examples/src/keyrings/hierarchical_keyring.py | 10 +++++----- examples/src/keyrings/module_.py | 1 + examples/src/module_.py | 1 + examples/test/examples_test_utils.py | 2 +- examples/test/test_i_basic_encryption.py | 1 - ..._i_basic_file_encryption_with_multiple_providers.py | 4 +--- ...st_i_basic_file_encryption_with_raw_key_provider.py | 1 - examples/test/test_i_data_key_caching_basic.py | 1 - examples/test/test_i_discovery_kms_provider.py | 4 +--- examples/test/test_i_mrk_aware_kms_provider.py | 4 +--- examples/test/test_i_multiple_kms_cmk.py | 4 +--- examples/test/test_i_one_kms_cmk.py | 4 +--- examples/test/test_i_one_kms_cmk_streaming_data.py | 1 - examples/test/test_i_one_kms_cmk_unsigned.py | 4 +--- examples/test/test_i_set_commitment.py | 4 +--- 15 files changed, 15 insertions(+), 31 deletions(-) diff --git a/examples/src/keyrings/hierarchical_keyring.py b/examples/src/keyrings/hierarchical_keyring.py index 81d02f786..acc594cc3 100644 --- a/examples/src/keyrings/hierarchical_keyring.py +++ b/examples/src/keyrings/hierarchical_keyring.py @@ -4,11 +4,6 @@ import sys import boto3 - -import aws_encryption_sdk -from aws_encryption_sdk import CommitmentPolicy -from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError - from aws_cryptographic_materialproviders.keystore.client import KeyStore from aws_cryptographic_materialproviders.keystore.config import KeyStoreConfig from aws_cryptographic_materialproviders.keystore.models import CreateKeyInput, KMSConfigurationKmsKeyArn @@ -23,6 +18,10 @@ ) from aws_cryptographic_materialproviders.mpl.references import IBranchKeyIdSupplier, IKeyring +import aws_encryption_sdk +from aws_encryption_sdk import CommitmentPolicy +from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError + module_root_dir = '/'.join(__file__.split("/")[:-1]) sys.path.append(module_root_dir) @@ -35,6 +34,7 @@ def encrypt_and_decrypt_with_keyring( logical_key_store_name: str, kms_key_id: str ): + """Creates a hierarchical keyring using the provided resources, then encrypts and decrypts a string with it.""" # 1. Instantiate the encryption SDK client. # This builds the client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy, diff --git a/examples/src/keyrings/module_.py b/examples/src/keyrings/module_.py index e69de29bb..2f64c8e0f 100644 --- a/examples/src/keyrings/module_.py +++ b/examples/src/keyrings/module_.py @@ -0,0 +1 @@ +"""Should remove this.""" \ No newline at end of file diff --git a/examples/src/module_.py b/examples/src/module_.py index e69de29bb..2f64c8e0f 100644 --- a/examples/src/module_.py +++ b/examples/src/module_.py @@ -0,0 +1 @@ +"""Should remove this.""" \ No newline at end of file diff --git a/examples/test/examples_test_utils.py b/examples/test/examples_test_utils.py index 8a51f21c8..08e8cf2f5 100644 --- a/examples/test/examples_test_utils.py +++ b/examples/test/examples_test_utils.py @@ -49,7 +49,7 @@ from integration_test_utils import ( # noqa pylint: disable=unused-import,import-error get_cmk_arn, - get_second_cmk_arn, get_mrk_arn, + get_second_cmk_arn, get_second_mrk_arn, ) diff --git a/examples/test/test_i_basic_encryption.py b/examples/test/test_i_basic_encryption.py index f2a4fab51..aa32d61fa 100644 --- a/examples/test/test_i_basic_encryption.py +++ b/examples/test/test_i_basic_encryption.py @@ -17,7 +17,6 @@ from ..src.basic_encryption import cycle_string from .examples_test_utils import get_cmk_arn, static_plaintext - pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_basic_file_encryption_with_multiple_providers.py b/examples/test/test_i_basic_file_encryption_with_multiple_providers.py index 282a272ab..0792f4958 100644 --- a/examples/test/test_i_basic_file_encryption_with_multiple_providers.py +++ b/examples/test/test_i_basic_file_encryption_with_multiple_providers.py @@ -18,9 +18,7 @@ import pytest from ..src.basic_file_encryption_with_multiple_providers import cycle_file -from .examples_test_utils import get_cmk_arn -from .examples_test_utils import static_plaintext - +from .examples_test_utils import get_cmk_arn, static_plaintext pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py b/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py index 710c0ccac..046b7f964 100644 --- a/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py +++ b/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py @@ -19,7 +19,6 @@ from ..src.basic_file_encryption_with_raw_key_provider import cycle_file from .examples_test_utils import static_plaintext - pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_data_key_caching_basic.py b/examples/test/test_i_data_key_caching_basic.py index 734c35692..7a30f4e53 100644 --- a/examples/test/test_i_data_key_caching_basic.py +++ b/examples/test/test_i_data_key_caching_basic.py @@ -16,7 +16,6 @@ from ..src.data_key_caching_basic import encrypt_with_caching from .examples_test_utils import get_cmk_arn - pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_discovery_kms_provider.py b/examples/test/test_i_discovery_kms_provider.py index e9a1c6e71..0f64cbf59 100644 --- a/examples/test/test_i_discovery_kms_provider.py +++ b/examples/test/test_i_discovery_kms_provider.py @@ -16,9 +16,7 @@ import pytest from ..src.discovery_kms_provider import encrypt_decrypt -from .examples_test_utils import get_cmk_arn -from .examples_test_utils import static_plaintext - +from .examples_test_utils import get_cmk_arn, static_plaintext pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_mrk_aware_kms_provider.py b/examples/test/test_i_mrk_aware_kms_provider.py index 8e7a003f8..a90101fa8 100644 --- a/examples/test/test_i_mrk_aware_kms_provider.py +++ b/examples/test/test_i_mrk_aware_kms_provider.py @@ -15,9 +15,7 @@ import pytest from ..src.mrk_aware_kms_provider import encrypt_decrypt -from .examples_test_utils import get_mrk_arn, get_second_mrk_arn -from .examples_test_utils import static_plaintext - +from .examples_test_utils import get_mrk_arn, get_second_mrk_arn, static_plaintext pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_multiple_kms_cmk.py b/examples/test/test_i_multiple_kms_cmk.py index 39369cbc6..2915a0fd7 100644 --- a/examples/test/test_i_multiple_kms_cmk.py +++ b/examples/test/test_i_multiple_kms_cmk.py @@ -16,9 +16,7 @@ import pytest from ..src.multiple_kms_cmk import encrypt_decrypt -from .examples_test_utils import get_cmk_arn, get_second_cmk_arn -from .examples_test_utils import static_plaintext - +from .examples_test_utils import get_cmk_arn, get_second_cmk_arn, static_plaintext pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_one_kms_cmk.py b/examples/test/test_i_one_kms_cmk.py index 71ce74d3d..96dd48dae 100644 --- a/examples/test/test_i_one_kms_cmk.py +++ b/examples/test/test_i_one_kms_cmk.py @@ -16,9 +16,7 @@ import pytest from ..src.one_kms_cmk import encrypt_decrypt -from .examples_test_utils import get_cmk_arn -from .examples_test_utils import static_plaintext - +from .examples_test_utils import get_cmk_arn, static_plaintext pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_one_kms_cmk_streaming_data.py b/examples/test/test_i_one_kms_cmk_streaming_data.py index b22fa4232..f0a3094d0 100644 --- a/examples/test/test_i_one_kms_cmk_streaming_data.py +++ b/examples/test/test_i_one_kms_cmk_streaming_data.py @@ -20,7 +20,6 @@ from ..src.one_kms_cmk_streaming_data import encrypt_decrypt_stream from .examples_test_utils import get_cmk_arn, static_plaintext - pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_one_kms_cmk_unsigned.py b/examples/test/test_i_one_kms_cmk_unsigned.py index 8a2758c96..41f16473d 100644 --- a/examples/test/test_i_one_kms_cmk_unsigned.py +++ b/examples/test/test_i_one_kms_cmk_unsigned.py @@ -16,9 +16,7 @@ import pytest from ..src.one_kms_cmk_unsigned import encrypt_decrypt -from .examples_test_utils import get_cmk_arn -from .examples_test_utils import static_plaintext - +from .examples_test_utils import get_cmk_arn, static_plaintext pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_set_commitment.py b/examples/test/test_i_set_commitment.py index 96247334b..c14a379bf 100644 --- a/examples/test/test_i_set_commitment.py +++ b/examples/test/test_i_set_commitment.py @@ -16,9 +16,7 @@ import pytest from ..src.set_commitment import encrypt_decrypt -from .examples_test_utils import get_cmk_arn -from .examples_test_utils import static_plaintext - +from .examples_test_utils import get_cmk_arn, static_plaintext pytestmark = [pytest.mark.examples] From 6cf01d4c6cfd0b67656a1faba6af4894675caaba Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 10:08:03 -0800 Subject: [PATCH 023/184] flake8/pylint examples --- examples/src/keyrings/hierarchical_keyring.py | 1 - examples/src/keyrings/module_.py | 2 +- examples/src/module_.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/src/keyrings/hierarchical_keyring.py b/examples/src/keyrings/hierarchical_keyring.py index acc594cc3..76aef25e0 100644 --- a/examples/src/keyrings/hierarchical_keyring.py +++ b/examples/src/keyrings/hierarchical_keyring.py @@ -35,7 +35,6 @@ def encrypt_and_decrypt_with_keyring( kms_key_id: str ): """Creates a hierarchical keyring using the provided resources, then encrypts and decrypts a string with it.""" - # 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 diff --git a/examples/src/keyrings/module_.py b/examples/src/keyrings/module_.py index 2f64c8e0f..d9a8c058f 100644 --- a/examples/src/keyrings/module_.py +++ b/examples/src/keyrings/module_.py @@ -1 +1 @@ -"""Should remove this.""" \ No newline at end of file +"""Should remove this.""" diff --git a/examples/src/module_.py b/examples/src/module_.py index 2f64c8e0f..d9a8c058f 100644 --- a/examples/src/module_.py +++ b/examples/src/module_.py @@ -1 +1 @@ -"""Should remove this.""" \ No newline at end of file +"""Should remove this.""" From 00cfed1f368752b872ebb25631331e04e4660893 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 10:12:24 -0800 Subject: [PATCH 024/184] reset tests --- examples/test/examples_test_utils.py | 2 +- examples/test/test_i_basic_encryption.py | 1 + .../test_i_basic_file_encryption_with_multiple_providers.py | 4 +++- .../test_i_basic_file_encryption_with_raw_key_provider.py | 1 + examples/test/test_i_data_key_caching_basic.py | 1 + examples/test/test_i_discovery_kms_provider.py | 4 +++- examples/test/test_i_mrk_aware_kms_provider.py | 4 +++- examples/test/test_i_multiple_kms_cmk.py | 4 +++- examples/test/test_i_one_kms_cmk.py | 4 +++- examples/test/test_i_one_kms_cmk_streaming_data.py | 1 + examples/test/test_i_one_kms_cmk_unsigned.py | 4 +++- examples/test/test_i_set_commitment.py | 4 +++- 12 files changed, 26 insertions(+), 8 deletions(-) diff --git a/examples/test/examples_test_utils.py b/examples/test/examples_test_utils.py index 08e8cf2f5..8a51f21c8 100644 --- a/examples/test/examples_test_utils.py +++ b/examples/test/examples_test_utils.py @@ -49,7 +49,7 @@ from integration_test_utils import ( # noqa pylint: disable=unused-import,import-error get_cmk_arn, - get_mrk_arn, get_second_cmk_arn, + get_mrk_arn, get_second_mrk_arn, ) diff --git a/examples/test/test_i_basic_encryption.py b/examples/test/test_i_basic_encryption.py index aa32d61fa..f2a4fab51 100644 --- a/examples/test/test_i_basic_encryption.py +++ b/examples/test/test_i_basic_encryption.py @@ -17,6 +17,7 @@ from ..src.basic_encryption import cycle_string from .examples_test_utils import get_cmk_arn, static_plaintext + pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_basic_file_encryption_with_multiple_providers.py b/examples/test/test_i_basic_file_encryption_with_multiple_providers.py index 0792f4958..282a272ab 100644 --- a/examples/test/test_i_basic_file_encryption_with_multiple_providers.py +++ b/examples/test/test_i_basic_file_encryption_with_multiple_providers.py @@ -18,7 +18,9 @@ import pytest from ..src.basic_file_encryption_with_multiple_providers import cycle_file -from .examples_test_utils import get_cmk_arn, static_plaintext +from .examples_test_utils import get_cmk_arn +from .examples_test_utils import static_plaintext + pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py b/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py index 046b7f964..710c0ccac 100644 --- a/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py +++ b/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py @@ -19,6 +19,7 @@ from ..src.basic_file_encryption_with_raw_key_provider import cycle_file from .examples_test_utils import static_plaintext + pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_data_key_caching_basic.py b/examples/test/test_i_data_key_caching_basic.py index 7a30f4e53..734c35692 100644 --- a/examples/test/test_i_data_key_caching_basic.py +++ b/examples/test/test_i_data_key_caching_basic.py @@ -16,6 +16,7 @@ from ..src.data_key_caching_basic import encrypt_with_caching from .examples_test_utils import get_cmk_arn + pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_discovery_kms_provider.py b/examples/test/test_i_discovery_kms_provider.py index 0f64cbf59..e9a1c6e71 100644 --- a/examples/test/test_i_discovery_kms_provider.py +++ b/examples/test/test_i_discovery_kms_provider.py @@ -16,7 +16,9 @@ import pytest from ..src.discovery_kms_provider import encrypt_decrypt -from .examples_test_utils import get_cmk_arn, static_plaintext +from .examples_test_utils import get_cmk_arn +from .examples_test_utils import static_plaintext + pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_mrk_aware_kms_provider.py b/examples/test/test_i_mrk_aware_kms_provider.py index a90101fa8..8e7a003f8 100644 --- a/examples/test/test_i_mrk_aware_kms_provider.py +++ b/examples/test/test_i_mrk_aware_kms_provider.py @@ -15,7 +15,9 @@ import pytest from ..src.mrk_aware_kms_provider import encrypt_decrypt -from .examples_test_utils import get_mrk_arn, get_second_mrk_arn, static_plaintext +from .examples_test_utils import get_mrk_arn, get_second_mrk_arn +from .examples_test_utils import static_plaintext + pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_multiple_kms_cmk.py b/examples/test/test_i_multiple_kms_cmk.py index 2915a0fd7..39369cbc6 100644 --- a/examples/test/test_i_multiple_kms_cmk.py +++ b/examples/test/test_i_multiple_kms_cmk.py @@ -16,7 +16,9 @@ import pytest from ..src.multiple_kms_cmk import encrypt_decrypt -from .examples_test_utils import get_cmk_arn, get_second_cmk_arn, static_plaintext +from .examples_test_utils import get_cmk_arn, get_second_cmk_arn +from .examples_test_utils import static_plaintext + pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_one_kms_cmk.py b/examples/test/test_i_one_kms_cmk.py index 96dd48dae..71ce74d3d 100644 --- a/examples/test/test_i_one_kms_cmk.py +++ b/examples/test/test_i_one_kms_cmk.py @@ -16,7 +16,9 @@ import pytest from ..src.one_kms_cmk import encrypt_decrypt -from .examples_test_utils import get_cmk_arn, static_plaintext +from .examples_test_utils import get_cmk_arn +from .examples_test_utils import static_plaintext + pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_one_kms_cmk_streaming_data.py b/examples/test/test_i_one_kms_cmk_streaming_data.py index f0a3094d0..b22fa4232 100644 --- a/examples/test/test_i_one_kms_cmk_streaming_data.py +++ b/examples/test/test_i_one_kms_cmk_streaming_data.py @@ -20,6 +20,7 @@ from ..src.one_kms_cmk_streaming_data import encrypt_decrypt_stream from .examples_test_utils import get_cmk_arn, static_plaintext + pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_one_kms_cmk_unsigned.py b/examples/test/test_i_one_kms_cmk_unsigned.py index 41f16473d..8a2758c96 100644 --- a/examples/test/test_i_one_kms_cmk_unsigned.py +++ b/examples/test/test_i_one_kms_cmk_unsigned.py @@ -16,7 +16,9 @@ import pytest from ..src.one_kms_cmk_unsigned import encrypt_decrypt -from .examples_test_utils import get_cmk_arn, static_plaintext +from .examples_test_utils import get_cmk_arn +from .examples_test_utils import static_plaintext + pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_set_commitment.py b/examples/test/test_i_set_commitment.py index c14a379bf..96247334b 100644 --- a/examples/test/test_i_set_commitment.py +++ b/examples/test/test_i_set_commitment.py @@ -16,7 +16,9 @@ import pytest from ..src.set_commitment import encrypt_decrypt -from .examples_test_utils import get_cmk_arn, static_plaintext +from .examples_test_utils import get_cmk_arn +from .examples_test_utils import static_plaintext + pytestmark = [pytest.mark.examples] From 61bbb3b474bbff6b360cbea867245a2c28405659 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 11:05:51 -0800 Subject: [PATCH 025/184] extend mpl --- .github/workflows/ci_tests.yaml | 12 ++++++++++ .../keyrings/test_i_hierarchical_keyring.py | 12 ++++++++++ tox.ini | 24 +++++++++++++++++-- 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 examples/test/keyrings/test_i_hierarchical_keyring.py diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index 9d491203c..f1701de76 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -45,12 +45,24 @@ jobs: # Enable them once we sort how to provide them. # - integ # - examples + optional_dependency: + - "" + - mpl exclude: # x86 builds are only meaningful for Windows - os: ubuntu-latest architecture: x86 - os: macos-latest architecture: x86 + # MPL is not supported on <3.11 + - python: 3.7 + optional_dependency: mpl + - python: 3.8 + optional_dependency: mpl + - python: 3.9 + optional_dependency: mpl + - python: 3.10 + optional_dependency: mpl steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 diff --git a/examples/test/keyrings/test_i_hierarchical_keyring.py b/examples/test/keyrings/test_i_hierarchical_keyring.py new file mode 100644 index 000000000..5df72383f --- /dev/null +++ b/examples/test/keyrings/test_i_hierarchical_keyring.py @@ -0,0 +1,12 @@ +"""Unit test suite for the hierarchical keyring example.""" +import pytest + +from ..src.keyrings.hierarchical_keyring import encrypt_and_decrypt_with_keyring + +pytestmark = [pytest.mark.examples] + + +def test_encrypt_and_decrypt_with_keyring(): + key_store_table_name = "KeyStoreDdbTable" + key_arn = "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126" + encrypt_and_decrypt_with_keyring(key_store_table_name, key_store_table_name, key_arn) diff --git a/tox.ini b/tox.ini index 9ca7a0cd6..c90a6fcd6 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,10 @@ [tox] envlist = - py{37,38,39,310,311,312}-{local,integ,accept,examples}, nocmk, + # <3.11: run all non-MPL tests + py{37,38,39,310}-{local,integ,accept,examples}, + # >=3.11: run all MPL tests and non-MPL tests + py{311,312}-{local,integ,accept,examples}{,-mpl}, + nocmk, bandit, doc8, readme, docs, {flake8,pylint}{,-tests,-examples}, isort-check, black-check, @@ -61,12 +65,17 @@ passenv = # Pass through custom pip config file settings PIP_CONFIG_FILE sitepackages = False -deps = -rdev_requirements/test-requirements.txt +deps = + -rdev_requirements/test-requirements.txt + # install the MPL if in environment + mpl: aws-cryptographic-material-providers>=0.0.1 commands = local: {[testenv:base-command]commands} test/ -m local integ: {[testenv:base-command]commands} test/ -m integ accept: {[testenv:base-command]commands} test/ -m accept examples: {[testenv:base-command]commands} examples/test/ -m examples + # append MPL examples to base examples command + examples-mpl: {[testenv:examples]commands} examples/mpl/test/ all: {[testenv:base-command]commands} test/ examples/test/ manual: {[testenv:base-command]commands} @@ -134,6 +143,17 @@ sitepackages = {[testenv:test-upstream-requirements-base]sitepackages} recreate = {[testenv:test-upstream-requirements-base]recreate} commands = {[testenv:test-upstream-requirements-base]commands} +# Test MPL +[testenv:py311-local-mpl] +basepython = {[testenv:pylint]basepython} +deps = {[testenv:pylint]deps} +commands = + pylint \ + --rcfile=test/pylintrc \ + test/unit/ \ + test/functional/ \ + test/integration/ + # Linters [testenv:flake8] basepython = python3 From 4d53ad695908384c6e3705fc7ea5982ed7be9d8f Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 11:15:27 -0800 Subject: [PATCH 026/184] mpl gha --- .github/workflows/ci_tests.yaml | 16 ++++++++-------- tox.ini | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index f1701de76..3d22ab05f 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -45,9 +45,9 @@ jobs: # Enable them once we sort how to provide them. # - integ # - examples - optional_dependency: + optional_mpl_dependency: - "" - - mpl + - -mpl exclude: # x86 builds are only meaningful for Windows - os: ubuntu-latest @@ -56,13 +56,13 @@ jobs: architecture: x86 # MPL is not supported on <3.11 - python: 3.7 - optional_dependency: mpl + optional_mpl_dependency: mpl - python: 3.8 - optional_dependency: mpl + optional_mpl_dependency: mpl - python: 3.9 - optional_dependency: mpl + optional_mpl_dependency: mpl - python: 3.10 - optional_dependency: mpl + optional_mpl_dependency: mpl steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 @@ -74,7 +74,7 @@ jobs: pip install --upgrade -r dev_requirements/ci-requirements.txt - name: run test env: - TOXENV: ${{ matrix.category }} + TOXENV: ${{ matrix.category }}${{ matrix.optional_mpl_dependency }} run: tox -- -vv upstream-py37: runs-on: ubuntu-latest @@ -114,5 +114,5 @@ jobs: pip install --upgrade -r dev_requirements/ci-requirements.txt - name: run test env: - TOXENV: ${{ matrix.category }} + TOXENV: ${{ matrix.category }}${{ matrix.optional_mpl_dependency }} run: tox -- -vv diff --git a/tox.ini b/tox.ini index c90a6fcd6..8f12ab8e7 100644 --- a/tox.ini +++ b/tox.ini @@ -75,7 +75,7 @@ commands = accept: {[testenv:base-command]commands} test/ -m accept examples: {[testenv:base-command]commands} examples/test/ -m examples # append MPL examples to base examples command - examples-mpl: {[testenv:examples]commands} examples/mpl/test/ + examples-mpl: {[testenv:base-command]commands} examples/test/ examples/mpl/test -m examples all: {[testenv:base-command]commands} test/ examples/test/ manual: {[testenv:base-command]commands} From c1736d3e91d3d78cd3412ab5c9095eccd41340d6 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 11:26:21 -0800 Subject: [PATCH 027/184] debug --- .github/workflows/ci_tests.yaml | 8 ++++---- tox.ini | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index 3d22ab05f..603f54371 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -56,13 +56,13 @@ jobs: architecture: x86 # MPL is not supported on <3.11 - python: 3.7 - optional_mpl_dependency: mpl + optional_mpl_dependency: -mpl - python: 3.8 - optional_mpl_dependency: mpl + optional_mpl_dependency: -mpl - python: 3.9 - optional_mpl_dependency: mpl + optional_mpl_dependency: -mpl - python: 3.10 - optional_mpl_dependency: mpl + optional_mpl_dependency: -mpl steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 diff --git a/tox.ini b/tox.ini index 8f12ab8e7..37fbbae51 100644 --- a/tox.ini +++ b/tox.ini @@ -68,7 +68,9 @@ sitepackages = False deps = -rdev_requirements/test-requirements.txt # install the MPL if in environment - mpl: aws-cryptographic-material-providers>=0.0.1 + mpl: "aws-cryptographic-material-providers @" \ + "git+https://github.com/aws/aws-cryptographic-material-providers-library.git@" \ + "lucmcdon/python-mpl#subdirectory=AwsCryptographicMaterialProviders/runtimes/python" commands = local: {[testenv:base-command]commands} test/ -m local integ: {[testenv:base-command]commands} test/ -m integ From 9991789b79842aa0812c84bcc3c33a6b36c2f182 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 12:40:53 -0800 Subject: [PATCH 028/184] debug --- tox.ini | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 37fbbae51..ef0f6fa29 100644 --- a/tox.ini +++ b/tox.ini @@ -68,9 +68,7 @@ sitepackages = False deps = -rdev_requirements/test-requirements.txt # install the MPL if in environment - mpl: "aws-cryptographic-material-providers @" \ - "git+https://github.com/aws/aws-cryptographic-material-providers-library.git@" \ - "lucmcdon/python-mpl#subdirectory=AwsCryptographicMaterialProviders/runtimes/python" + mpl: "aws-cryptographic-material-providers @git+https://github.com/aws/aws-cryptographic-material-providers-library.git@lucmcdon/python-mpl#subdirectory=AwsCryptographicMaterialProviders/runtimes/python" commands = local: {[testenv:base-command]commands} test/ -m local integ: {[testenv:base-command]commands} test/ -m integ From a501e8f07d1bd125fd77e6b5c28710eb19402e9a Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 12:53:41 -0800 Subject: [PATCH 029/184] debug --- .../keyrings/test_i_hierarchical_keyring.py | 2 +- tox.ini | 38 ++++++++++--------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/examples/test/keyrings/test_i_hierarchical_keyring.py b/examples/test/keyrings/test_i_hierarchical_keyring.py index 5df72383f..d80bb565d 100644 --- a/examples/test/keyrings/test_i_hierarchical_keyring.py +++ b/examples/test/keyrings/test_i_hierarchical_keyring.py @@ -1,7 +1,7 @@ """Unit test suite for the hierarchical keyring example.""" import pytest -from ..src.keyrings.hierarchical_keyring import encrypt_and_decrypt_with_keyring +from ...src.keyrings.hierarchical_keyring import encrypt_and_decrypt_with_keyring pytestmark = [pytest.mark.examples] diff --git a/tox.ini b/tox.ini index ef0f6fa29..d06cbab2e 100644 --- a/tox.ini +++ b/tox.ini @@ -10,6 +10,7 @@ envlist = isort-check, black-check, # prone to false positives vulture +ignore_base_python_conflict = true # Additional test environments: # @@ -47,28 +48,29 @@ envlist = commands = pytest --basetemp={envtmpdir} -l {posargs} [testenv] -passenv = - # Identifies AWS KMS key id to use in integration tests - AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID \ - # Identifies a second AWS KMS key id to use in integration tests - AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2 \ - # Identifies AWS KMS MRK key id to use in integration tests - AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_1 \ - # Identifies a related AWS KMS MRK key id to use in integration tests - AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_2 \ - # Pass through AWS credentials - AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN \ - # AWS Role access in CodeBuild is via the contaner URI - AWS_CONTAINER_CREDENTIALS_RELATIVE_URI \ - # Pass through AWS profile name (useful for local testing) - AWS_PROFILE \ - # Pass through custom pip config file settings - PIP_CONFIG_FILE +passenv = AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID,AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2,AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_1,AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_2,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKEN,AWS_CONTAINER_CREDENTIALS_RELATIVE_URI,AWS_PROFILE,PIP_CONFIG_FILE +; passenv = +; # Identifies AWS KMS key id to use in integration tests +; AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID \ +; # Identifies a second AWS KMS key id to use in integration tests +; AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2 \ +; # Identifies AWS KMS MRK key id to use in integration tests +; AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_1 \ +; # Identifies a related AWS KMS MRK key id to use in integration tests +; AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_2 \ +; # Pass through AWS credentials +; AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN \ +; # AWS Role access in CodeBuild is via the contaner URI +; AWS_CONTAINER_CREDENTIALS_RELATIVE_URI \ +; # Pass through AWS profile name (useful for local testing) +; AWS_PROFILE \ +; # Pass through custom pip config file settings +; PIP_CONFIG_FILE sitepackages = False deps = -rdev_requirements/test-requirements.txt # install the MPL if in environment - mpl: "aws-cryptographic-material-providers @git+https://github.com/aws/aws-cryptographic-material-providers-library.git@lucmcdon/python-mpl#subdirectory=AwsCryptographicMaterialProviders/runtimes/python" + mpl: -rrequirements_mpl.txt commands = local: {[testenv:base-command]commands} test/ -m local integ: {[testenv:base-command]commands} test/ -m integ From 6eb8f82edb6139e6df62ec21957ad89ef5efa58c Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 12:53:55 -0800 Subject: [PATCH 030/184] debug --- examples/src/keyrings/__init__.py | 13 +++++++++++++ examples/test/keyrings/__init__.py | 13 +++++++++++++ requirements_mpl.txt | 1 + 3 files changed, 27 insertions(+) create mode 100644 examples/src/keyrings/__init__.py create mode 100644 examples/test/keyrings/__init__.py create mode 100644 requirements_mpl.txt diff --git a/examples/src/keyrings/__init__.py b/examples/src/keyrings/__init__.py new file mode 100644 index 000000000..e8fd618b1 --- /dev/null +++ b/examples/src/keyrings/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Stub module indicator to make linter configuration simpler.""" diff --git a/examples/test/keyrings/__init__.py b/examples/test/keyrings/__init__.py new file mode 100644 index 000000000..e8fd618b1 --- /dev/null +++ b/examples/test/keyrings/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Stub module indicator to make linter configuration simpler.""" diff --git a/requirements_mpl.txt b/requirements_mpl.txt new file mode 100644 index 000000000..209e10f2c --- /dev/null +++ b/requirements_mpl.txt @@ -0,0 +1 @@ +aws-cryptographic-material-providers @ git+https://github.com/aws/aws-cryptographic-material-providers-library.git@lucmcdon/python-mpl#subdirectory=AwsCryptographicMaterialProviders/runtimes/python \ No newline at end of file From 5ccfa0cce6afdde5e598ceb35719feb109da1d48 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 13:14:29 -0800 Subject: [PATCH 031/184] codebuild mpl --- tox.ini | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tox.ini b/tox.ini index d06cbab2e..8eb141821 100644 --- a/tox.ini +++ b/tox.ini @@ -145,17 +145,6 @@ sitepackages = {[testenv:test-upstream-requirements-base]sitepackages} recreate = {[testenv:test-upstream-requirements-base]recreate} commands = {[testenv:test-upstream-requirements-base]commands} -# Test MPL -[testenv:py311-local-mpl] -basepython = {[testenv:pylint]basepython} -deps = {[testenv:pylint]deps} -commands = - pylint \ - --rcfile=test/pylintrc \ - test/unit/ \ - test/functional/ \ - test/integration/ - # Linters [testenv:flake8] basepython = python3 From 5e7ec9b94694ed4ace20aabd0ea87edd9e74f479 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 13:16:13 -0800 Subject: [PATCH 032/184] codebuild mpl --- codebuild/py311/examples_mpl.yml | 22 ++++++++++++++++++++++ codebuild/py311/integ_mpl.yml | 22 ++++++++++++++++++++++ codebuild/py312/examples_mpl.yml | 27 +++++++++++++++++++++++++++ codebuild/py312/integ_mpl.yml | 27 +++++++++++++++++++++++++++ 4 files changed, 98 insertions(+) create mode 100644 codebuild/py311/examples_mpl.yml create mode 100644 codebuild/py311/integ_mpl.yml create mode 100644 codebuild/py312/examples_mpl.yml create mode 100644 codebuild/py312/integ_mpl.yml diff --git a/codebuild/py311/examples_mpl.yml b/codebuild/py311/examples_mpl.yml new file mode 100644 index 000000000..abea2ad8c --- /dev/null +++ b/codebuild/py311/examples_mpl.yml @@ -0,0 +1,22 @@ +version: 0.2 + +env: + variables: + TOXENV: "py311-examples-mpl" + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: >- + arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2: >- + arn:aws:kms:eu-central-1:658956600833:key/75414c93-5285-4b57-99c9-30c1cf0a22c2 + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_1: >- + arn:aws:kms:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7 + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_2: >- + arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7 + +phases: + install: + runtime-versions: + python: 3.11 + build: + commands: + - pip install "tox < 4.0" + - tox diff --git a/codebuild/py311/integ_mpl.yml b/codebuild/py311/integ_mpl.yml new file mode 100644 index 000000000..ad969c621 --- /dev/null +++ b/codebuild/py311/integ_mpl.yml @@ -0,0 +1,22 @@ +version: 0.2 + +env: + variables: + TOXENV: "py311-integ-mpl" + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: >- + arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2: >- + arn:aws:kms:eu-central-1:658956600833:key/75414c93-5285-4b57-99c9-30c1cf0a22c2 + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_1: >- + arn:aws:kms:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7 + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_2: >- + arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7 + +phases: + install: + runtime-versions: + python: 3.11 + build: + commands: + - pip install "tox < 4.0" + - tox diff --git a/codebuild/py312/examples_mpl.yml b/codebuild/py312/examples_mpl.yml new file mode 100644 index 000000000..8ffd24964 --- /dev/null +++ b/codebuild/py312/examples_mpl.yml @@ -0,0 +1,27 @@ +version: 0.2 + +env: + variables: + TOXENV: "py312-examples-mpl" + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: >- + arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2: >- + arn:aws:kms:eu-central-1:658956600833:key/75414c93-5285-4b57-99c9-30c1cf0a22c2 + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_1: >- + arn:aws:kms:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7 + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_2: >- + arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7 + +phases: + install: + runtime-versions: + python: latest + build: + commands: + - cd /root/.pyenv/plugins/python-build/../.. && git pull && cd - + - pyenv install 3.12.0 + - pyenv local 3.12.0 + - pip install --upgrade pip + - pip install setuptools + - pip install "tox < 4.0" + - tox diff --git a/codebuild/py312/integ_mpl.yml b/codebuild/py312/integ_mpl.yml new file mode 100644 index 000000000..085cb4660 --- /dev/null +++ b/codebuild/py312/integ_mpl.yml @@ -0,0 +1,27 @@ +version: 0.2 + +env: + variables: + TOXENV: "py312-integ-mpl" + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: >- + arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2: >- + arn:aws:kms:eu-central-1:658956600833:key/75414c93-5285-4b57-99c9-30c1cf0a22c2 + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_1: >- + arn:aws:kms:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7 + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_2: >- + arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7 + +phases: + install: + runtime-versions: + python: latest + build: + commands: + - cd /root/.pyenv/plugins/python-build/../.. && git pull && cd - + - pyenv install 3.12.0 + - pyenv local 3.12.0 + - pip install --upgrade pip + - pip install setuptools + - pip install "tox < 4.0" + - tox From cc48697824accd00df18e71877a4f888d0f32125 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 13:20:54 -0800 Subject: [PATCH 033/184] codebuild mpl --- buildspec.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/buildspec.yml b/buildspec.yml index f92d203a0..c718c3df5 100644 --- a/buildspec.yml +++ b/buildspec.yml @@ -58,27 +58,51 @@ batch: buildspec: codebuild/py311/integ.yml env: image: aws/codebuild/standard:7.0 + - identifier: py311_integ_mpl + buildspec: codebuild/py311/integ_mpl.yml + env: + image: aws/codebuild/standard:7.0 - identifier: py311_examples buildspec: codebuild/py311/examples.yml env: image: aws/codebuild/standard:7.0 + - identifier: py311_examples_mpl + buildspec: codebuild/py311/examples_mpl.yml + env: + image: aws/codebuild/standard:7.0 - identifier: py311_awses_latest buildspec: codebuild/py311/awses_local.yml env: image: aws/codebuild/standard:7.0 + - identifier: py311_awses_latest_mpl + buildspec: codebuild/py311/awses_local_mpl.yml + env: + image: aws/codebuild/standard:7.0 - identifier: py312_integ buildspec: codebuild/py312/integ.yml env: image: aws/codebuild/standard:7.0 + - identifier: py312_integ_mpl + buildspec: codebuild/py312/integ_mpl.yml + env: + image: aws/codebuild/standard:7.0 - identifier: py312_examples buildspec: codebuild/py312/examples.yml env: image: aws/codebuild/standard:7.0 + - identifier: py312_examples_mpl + buildspec: codebuild/py312/examples_mpl.yml + env: + image: aws/codebuild/standard:7.0 - identifier: py312_awses_latest buildspec: codebuild/py312/awses_local.yml env: image: aws/codebuild/standard:7.0 + - identifier: py312_awses_latest_mpl + buildspec: codebuild/py312/awses_local_mpl.yml + env: + image: aws/codebuild/standard:7.0 - identifier: code_coverage buildspec: codebuild/coverage/coverage.yml From fae43d14db29780616356893d3ae7da9ce996dab Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 13:21:05 -0800 Subject: [PATCH 034/184] codebuild mpl --- codebuild/py311/awses_local_mpl.yml | 25 ++++++++++++++++++++++++ codebuild/py312/awses_local_mpl.yml | 30 +++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 codebuild/py311/awses_local_mpl.yml create mode 100644 codebuild/py312/awses_local_mpl.yml diff --git a/codebuild/py311/awses_local_mpl.yml b/codebuild/py311/awses_local_mpl.yml new file mode 100644 index 000000000..f98859b40 --- /dev/null +++ b/codebuild/py311/awses_local_mpl.yml @@ -0,0 +1,25 @@ +version: 0.2 + +env: + variables: + TOXENV: "py311-awses_local-mpl" + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: >- + arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2: >- + arn:aws:kms:eu-central-1:658956600833:key/75414c93-5285-4b57-99c9-30c1cf0a22c2 + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_1: >- + arn:aws:kms:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7 + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_2: >- + arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7 + AWS_ENCRYPTION_SDK_PYTHON_DECRYPT_ORACLE_API_DEPLOYMENT_ID: "xi1mwx3ttb" + AWS_ENCRYPTION_SDK_PYTHON_DECRYPT_ORACLE_REGION: "us-west-2" + +phases: + install: + runtime-versions: + python: 3.11 + build: + commands: + - pip install "tox < 4.0" + - cd test_vector_handlers + - tox diff --git a/codebuild/py312/awses_local_mpl.yml b/codebuild/py312/awses_local_mpl.yml new file mode 100644 index 000000000..689d40da8 --- /dev/null +++ b/codebuild/py312/awses_local_mpl.yml @@ -0,0 +1,30 @@ +version: 0.2 + +env: + variables: + TOXENV: "py312-awses_local-mpl" + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: >- + arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2: >- + arn:aws:kms:eu-central-1:658956600833:key/75414c93-5285-4b57-99c9-30c1cf0a22c2 + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_1: >- + arn:aws:kms:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7 + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_2: >- + arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7 + AWS_ENCRYPTION_SDK_PYTHON_DECRYPT_ORACLE_API_DEPLOYMENT_ID: "xi1mwx3ttb" + AWS_ENCRYPTION_SDK_PYTHON_DECRYPT_ORACLE_REGION: "us-west-2" + +phases: + install: + runtime-versions: + python: latest + build: + commands: + - cd /root/.pyenv/plugins/python-build/../.. && git pull && cd - + - pyenv install 3.12.0 + - pyenv local 3.12.0 + - pip install --upgrade pip + - pip install setuptools + - pip install "tox < 4.0" + - cd test_vector_handlers + - tox From 263761678b201618c488874c040cfd7d50d0db7f Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 13:32:27 -0800 Subject: [PATCH 035/184] debug --- tox.ini | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tox.ini b/tox.ini index 8eb141821..8e6cf3f34 100644 --- a/tox.ini +++ b/tox.ini @@ -48,24 +48,24 @@ ignore_base_python_conflict = true commands = pytest --basetemp={envtmpdir} -l {posargs} [testenv] -passenv = AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID,AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2,AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_1,AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_2,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKEN,AWS_CONTAINER_CREDENTIALS_RELATIVE_URI,AWS_PROFILE,PIP_CONFIG_FILE -; passenv = -; # Identifies AWS KMS key id to use in integration tests -; AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID \ -; # Identifies a second AWS KMS key id to use in integration tests -; AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2 \ -; # Identifies AWS KMS MRK key id to use in integration tests -; AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_1 \ -; # Identifies a related AWS KMS MRK key id to use in integration tests -; AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_2 \ -; # Pass through AWS credentials -; AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN \ -; # AWS Role access in CodeBuild is via the contaner URI -; AWS_CONTAINER_CREDENTIALS_RELATIVE_URI \ -; # Pass through AWS profile name (useful for local testing) -; AWS_PROFILE \ -; # Pass through custom pip config file settings -; PIP_CONFIG_FILE +; passenv = AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID,AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2,AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_1,AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_2,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKEN,AWS_CONTAINER_CREDENTIALS_RELATIVE_URI,AWS_PROFILE,PIP_CONFIG_FILE +passenv = + # Identifies AWS KMS key id to use in integration tests + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID \ + # Identifies a second AWS KMS key id to use in integration tests + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2 \ + # Identifies AWS KMS MRK key id to use in integration tests + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_1 \ + # Identifies a related AWS KMS MRK key id to use in integration tests + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_2 \ + # Pass through AWS credentials + AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN \ + # AWS Role access in CodeBuild is via the contaner URI + AWS_CONTAINER_CREDENTIALS_RELATIVE_URI \ + # Pass through AWS profile name (useful for local testing) + AWS_PROFILE \ + # Pass through custom pip config file settings + PIP_CONFIG_FILE sitepackages = False deps = -rdev_requirements/test-requirements.txt From 2694932f5090404eaacf9c5d442b6acac98c0246 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 13:37:16 -0800 Subject: [PATCH 036/184] debug --- tox.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 8e6cf3f34..59a1dde74 100644 --- a/tox.ini +++ b/tox.ini @@ -65,7 +65,9 @@ passenv = # Pass through AWS profile name (useful for local testing) AWS_PROFILE \ # Pass through custom pip config file settings - PIP_CONFIG_FILE + PIP_CONFIG_FILE \ + # Pass through any configured AWS region + REGION sitepackages = False deps = -rdev_requirements/test-requirements.txt From f674d3e27a2f21f3a340c9c95b3ef60fc786dd3d Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 13:40:29 -0800 Subject: [PATCH 037/184] debug --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 59a1dde74..26e0e5433 100644 --- a/tox.ini +++ b/tox.ini @@ -67,7 +67,7 @@ passenv = # Pass through custom pip config file settings PIP_CONFIG_FILE \ # Pass through any configured AWS region - REGION + AWS_REGION sitepackages = False deps = -rdev_requirements/test-requirements.txt From 0b5e655b1100f4b236a14dd47ab7e7451a90eed1 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 13:44:55 -0800 Subject: [PATCH 038/184] debug --- codebuild/py311/awses_local_mpl.yml | 1 + codebuild/py311/examples_mpl.yml | 1 + codebuild/py311/integ_mpl.yml | 1 + codebuild/py312/awses_local_mpl.yml | 1 + codebuild/py312/examples_mpl.yml | 1 + codebuild/py312/integ_mpl.yml | 1 + tox.ini | 4 ++-- 7 files changed, 8 insertions(+), 2 deletions(-) diff --git a/codebuild/py311/awses_local_mpl.yml b/codebuild/py311/awses_local_mpl.yml index f98859b40..04d268d5a 100644 --- a/codebuild/py311/awses_local_mpl.yml +++ b/codebuild/py311/awses_local_mpl.yml @@ -3,6 +3,7 @@ version: 0.2 env: variables: TOXENV: "py311-awses_local-mpl" + AWS_REGION: "us-west-2" AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: >- arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2: >- diff --git a/codebuild/py311/examples_mpl.yml b/codebuild/py311/examples_mpl.yml index abea2ad8c..05bdc07c0 100644 --- a/codebuild/py311/examples_mpl.yml +++ b/codebuild/py311/examples_mpl.yml @@ -3,6 +3,7 @@ version: 0.2 env: variables: TOXENV: "py311-examples-mpl" + AWS_REGION: "us-west-2" AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: >- arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2: >- diff --git a/codebuild/py311/integ_mpl.yml b/codebuild/py311/integ_mpl.yml index ad969c621..e6766619c 100644 --- a/codebuild/py311/integ_mpl.yml +++ b/codebuild/py311/integ_mpl.yml @@ -3,6 +3,7 @@ version: 0.2 env: variables: TOXENV: "py311-integ-mpl" + AWS_REGION: "us-west-2" AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: >- arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2: >- diff --git a/codebuild/py312/awses_local_mpl.yml b/codebuild/py312/awses_local_mpl.yml index 689d40da8..a504696ec 100644 --- a/codebuild/py312/awses_local_mpl.yml +++ b/codebuild/py312/awses_local_mpl.yml @@ -3,6 +3,7 @@ version: 0.2 env: variables: TOXENV: "py312-awses_local-mpl" + AWS_REGION: "us-west-2" AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: >- arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2: >- diff --git a/codebuild/py312/examples_mpl.yml b/codebuild/py312/examples_mpl.yml index 8ffd24964..a947c67b3 100644 --- a/codebuild/py312/examples_mpl.yml +++ b/codebuild/py312/examples_mpl.yml @@ -3,6 +3,7 @@ version: 0.2 env: variables: TOXENV: "py312-examples-mpl" + AWS_REGION: "us-west-2" AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: >- arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2: >- diff --git a/codebuild/py312/integ_mpl.yml b/codebuild/py312/integ_mpl.yml index 085cb4660..3cf473d08 100644 --- a/codebuild/py312/integ_mpl.yml +++ b/codebuild/py312/integ_mpl.yml @@ -3,6 +3,7 @@ version: 0.2 env: variables: TOXENV: "py312-integ-mpl" + AWS_REGION: "us-west-2" AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: >- arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2: >- diff --git a/tox.ini b/tox.ini index 26e0e5433..8b50e6b01 100644 --- a/tox.ini +++ b/tox.ini @@ -77,9 +77,9 @@ commands = local: {[testenv:base-command]commands} test/ -m local integ: {[testenv:base-command]commands} test/ -m integ accept: {[testenv:base-command]commands} test/ -m accept - examples: {[testenv:base-command]commands} examples/test/ -m examples + examples: {[testenv:base-command]commands} examples/test/ -m examples --ignore examples/test/keyrings/ # append MPL examples to base examples command - examples-mpl: {[testenv:base-command]commands} examples/test/ examples/mpl/test -m examples + examples-mpl: {[testenv:base-command]commands} examples/test/ -m examples all: {[testenv:base-command]commands} test/ examples/test/ manual: {[testenv:base-command]commands} From 831df1713823dd185883c87dfcab0e521b5fefcd Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 13:52:00 -0800 Subject: [PATCH 039/184] debug --- codebuild/py311/awses_local_mpl.yml | 2 +- codebuild/py311/examples_mpl.yml | 2 +- codebuild/py311/integ_mpl.yml | 2 +- codebuild/py312/awses_local_mpl.yml | 2 +- codebuild/py312/examples_mpl.yml | 2 +- codebuild/py312/integ_mpl.yml | 2 +- test_vector_handlers/tox.ini | 4 +++- tox.ini | 2 -- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/codebuild/py311/awses_local_mpl.yml b/codebuild/py311/awses_local_mpl.yml index 04d268d5a..859931aa3 100644 --- a/codebuild/py311/awses_local_mpl.yml +++ b/codebuild/py311/awses_local_mpl.yml @@ -3,7 +3,7 @@ version: 0.2 env: variables: TOXENV: "py311-awses_local-mpl" - AWS_REGION: "us-west-2" + REGION: "us-west-2" AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: >- arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2: >- diff --git a/codebuild/py311/examples_mpl.yml b/codebuild/py311/examples_mpl.yml index 05bdc07c0..e29472507 100644 --- a/codebuild/py311/examples_mpl.yml +++ b/codebuild/py311/examples_mpl.yml @@ -3,7 +3,7 @@ version: 0.2 env: variables: TOXENV: "py311-examples-mpl" - AWS_REGION: "us-west-2" + REGION: "us-west-2" AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: >- arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2: >- diff --git a/codebuild/py311/integ_mpl.yml b/codebuild/py311/integ_mpl.yml index e6766619c..694bc0850 100644 --- a/codebuild/py311/integ_mpl.yml +++ b/codebuild/py311/integ_mpl.yml @@ -3,7 +3,7 @@ version: 0.2 env: variables: TOXENV: "py311-integ-mpl" - AWS_REGION: "us-west-2" + REGION: "us-west-2" AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: >- arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2: >- diff --git a/codebuild/py312/awses_local_mpl.yml b/codebuild/py312/awses_local_mpl.yml index a504696ec..f39bf8760 100644 --- a/codebuild/py312/awses_local_mpl.yml +++ b/codebuild/py312/awses_local_mpl.yml @@ -3,7 +3,7 @@ version: 0.2 env: variables: TOXENV: "py312-awses_local-mpl" - AWS_REGION: "us-west-2" + REGION: "us-west-2" AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: >- arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2: >- diff --git a/codebuild/py312/examples_mpl.yml b/codebuild/py312/examples_mpl.yml index a947c67b3..d6bc3f440 100644 --- a/codebuild/py312/examples_mpl.yml +++ b/codebuild/py312/examples_mpl.yml @@ -3,7 +3,7 @@ version: 0.2 env: variables: TOXENV: "py312-examples-mpl" - AWS_REGION: "us-west-2" + REGION: "us-west-2" AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: >- arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2: >- diff --git a/codebuild/py312/integ_mpl.yml b/codebuild/py312/integ_mpl.yml index 3cf473d08..8ffda4bd0 100644 --- a/codebuild/py312/integ_mpl.yml +++ b/codebuild/py312/integ_mpl.yml @@ -3,7 +3,7 @@ version: 0.2 env: variables: TOXENV: "py312-integ-mpl" - AWS_REGION: "us-west-2" + REGION: "us-west-2" AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: >- arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2: >- diff --git a/test_vector_handlers/tox.ini b/test_vector_handlers/tox.ini index 643750cd2..b6711361e 100644 --- a/test_vector_handlers/tox.ini +++ b/test_vector_handlers/tox.ini @@ -2,7 +2,7 @@ envlist = # The test vectors depend on new features now, # so until release we can only effectively test the local version of the ESDK. - py{37,38,39,310}-awses_local, + py{37,38,39,310}-awses_local{,-mpl}, # 1.2.0 and 1.2.max are being difficult because of attrs bandit, doc8, readme, {flake8,pylint}{,-tests}, @@ -48,6 +48,8 @@ passenv = sitepackages = False deps = -rtest/requirements.txt + # install the MPL if in environment + mpl: -rrequirements_mpl.txt .. commands = {[testenv:base-command]commands} diff --git a/tox.ini b/tox.ini index 8b50e6b01..903ea5170 100644 --- a/tox.ini +++ b/tox.ini @@ -66,8 +66,6 @@ passenv = AWS_PROFILE \ # Pass through custom pip config file settings PIP_CONFIG_FILE \ - # Pass through any configured AWS region - AWS_REGION sitepackages = False deps = -rdev_requirements/test-requirements.txt From 477e3a097da42fddfded9894d48fad6923d96144 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 13:56:57 -0800 Subject: [PATCH 040/184] debug --- examples/src/keyrings/hierarchical_keyring.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/src/keyrings/hierarchical_keyring.py b/examples/src/keyrings/hierarchical_keyring.py index 76aef25e0..a99728b6e 100644 --- a/examples/src/keyrings/hierarchical_keyring.py +++ b/examples/src/keyrings/hierarchical_keyring.py @@ -48,8 +48,8 @@ def encrypt_and_decrypt_with_keyring( ) # 2. Create boto3 clients for DynamoDB and KMS. - ddb_client = boto3.client('dynamodb') - kms_client = boto3.client('kms') + ddb_client = boto3.client('dynamodb', region_name="us-west-2") + kms_client = boto3.client('kms', region_name="us-west-2") # 3. Configure your KeyStore resource. # This SHOULD be the same configuration that you used From 166c5ab6ff339f8fd6dafa6ffea071704eb484a5 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 14:24:13 -0800 Subject: [PATCH 041/184] debug --- test_vector_handlers/tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_vector_handlers/tox.ini b/test_vector_handlers/tox.ini index b6711361e..7004080e3 100644 --- a/test_vector_handlers/tox.ini +++ b/test_vector_handlers/tox.ini @@ -49,7 +49,7 @@ sitepackages = False deps = -rtest/requirements.txt # install the MPL if in environment - mpl: -rrequirements_mpl.txt + mpl: -r../requirements_mpl.txt .. commands = {[testenv:base-command]commands} From 7ac88805b863add77147cabedc08c5968480ad55 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 14:35:06 -0800 Subject: [PATCH 042/184] debug --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 903ea5170..4202979a4 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,6 @@ envlist = isort-check, black-check, # prone to false positives vulture -ignore_base_python_conflict = true # Additional test environments: # @@ -65,7 +64,7 @@ passenv = # Pass through AWS profile name (useful for local testing) AWS_PROFILE \ # Pass through custom pip config file settings - PIP_CONFIG_FILE \ + PIP_CONFIG_FILE sitepackages = False deps = -rdev_requirements/test-requirements.txt From 7e3ca151e85eb44d4d125f172f32ed70d3b6523b Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 14:41:54 -0800 Subject: [PATCH 043/184] fix --- codebuild/py312/awses_local_mpl.yml | 2 +- codebuild/py312/examples_mpl.yml | 2 +- codebuild/py312/integ_mpl.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/codebuild/py312/awses_local_mpl.yml b/codebuild/py312/awses_local_mpl.yml index f39bf8760..db25f4f57 100644 --- a/codebuild/py312/awses_local_mpl.yml +++ b/codebuild/py312/awses_local_mpl.yml @@ -22,7 +22,7 @@ phases: build: commands: - cd /root/.pyenv/plugins/python-build/../.. && git pull && cd - - - pyenv install 3.12.0 + - pyenv install --skip-existing 3.12.0 - pyenv local 3.12.0 - pip install --upgrade pip - pip install setuptools diff --git a/codebuild/py312/examples_mpl.yml b/codebuild/py312/examples_mpl.yml index d6bc3f440..ff2168cd5 100644 --- a/codebuild/py312/examples_mpl.yml +++ b/codebuild/py312/examples_mpl.yml @@ -20,7 +20,7 @@ phases: build: commands: - cd /root/.pyenv/plugins/python-build/../.. && git pull && cd - - - pyenv install 3.12.0 + - pyenv install --skip-existing 3.12.0 - pyenv local 3.12.0 - pip install --upgrade pip - pip install setuptools diff --git a/codebuild/py312/integ_mpl.yml b/codebuild/py312/integ_mpl.yml index 8ffda4bd0..553f41e8a 100644 --- a/codebuild/py312/integ_mpl.yml +++ b/codebuild/py312/integ_mpl.yml @@ -20,7 +20,7 @@ phases: build: commands: - cd /root/.pyenv/plugins/python-build/../.. && git pull && cd - - - pyenv install 3.12.0 + - pyenv install --skip-existing 3.12.0 - pyenv local 3.12.0 - pip install --upgrade pip - pip install setuptools From 4c6a1d00711352236d63b2adc44081dded3a1a65 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 15:08:53 -0800 Subject: [PATCH 044/184] fix --- src/aws_encryption_sdk/cmm_handler.py | 158 ----------------- src/aws_encryption_sdk/materials_handlers.py | 170 ------------------- 2 files changed, 328 deletions(-) delete mode 100644 src/aws_encryption_sdk/cmm_handler.py delete mode 100644 src/aws_encryption_sdk/materials_handlers.py diff --git a/src/aws_encryption_sdk/cmm_handler.py b/src/aws_encryption_sdk/cmm_handler.py deleted file mode 100644 index 5bac15b87..000000000 --- a/src/aws_encryption_sdk/cmm_handler.py +++ /dev/null @@ -1,158 +0,0 @@ -"""Retrieves encryption/decryption materials from an underlying materials provider.""" - -# These dependencies are only loaded if you install the MPL. -try: - # pylint seems to struggle with this conditional import - # pylint: disable=unused-import - from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException - from aws_cryptographic_materialproviders.mpl.models import ( - AlgorithmSuiteIdESDK, - CommitmentPolicyESDK, - DecryptMaterialsInput, - DecryptMaterialsOutput, - EncryptedDataKey as MPL_EncryptedDataKey, - GetEncryptionMaterialsInput, - GetEncryptionMaterialsOutput, - ) - from aws_cryptographic_materialproviders.mpl.references import ICryptographicMaterialsManager - -except ImportError: - pass - -from typing import List - -from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError -from aws_encryption_sdk.identifiers import CommitmentPolicy -from aws_encryption_sdk.materials_handlers import DecryptionMaterialsHandler, EncryptionMaterialsHandler -from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest -from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager -from aws_encryption_sdk.structures import EncryptedDataKey as Native_EncryptedDataKey - - -# TODO-MPL Should this implement interface..? seems like yes since it implements all of interface methods -class CMMHandler(CryptoMaterialsManager): - """ - In instances where encryption materials may be provided by either - an implementation of the native - `aws_encryption_sdk.materials_managers.base.CryptoMaterialsManager` - or an implementation of the MPL's - `aws_cryptographic_materialproviders.mpl.references.ICryptographicMaterialsManager`, - this provides the correct materials based on the underlying materials manager. - """ - - native_cmm: CryptoMaterialsManager - mpl_cmm: 'ICryptographicMaterialsManager' - - def _is_using_native_cmm(self): - return hasattr(self, "native_cmm") and not hasattr(self, "mpl_cmm") - - def __init__( - self, - cmm: 'CryptoMaterialsManager | ICryptographicMaterialsManager' - ): - """ - Create DecryptionMaterialsHandler. - :param cmm: Underlying cryptographic materials manager - """ - if isinstance(cmm, CryptoMaterialsManager): - self.native_cmm = cmm - elif isinstance(cmm, ICryptographicMaterialsManager): - self.mpl_cmm = cmm - else: - raise ValueError(f"Invalid CMM passed to CMMHandler. cmm: {cmm}") - - def get_encryption_materials( - self, - request: EncryptionMaterialsRequest - ) -> EncryptionMaterialsHandler: - """ - Returns an EncryptionMaterialsHandler for the configured CMM. - :param request: Request for encryption materials - """ - if self._is_using_native_cmm(): - return EncryptionMaterialsHandler(self.native_cmm.get_encryption_materials(request)) - else: - try: - mpl_input: GetEncryptionMaterialsInput = CMMHandler._native_to_mpl_get_encryption_materials( - request - ) - mpl_output: GetEncryptionMaterialsOutput = self.mpl_cmm.get_encryption_materials(mpl_input) - return EncryptionMaterialsHandler(mpl_output.encryption_materials) - except AwsCryptographicMaterialProvidersException as mpl_exception: - # Wrap MPL error into the ESDK error type - # so customers only have to catch ESDK error types. - raise AWSEncryptionSDKClientError(mpl_exception) - - @staticmethod - def _native_to_mpl_get_encryption_materials( - request: EncryptionMaterialsRequest - ) -> 'GetEncryptionMaterialsInput': - output: GetEncryptionMaterialsInput = GetEncryptionMaterialsInput( - encryption_context=request.encryption_context, - commitment_policy=CMMHandler._native_to_mpl_commmitment_policy( - request.commitment_policy - ), - max_plaintext_length=request.plaintext_length, - ) - return output - - @staticmethod - def _native_to_mpl_commmitment_policy( - native_commitment_policy: CommitmentPolicy - ) -> 'CommitmentPolicyESDK': - if native_commitment_policy == CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT: - return CommitmentPolicyESDK(value="FORBID_ENCRYPT_ALLOW_DECRYPT") - elif native_commitment_policy == CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT: - return CommitmentPolicyESDK(value="REQUIRE_ENCRYPT_ALLOW_DECRYPT") - elif native_commitment_policy == CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT: - return CommitmentPolicyESDK(value="REQUIRE_ENCRYPT_REQUIRE_DECRYPT") - else: - raise ValueError(f"Invalid native_commitment_policy: {native_commitment_policy}") - - def decrypt_materials( - self, - request: DecryptionMaterialsRequest - ) -> DecryptionMaterialsHandler: - """ - Returns a DecryptionMaterialsHandler for the configured CMM. - :param request: Request for decryption materials - """ - if self._is_using_native_cmm(): - return DecryptionMaterialsHandler(self.native_cmm.decrypt_materials(request)) - else: - try: - mpl_input: 'DecryptMaterialsInput' = \ - CMMHandler._create_mpl_decrypt_materials_input_from_request(request) - mpl_output: 'DecryptMaterialsOutput' = self.mpl_cmm.decrypt_materials(mpl_input) - return DecryptionMaterialsHandler(mpl_output.decryption_materials) - except AwsCryptographicMaterialProvidersException as mpl_exception: - # Wrap MPL error into the ESDK error type - # so customers only have to catch ESDK error types. - raise AWSEncryptionSDKClientError(mpl_exception) - - @staticmethod - def _native_algorithm_id_to_mpl_algorithm_id(native_algorithm_id: str) -> 'AlgorithmSuiteIdESDK': - # MPL algorithm suite ID = hexstr(native_algorithm_id) padded to 4 digits post-`x`. - return AlgorithmSuiteIdESDK(f"{native_algorithm_id:#0{6}x}") - - @staticmethod - def _create_mpl_decrypt_materials_input_from_request( - request: DecryptionMaterialsRequest - ) -> 'DecryptMaterialsInput': - key_blob_list: List[Native_EncryptedDataKey] = request.encrypted_data_keys - list_edks = [MPL_EncryptedDataKey( - key_provider_id=key_blob.key_provider.provider_id, - key_provider_info=key_blob.key_provider.key_info, - ciphertext=key_blob.encrypted_data_key, - ) for key_blob in key_blob_list] - output: DecryptMaterialsInput = DecryptMaterialsInput( - algorithm_suite_id=CMMHandler._native_algorithm_id_to_mpl_algorithm_id( - request.algorithm.algorithm_id - ), - commitment_policy=CMMHandler._native_to_mpl_commmitment_policy( - request.commitment_policy - ), - encrypted_data_keys=list_edks, - encryption_context=request.encryption_context, - ) - return output diff --git a/src/aws_encryption_sdk/materials_handlers.py b/src/aws_encryption_sdk/materials_handlers.py deleted file mode 100644 index 57f54144e..000000000 --- a/src/aws_encryption_sdk/materials_handlers.py +++ /dev/null @@ -1,170 +0,0 @@ -"""Provides encryption/decryption materials from an underlying materials provider.""" -# These dependencies are only loaded if you install the MPL. -try: - from aws_cryptographic_materialproviders.mpl.models import ( - DecryptionMaterials as MPL_DecryptionMaterials, - EncryptedDataKey as MPL_EncryptedDataKey, - EncryptionMaterials as MPL_EncryptionMaterials, - ) -except ImportError: - pass - -from typing import Dict, List, Set - -from aws_encryption_sdk.identifiers import Algorithm, AlgorithmSuite -from aws_encryption_sdk.materials_managers import ( - DecryptionMaterials as Native_DecryptionMaterials, - EncryptionMaterials as Native_EncryptionMaterials, -) -from aws_encryption_sdk.structures import DataKey, EncryptedDataKey as Native_EncryptedDataKey, MasterKeyInfo - - -def _mpl_algorithm_id_to_native_algorithm_id(mpl_algorithm_id: str): - # MPL algorithm suite ID == hex(native algorithm suite ID) - return int(mpl_algorithm_id, 16) - - -class EncryptionMaterialsHandler: - """ - In instances where encryption materials may be provided by either - the native `aws_encryption_sdk.materials_managers.EncryptionMaterials` - or the MPL's `aws_cryptographic_materialproviders.mpl.models.EncryptionMaterials`, - this provides the correct materials based on the configured materials provider. - """ - - native_materials: Native_EncryptionMaterials - mpl_materials: 'MPL_EncryptionMaterials' - - def __init__( - self, - materials: 'Native_EncryptionMaterials | MPL_EncryptionMaterials' - ): - """ - Create EncryptionMaterialsHandler. - :param materials: Underlying encryption materials - """ - if isinstance(materials, Native_EncryptionMaterials): - self.native_materials = materials - elif isinstance(materials, MPL_EncryptionMaterials): - self.mpl_materials = materials - else: - raise ValueError(f"Invalid EncryptionMaterials passed to EncryptionMaterialsHandler.\ - materials: {materials}") - - @property - def algorithm(self) -> Algorithm: - """Materials' native Algorithm.""" - if hasattr(self, "native_materials"): - return self.native_materials.algorithm - else: - return AlgorithmSuite.get_by_id( - _mpl_algorithm_id_to_native_algorithm_id( - self.mpl_materials.algorithm_suite.id.value - ) - ) - - @property - def encryption_context(self) -> Dict[str, str]: - """Materials' encryption context.""" - if hasattr(self, "native_materials"): - return self.native_materials.encryption_context - else: - return self.mpl_materials.encryption_context - - @property - def encrypted_data_keys(self) -> List[Native_EncryptedDataKey]: - """Materials' encrypted data keys.""" - if hasattr(self, "native_materials"): - return self.native_materials.encrypted_data_keys - else: - mpl_edk_list: List[MPL_EncryptedDataKey] = self.mpl_materials.encrypted_data_keys - key_blob_list: Set[Native_EncryptedDataKey] = {Native_EncryptedDataKey( - key_provider=MasterKeyInfo( - provider_id=mpl_edk.key_provider_id, - key_info=mpl_edk.key_provider_info, - ), - encrypted_data_key=mpl_edk.ciphertext, - ) for mpl_edk in mpl_edk_list} - return key_blob_list - - @property - def data_encryption_key(self) -> DataKey: - """Materials' data encryption key.""" - if hasattr(self, "native_materials"): - return self.native_materials.data_encryption_key - else: - # TODO-MPL This impl is probably wrong, but works for for now - # If this works for all features, great! Remove this comment before launch. - # Otherwise, fix the implementation. - mpl_dek = self.mpl_materials.plaintext_data_key - return DataKey( - # key_provider is unused, but the return type is DataKey - key_provider=MasterKeyInfo( - provider_id="", - key_info=b'' - ), - data_key=mpl_dek, - encrypted_data_key=b'', # No encrypted DEK - ) - - @property - def signing_key(self) -> bytes: - """Materials' signing key.""" - if hasattr(self, "native_materials"): - return self.native_materials.signing_key - else: - return self.mpl_materials.signing_key - - -class DecryptionMaterialsHandler: - """ - In instances where decryption materials may be provided by either - the native `aws_encryption_sdk.materials_managers.DecryptionMaterials` - or the MPL's `aws_cryptographic_materialproviders.mpl.models.DecryptionMaterials`, - this provides the correct materials based on the configured materials provider. - """ - - native_materials: Native_DecryptionMaterials - mpl_materials: 'MPL_DecryptionMaterials' - - def __init__( - self, - materials: 'Native_DecryptionMaterials | MPL_DecryptionMaterials' - ): - """ - Create DecryptionMaterialsHandler. - :param materials: Underlying decryption materials - """ - if isinstance(materials, Native_DecryptionMaterials): - self.native_materials = materials - elif isinstance(materials, MPL_DecryptionMaterials): - self.mpl_materials = materials - else: - raise ValueError(f"Invalid DecryptionMaterials passed to DecryptionMaterialsHandler.\ - materials: {materials}") - - @property - def data_key(self) -> DataKey: - """Materials' data key.""" - if hasattr(self, "native_materials"): - return self.native_materials.data_key - else: - # TODO-MPL This impl is probably wrong, but works for for now - # If this works for all features, great! Remove this comment before launch. - # Otherwise, fix the implementation. - return DataKey( - key_provider=MasterKeyInfo( - provider_id="", - key_info=b'' - ), - data_key=self.mpl_materials.plaintext_data_key, - encrypted_data_key=b'', - ) - - @property - def verification_key(self) -> bytes: - """Materials' verification key.""" - if hasattr(self, "native_materials"): - return self.native_materials.verification_key - else: - return self.mpl_materials.verification_key From e2e185844e0ac83f07dce346156ed9a7ab693275 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 15:09:10 -0800 Subject: [PATCH 045/184] fix --- src/aws_encryption_sdk/mpl/cmm_handler.py | 158 ++++++++++++++++ .../mpl/materials_handlers.py | 170 ++++++++++++++++++ 2 files changed, 328 insertions(+) create mode 100644 src/aws_encryption_sdk/mpl/cmm_handler.py create mode 100644 src/aws_encryption_sdk/mpl/materials_handlers.py diff --git a/src/aws_encryption_sdk/mpl/cmm_handler.py b/src/aws_encryption_sdk/mpl/cmm_handler.py new file mode 100644 index 000000000..5dfaab973 --- /dev/null +++ b/src/aws_encryption_sdk/mpl/cmm_handler.py @@ -0,0 +1,158 @@ +"""Retrieves encryption/decryption materials from an underlying materials provider.""" + +# These dependencies are only loaded if you install the MPL. +try: + # pylint seems to struggle with this conditional import + # pylint: disable=unused-import + from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException + from aws_cryptographic_materialproviders.mpl.models import ( + AlgorithmSuiteIdESDK, + CommitmentPolicyESDK, + DecryptMaterialsInput, + DecryptMaterialsOutput, + EncryptedDataKey as MPL_EncryptedDataKey, + GetEncryptionMaterialsInput, + GetEncryptionMaterialsOutput, + ) + from aws_cryptographic_materialproviders.mpl.references import ICryptographicMaterialsManager + +except ImportError: + pass + +from typing import List + +from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError +from aws_encryption_sdk.identifiers import CommitmentPolicy +from aws_encryption_sdk.mpl.materials_handlers import DecryptionMaterialsHandler, EncryptionMaterialsHandler +from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest +from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager +from aws_encryption_sdk.structures import EncryptedDataKey as Native_EncryptedDataKey + + +# TODO-MPL Should this implement interface...? seems like yes since it implements all of interface methods +class CMMHandler(CryptoMaterialsManager): + """ + In instances where encryption materials may be provided by either + an implementation of the native + `aws_encryption_sdk.materials_managers.base.CryptoMaterialsManager` + or an implementation of the MPL's + `aws_cryptographic_materialproviders.mpl.references.ICryptographicMaterialsManager`, + this provides the correct materials based on the underlying materials manager. + """ + + native_cmm: CryptoMaterialsManager + mpl_cmm: 'ICryptographicMaterialsManager' + + def _is_using_native_cmm(self): + return hasattr(self, "native_cmm") and not hasattr(self, "mpl_cmm") + + def __init__( + self, + cmm: 'CryptoMaterialsManager | ICryptographicMaterialsManager' + ): + """ + Create DecryptionMaterialsHandler. + :param cmm: Underlying cryptographic materials manager + """ + if isinstance(cmm, CryptoMaterialsManager): + self.native_cmm = cmm + elif isinstance(cmm, ICryptographicMaterialsManager): + self.mpl_cmm = cmm + else: + raise ValueError(f"Invalid CMM passed to CMMHandler. cmm: {cmm}") + + def get_encryption_materials( + self, + request: EncryptionMaterialsRequest + ) -> EncryptionMaterialsHandler: + """ + Returns an EncryptionMaterialsHandler for the configured CMM. + :param request: Request for encryption materials + """ + if self._is_using_native_cmm(): + return EncryptionMaterialsHandler(self.native_cmm.get_encryption_materials(request)) + else: + try: + mpl_input: GetEncryptionMaterialsInput = CMMHandler._native_to_mpl_get_encryption_materials( + request + ) + mpl_output: GetEncryptionMaterialsOutput = self.mpl_cmm.get_encryption_materials(mpl_input) + return EncryptionMaterialsHandler(mpl_output.encryption_materials) + except AwsCryptographicMaterialProvidersException as mpl_exception: + # Wrap MPL error into the ESDK error type + # so customers only have to catch ESDK error types. + raise AWSEncryptionSDKClientError(mpl_exception) + + @staticmethod + def _native_to_mpl_get_encryption_materials( + request: EncryptionMaterialsRequest + ) -> 'GetEncryptionMaterialsInput': + output: GetEncryptionMaterialsInput = GetEncryptionMaterialsInput( + encryption_context=request.encryption_context, + commitment_policy=CMMHandler._native_to_mpl_commmitment_policy( + request.commitment_policy + ), + max_plaintext_length=request.plaintext_length, + ) + return output + + @staticmethod + def _native_to_mpl_commmitment_policy( + native_commitment_policy: CommitmentPolicy + ) -> 'CommitmentPolicyESDK': + if native_commitment_policy == CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT: + return CommitmentPolicyESDK(value="FORBID_ENCRYPT_ALLOW_DECRYPT") + elif native_commitment_policy == CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT: + return CommitmentPolicyESDK(value="REQUIRE_ENCRYPT_ALLOW_DECRYPT") + elif native_commitment_policy == CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT: + return CommitmentPolicyESDK(value="REQUIRE_ENCRYPT_REQUIRE_DECRYPT") + else: + raise ValueError(f"Invalid native_commitment_policy: {native_commitment_policy}") + + def decrypt_materials( + self, + request: DecryptionMaterialsRequest + ) -> DecryptionMaterialsHandler: + """ + Returns a DecryptionMaterialsHandler for the configured CMM. + :param request: Request for decryption materials + """ + if self._is_using_native_cmm(): + return DecryptionMaterialsHandler(self.native_cmm.decrypt_materials(request)) + else: + try: + mpl_input: 'DecryptMaterialsInput' = \ + CMMHandler._create_mpl_decrypt_materials_input_from_request(request) + mpl_output: 'DecryptMaterialsOutput' = self.mpl_cmm.decrypt_materials(mpl_input) + return DecryptionMaterialsHandler(mpl_output.decryption_materials) + except AwsCryptographicMaterialProvidersException as mpl_exception: + # Wrap MPL error into the ESDK error type + # so customers only have to catch ESDK error types. + raise AWSEncryptionSDKClientError(mpl_exception) + + @staticmethod + def _native_algorithm_id_to_mpl_algorithm_id(native_algorithm_id: str) -> 'AlgorithmSuiteIdESDK': + # MPL algorithm suite ID = hexstr(native_algorithm_id) padded to 4 digits post-`x`. + return AlgorithmSuiteIdESDK(f"{native_algorithm_id:#0{6}x}") + + @staticmethod + def _create_mpl_decrypt_materials_input_from_request( + request: DecryptionMaterialsRequest + ) -> 'DecryptMaterialsInput': + key_blob_list: List[Native_EncryptedDataKey] = request.encrypted_data_keys + list_edks = [MPL_EncryptedDataKey( + key_provider_id=key_blob.key_provider.provider_id, + key_provider_info=key_blob.key_provider.key_info, + ciphertext=key_blob.encrypted_data_key, + ) for key_blob in key_blob_list] + output: DecryptMaterialsInput = DecryptMaterialsInput( + algorithm_suite_id=CMMHandler._native_algorithm_id_to_mpl_algorithm_id( + request.algorithm.algorithm_id + ), + commitment_policy=CMMHandler._native_to_mpl_commmitment_policy( + request.commitment_policy + ), + encrypted_data_keys=list_edks, + encryption_context=request.encryption_context, + ) + return output diff --git a/src/aws_encryption_sdk/mpl/materials_handlers.py b/src/aws_encryption_sdk/mpl/materials_handlers.py new file mode 100644 index 000000000..57f54144e --- /dev/null +++ b/src/aws_encryption_sdk/mpl/materials_handlers.py @@ -0,0 +1,170 @@ +"""Provides encryption/decryption materials from an underlying materials provider.""" +# These dependencies are only loaded if you install the MPL. +try: + from aws_cryptographic_materialproviders.mpl.models import ( + DecryptionMaterials as MPL_DecryptionMaterials, + EncryptedDataKey as MPL_EncryptedDataKey, + EncryptionMaterials as MPL_EncryptionMaterials, + ) +except ImportError: + pass + +from typing import Dict, List, Set + +from aws_encryption_sdk.identifiers import Algorithm, AlgorithmSuite +from aws_encryption_sdk.materials_managers import ( + DecryptionMaterials as Native_DecryptionMaterials, + EncryptionMaterials as Native_EncryptionMaterials, +) +from aws_encryption_sdk.structures import DataKey, EncryptedDataKey as Native_EncryptedDataKey, MasterKeyInfo + + +def _mpl_algorithm_id_to_native_algorithm_id(mpl_algorithm_id: str): + # MPL algorithm suite ID == hex(native algorithm suite ID) + return int(mpl_algorithm_id, 16) + + +class EncryptionMaterialsHandler: + """ + In instances where encryption materials may be provided by either + the native `aws_encryption_sdk.materials_managers.EncryptionMaterials` + or the MPL's `aws_cryptographic_materialproviders.mpl.models.EncryptionMaterials`, + this provides the correct materials based on the configured materials provider. + """ + + native_materials: Native_EncryptionMaterials + mpl_materials: 'MPL_EncryptionMaterials' + + def __init__( + self, + materials: 'Native_EncryptionMaterials | MPL_EncryptionMaterials' + ): + """ + Create EncryptionMaterialsHandler. + :param materials: Underlying encryption materials + """ + if isinstance(materials, Native_EncryptionMaterials): + self.native_materials = materials + elif isinstance(materials, MPL_EncryptionMaterials): + self.mpl_materials = materials + else: + raise ValueError(f"Invalid EncryptionMaterials passed to EncryptionMaterialsHandler.\ + materials: {materials}") + + @property + def algorithm(self) -> Algorithm: + """Materials' native Algorithm.""" + if hasattr(self, "native_materials"): + return self.native_materials.algorithm + else: + return AlgorithmSuite.get_by_id( + _mpl_algorithm_id_to_native_algorithm_id( + self.mpl_materials.algorithm_suite.id.value + ) + ) + + @property + def encryption_context(self) -> Dict[str, str]: + """Materials' encryption context.""" + if hasattr(self, "native_materials"): + return self.native_materials.encryption_context + else: + return self.mpl_materials.encryption_context + + @property + def encrypted_data_keys(self) -> List[Native_EncryptedDataKey]: + """Materials' encrypted data keys.""" + if hasattr(self, "native_materials"): + return self.native_materials.encrypted_data_keys + else: + mpl_edk_list: List[MPL_EncryptedDataKey] = self.mpl_materials.encrypted_data_keys + key_blob_list: Set[Native_EncryptedDataKey] = {Native_EncryptedDataKey( + key_provider=MasterKeyInfo( + provider_id=mpl_edk.key_provider_id, + key_info=mpl_edk.key_provider_info, + ), + encrypted_data_key=mpl_edk.ciphertext, + ) for mpl_edk in mpl_edk_list} + return key_blob_list + + @property + def data_encryption_key(self) -> DataKey: + """Materials' data encryption key.""" + if hasattr(self, "native_materials"): + return self.native_materials.data_encryption_key + else: + # TODO-MPL This impl is probably wrong, but works for for now + # If this works for all features, great! Remove this comment before launch. + # Otherwise, fix the implementation. + mpl_dek = self.mpl_materials.plaintext_data_key + return DataKey( + # key_provider is unused, but the return type is DataKey + key_provider=MasterKeyInfo( + provider_id="", + key_info=b'' + ), + data_key=mpl_dek, + encrypted_data_key=b'', # No encrypted DEK + ) + + @property + def signing_key(self) -> bytes: + """Materials' signing key.""" + if hasattr(self, "native_materials"): + return self.native_materials.signing_key + else: + return self.mpl_materials.signing_key + + +class DecryptionMaterialsHandler: + """ + In instances where decryption materials may be provided by either + the native `aws_encryption_sdk.materials_managers.DecryptionMaterials` + or the MPL's `aws_cryptographic_materialproviders.mpl.models.DecryptionMaterials`, + this provides the correct materials based on the configured materials provider. + """ + + native_materials: Native_DecryptionMaterials + mpl_materials: 'MPL_DecryptionMaterials' + + def __init__( + self, + materials: 'Native_DecryptionMaterials | MPL_DecryptionMaterials' + ): + """ + Create DecryptionMaterialsHandler. + :param materials: Underlying decryption materials + """ + if isinstance(materials, Native_DecryptionMaterials): + self.native_materials = materials + elif isinstance(materials, MPL_DecryptionMaterials): + self.mpl_materials = materials + else: + raise ValueError(f"Invalid DecryptionMaterials passed to DecryptionMaterialsHandler.\ + materials: {materials}") + + @property + def data_key(self) -> DataKey: + """Materials' data key.""" + if hasattr(self, "native_materials"): + return self.native_materials.data_key + else: + # TODO-MPL This impl is probably wrong, but works for for now + # If this works for all features, great! Remove this comment before launch. + # Otherwise, fix the implementation. + return DataKey( + key_provider=MasterKeyInfo( + provider_id="", + key_info=b'' + ), + data_key=self.mpl_materials.plaintext_data_key, + encrypted_data_key=b'', + ) + + @property + def verification_key(self) -> bytes: + """Materials' verification key.""" + if hasattr(self, "native_materials"): + return self.native_materials.verification_key + else: + return self.mpl_materials.verification_key From c790011ed67a16cfb6790ad56941c12099552bbe Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 15:12:52 -0800 Subject: [PATCH 046/184] mpl --- src/aws_encryption_sdk/streaming_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index afe9987ff..680784b1a 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -24,7 +24,7 @@ import six import aws_encryption_sdk.internal.utils -from aws_encryption_sdk.cmm_handler import CMMHandler +from aws_encryption_sdk.mpl.cmm_handler import CMMHandler from aws_encryption_sdk.exceptions import ( ActionNotAllowedError, AWSEncryptionSDKClientError, From 33ace5897bb532fbc15942c3317bee8e90215adc Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 15:18:27 -0800 Subject: [PATCH 047/184] fix --- src/aws_encryption_sdk/mpl/__init__.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/aws_encryption_sdk/mpl/__init__.py diff --git a/src/aws_encryption_sdk/mpl/__init__.py b/src/aws_encryption_sdk/mpl/__init__.py new file mode 100644 index 000000000..41497cc20 --- /dev/null +++ b/src/aws_encryption_sdk/mpl/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Modules related to the MPL.""" From cbf2cdf79d107bb371ed76646307f9e3ec038d9e Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 15:32:30 -0800 Subject: [PATCH 048/184] fix --- src/aws_encryption_sdk/mpl/cmm_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aws_encryption_sdk/mpl/cmm_handler.py b/src/aws_encryption_sdk/mpl/cmm_handler.py index 5dfaab973..2ccbcb5f3 100644 --- a/src/aws_encryption_sdk/mpl/cmm_handler.py +++ b/src/aws_encryption_sdk/mpl/cmm_handler.py @@ -29,7 +29,7 @@ from aws_encryption_sdk.structures import EncryptedDataKey as Native_EncryptedDataKey -# TODO-MPL Should this implement interface...? seems like yes since it implements all of interface methods +# TODO-MPL Should this implement interface..? seems like yes since it implements all of interface methods class CMMHandler(CryptoMaterialsManager): """ In instances where encryption materials may be provided by either From b2594771818c265c6f6ce31a56a4339b86840f38 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 7 Feb 2024 15:43:03 -0800 Subject: [PATCH 049/184] fix --- src/aws_encryption_sdk/mpl/cmm_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aws_encryption_sdk/mpl/cmm_handler.py b/src/aws_encryption_sdk/mpl/cmm_handler.py index 2ccbcb5f3..5dfaab973 100644 --- a/src/aws_encryption_sdk/mpl/cmm_handler.py +++ b/src/aws_encryption_sdk/mpl/cmm_handler.py @@ -29,7 +29,7 @@ from aws_encryption_sdk.structures import EncryptedDataKey as Native_EncryptedDataKey -# TODO-MPL Should this implement interface..? seems like yes since it implements all of interface methods +# TODO-MPL Should this implement interface...? seems like yes since it implements all of interface methods class CMMHandler(CryptoMaterialsManager): """ In instances where encryption materials may be provided by either From 9d52cf2ad37bf69fcc42bee1375365ea4e19bf15 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Thu, 8 Feb 2024 14:18:52 -0800 Subject: [PATCH 050/184] . --- src/aws_encryption_sdk/streaming_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 680784b1a..04d44334a 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -915,7 +915,7 @@ def _read_header(self): self.verifier = None else: # MPL verification key is NOT key bytes, it is bytes of the compressed point - # TODO-MPL: clean this up, least-privilege violation + # TODO-MPL: clean this up, least-privilege violation. if (isinstance(self.config.materials_manager, CMMHandler) and hasattr(self.config.materials_manager, "mpl_cmm")): self.verifier = Verifier.from_encoded_point( From 31b761616f7741293c8e6a33e2e9174c758f7c70 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 9 Feb 2024 09:45:18 -0800 Subject: [PATCH 051/184] debug tox mpl keystore env --- codebuild/py311/examples_mpl.yml | 6 ++++-- codebuild/py312/examples_mpl.yml | 5 +++-- tox.ini | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/codebuild/py311/examples_mpl.yml b/codebuild/py311/examples_mpl.yml index e29472507..b1afa5016 100644 --- a/codebuild/py311/examples_mpl.yml +++ b/codebuild/py311/examples_mpl.yml @@ -2,7 +2,7 @@ version: 0.2 env: variables: - TOXENV: "py311-examples-mpl" + # No TOXENV; examples using the MPL switch envs REGION: "us-west-2" AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: >- arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f @@ -20,4 +20,6 @@ phases: build: commands: - pip install "tox < 4.0" - - tox + - tox -e py311-examples-mpl + - tox -e py311-examples-mpl-keystore + diff --git a/codebuild/py312/examples_mpl.yml b/codebuild/py312/examples_mpl.yml index ff2168cd5..cf53585b4 100644 --- a/codebuild/py312/examples_mpl.yml +++ b/codebuild/py312/examples_mpl.yml @@ -2,7 +2,7 @@ version: 0.2 env: variables: - TOXENV: "py312-examples-mpl" + # No TOXENV; examples using the MPL switch envs REGION: "us-west-2" AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: >- arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f @@ -25,4 +25,5 @@ phases: - pip install --upgrade pip - pip install setuptools - pip install "tox < 4.0" - - tox + - tox -e py312-examples-mpl + - tox -e py312-examples-mpl-keystore diff --git a/tox.ini b/tox.ini index 4202979a4..d618cb030 100644 --- a/tox.ini +++ b/tox.ini @@ -75,8 +75,8 @@ commands = integ: {[testenv:base-command]commands} test/ -m integ accept: {[testenv:base-command]commands} test/ -m accept examples: {[testenv:base-command]commands} examples/test/ -m examples --ignore examples/test/keyrings/ - # append MPL examples to base examples command - examples-mpl: {[testenv:base-command]commands} examples/test/ -m examples + # MPL keyring examples require a special IAM role; run these separately under a separate set of permissions + examples-mpl-keyring: {[testenv:base-command]commands} examples/test/keyrings -m examples all: {[testenv:base-command]commands} test/ examples/test/ manual: {[testenv:base-command]commands} From 353b8cfc944b437bfb86f24390c0019693b5b76f Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 9 Feb 2024 09:51:54 -0800 Subject: [PATCH 052/184] debug tox mpl keystore env --- codebuild/py311/examples_mpl.yml | 2 +- codebuild/py312/examples_mpl.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codebuild/py311/examples_mpl.yml b/codebuild/py311/examples_mpl.yml index b1afa5016..c5b1e1872 100644 --- a/codebuild/py311/examples_mpl.yml +++ b/codebuild/py311/examples_mpl.yml @@ -21,5 +21,5 @@ phases: commands: - pip install "tox < 4.0" - tox -e py311-examples-mpl - - tox -e py311-examples-mpl-keystore + - tox -e py311-examples-mpl-keyring diff --git a/codebuild/py312/examples_mpl.yml b/codebuild/py312/examples_mpl.yml index cf53585b4..97a19ad50 100644 --- a/codebuild/py312/examples_mpl.yml +++ b/codebuild/py312/examples_mpl.yml @@ -26,4 +26,4 @@ phases: - pip install setuptools - pip install "tox < 4.0" - tox -e py312-examples-mpl - - tox -e py312-examples-mpl-keystore + - tox -e py312-examples-mpl-keyring From fb64d950a31a82169cbedaf289367dc3001a0c68 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 9 Feb 2024 09:57:15 -0800 Subject: [PATCH 053/184] debug tox mpl keystore env --- codebuild/py311/examples_mpl.yml | 8 ++++++++ codebuild/py312/examples_mpl.yml | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/codebuild/py311/examples_mpl.yml b/codebuild/py311/examples_mpl.yml index c5b1e1872..f7d705923 100644 --- a/codebuild/py311/examples_mpl.yml +++ b/codebuild/py311/examples_mpl.yml @@ -21,5 +21,13 @@ phases: commands: - pip install "tox < 4.0" - tox -e py311-examples-mpl + # Assume special role + - TMP_ROLE=$(aws sts assume-role --role-arn "arn:aws:iam::370957321024:role/GitHub-CI-Public-ESDK-Python-Role-us-west-2" --role-session-name "CB-Py311ExamplesMpl") + - export TMP_ROLE + - export AWS_ACCESS_KEY_ID=$(echo "${TMP_ROLE}" | jq -r '.Credentials.AccessKeyId') + - export AWS_SECRET_ACCESS_KEY=$(echo "${TMP_ROLE}" | jq -r '.Credentials.SecretAccessKey') + - export AWS_SESSION_TOKEN=$(echo "${TMP_ROLE}" | jq -r '.Credentials.SessionToken') + - aws sts get-caller-identity + # Run special role-specific examples - tox -e py311-examples-mpl-keyring diff --git a/codebuild/py312/examples_mpl.yml b/codebuild/py312/examples_mpl.yml index 97a19ad50..c95f606e6 100644 --- a/codebuild/py312/examples_mpl.yml +++ b/codebuild/py312/examples_mpl.yml @@ -26,4 +26,12 @@ phases: - pip install setuptools - pip install "tox < 4.0" - tox -e py312-examples-mpl + # Assume special role + - TMP_ROLE=$(aws sts assume-role --role-arn "arn:aws:iam::370957321024:role/GitHub-CI-Public-ESDK-Python-Role-us-west-2" --role-session-name "CB-Py311ExamplesMpl") + - export TMP_ROLE + - export AWS_ACCESS_KEY_ID=$(echo "${TMP_ROLE}" | jq -r '.Credentials.AccessKeyId') + - export AWS_SECRET_ACCESS_KEY=$(echo "${TMP_ROLE}" | jq -r '.Credentials.SecretAccessKey') + - export AWS_SESSION_TOKEN=$(echo "${TMP_ROLE}" | jq -r '.Credentials.SessionToken') + - aws sts get-caller-identity + # Run special role-specific examples - tox -e py312-examples-mpl-keyring From 916ae8e00b6195ed91dcbfa0448aa0e2c23c49bc Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 9 Feb 2024 10:19:26 -0800 Subject: [PATCH 054/184] debug tox mpl keystore env --- .../internal/crypto/authentication.py | 9 +++++++-- src/aws_encryption_sdk/streaming_client.py | 17 ++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/aws_encryption_sdk/internal/crypto/authentication.py b/src/aws_encryption_sdk/internal/crypto/authentication.py index f90ac77e0..a6446981e 100644 --- a/src/aws_encryption_sdk/internal/crypto/authentication.py +++ b/src/aws_encryption_sdk/internal/crypto/authentication.py @@ -68,7 +68,7 @@ class Signer(_PrehashingAuthenticator): """ @classmethod - def from_key_bytes(cls, algorithm, key_bytes): + def from_key_bytes(cls, algorithm, key_bytes, encoding=serialization.Encoding.DER): """Builds a `Signer` from an algorithm suite and a raw signing key. :param algorithm: Algorithm on which to base signer @@ -76,7 +76,12 @@ def from_key_bytes(cls, algorithm, key_bytes): :param bytes key_bytes: Raw signing key :rtype: aws_encryption_sdk.internal.crypto.Signer """ - key = serialization.load_der_private_key(data=key_bytes, password=None, backend=default_backend()) + if encoding == serialization.Encoding.DER: + key = serialization.load_der_private_key(data=key_bytes, password=None, backend=default_backend()) + elif serialization.Encoding.PEM: + key = serialization.load_pem_private_key(data=key_bytes, password=None, backend=default_backend()) + else: + raise ValueError(f"Unsupported encoding for Signer: {encoding}") return cls(algorithm, key) def key_bytes(self): diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 04d44334a..e514337f5 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -23,6 +23,8 @@ import attr import six +from cryptography.hazmat.primitives import serialization + import aws_encryption_sdk.internal.utils from aws_encryption_sdk.mpl.cmm_handler import CMMHandler from aws_encryption_sdk.exceptions import ( @@ -555,9 +557,18 @@ def _prep_message(self): if self._encryption_materials.signing_key is None: self.signer = None else: - self.signer = Signer.from_key_bytes( - algorithm=self._encryption_materials.algorithm, key_bytes=self._encryption_materials.signing_key - ) + # MPL verification key is NOT key bytes, it is bytes of the compressed point + # TODO-MPL: clean this up, least-privilege violation. + if (isinstance(self.config.materials_manager, CMMHandler) + and hasattr(self.config.materials_manager, "mpl_cmm")): + self.signer = Signer.from_key_bytes( + algorithm=self._encryption_materials.algorithm, key_bytes=self._encryption_materials.signing_key, + encoding=serialization.Encoding.PEM, + ) + else: + self.signer = Signer.from_key_bytes( + algorithm=self._encryption_materials.algorithm, key_bytes=self._encryption_materials.signing_key + ) aws_encryption_sdk.internal.utils.validate_frame_length( frame_length=self.config.frame_length, algorithm=self._encryption_materials.algorithm ) From 222b13549febbbfe144e7f865a89203b4f3789fd Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 9 Feb 2024 10:38:14 -0800 Subject: [PATCH 055/184] debug tox mpl keystore env --- codebuild/py311/examples_mpl.yml | 2 +- codebuild/py312/examples_mpl.yml | 2 +- tox.ini | 7 +++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/codebuild/py311/examples_mpl.yml b/codebuild/py311/examples_mpl.yml index f7d705923..f8f2a6a01 100644 --- a/codebuild/py311/examples_mpl.yml +++ b/codebuild/py311/examples_mpl.yml @@ -29,5 +29,5 @@ phases: - export AWS_SESSION_TOKEN=$(echo "${TMP_ROLE}" | jq -r '.Credentials.SessionToken') - aws sts get-caller-identity # Run special role-specific examples - - tox -e py311-examples-mpl-keyring + - tox -e py311-mplexamples-mpl diff --git a/codebuild/py312/examples_mpl.yml b/codebuild/py312/examples_mpl.yml index c95f606e6..ba0660024 100644 --- a/codebuild/py312/examples_mpl.yml +++ b/codebuild/py312/examples_mpl.yml @@ -34,4 +34,4 @@ phases: - export AWS_SESSION_TOKEN=$(echo "${TMP_ROLE}" | jq -r '.Credentials.SessionToken') - aws sts get-caller-identity # Run special role-specific examples - - tox -e py312-examples-mpl-keyring + - tox -e py312-mplexamples-mpl diff --git a/tox.ini b/tox.ini index d618cb030..61d65b11d 100644 --- a/tox.ini +++ b/tox.ini @@ -2,8 +2,11 @@ envlist = # <3.11: run all non-MPL tests py{37,38,39,310}-{local,integ,accept,examples}, - # >=3.11: run all MPL tests and non-MPL tests + # >=3.11: run all tests with MPL installed and without MPL installed + # The `-mpl` suffix tells tox to install the MPL py{311,312}-{local,integ,accept,examples}{,-mpl}, + # >=3.11: run ONLY the MPL-specific tests (requires a special IAM role) + py{311,312}-{mplexamples}-mpl nocmk, bandit, doc8, readme, docs, {flake8,pylint}{,-tests,-examples}, @@ -76,7 +79,7 @@ commands = accept: {[testenv:base-command]commands} test/ -m accept examples: {[testenv:base-command]commands} examples/test/ -m examples --ignore examples/test/keyrings/ # MPL keyring examples require a special IAM role; run these separately under a separate set of permissions - examples-mpl-keyring: {[testenv:base-command]commands} examples/test/keyrings -m examples + mplexamples: {[testenv:base-command]commands} examples/test/keyrings -m examples all: {[testenv:base-command]commands} test/ examples/test/ manual: {[testenv:base-command]commands} From cab60167b76eb142258b97f2493879ad0028e818 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 9 Feb 2024 16:03:12 -0800 Subject: [PATCH 056/184] some unit tests --- src/aws_encryption_sdk/mpl/cmm_handler.py | 2 + .../mpl/materials_handlers.py | 4 +- .../mpl/mpl_import_handler.py | 14 +++ src/aws_encryption_sdk/streaming_client.py | 16 +-- test/unit/mpl/README.md | 1 + test/unit/mpl/test_cmm_handler.py | 111 ++++++++++++++++++ test/unit/test_mpl_import_handler.py | 34 ++++++ test/unit/test_streaming_client_configs.py | 3 + test/unit/test_streaming_client_mpl_import.py | 42 +++++++ tox.ini | 6 +- 10 files changed, 221 insertions(+), 12 deletions(-) create mode 100644 src/aws_encryption_sdk/mpl/mpl_import_handler.py create mode 100644 test/unit/mpl/README.md create mode 100644 test/unit/mpl/test_cmm_handler.py create mode 100644 test/unit/test_mpl_import_handler.py create mode 100644 test/unit/test_streaming_client_mpl_import.py diff --git a/src/aws_encryption_sdk/mpl/cmm_handler.py b/src/aws_encryption_sdk/mpl/cmm_handler.py index 5dfaab973..1f6c9ff41 100644 --- a/src/aws_encryption_sdk/mpl/cmm_handler.py +++ b/src/aws_encryption_sdk/mpl/cmm_handler.py @@ -76,7 +76,9 @@ def get_encryption_materials( mpl_input: GetEncryptionMaterialsInput = CMMHandler._native_to_mpl_get_encryption_materials( request ) + print(f"mpl_input: {mpl_input}") mpl_output: GetEncryptionMaterialsOutput = self.mpl_cmm.get_encryption_materials(mpl_input) + print(f"mpl_output: {mpl_output}") return EncryptionMaterialsHandler(mpl_output.encryption_materials) except AwsCryptographicMaterialProvidersException as mpl_exception: # Wrap MPL error into the ESDK error type diff --git a/src/aws_encryption_sdk/mpl/materials_handlers.py b/src/aws_encryption_sdk/mpl/materials_handlers.py index 57f54144e..df5b57d53 100644 --- a/src/aws_encryption_sdk/mpl/materials_handlers.py +++ b/src/aws_encryption_sdk/mpl/materials_handlers.py @@ -48,8 +48,8 @@ def __init__( elif isinstance(materials, MPL_EncryptionMaterials): self.mpl_materials = materials else: - raise ValueError(f"Invalid EncryptionMaterials passed to EncryptionMaterialsHandler.\ - materials: {materials}") + raise ValueError("Invalid EncryptionMaterials passed to EncryptionMaterialsHandler. " \ + f"materials: {materials}") @property def algorithm(self) -> Algorithm: diff --git a/src/aws_encryption_sdk/mpl/mpl_import_handler.py b/src/aws_encryption_sdk/mpl/mpl_import_handler.py new file mode 100644 index 000000000..40669da1e --- /dev/null +++ b/src/aws_encryption_sdk/mpl/mpl_import_handler.py @@ -0,0 +1,14 @@ +def has_mpl(): + """Returns True if the aws_cryptographic_materialproviders library is installed, False otherwise.""" + try: + _import_mpl() + return True + except ImportError: + return False + +def _import_mpl(): + """Private wrapper for import to help with unit test coverage. + + This is not directly tested. + """ + import aws_cryptographic_materialproviders \ No newline at end of file diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index e514337f5..106121377 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -71,15 +71,15 @@ from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager from aws_encryption_sdk.structures import MessageHeader -try: +from aws_encryption_sdk.mpl import mpl_import_handler +if mpl_import_handler.has_mpl(): from aws_cryptographic_materialproviders.mpl.client import AwsCryptographicMaterialProviders from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig from aws_cryptographic_materialproviders.mpl.models import CreateDefaultCryptographicMaterialsManagerInput from aws_cryptographic_materialproviders.mpl.references import IKeyring - - HAS_MPL = True -except ImportError: - HAS_MPL = False + _HAS_MPL = True +else: + _HAS_MPL = False _LOGGER = logging.getLogger(__name__) @@ -146,7 +146,7 @@ class _ClientConfig(object): # pylint: disable=too-many-instance-attributes key_provider = attr.ib( hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(MasterKeyProvider)) ) - if HAS_MPL: + if _HAS_MPL: keyring = attr.ib( hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(IKeyring)) ) @@ -194,9 +194,9 @@ def _no_mpl_attrs_post_init(self): def __attrs_post_init__(self): """Normalize inputs to crypto material manager.""" - if HAS_MPL: + if _HAS_MPL: self._has_mpl_attrs_post_init() - elif not HAS_MPL: + else: self._no_mpl_attrs_post_init() diff --git a/test/unit/mpl/README.md b/test/unit/mpl/README.md new file mode 100644 index 000000000..839feb7a2 --- /dev/null +++ b/test/unit/mpl/README.md @@ -0,0 +1 @@ +Tests in this file REQUIRE the aws-cryptographic-material-providers module to be installed in order to run. \ No newline at end of file diff --git a/test/unit/mpl/test_cmm_handler.py b/test/unit/mpl/test_cmm_handler.py new file mode 100644 index 000000000..45b49ed91 --- /dev/null +++ b/test/unit/mpl/test_cmm_handler.py @@ -0,0 +1,111 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Test suite to verify the mpl_import_handler module handles importing the MPL correctly.""" +import pytest +from mock import MagicMock, patch + +from aws_encryption_sdk.mpl.cmm_handler import CMMHandler + +from aws_encryption_sdk.mpl.materials_handlers import DecryptionMaterialsHandler, EncryptionMaterialsHandler +from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest +from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager +from aws_cryptographic_materialproviders.mpl.references import ICryptographicMaterialsManager + +from aws_encryption_sdk.materials_managers import ( + DecryptionMaterials as Native_DecryptionMaterials, + EncryptionMaterials as Native_EncryptionMaterials, +) + +from aws_cryptographic_materialproviders.mpl.models import ( + AlgorithmSuiteIdESDK, + CommitmentPolicyESDK, + DecryptMaterialsInput, + DecryptMaterialsOutput, + EncryptedDataKey as MPL_EncryptedDataKey, + GetEncryptionMaterialsInput, + GetEncryptionMaterialsOutput, + ) + +from aws_cryptographic_materialproviders.mpl.models import ( + DecryptionMaterials as MPL_DecryptionMaterials, + EncryptedDataKey as MPL_EncryptedDataKey, + EncryptionMaterials as MPL_EncryptionMaterials, + ) + +mock_native_cmm = MagicMock(__class__=CryptoMaterialsManager) +mock_mpl_cmm = MagicMock(__class__=ICryptographicMaterialsManager) +mock_encryption_materials_request = MagicMock(__class__=EncryptionMaterialsRequest) +mock_encryption_materials_handler = MagicMock(__class__=EncryptionMaterialsHandler) +mock_native_encryption_materials = MagicMock(__class__=Native_EncryptionMaterials) +mock_mpl_encryption_materials = MagicMock(__class__=MPL_EncryptionMaterials) + +pytestmark = [pytest.mark.unit, pytest.mark.local] + + +def test_GIVEN_native_CMM_WHEN_create_CMMHandler_THEN_is_using_native_cmm_returns_True(): + cmm_handler = CMMHandler(cmm=mock_native_cmm) + assert cmm_handler._is_using_native_cmm() + + +def test_GIVEN_mpl_CMM_WHEN_create_CMMHandler_THEN_is_using_native_cmm_returns_False(): + cmm_handler = CMMHandler(cmm=mock_mpl_cmm) + assert not cmm_handler._is_using_native_cmm() + + +def test_GIVEN_unknown_CMM_WHEN_create_CMMHandler_THEN_raise_ValueError(): + with pytest.raises(ValueError): + CMMHandler(cmm="not a CMM") + + +@patch.object(mock_native_cmm, "get_encryption_materials") +def test_GIVEN_native_CMM_WHEN_get_encryption_materials_THEN_return_native_encryption_materials(mock_get_encryption_materials): + # Mock: native_cmm.get_encryption_materials returns mock native encryption materials + mock_get_encryption_materials.return_value = mock_native_encryption_materials + + cmm_handler = CMMHandler(cmm=mock_native_cmm) + test = cmm_handler.get_encryption_materials(mock_encryption_materials_request) + + # Verify cmm_handler returns EncryptionMaterialsHandler + assert isinstance(test, EncryptionMaterialsHandler) + # Verify returned EncryptionMaterialsHandler uses the output of `get_encryption_materials` + assert test.native_materials == mock_native_encryption_materials + # Verify we actually called `get_encryption_materials` + mock_native_cmm.get_encryption_materials.assert_called_once_with(mock_encryption_materials_request) + + +@patch.object(mock_mpl_cmm, "get_encryption_materials") +@patch("aws_encryption_sdk.mpl.cmm_handler.CMMHandler._native_to_mpl_get_encryption_materials") +def test_GIVEN_mpl_CMM_WHEN_get_encryption_materials_THEN_return_mpl_encryption_materials( + mock_native_to_mpl_get_encryption_materials, + mock_get_encryption_materials, + +): + # Mock: mpl_cmm.get_encryption_materials returns mock MPL encryption materials + mock_get_encryption_materials_output = MagicMock(__class__=GetEncryptionMaterialsOutput) + mock_get_encryption_materials_output.encryption_materials = mock_mpl_encryption_materials + mock_get_encryption_materials.return_value = mock_get_encryption_materials_output + + # Mock: CMMHandler._native_to_mpl_get_encryption_materials creates a GetEncryptionMaterialsInput + mock_get_encryption_materials_input = MagicMock(__class__=GetEncryptionMaterialsInput) + mock_native_to_mpl_get_encryption_materials.return_value = mock_get_encryption_materials_input + + cmm_handler = CMMHandler(cmm=mock_mpl_cmm) + test = cmm_handler.get_encryption_materials(mock_encryption_materials_request) + + # Verify cmm_handler returns EncryptionMaterialsHandler + assert isinstance(test, EncryptionMaterialsHandler) + # Verify returned EncryptionMaterialsHandler uses the output of `get_encryption_materials` + assert test.mpl_materials == mock_mpl_encryption_materials + # Verify we actually called `get_encryption_materials` + mock_mpl_cmm.get_encryption_materials.assert_called_once_with(mock_get_encryption_materials_input) + diff --git a/test/unit/test_mpl_import_handler.py b/test/unit/test_mpl_import_handler.py new file mode 100644 index 000000000..c17c358b4 --- /dev/null +++ b/test/unit/test_mpl_import_handler.py @@ -0,0 +1,34 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Test suite to verify the mpl_import_handler module handles importing the MPL correctly.""" +import pytest +from mock import patch + +from aws_encryption_sdk.mpl import mpl_import_handler + +pytestmark = [pytest.mark.unit, pytest.mark.local] + +@patch("aws_encryption_sdk.mpl.mpl_import_handler._import_mpl") +def test_GIVEN_import_mpl_succeeds_WHEN_call_has_mpl_THEN_return_True(import_mock): + # Mock a successful import of `aws_cryptographic_material_providers` + import_mock.return_value = None # No exception means successful import + + assert mpl_import_handler.has_mpl() is True + +@patch("aws_encryption_sdk.mpl.mpl_import_handler._import_mpl") +def test_GIVEN_import_mpl_fails_WHEN_call_has_mpl_THEN_return_False(import_mock): + # Mock not having a `aws_cryptographic_material_providers` module, + # even if it is installed in the Python environment + import_mock.side_effect = ImportError() + + assert not mpl_import_handler.has_mpl() \ No newline at end of file diff --git a/test/unit/test_streaming_client_configs.py b/test/unit/test_streaming_client_configs.py index 426f8f85f..80b7fdb28 100644 --- a/test/unit/test_streaming_client_configs.py +++ b/test/unit/test_streaming_client_configs.py @@ -154,3 +154,6 @@ def test_client_config_converts(kwargs, stream_type): assert isinstance(test.source, stream_type) if test.key_provider is not None: assert isinstance(test.materials_manager, DefaultCryptoMaterialsManager) + +def test_GIVEN_has_mpl_WHEN_import_THEN_imports_mpl_modules(): + \ No newline at end of file diff --git a/test/unit/test_streaming_client_mpl_import.py b/test/unit/test_streaming_client_mpl_import.py new file mode 100644 index 000000000..8ce016caf --- /dev/null +++ b/test/unit/test_streaming_client_mpl_import.py @@ -0,0 +1,42 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Unit test suite to validate aws_encryption_sdk.streaming_client MPL import logic.""" +import io + +import pytest +from mock import patch +from importlib import reload + +import aws_encryption_sdk.streaming_client + +pytestmark = [pytest.mark.unit, pytest.mark.local] + +@patch.object(aws_encryption_sdk.streaming_client.mpl_import_handler, "has_mpl") +def test_GIVEN_has_mpl_returns_True_WHEN_import_streaming_client_THEN_imports_mpl_modules(has_mpl_mock): + has_mpl_mock.return_value = True + + # Reload module given the mock + reload(aws_encryption_sdk.streaming_client) + + assert hasattr(aws_encryption_sdk.streaming_client, "_HAS_MPL") + assert aws_encryption_sdk.streaming_client._HAS_MPL is True + +@patch.object(aws_encryption_sdk.streaming_client.mpl_import_handler, "has_mpl") +def test_GIVEN_has_mpl_returns_False_WHEN_import_streaming_client_THEN_does_not_import_mpl_modules(has_mpl_mock): + has_mpl_mock.return_value = False + + # Reload module given the mock + reload(aws_encryption_sdk.streaming_client) + + assert hasattr(aws_encryption_sdk.streaming_client, "_HAS_MPL") + assert aws_encryption_sdk.streaming_client._HAS_MPL is False \ No newline at end of file diff --git a/tox.ini b/tox.ini index 61d65b11d..3cc7017e1 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,8 @@ envlist = # The `-mpl` suffix tells tox to install the MPL py{311,312}-{local,integ,accept,examples}{,-mpl}, # >=3.11: run ONLY the MPL-specific tests (requires a special IAM role) - py{311,312}-{mplexamples}-mpl + # the extra `-mpl` suffix tells tox to install the MPL + py{311,312}-mpl{local,examples}-mpl nocmk, bandit, doc8, readme, docs, {flake8,pylint}{,-tests,-examples}, @@ -74,7 +75,8 @@ deps = # install the MPL if in environment mpl: -rrequirements_mpl.txt commands = - local: {[testenv:base-command]commands} test/ -m local + local: {[testenv:base-command]commands} test/ -m local --ignore test/mpl/ + local: {[testenv:base-command]commands} test/mpl/ -m local integ: {[testenv:base-command]commands} test/ -m integ accept: {[testenv:base-command]commands} test/ -m accept examples: {[testenv:base-command]commands} examples/test/ -m examples --ignore examples/test/keyrings/ From a7416b18f9afa367e5053f0bd936735da7ec6e01 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 13 Feb 2024 09:26:04 -0800 Subject: [PATCH 057/184] add mpl coverage --- buildspec.yml | 2 ++ codebuild/coverage/coverage_mpl.yml | 14 ++++++++++++++ tox.ini | 6 +++++- 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 codebuild/coverage/coverage_mpl.yml diff --git a/buildspec.yml b/buildspec.yml index c718c3df5..3d70c144d 100644 --- a/buildspec.yml +++ b/buildspec.yml @@ -106,6 +106,8 @@ batch: - identifier: code_coverage buildspec: codebuild/coverage/coverage.yml + - identifier: code_coverage_mpl + buildspec: codebuild/coverage/coverage_mpl.yml - identifier: compliance buildspec: codebuild/compliance/compliance.yml diff --git a/codebuild/coverage/coverage_mpl.yml b/codebuild/coverage/coverage_mpl.yml new file mode 100644 index 000000000..5dcc65382 --- /dev/null +++ b/codebuild/coverage/coverage_mpl.yml @@ -0,0 +1,14 @@ +version: 0.2 + +env: + variables: + TOXENV: "mplcoverage-mpl" + +phases: + install: + runtime-versions: + python: latest + build: + commands: + - pip install "tox < 4.0" + - tox diff --git a/tox.ini b/tox.ini index 3cc7017e1..7aacb047a 100644 --- a/tox.ini +++ b/tox.ini @@ -6,6 +6,8 @@ envlist = # The `-mpl` suffix tells tox to install the MPL py{311,312}-{local,integ,accept,examples}{,-mpl}, # >=3.11: run ONLY the MPL-specific tests (requires a special IAM role) + # the `mpl` prefix runs only MPL-specific tests + # (non-MPL-specific tests are run from the line above) # the extra `-mpl` suffix tells tox to install the MPL py{311,312}-mpl{local,examples}-mpl nocmk, @@ -87,7 +89,9 @@ commands = # Run code coverage on the unit tests [testenv:coverage] -commands = {[testenv:base-command]commands} --cov aws_encryption_sdk test/ -m local +commands = {[testenv:base-command]commands} --cov aws_encryption_sdk test/ -m local --ignore test/unit/mpl/ +[testenv:mplcoverage] +commands = {[testenv:base-command]commands} --cov aws_encryption_sdk test/unit/mpl/ -m local # Verify that local tests work without environment variables present [testenv:nocmk] From 7b3dc5fc303afad5b0d3519b12de833135e3d326 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 13 Feb 2024 09:30:19 -0800 Subject: [PATCH 058/184] . --- tox.ini | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tox.ini b/tox.ini index 7aacb047a..194b4d412 100644 --- a/tox.ini +++ b/tox.ini @@ -77,14 +77,14 @@ deps = # install the MPL if in environment mpl: -rrequirements_mpl.txt commands = - local: {[testenv:base-command]commands} test/ -m local --ignore test/mpl/ - local: {[testenv:base-command]commands} test/mpl/ -m local - integ: {[testenv:base-command]commands} test/ -m integ - accept: {[testenv:base-command]commands} test/ -m accept + local: {[testenv:base-command]commands} test/ -m local --ignore test/unit/mpl/ + mpllocal: {[testenv:base-command]commands} test/unit/mpl/ -m local + integ: {[testenv:base-command]commands} test/ -m integ --ignore test/unit/mpl/ + accept: {[testenv:base-command]commands} test/ -m accept --ignore test/unit/mpl/ examples: {[testenv:base-command]commands} examples/test/ -m examples --ignore examples/test/keyrings/ # MPL keyring examples require a special IAM role; run these separately under a separate set of permissions mplexamples: {[testenv:base-command]commands} examples/test/keyrings -m examples - all: {[testenv:base-command]commands} test/ examples/test/ + all: {[testenv:base-command]commands} test/ examples/test/ --ignore test/unit/mpl/ manual: {[testenv:base-command]commands} # Run code coverage on the unit tests From 7a5e4eb9e6055576759d45950bd66ace921d65aa Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 13 Feb 2024 09:33:38 -0800 Subject: [PATCH 059/184] . --- test/unit/test_streaming_client_configs.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/unit/test_streaming_client_configs.py b/test/unit/test_streaming_client_configs.py index 80b7fdb28..426f8f85f 100644 --- a/test/unit/test_streaming_client_configs.py +++ b/test/unit/test_streaming_client_configs.py @@ -154,6 +154,3 @@ def test_client_config_converts(kwargs, stream_type): assert isinstance(test.source, stream_type) if test.key_provider is not None: assert isinstance(test.materials_manager, DefaultCryptoMaterialsManager) - -def test_GIVEN_has_mpl_WHEN_import_THEN_imports_mpl_modules(): - \ No newline at end of file From 0649995f59e6b3de7ad68d5ff7497a44dc021c31 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 13 Feb 2024 15:46:47 -0800 Subject: [PATCH 060/184] mock imports --- src/aws_encryption_sdk/streaming_client.py | 2 +- test/unit/test_streaming_client_mpl_import.py | 11 +++++++++++ test/unit/test_streaming_client_stream_decryptor.py | 2 +- tox.ini | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 106121377..044626c7f 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -1095,7 +1095,7 @@ def close(self): """Closes out the stream.""" _LOGGER.debug("Closing stream") if not hasattr(self, "footer"): - raise SerializationError("Footer not read, message may be corrupted or data key may be incorrect") + raise SerializationError("Footer not read") super(StreamDecryptor, self).close() diff --git a/test/unit/test_streaming_client_mpl_import.py b/test/unit/test_streaming_client_mpl_import.py index 8ce016caf..f71f337b7 100644 --- a/test/unit/test_streaming_client_mpl_import.py +++ b/test/unit/test_streaming_client_mpl_import.py @@ -17,6 +17,8 @@ from mock import patch from importlib import reload +from mock import Mock + import aws_encryption_sdk.streaming_client pytestmark = [pytest.mark.unit, pytest.mark.local] @@ -25,6 +27,15 @@ def test_GIVEN_has_mpl_returns_True_WHEN_import_streaming_client_THEN_imports_mpl_modules(has_mpl_mock): has_mpl_mock.return_value = True + # Mock any imports used in the try/catch block + # If more imports are added there, then this needs to be expanded + # This unit test should pass even if the MPL is not installed + import sys + sys.modules['aws_cryptographic_materialproviders.mpl.client'] = Mock() + sys.modules['aws_cryptographic_materialproviders.mpl.config'] = Mock() + sys.modules['aws_cryptographic_materialproviders.mpl.models'] = Mock() + sys.modules['aws_cryptographic_materialproviders.mpl.references'] = Mock() + # Reload module given the mock reload(aws_encryption_sdk.streaming_client) diff --git a/test/unit/test_streaming_client_stream_decryptor.py b/test/unit/test_streaming_client_stream_decryptor.py index 94b22b092..157755094 100644 --- a/test/unit/test_streaming_client_stream_decryptor.py +++ b/test/unit/test_streaming_client_stream_decryptor.py @@ -767,4 +767,4 @@ def test_close_no_footer(self, mock_close): ) with pytest.raises(SerializationError) as excinfo: test_decryptor.close() - excinfo.match("Footer not read, message may be corrupted or data key may be incorrect") + excinfo.match("Footer not read") diff --git a/tox.ini b/tox.ini index 194b4d412..9e2d95477 100644 --- a/tox.ini +++ b/tox.ini @@ -90,7 +90,7 @@ commands = # Run code coverage on the unit tests [testenv:coverage] commands = {[testenv:base-command]commands} --cov aws_encryption_sdk test/ -m local --ignore test/unit/mpl/ -[testenv:mplcoverage] +[testenv:mplcoverage-mpl] commands = {[testenv:base-command]commands} --cov aws_encryption_sdk test/unit/mpl/ -m local # Verify that local tests work without environment variables present From 6691fa2d81c211e7a97be9450f028ca89b47bcb6 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 20 Feb 2024 15:19:17 -0800 Subject: [PATCH 061/184] refactor, fix --- .github/workflows/ci_static-analysis.yaml | 14 +++++- examples/src/keyrings/hierarchical_keyring.py | 50 ++++++++++--------- .../{ => internal}/mpl/__init__.py | 0 .../{ => internal}/mpl/cmm_handler.py | 2 +- .../{ => internal}/mpl/materials_handlers.py | 4 +- .../internal/mpl/mpl_import_handler.py | 21 ++++++++ .../mpl/mpl_import_handler.py | 14 ------ src/aws_encryption_sdk/streaming_client.py | 6 +-- test/unit/mpl/test_cmm_handler.py | 44 ++++++---------- test/unit/test_mpl_import_handler.py | 10 ++-- test/unit/test_streaming_client_mpl_import.py | 12 ++--- tox.ini | 22 +++++--- 12 files changed, 108 insertions(+), 91 deletions(-) rename src/aws_encryption_sdk/{ => internal}/mpl/__init__.py (100%) rename src/aws_encryption_sdk/{ => internal}/mpl/cmm_handler.py (98%) rename src/aws_encryption_sdk/{ => internal}/mpl/materials_handlers.py (98%) create mode 100644 src/aws_encryption_sdk/internal/mpl/mpl_import_handler.py delete mode 100644 src/aws_encryption_sdk/mpl/mpl_import_handler.py diff --git a/.github/workflows/ci_static-analysis.yaml b/.github/workflows/ci_static-analysis.yaml index 0093ae9a9..85d7f4a62 100644 --- a/.github/workflows/ci_static-analysis.yaml +++ b/.github/workflows/ci_static-analysis.yaml @@ -13,6 +13,9 @@ jobs: strategy: fail-fast: false matrix: + python: + - 3.8 + - 3.11 category: - bandit - doc8 @@ -26,15 +29,22 @@ jobs: - pylint-examples - black-check - isort-check + optional_mpl_dependency: + - "" + - -mpl + exclude: + # MPL is not supported on <3.11 + - python: 3.8 + optional_mpl_dependency: -mpl steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: ${{ matrix.python }} - run: | python -m pip install --upgrade pip pip install --upgrade -r dev_requirements/ci-requirements.txt - name: run test env: - TOXENV: ${{ matrix.category }} + TOXENV: ${{ matrix.category }}${{ matrix.optional_mpl_dependency }} run: tox -- -vv diff --git a/examples/src/keyrings/hierarchical_keyring.py b/examples/src/keyrings/hierarchical_keyring.py index a99728b6e..21108d9a0 100644 --- a/examples/src/keyrings/hierarchical_keyring.py +++ b/examples/src/keyrings/hierarchical_keyring.py @@ -4,23 +4,28 @@ import sys import boto3 -from aws_cryptographic_materialproviders.keystore.client import KeyStore -from aws_cryptographic_materialproviders.keystore.config import KeyStoreConfig -from aws_cryptographic_materialproviders.keystore.models import CreateKeyInput, KMSConfigurationKmsKeyArn -from aws_cryptographic_materialproviders.mpl.client import AwsCryptographicMaterialProviders -from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig -from aws_cryptographic_materialproviders.mpl.models import ( - CacheTypeDefault, - CreateAwsKmsHierarchicalKeyringInput, - DefaultCache, - GetBranchKeyIdInput, - GetBranchKeyIdOutput, -) -from aws_cryptographic_materialproviders.mpl.references import IBranchKeyIdSupplier, IKeyring +from typing import Dict import aws_encryption_sdk from aws_encryption_sdk import CommitmentPolicy from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError +from aws_encryption_sdk.internal.mpl import mpl_import_handler + +if mpl_import_handler.has_mpl(): + # noqa pylint: disable=import-error + from aws_cryptographic_materialproviders.keystore.client import KeyStore + from aws_cryptographic_materialproviders.keystore.config import KeyStoreConfig + from aws_cryptographic_materialproviders.keystore.models import CreateKeyInput, KMSConfigurationKmsKeyArn + from aws_cryptographic_materialproviders.mpl.client import AwsCryptographicMaterialProviders + from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig + from aws_cryptographic_materialproviders.mpl.models import ( + CacheTypeDefault, + CreateAwsKmsHierarchicalKeyringInput, + DefaultCache, + GetBranchKeyIdInput, + GetBranchKeyIdOutput, + ) + from aws_cryptographic_materialproviders.mpl.references import IBranchKeyIdSupplier, IKeyring module_root_dir = '/'.join(__file__.split("/")[:-1]) @@ -71,6 +76,7 @@ def encrypt_and_decrypt_with_keyring( branch_key_id_B: str = keystore.create_key(input=CreateKeyInput()).branch_key_identifier class ExampleBranchKeyIdSupplier(IBranchKeyIdSupplier): + """Example implementation of a branch key ID supplier.""" branch_key_id_for_tenant_A: str branch_key_id_for_tenant_B: str @@ -80,9 +86,11 @@ def __init__(self, tenant_1_id, tenant_2_id): def get_branch_key_id( self, - input: GetBranchKeyIdInput + # Change this to `native_input` + input: GetBranchKeyIdInput # noqa pylint: disable=redefined-builtin ) -> GetBranchKeyIdOutput: - encryption_context: dict[str, str] = input.encryption_context + """Returns branch key ID from the tenant ID in input's encryption context.""" + encryption_context: Dict[str, str] = input.encryption_context if b"tenant" not in encryption_context: raise ValueError("EncryptionContext invalid, does not contain expected tenant key value pair.") @@ -128,7 +136,7 @@ def get_branch_key_id( # The Branch Key Id supplier uses the encryption context to determine which branch key id will # be used to encrypt data. # Create encryption context for TenantA - encryption_context_A: dict[str, str] = { + encryption_context_A: Dict[str, str] = { "tenant": "TenantA", "encryption": "context", "is not": "secret", @@ -138,7 +146,7 @@ def get_branch_key_id( } # Create encryption context for TenantB - encryption_context_B: dict[str, str] = { + encryption_context_B: Dict[str, str] = { "tenant": "TenantB", "encryption": "context", "is not": "secret", @@ -191,8 +199,6 @@ def get_branch_key_id( input=keyring_input_B ) - # TODO: Run the decrypt, get expected exception type - # This should fail try: client.decrypt( source=ciphertext_A, @@ -201,7 +207,7 @@ def get_branch_key_id( except AWSEncryptionSDKClientError: pass - # # This should fail + # This should fail try: client.decrypt( source=ciphertext_B, @@ -220,6 +226,4 @@ def get_branch_key_id( source=ciphertext_B, keyring=hierarchical_keyring_B ) - assert plaintext_bytes_B == EXAMPLE_DATA - -# Also, a thread-safe example ig + assert plaintext_bytes_B == EXAMPLE_DATA \ No newline at end of file diff --git a/src/aws_encryption_sdk/mpl/__init__.py b/src/aws_encryption_sdk/internal/mpl/__init__.py similarity index 100% rename from src/aws_encryption_sdk/mpl/__init__.py rename to src/aws_encryption_sdk/internal/mpl/__init__.py diff --git a/src/aws_encryption_sdk/mpl/cmm_handler.py b/src/aws_encryption_sdk/internal/mpl/cmm_handler.py similarity index 98% rename from src/aws_encryption_sdk/mpl/cmm_handler.py rename to src/aws_encryption_sdk/internal/mpl/cmm_handler.py index 1f6c9ff41..c285afa04 100644 --- a/src/aws_encryption_sdk/mpl/cmm_handler.py +++ b/src/aws_encryption_sdk/internal/mpl/cmm_handler.py @@ -23,7 +23,7 @@ from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError from aws_encryption_sdk.identifiers import CommitmentPolicy -from aws_encryption_sdk.mpl.materials_handlers import DecryptionMaterialsHandler, EncryptionMaterialsHandler +from aws_encryption_sdk.internal.mpl.materials_handlers import DecryptionMaterialsHandler, EncryptionMaterialsHandler from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager from aws_encryption_sdk.structures import EncryptedDataKey as Native_EncryptedDataKey diff --git a/src/aws_encryption_sdk/mpl/materials_handlers.py b/src/aws_encryption_sdk/internal/mpl/materials_handlers.py similarity index 98% rename from src/aws_encryption_sdk/mpl/materials_handlers.py rename to src/aws_encryption_sdk/internal/mpl/materials_handlers.py index df5b57d53..bf32c2718 100644 --- a/src/aws_encryption_sdk/mpl/materials_handlers.py +++ b/src/aws_encryption_sdk/internal/mpl/materials_handlers.py @@ -48,8 +48,8 @@ def __init__( elif isinstance(materials, MPL_EncryptionMaterials): self.mpl_materials = materials else: - raise ValueError("Invalid EncryptionMaterials passed to EncryptionMaterialsHandler. " \ - f"materials: {materials}") + raise ValueError("Invalid EncryptionMaterials passed to EncryptionMaterialsHandler. " + f"materials: {materials}") @property def algorithm(self) -> Algorithm: diff --git a/src/aws_encryption_sdk/internal/mpl/mpl_import_handler.py b/src/aws_encryption_sdk/internal/mpl/mpl_import_handler.py new file mode 100644 index 000000000..55319bc43 --- /dev/null +++ b/src/aws_encryption_sdk/internal/mpl/mpl_import_handler.py @@ -0,0 +1,21 @@ +"""Detects whether the MPL is installed for use by internal ESDK code. +External customers should not need to interact with this. +""" + + +def has_mpl(): + """Returns True if the aws-cryptographic-material-providers library is installed, False otherwise.""" + try: + _import_mpl() + return True + except ImportError: + return False + + +def _import_mpl(): + """Private wrapper for import. + This only exists to help with unit test coverage. + This is not directly tested. + """ + # noqa pylint:disable=unused-import,import-outside-toplevel,import-error + import aws_cryptographic_materialproviders diff --git a/src/aws_encryption_sdk/mpl/mpl_import_handler.py b/src/aws_encryption_sdk/mpl/mpl_import_handler.py deleted file mode 100644 index 40669da1e..000000000 --- a/src/aws_encryption_sdk/mpl/mpl_import_handler.py +++ /dev/null @@ -1,14 +0,0 @@ -def has_mpl(): - """Returns True if the aws_cryptographic_materialproviders library is installed, False otherwise.""" - try: - _import_mpl() - return True - except ImportError: - return False - -def _import_mpl(): - """Private wrapper for import to help with unit test coverage. - - This is not directly tested. - """ - import aws_cryptographic_materialproviders \ No newline at end of file diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 044626c7f..c4c15559b 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -22,11 +22,9 @@ import attr import six - from cryptography.hazmat.primitives import serialization import aws_encryption_sdk.internal.utils -from aws_encryption_sdk.mpl.cmm_handler import CMMHandler from aws_encryption_sdk.exceptions import ( ActionNotAllowedError, AWSEncryptionSDKClientError, @@ -60,6 +58,8 @@ serialize_non_framed_close, serialize_non_framed_open, ) +from aws_encryption_sdk.internal.mpl import mpl_import_handler +from aws_encryption_sdk.internal.mpl.cmm_handler import CMMHandler from aws_encryption_sdk.internal.utils.commitment import ( validate_commitment_policy_on_decrypt, validate_commitment_policy_on_encrypt, @@ -71,8 +71,8 @@ from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager from aws_encryption_sdk.structures import MessageHeader -from aws_encryption_sdk.mpl import mpl_import_handler if mpl_import_handler.has_mpl(): + # noqa pylint: disable=import-error from aws_cryptographic_materialproviders.mpl.client import AwsCryptographicMaterialProviders from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig from aws_cryptographic_materialproviders.mpl.models import CreateDefaultCryptographicMaterialsManagerInput diff --git a/test/unit/mpl/test_cmm_handler.py b/test/unit/mpl/test_cmm_handler.py index 45b49ed91..343ac514b 100644 --- a/test/unit/mpl/test_cmm_handler.py +++ b/test/unit/mpl/test_cmm_handler.py @@ -12,35 +12,21 @@ # language governing permissions and limitations under the License. """Test suite to verify the mpl_import_handler module handles importing the MPL correctly.""" import pytest -from mock import MagicMock, patch - -from aws_encryption_sdk.mpl.cmm_handler import CMMHandler - -from aws_encryption_sdk.mpl.materials_handlers import DecryptionMaterialsHandler, EncryptionMaterialsHandler -from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest -from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager +from aws_cryptographic_materialproviders.mpl.models import ( + EncryptionMaterials as MPL_EncryptionMaterials, + GetEncryptionMaterialsInput, + GetEncryptionMaterialsOutput, +) from aws_cryptographic_materialproviders.mpl.references import ICryptographicMaterialsManager +from mock import MagicMock, patch +from aws_encryption_sdk.internal.mpl.cmm_handler import CMMHandler +from aws_encryption_sdk.internal.mpl.materials_handlers import EncryptionMaterialsHandler from aws_encryption_sdk.materials_managers import ( - DecryptionMaterials as Native_DecryptionMaterials, EncryptionMaterials as Native_EncryptionMaterials, + EncryptionMaterialsRequest, ) - -from aws_cryptographic_materialproviders.mpl.models import ( - AlgorithmSuiteIdESDK, - CommitmentPolicyESDK, - DecryptMaterialsInput, - DecryptMaterialsOutput, - EncryptedDataKey as MPL_EncryptedDataKey, - GetEncryptionMaterialsInput, - GetEncryptionMaterialsOutput, - ) - -from aws_cryptographic_materialproviders.mpl.models import ( - DecryptionMaterials as MPL_DecryptionMaterials, - EncryptedDataKey as MPL_EncryptedDataKey, - EncryptionMaterials as MPL_EncryptionMaterials, - ) +from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager mock_native_cmm = MagicMock(__class__=CryptoMaterialsManager) mock_mpl_cmm = MagicMock(__class__=ICryptographicMaterialsManager) @@ -68,7 +54,9 @@ def test_GIVEN_unknown_CMM_WHEN_create_CMMHandler_THEN_raise_ValueError(): @patch.object(mock_native_cmm, "get_encryption_materials") -def test_GIVEN_native_CMM_WHEN_get_encryption_materials_THEN_return_native_encryption_materials(mock_get_encryption_materials): +def test_GIVEN_native_CMM_WHEN_get_encryption_materials_THEN_return_native_encryption_materials( + mock_get_encryption_materials +): # Mock: native_cmm.get_encryption_materials returns mock native encryption materials mock_get_encryption_materials.return_value = mock_native_encryption_materials @@ -84,17 +72,16 @@ def test_GIVEN_native_CMM_WHEN_get_encryption_materials_THEN_return_native_encry @patch.object(mock_mpl_cmm, "get_encryption_materials") -@patch("aws_encryption_sdk.mpl.cmm_handler.CMMHandler._native_to_mpl_get_encryption_materials") +@patch("aws_encryption_sdk.internal.mpl.cmm_handler.CMMHandler._native_to_mpl_get_encryption_materials") def test_GIVEN_mpl_CMM_WHEN_get_encryption_materials_THEN_return_mpl_encryption_materials( mock_native_to_mpl_get_encryption_materials, mock_get_encryption_materials, - ): # Mock: mpl_cmm.get_encryption_materials returns mock MPL encryption materials mock_get_encryption_materials_output = MagicMock(__class__=GetEncryptionMaterialsOutput) mock_get_encryption_materials_output.encryption_materials = mock_mpl_encryption_materials mock_get_encryption_materials.return_value = mock_get_encryption_materials_output - + # Mock: CMMHandler._native_to_mpl_get_encryption_materials creates a GetEncryptionMaterialsInput mock_get_encryption_materials_input = MagicMock(__class__=GetEncryptionMaterialsInput) mock_native_to_mpl_get_encryption_materials.return_value = mock_get_encryption_materials_input @@ -108,4 +95,3 @@ def test_GIVEN_mpl_CMM_WHEN_get_encryption_materials_THEN_return_mpl_encryption_ assert test.mpl_materials == mock_mpl_encryption_materials # Verify we actually called `get_encryption_materials` mock_mpl_cmm.get_encryption_materials.assert_called_once_with(mock_get_encryption_materials_input) - diff --git a/test/unit/test_mpl_import_handler.py b/test/unit/test_mpl_import_handler.py index c17c358b4..b82c3092b 100644 --- a/test/unit/test_mpl_import_handler.py +++ b/test/unit/test_mpl_import_handler.py @@ -14,21 +14,23 @@ import pytest from mock import patch -from aws_encryption_sdk.mpl import mpl_import_handler +from aws_encryption_sdk.internal.mpl import mpl_import_handler pytestmark = [pytest.mark.unit, pytest.mark.local] -@patch("aws_encryption_sdk.mpl.mpl_import_handler._import_mpl") + +@patch("aws_encryption_sdk.internal.mpl.mpl_import_handler._import_mpl") def test_GIVEN_import_mpl_succeeds_WHEN_call_has_mpl_THEN_return_True(import_mock): # Mock a successful import of `aws_cryptographic_material_providers` import_mock.return_value = None # No exception means successful import assert mpl_import_handler.has_mpl() is True -@patch("aws_encryption_sdk.mpl.mpl_import_handler._import_mpl") + +@patch("aws_encryption_sdk.internal.mpl.mpl_import_handler._import_mpl") def test_GIVEN_import_mpl_fails_WHEN_call_has_mpl_THEN_return_False(import_mock): # Mock not having a `aws_cryptographic_material_providers` module, # even if it is installed in the Python environment import_mock.side_effect = ImportError() - assert not mpl_import_handler.has_mpl() \ No newline at end of file + assert not mpl_import_handler.has_mpl() diff --git a/test/unit/test_streaming_client_mpl_import.py b/test/unit/test_streaming_client_mpl_import.py index f71f337b7..594ef3478 100644 --- a/test/unit/test_streaming_client_mpl_import.py +++ b/test/unit/test_streaming_client_mpl_import.py @@ -11,18 +11,18 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Unit test suite to validate aws_encryption_sdk.streaming_client MPL import logic.""" -import io -import pytest -from mock import patch +import sys from importlib import reload -from mock import Mock +import pytest +from mock import Mock, patch import aws_encryption_sdk.streaming_client pytestmark = [pytest.mark.unit, pytest.mark.local] + @patch.object(aws_encryption_sdk.streaming_client.mpl_import_handler, "has_mpl") def test_GIVEN_has_mpl_returns_True_WHEN_import_streaming_client_THEN_imports_mpl_modules(has_mpl_mock): has_mpl_mock.return_value = True @@ -30,7 +30,6 @@ def test_GIVEN_has_mpl_returns_True_WHEN_import_streaming_client_THEN_imports_mp # Mock any imports used in the try/catch block # If more imports are added there, then this needs to be expanded # This unit test should pass even if the MPL is not installed - import sys sys.modules['aws_cryptographic_materialproviders.mpl.client'] = Mock() sys.modules['aws_cryptographic_materialproviders.mpl.config'] = Mock() sys.modules['aws_cryptographic_materialproviders.mpl.models'] = Mock() @@ -42,6 +41,7 @@ def test_GIVEN_has_mpl_returns_True_WHEN_import_streaming_client_THEN_imports_mp assert hasattr(aws_encryption_sdk.streaming_client, "_HAS_MPL") assert aws_encryption_sdk.streaming_client._HAS_MPL is True + @patch.object(aws_encryption_sdk.streaming_client.mpl_import_handler, "has_mpl") def test_GIVEN_has_mpl_returns_False_WHEN_import_streaming_client_THEN_does_not_import_mpl_modules(has_mpl_mock): has_mpl_mock.return_value = False @@ -50,4 +50,4 @@ def test_GIVEN_has_mpl_returns_False_WHEN_import_streaming_client_THEN_does_not_ reload(aws_encryption_sdk.streaming_client) assert hasattr(aws_encryption_sdk.streaming_client, "_HAS_MPL") - assert aws_encryption_sdk.streaming_client._HAS_MPL is False \ No newline at end of file + assert aws_encryption_sdk.streaming_client._HAS_MPL is False diff --git a/tox.ini b/tox.ini index 9e2d95477..20b4d9426 100644 --- a/tox.ini +++ b/tox.ini @@ -3,12 +3,18 @@ envlist = # <3.11: run all non-MPL tests py{37,38,39,310}-{local,integ,accept,examples}, # >=3.11: run all tests with MPL installed and without MPL installed - # The `-mpl` suffix tells tox to install the MPL + # The `-mpl` suffix tells tox to install the MPL. + # In the case where the suffix IS NOT appended, + # this runs tests for the target version WITHOUT the MPL installed. + # In the case where the suffix IS appended, + # this runs tests for the target version WITH the MPL installed. + # This does not run any MPL-specific tests; it only runs non-MPL-specific + # tests in a test environment that also has the MPL. py{311,312}-{local,integ,accept,examples}{,-mpl}, - # >=3.11: run ONLY the MPL-specific tests (requires a special IAM role) - # the `mpl` prefix runs only MPL-specific tests - # (non-MPL-specific tests are run from the line above) - # the extra `-mpl` suffix tells tox to install the MPL + # >=3.11: Run ONLY the MPL-specific tests. + # These must be separate from the above target. + # These require the `-mpl` suffix so tox installs the MPL. + # The `mpl` prefix runs only MPL-specific tests py{311,312}-mpl{local,examples}-mpl nocmk, bandit, doc8, readme, docs, @@ -78,6 +84,7 @@ deps = mpl: -rrequirements_mpl.txt commands = local: {[testenv:base-command]commands} test/ -m local --ignore test/unit/mpl/ + # MPL unit tests require the MPL to be installed mpllocal: {[testenv:base-command]commands} test/unit/mpl/ -m local integ: {[testenv:base-command]commands} test/ -m integ --ignore test/unit/mpl/ accept: {[testenv:base-command]commands} test/ -m accept --ignore test/unit/mpl/ @@ -194,13 +201,14 @@ commands = --max-module-lines=1500 \ src/aws_encryption_sdk/ \ setup.py + --ignore-paths=src/aws_encryption_sdk/internal/mpl/ [testenv:pylint-examples] basepython = {[testenv:pylint]basepython} deps = {[testenv:pylint]deps} commands = - pylint --rcfile=examples/src/pylintrc examples/src/ - pylint --rcfile=examples/test/pylintrc --disable R0801 examples/test/ + pylint --rcfile=examples/src/pylintrc examples/src/ --ignore-paths=examples/src/keyrings + pylint --rcfile=examples/test/pylintrc --disable R0801 examples/test/ --ignore-paths=examples/test/keyrings [testenv:pylint-tests] basepython = {[testenv:pylint]basepython} From 3ae1e069df8b2ea75762ce61ba4ea5f3798c24a0 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 20 Feb 2024 15:22:21 -0800 Subject: [PATCH 062/184] refactor, fix --- .github/workflows/ci_static-analysis.yaml | 8 -------- examples/src/keyrings/hierarchical_keyring.py | 3 ++- src/aws_encryption_sdk/internal/mpl/cmm_handler.py | 2 -- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci_static-analysis.yaml b/.github/workflows/ci_static-analysis.yaml index 85d7f4a62..802bad2bc 100644 --- a/.github/workflows/ci_static-analysis.yaml +++ b/.github/workflows/ci_static-analysis.yaml @@ -15,7 +15,6 @@ jobs: matrix: python: - 3.8 - - 3.11 category: - bandit - doc8 @@ -29,13 +28,6 @@ jobs: - pylint-examples - black-check - isort-check - optional_mpl_dependency: - - "" - - -mpl - exclude: - # MPL is not supported on <3.11 - - python: 3.8 - optional_mpl_dependency: -mpl steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 diff --git a/examples/src/keyrings/hierarchical_keyring.py b/examples/src/keyrings/hierarchical_keyring.py index 21108d9a0..56af60115 100644 --- a/examples/src/keyrings/hierarchical_keyring.py +++ b/examples/src/keyrings/hierarchical_keyring.py @@ -77,6 +77,7 @@ def encrypt_and_decrypt_with_keyring( class ExampleBranchKeyIdSupplier(IBranchKeyIdSupplier): """Example implementation of a branch key ID supplier.""" + branch_key_id_for_tenant_A: str branch_key_id_for_tenant_B: str @@ -226,4 +227,4 @@ def get_branch_key_id( source=ciphertext_B, keyring=hierarchical_keyring_B ) - assert plaintext_bytes_B == EXAMPLE_DATA \ No newline at end of file + assert plaintext_bytes_B == EXAMPLE_DATA diff --git a/src/aws_encryption_sdk/internal/mpl/cmm_handler.py b/src/aws_encryption_sdk/internal/mpl/cmm_handler.py index c285afa04..9789651e5 100644 --- a/src/aws_encryption_sdk/internal/mpl/cmm_handler.py +++ b/src/aws_encryption_sdk/internal/mpl/cmm_handler.py @@ -76,9 +76,7 @@ def get_encryption_materials( mpl_input: GetEncryptionMaterialsInput = CMMHandler._native_to_mpl_get_encryption_materials( request ) - print(f"mpl_input: {mpl_input}") mpl_output: GetEncryptionMaterialsOutput = self.mpl_cmm.get_encryption_materials(mpl_input) - print(f"mpl_output: {mpl_output}") return EncryptionMaterialsHandler(mpl_output.encryption_materials) except AwsCryptographicMaterialProvidersException as mpl_exception: # Wrap MPL error into the ESDK error type From 2b5fc7281f1ec263529f4b9c0c9635d62a7a4524 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 20 Feb 2024 15:27:26 -0800 Subject: [PATCH 063/184] refactor, fix --- src/aws_encryption_sdk/internal/mpl/mpl_import_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aws_encryption_sdk/internal/mpl/mpl_import_handler.py b/src/aws_encryption_sdk/internal/mpl/mpl_import_handler.py index 55319bc43..5dd0a7b3e 100644 --- a/src/aws_encryption_sdk/internal/mpl/mpl_import_handler.py +++ b/src/aws_encryption_sdk/internal/mpl/mpl_import_handler.py @@ -17,5 +17,5 @@ def _import_mpl(): This only exists to help with unit test coverage. This is not directly tested. """ - # noqa pylint:disable=unused-import,import-outside-toplevel,import-error - import aws_cryptographic_materialproviders + # pylint:disable=unused-import,import-outside-toplevel,import-error + import aws_cryptographic_materialproviders # noqa F401 From a940dc57a7c9a11296d5200446dde48f0def7d77 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 20 Feb 2024 15:33:45 -0800 Subject: [PATCH 064/184] refactor, fix --- .github/workflows/ci_static-analysis.yaml | 2 +- src/aws_encryption_sdk/internal/crypto/authentication.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci_static-analysis.yaml b/.github/workflows/ci_static-analysis.yaml index 802bad2bc..03fa62165 100644 --- a/.github/workflows/ci_static-analysis.yaml +++ b/.github/workflows/ci_static-analysis.yaml @@ -38,5 +38,5 @@ jobs: pip install --upgrade -r dev_requirements/ci-requirements.txt - name: run test env: - TOXENV: ${{ matrix.category }}${{ matrix.optional_mpl_dependency }} + TOXENV: ${{ matrix.category }} run: tox -- -vv diff --git a/src/aws_encryption_sdk/internal/crypto/authentication.py b/src/aws_encryption_sdk/internal/crypto/authentication.py index a6446981e..d7ff35278 100644 --- a/src/aws_encryption_sdk/internal/crypto/authentication.py +++ b/src/aws_encryption_sdk/internal/crypto/authentication.py @@ -78,7 +78,7 @@ def from_key_bytes(cls, algorithm, key_bytes, encoding=serialization.Encoding.DE """ if encoding == serialization.Encoding.DER: key = serialization.load_der_private_key(data=key_bytes, password=None, backend=default_backend()) - elif serialization.Encoding.PEM: + elif encoding == serialization.Encoding.PEM: key = serialization.load_pem_private_key(data=key_bytes, password=None, backend=default_backend()) else: raise ValueError(f"Unsupported encoding for Signer: {encoding}") From 708ab5e26f1227cc5f72b58abd5d92bcf27cc89a Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 20 Feb 2024 15:55:36 -0800 Subject: [PATCH 065/184] it works locally but fails on gha --- test/unit/test_crypto_authentication_signer.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/unit/test_crypto_authentication_signer.py b/test/unit/test_crypto_authentication_signer.py index 11271abfb..9584cf441 100644 --- a/test/unit/test_crypto_authentication_signer.py +++ b/test/unit/test_crypto_authentication_signer.py @@ -81,7 +81,13 @@ def test_signer_from_key_bytes(patch_default_backend, patch_serialization, patch mock_algorithm_info = MagicMock(return_value=sentinel.algorithm_info, spec=patch_ec.EllipticCurve) _algorithm = MagicMock(signing_algorithm_info=mock_algorithm_info) - signer = Signer.from_key_bytes(algorithm=_algorithm, key_bytes=sentinel.key_bytes) + # signer = Signer.from_key_bytes(algorithm=_algorithm, key_bytes=sentinel.key_bytes) + + signer = Signer.from_key_bytes( + algorithm=_algorithm, + key_bytes=sentinel.key_bytes, + encoding=patch_serialization.encoding.DER + ) patch_serialization.load_der_private_key.assert_called_once_with( data=sentinel.key_bytes, password=None, backend=patch_default_backend.return_value From ffd295c10253a7c0f1437309396eef28f076118f Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 20 Feb 2024 15:57:57 -0800 Subject: [PATCH 066/184] it works locally but fails on gha --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 20b4d9426..1c133adaa 100644 --- a/tox.ini +++ b/tox.ini @@ -110,7 +110,7 @@ passenv = setenv = ######################################################### deps = -rdev_requirements/test-requirements.txt -commands = {[testenv:base-command]commands} test/ -m local +commands = {[testenv:base-command]commands} test/ -m local --ignore test/unit/mpl/ # Collect requirements for use in upstream tests [testenv:freeze-upstream-requirements-base] @@ -142,7 +142,7 @@ commands = {[testenv:freeze-upstream-requirements-base]commands} test/upstream-r [testenv:test-upstream-requirements-base] sitepackages = False recreate = True -commands = {[testenv:base-command]commands} test/ -m local +commands = {[testenv:base-command]commands} test/ -m local --ignore test/unit/mpl/ # Test frozen upstream requirements for Python 3.7 [testenv:test-upstream-requirements-py37] From 1ba175c5391b7cf673b5c8e56be661951a072730 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 20 Feb 2024 16:01:21 -0800 Subject: [PATCH 067/184] it works locally but fails on gha --- test/unit/test_crypto_authentication_signer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/test_crypto_authentication_signer.py b/test/unit/test_crypto_authentication_signer.py index 9584cf441..2e5f5a4fd 100644 --- a/test/unit/test_crypto_authentication_signer.py +++ b/test/unit/test_crypto_authentication_signer.py @@ -86,7 +86,7 @@ def test_signer_from_key_bytes(patch_default_backend, patch_serialization, patch signer = Signer.from_key_bytes( algorithm=_algorithm, key_bytes=sentinel.key_bytes, - encoding=patch_serialization.encoding.DER + encoding=patch_serialization.Encoding.DER ) patch_serialization.load_der_private_key.assert_called_once_with( From fa175ba6ed28758222b39e08569b310bfa08e363 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 20 Feb 2024 16:09:12 -0800 Subject: [PATCH 068/184] it works locally but fails on gha --- .github/workflows/ci_tests.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index 603f54371..d46c19c48 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -24,8 +24,8 @@ jobs: fail-fast: true matrix: os: - - ubuntu-latest - - windows-latest + # - ubuntu-latest + # - windows-latest - macos-latest python: - 3.7 From 2f90a970683bb9cf17469c88d51a28b8d5bd8baf Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 20 Feb 2024 16:15:22 -0800 Subject: [PATCH 069/184] it works locally but fails on gha --- src/aws_encryption_sdk/streaming_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index c4c15559b..4e9d5d07c 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -247,7 +247,7 @@ def __new__(cls, **kwargs): ): raise TypeError("Can't instantiate abstract class {}".format(cls.__name__)) - instance = super(_EncryptionStream, cls).__new__(cls) + instance = super().__new__(cls) config = kwargs.pop("config", None) if not isinstance(config, instance._config_class): # pylint: disable=protected-access From df9215f6e72695a69fc042e2d7d326c341c006ff Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 20 Feb 2024 16:19:20 -0800 Subject: [PATCH 070/184] it works locally but fails on gha --- src/aws_encryption_sdk/streaming_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 4e9d5d07c..c4c15559b 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -247,7 +247,7 @@ def __new__(cls, **kwargs): ): raise TypeError("Can't instantiate abstract class {}".format(cls.__name__)) - instance = super().__new__(cls) + instance = super(_EncryptionStream, cls).__new__(cls) config = kwargs.pop("config", None) if not isinstance(config, instance._config_class): # pylint: disable=protected-access From b57e4a397cfa4ca28f0877df1f0dbc608fbf0cfe Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 20 Feb 2024 16:22:47 -0800 Subject: [PATCH 071/184] it works locally but fails on gha --- src/aws_encryption_sdk/streaming_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index c4c15559b..4e9d5d07c 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -247,7 +247,7 @@ def __new__(cls, **kwargs): ): raise TypeError("Can't instantiate abstract class {}".format(cls.__name__)) - instance = super(_EncryptionStream, cls).__new__(cls) + instance = super().__new__(cls) config = kwargs.pop("config", None) if not isinstance(config, instance._config_class): # pylint: disable=protected-access From 9d7ec6d3a2baca380c2e7bdb466b554c36e4a5f2 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 20 Feb 2024 16:25:37 -0800 Subject: [PATCH 072/184] it works locally but fails on gha --- src/aws_encryption_sdk/streaming_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 4e9d5d07c..c4c15559b 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -247,7 +247,7 @@ def __new__(cls, **kwargs): ): raise TypeError("Can't instantiate abstract class {}".format(cls.__name__)) - instance = super().__new__(cls) + instance = super(_EncryptionStream, cls).__new__(cls) config = kwargs.pop("config", None) if not isinstance(config, instance._config_class): # pylint: disable=protected-access From 2cbc8451756cedc0efde5a1c73141a8d8741748c Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 20 Feb 2024 16:31:22 -0800 Subject: [PATCH 073/184] it works locally but fails on gha --- src/aws_encryption_sdk/streaming_client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index c4c15559b..3bda700e7 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -247,7 +247,10 @@ def __new__(cls, **kwargs): ): raise TypeError("Can't instantiate abstract class {}".format(cls.__name__)) - instance = super(_EncryptionStream, cls).__new__(cls) + if issubclass(StreamEncryptor, _EncryptionStream): + instance = super(_EncryptionStream, cls).__new__(cls) + else: + raise ValueError(f"issubclass {issubclass(StreamEncryptor, _EncryptionStream)}") config = kwargs.pop("config", None) if not isinstance(config, instance._config_class): # pylint: disable=protected-access From def946d841461aa19a5dc73aeaf1471de7f7e351 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 20 Feb 2024 16:37:51 -0800 Subject: [PATCH 074/184] it works locally but fails on gha --- src/aws_encryption_sdk/streaming_client.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 3bda700e7..4e9d5d07c 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -247,10 +247,7 @@ def __new__(cls, **kwargs): ): raise TypeError("Can't instantiate abstract class {}".format(cls.__name__)) - if issubclass(StreamEncryptor, _EncryptionStream): - instance = super(_EncryptionStream, cls).__new__(cls) - else: - raise ValueError(f"issubclass {issubclass(StreamEncryptor, _EncryptionStream)}") + instance = super().__new__(cls) config = kwargs.pop("config", None) if not isinstance(config, instance._config_class): # pylint: disable=protected-access From dff6ac0134e29ed4b98d046701ee75b11cd26450 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 20 Feb 2024 17:12:17 -0800 Subject: [PATCH 075/184] it works locally but fails on gha --- src/aws_encryption_sdk/streaming_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 4e9d5d07c..4b849b818 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -71,14 +71,14 @@ from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager from aws_encryption_sdk.structures import MessageHeader -if mpl_import_handler.has_mpl(): +try: # noqa pylint: disable=import-error from aws_cryptographic_materialproviders.mpl.client import AwsCryptographicMaterialProviders from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig from aws_cryptographic_materialproviders.mpl.models import CreateDefaultCryptographicMaterialsManagerInput from aws_cryptographic_materialproviders.mpl.references import IKeyring _HAS_MPL = True -else: +except ImportError: _HAS_MPL = False _LOGGER = logging.getLogger(__name__) From 78f0b0faed85977346c2bb6652899a8316c0e10e Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 20 Feb 2024 17:22:16 -0800 Subject: [PATCH 076/184] it works locally but fails on gha --- src/aws_encryption_sdk/streaming_client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 4b849b818..1186faf2b 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -158,6 +158,9 @@ class _ClientConfig(object): # pylint: disable=too-many-instance-attributes ) # DEPRECATED: Value is no longer configurable here. Parameter left here to avoid breaking consumers. def _has_mpl_attrs_post_init(self): + if not hasattr(self, "keyring"): + self._no_mpl_attrs_post_init() + return if not _exactly_one_arg_is_not_none(self.materials_manager, self.key_provider, self.keyring): raise TypeError("Exactly one of keyring, materials_manager, or key_provider must be provided") if self.materials_manager is None: From 20a469e3b2e7fb8357096af9462856fa6b43604c Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 20 Feb 2024 17:24:24 -0800 Subject: [PATCH 077/184] it works locally but fails on gha --- src/aws_encryption_sdk/streaming_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 1186faf2b..f9b550e6d 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -250,7 +250,7 @@ def __new__(cls, **kwargs): ): raise TypeError("Can't instantiate abstract class {}".format(cls.__name__)) - instance = super().__new__(cls) + instance = super(_EncryptionStream, cls).__new__(cls) config = kwargs.pop("config", None) if not isinstance(config, instance._config_class): # pylint: disable=protected-access From 66859a7b5316013200b42a6b00c2ebb1a51fcc18 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 10:10:57 -0800 Subject: [PATCH 078/184] fix tests --- examples/src/keyrings/hierarchical_keyring.py | 33 +++++++++-------- .../internal/mpl/mpl_import_handler.py | 21 ----------- src/aws_encryption_sdk/streaming_client.py | 4 +-- test/unit/mpl/test_cmm_handler.py | 2 +- test/unit/test_mpl_import_handler.py | 36 ------------------- test/unit/test_streaming_client_mpl_import.py | 32 ++++++++--------- 6 files changed, 33 insertions(+), 95 deletions(-) delete mode 100644 src/aws_encryption_sdk/internal/mpl/mpl_import_handler.py delete mode 100644 test/unit/test_mpl_import_handler.py diff --git a/examples/src/keyrings/hierarchical_keyring.py b/examples/src/keyrings/hierarchical_keyring.py index 56af60115..3e56d1e56 100644 --- a/examples/src/keyrings/hierarchical_keyring.py +++ b/examples/src/keyrings/hierarchical_keyring.py @@ -9,23 +9,22 @@ import aws_encryption_sdk from aws_encryption_sdk import CommitmentPolicy from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError -from aws_encryption_sdk.internal.mpl import mpl_import_handler - -if mpl_import_handler.has_mpl(): - # noqa pylint: disable=import-error - from aws_cryptographic_materialproviders.keystore.client import KeyStore - from aws_cryptographic_materialproviders.keystore.config import KeyStoreConfig - from aws_cryptographic_materialproviders.keystore.models import CreateKeyInput, KMSConfigurationKmsKeyArn - from aws_cryptographic_materialproviders.mpl.client import AwsCryptographicMaterialProviders - from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig - from aws_cryptographic_materialproviders.mpl.models import ( - CacheTypeDefault, - CreateAwsKmsHierarchicalKeyringInput, - DefaultCache, - GetBranchKeyIdInput, - GetBranchKeyIdOutput, - ) - from aws_cryptographic_materialproviders.mpl.references import IBranchKeyIdSupplier, IKeyring + +# ignore missing MPL for pylint, but the MPL is required for this example +# noqa pylint: disable=import-error +from aws_cryptographic_materialproviders.keystore.client import KeyStore +from aws_cryptographic_materialproviders.keystore.config import KeyStoreConfig +from aws_cryptographic_materialproviders.keystore.models import CreateKeyInput, KMSConfigurationKmsKeyArn +from aws_cryptographic_materialproviders.mpl.client import AwsCryptographicMaterialProviders +from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig +from aws_cryptographic_materialproviders.mpl.models import ( + CacheTypeDefault, + CreateAwsKmsHierarchicalKeyringInput, + DefaultCache, + GetBranchKeyIdInput, + GetBranchKeyIdOutput, +) +from aws_cryptographic_materialproviders.mpl.references import IBranchKeyIdSupplier, IKeyring module_root_dir = '/'.join(__file__.split("/")[:-1]) diff --git a/src/aws_encryption_sdk/internal/mpl/mpl_import_handler.py b/src/aws_encryption_sdk/internal/mpl/mpl_import_handler.py deleted file mode 100644 index 5dd0a7b3e..000000000 --- a/src/aws_encryption_sdk/internal/mpl/mpl_import_handler.py +++ /dev/null @@ -1,21 +0,0 @@ -"""Detects whether the MPL is installed for use by internal ESDK code. -External customers should not need to interact with this. -""" - - -def has_mpl(): - """Returns True if the aws-cryptographic-material-providers library is installed, False otherwise.""" - try: - _import_mpl() - return True - except ImportError: - return False - - -def _import_mpl(): - """Private wrapper for import. - This only exists to help with unit test coverage. - This is not directly tested. - """ - # pylint:disable=unused-import,import-outside-toplevel,import-error - import aws_cryptographic_materialproviders # noqa F401 diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index f9b550e6d..8e004e84e 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -58,7 +58,6 @@ serialize_non_framed_close, serialize_non_framed_open, ) -from aws_encryption_sdk.internal.mpl import mpl_import_handler from aws_encryption_sdk.internal.mpl.cmm_handler import CMMHandler from aws_encryption_sdk.internal.utils.commitment import ( validate_commitment_policy_on_decrypt, @@ -72,13 +71,14 @@ from aws_encryption_sdk.structures import MessageHeader try: + # pylint should pass even if the MPL isn't installed # noqa pylint: disable=import-error from aws_cryptographic_materialproviders.mpl.client import AwsCryptographicMaterialProviders from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig from aws_cryptographic_materialproviders.mpl.models import CreateDefaultCryptographicMaterialsManagerInput from aws_cryptographic_materialproviders.mpl.references import IKeyring _HAS_MPL = True -except ImportError: +except ImportError as e: _HAS_MPL = False _LOGGER = logging.getLogger(__name__) diff --git a/test/unit/mpl/test_cmm_handler.py b/test/unit/mpl/test_cmm_handler.py index 343ac514b..d16374899 100644 --- a/test/unit/mpl/test_cmm_handler.py +++ b/test/unit/mpl/test_cmm_handler.py @@ -10,7 +10,7 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -"""Test suite to verify the mpl_import_handler module handles importing the MPL correctly.""" +"""Test suite to verify the cmm_handler module delegates correctly.""" import pytest from aws_cryptographic_materialproviders.mpl.models import ( EncryptionMaterials as MPL_EncryptionMaterials, diff --git a/test/unit/test_mpl_import_handler.py b/test/unit/test_mpl_import_handler.py deleted file mode 100644 index b82c3092b..000000000 --- a/test/unit/test_mpl_import_handler.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. -"""Test suite to verify the mpl_import_handler module handles importing the MPL correctly.""" -import pytest -from mock import patch - -from aws_encryption_sdk.internal.mpl import mpl_import_handler - -pytestmark = [pytest.mark.unit, pytest.mark.local] - - -@patch("aws_encryption_sdk.internal.mpl.mpl_import_handler._import_mpl") -def test_GIVEN_import_mpl_succeeds_WHEN_call_has_mpl_THEN_return_True(import_mock): - # Mock a successful import of `aws_cryptographic_material_providers` - import_mock.return_value = None # No exception means successful import - - assert mpl_import_handler.has_mpl() is True - - -@patch("aws_encryption_sdk.internal.mpl.mpl_import_handler._import_mpl") -def test_GIVEN_import_mpl_fails_WHEN_call_has_mpl_THEN_return_False(import_mock): - # Mock not having a `aws_cryptographic_material_providers` module, - # even if it is installed in the Python environment - import_mock.side_effect = ImportError() - - assert not mpl_import_handler.has_mpl() diff --git a/test/unit/test_streaming_client_mpl_import.py b/test/unit/test_streaming_client_mpl_import.py index 594ef3478..ebafd199f 100644 --- a/test/unit/test_streaming_client_mpl_import.py +++ b/test/unit/test_streaming_client_mpl_import.py @@ -23,31 +23,27 @@ pytestmark = [pytest.mark.unit, pytest.mark.local] -@patch.object(aws_encryption_sdk.streaming_client.mpl_import_handler, "has_mpl") -def test_GIVEN_has_mpl_returns_True_WHEN_import_streaming_client_THEN_imports_mpl_modules(has_mpl_mock): - has_mpl_mock.return_value = True +# Check if MPL is installed, and skip tests based on whether it is +try: + import aws_cryptographic_materialproviders + HAS_MPL = True +except ImportError as e: + HAS_MPL = False - # Mock any imports used in the try/catch block - # If more imports are added there, then this needs to be expanded - # This unit test should pass even if the MPL is not installed - sys.modules['aws_cryptographic_materialproviders.mpl.client'] = Mock() - sys.modules['aws_cryptographic_materialproviders.mpl.config'] = Mock() - sys.modules['aws_cryptographic_materialproviders.mpl.models'] = Mock() - sys.modules['aws_cryptographic_materialproviders.mpl.references'] = Mock() - # Reload module given the mock - reload(aws_encryption_sdk.streaming_client) +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GIVEN_test_has_mpl_is_True_THEN_streaming_client_has_mpl_is_TRUE(): + """If the MPL IS installed in the runtime environment, + assert the streaming client has _HAS_MPL set to True""" assert hasattr(aws_encryption_sdk.streaming_client, "_HAS_MPL") assert aws_encryption_sdk.streaming_client._HAS_MPL is True -@patch.object(aws_encryption_sdk.streaming_client.mpl_import_handler, "has_mpl") -def test_GIVEN_has_mpl_returns_False_WHEN_import_streaming_client_THEN_does_not_import_mpl_modules(has_mpl_mock): - has_mpl_mock.return_value = False - - # Reload module given the mock - reload(aws_encryption_sdk.streaming_client) +@pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") +def test_GIVEN_test_has_mpl_is_False_THEN_streaming_client_has_mpl_is_TRUE(): + """If the MPL IS NOT installed in the runtime environment, + assert the streaming client has _HAS_MPL set to False""" assert hasattr(aws_encryption_sdk.streaming_client, "_HAS_MPL") assert aws_encryption_sdk.streaming_client._HAS_MPL is False From bf8f67c1fd4a8523c4a0a76eb5f5e0245e5b25b5 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 10:18:23 -0800 Subject: [PATCH 079/184] cleanup --- examples/src/keyrings/hierarchical_keyring.py | 11 +++++------ src/aws_encryption_sdk/streaming_client.py | 2 +- test/unit/test_streaming_client_mpl_import.py | 12 ++++-------- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/examples/src/keyrings/hierarchical_keyring.py b/examples/src/keyrings/hierarchical_keyring.py index 3e56d1e56..50f620456 100644 --- a/examples/src/keyrings/hierarchical_keyring.py +++ b/examples/src/keyrings/hierarchical_keyring.py @@ -4,12 +4,6 @@ import sys import boto3 -from typing import Dict - -import aws_encryption_sdk -from aws_encryption_sdk import CommitmentPolicy -from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError - # ignore missing MPL for pylint, but the MPL is required for this example # noqa pylint: disable=import-error from aws_cryptographic_materialproviders.keystore.client import KeyStore @@ -25,6 +19,11 @@ GetBranchKeyIdOutput, ) from aws_cryptographic_materialproviders.mpl.references import IBranchKeyIdSupplier, IKeyring +from typing import Dict + +import aws_encryption_sdk +from aws_encryption_sdk import CommitmentPolicy +from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError module_root_dir = '/'.join(__file__.split("/")[:-1]) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 8e004e84e..ad998088c 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -78,7 +78,7 @@ from aws_cryptographic_materialproviders.mpl.models import CreateDefaultCryptographicMaterialsManagerInput from aws_cryptographic_materialproviders.mpl.references import IKeyring _HAS_MPL = True -except ImportError as e: +except ImportError: _HAS_MPL = False _LOGGER = logging.getLogger(__name__) diff --git a/test/unit/test_streaming_client_mpl_import.py b/test/unit/test_streaming_client_mpl_import.py index ebafd199f..3eda0ad63 100644 --- a/test/unit/test_streaming_client_mpl_import.py +++ b/test/unit/test_streaming_client_mpl_import.py @@ -12,11 +12,7 @@ # language governing permissions and limitations under the License. """Unit test suite to validate aws_encryption_sdk.streaming_client MPL import logic.""" -import sys -from importlib import reload - import pytest -from mock import Mock, patch import aws_encryption_sdk.streaming_client @@ -25,14 +21,14 @@ # Check if MPL is installed, and skip tests based on whether it is try: - import aws_cryptographic_materialproviders + import aws_cryptographic_materialproviders # noqa pylint: disable=unused-import HAS_MPL = True -except ImportError as e: +except ImportError: HAS_MPL = False @pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GIVEN_test_has_mpl_is_True_THEN_streaming_client_has_mpl_is_TRUE(): +def test_GIVEN_test_has_mpl_is_True_THEN_streaming_client_has_mpl_is_True(): """If the MPL IS installed in the runtime environment, assert the streaming client has _HAS_MPL set to True""" @@ -41,7 +37,7 @@ def test_GIVEN_test_has_mpl_is_True_THEN_streaming_client_has_mpl_is_TRUE(): @pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") -def test_GIVEN_test_has_mpl_is_False_THEN_streaming_client_has_mpl_is_TRUE(): +def test_GIVEN_test_has_mpl_is_False_THEN_streaming_client_has_mpl_is_False(): """If the MPL IS NOT installed in the runtime environment, assert the streaming client has _HAS_MPL set to False""" From b24be113a9a179d9cc22602b06a55222fc557ef3 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 10:23:07 -0800 Subject: [PATCH 080/184] re-enable test --- .github/workflows/ci_tests.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index d46c19c48..603f54371 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -24,8 +24,8 @@ jobs: fail-fast: true matrix: os: - # - ubuntu-latest - # - windows-latest + - ubuntu-latest + - windows-latest - macos-latest python: - 3.7 From acba1b0143ee6b40312b5a02ba6d95ea736b9d0a Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 10:25:52 -0800 Subject: [PATCH 081/184] re-enable test --- test/unit/test_streaming_client_mpl_import.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/unit/test_streaming_client_mpl_import.py b/test/unit/test_streaming_client_mpl_import.py index 3eda0ad63..a4ca87e2a 100644 --- a/test/unit/test_streaming_client_mpl_import.py +++ b/test/unit/test_streaming_client_mpl_import.py @@ -19,7 +19,9 @@ pytestmark = [pytest.mark.unit, pytest.mark.local] -# Check if MPL is installed, and skip tests based on whether it is +# Check if MPL is installed, and skip tests based on its installation status +# Ideally, this logic would be based on mocking imports and testing logic, +# but doing that introduces errors that cause other tests to fail. try: import aws_cryptographic_materialproviders # noqa pylint: disable=unused-import HAS_MPL = True From 42b7b745dec470b06bf1fecaae6d0578b17df2e1 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 10:29:57 -0800 Subject: [PATCH 082/184] longpaths --- .github/workflows/ci_tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index 603f54371..a8bad7bfb 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -70,6 +70,7 @@ jobs: python-version: ${{ matrix.python }} architecture: ${{ matrix.architecture }} - run: | + git config --system core.longpaths true python -m pip install --upgrade pip pip install --upgrade -r dev_requirements/ci-requirements.txt - name: run test From f226e7e8bd5b3df06fda12f63be601939a0a8711 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 10:34:14 -0800 Subject: [PATCH 083/184] longpaths --- .github/workflows/ci_tests.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index a8bad7bfb..cab32dcc7 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -64,13 +64,16 @@ jobs: - python: 3.10 optional_mpl_dependency: -mpl steps: + - name: Support longpaths + run: | + git config --global core.longpaths true + - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} architecture: ${{ matrix.architecture }} - run: | - git config --system core.longpaths true python -m pip install --upgrade pip pip install --upgrade -r dev_requirements/ci-requirements.txt - name: run test From aa2f80a2aaff191082ed0844ded4ebe812eafb2c Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 10:39:24 -0800 Subject: [PATCH 084/184] debug windows fail --- .github/workflows/ci_tests.yaml | 4 ++-- src/aws_encryption_sdk/streaming_client.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index cab32dcc7..132c65bd0 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -24,9 +24,9 @@ jobs: fail-fast: true matrix: os: - - ubuntu-latest + # - ubuntu-latest - windows-latest - - macos-latest + # - macos-latest python: - 3.7 - 3.8 diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index ad998088c..bc37d7de5 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -78,7 +78,8 @@ from aws_cryptographic_materialproviders.mpl.models import CreateDefaultCryptographicMaterialsManagerInput from aws_cryptographic_materialproviders.mpl.references import IKeyring _HAS_MPL = True -except ImportError: +except ImportError as e: + print(e) _HAS_MPL = False _LOGGER = logging.getLogger(__name__) From bc002b682dd7113734cf8494e17e48a1462e18c6 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 10:42:48 -0800 Subject: [PATCH 085/184] debug windows fail --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 1c133adaa..fa3e8530f 100644 --- a/tox.ini +++ b/tox.ini @@ -56,7 +56,7 @@ envlist = # coverage :: Runs code coverage, failing the build if coverage is below the configured threshold [testenv:base-command] -commands = pytest --basetemp={envtmpdir} -l {posargs} +commands = pytest --basetemp={envtmpdir} -l {posargs} -s -v [testenv] ; passenv = AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID,AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2,AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_1,AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_2,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKEN,AWS_CONTAINER_CREDENTIALS_RELATIVE_URI,AWS_PROFILE,PIP_CONFIG_FILE From 8dd0303615d72712dc7c190f6e0724656c26bfb2 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 10:46:34 -0800 Subject: [PATCH 086/184] debug windows fail --- src/aws_encryption_sdk/streaming_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index bc37d7de5..4a00b99f4 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -79,7 +79,7 @@ from aws_cryptographic_materialproviders.mpl.references import IKeyring _HAS_MPL = True except ImportError as e: - print(e) + print("IMPORT ERROR" + str(e)) _HAS_MPL = False _LOGGER = logging.getLogger(__name__) From 1e9db3b3b0d32da9c55e4e918c9320318026a611 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 10:50:22 -0800 Subject: [PATCH 087/184] debug windows fail --- .github/workflows/ci_tests.yaml | 9 ++++++--- src/aws_encryption_sdk/streaming_client.py | 1 - 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index 132c65bd0..d64d3ce79 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -24,9 +24,12 @@ jobs: fail-fast: true matrix: os: - # - ubuntu-latest - - windows-latest - # - macos-latest + - ubuntu-latest + # Windows fails due to "No module named 'Wrappers'" + # This SHOULD be fixed once Dafny generates fully-qualified import statements + # Disable for now + # - windows-latest + - macos-latest python: - 3.7 - 3.8 diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 4a00b99f4..8e004e84e 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -79,7 +79,6 @@ from aws_cryptographic_materialproviders.mpl.references import IKeyring _HAS_MPL = True except ImportError as e: - print("IMPORT ERROR" + str(e)) _HAS_MPL = False _LOGGER = logging.getLogger(__name__) From 74d4e667d1828dd6437f1aeb42436c0b44c2d7c0 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 10:53:26 -0800 Subject: [PATCH 088/184] disable windows until pythonpath --- src/aws_encryption_sdk/streaming_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 8e004e84e..ad998088c 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -78,7 +78,7 @@ from aws_cryptographic_materialproviders.mpl.models import CreateDefaultCryptographicMaterialsManagerInput from aws_cryptographic_materialproviders.mpl.references import IKeyring _HAS_MPL = True -except ImportError as e: +except ImportError: _HAS_MPL = False _LOGGER = logging.getLogger(__name__) From 1bb23e862a73d96a376e207aeb82b3b2d3e19ec4 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 11:07:51 -0800 Subject: [PATCH 089/184] expand testing --- .github/workflows/ci_static-analysis.yaml | 4 +--- .github/workflows/ci_tests.yaml | 10 ++++++++++ codebuild/py311/examples_mpl.yml | 7 ++++--- codebuild/py312/awses_local_mpl.yml | 2 ++ codebuild/py312/examples_mpl.yml | 10 +++++++--- codebuild/py312/integ_mpl.yml | 2 ++ tox.ini | 11 ++++++++--- 7 files changed, 34 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci_static-analysis.yaml b/.github/workflows/ci_static-analysis.yaml index 03fa62165..0093ae9a9 100644 --- a/.github/workflows/ci_static-analysis.yaml +++ b/.github/workflows/ci_static-analysis.yaml @@ -13,8 +13,6 @@ jobs: strategy: fail-fast: false matrix: - python: - - 3.8 category: - bandit - doc8 @@ -32,7 +30,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: ${{ matrix.python }} + python-version: 3.8 - run: | python -m pip install --upgrade pip pip install --upgrade -r dev_requirements/ci-requirements.txt diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index d64d3ce79..58f9b1b11 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -44,10 +44,14 @@ jobs: category: - local - accept + - mpllocal + - mplaccept # These require credentials. # Enable them once we sort how to provide them. # - integ # - examples + # Append '-mpl' to some test environments. + # This suffix signals to tox to install the MPL in the test environment. optional_mpl_dependency: - "" - -mpl @@ -66,7 +70,13 @@ jobs: optional_mpl_dependency: -mpl - python: 3.10 optional_mpl_dependency: -mpl + # mpllocal and mplaccept require the MPL to be installed + - python: mpllocal + optional_mpl_dependency: + - python: mplaccept + optional_mpl_dependency: steps: + # Support long Dafny filenames (used in MPL and DBESDK repos) - name: Support longpaths run: | git config --global core.longpaths true diff --git a/codebuild/py311/examples_mpl.yml b/codebuild/py311/examples_mpl.yml index f8f2a6a01..19a5dec05 100644 --- a/codebuild/py311/examples_mpl.yml +++ b/codebuild/py311/examples_mpl.yml @@ -2,7 +2,7 @@ version: 0.2 env: variables: - # No TOXENV; examples using the MPL switch envs + # No TOXENV. This runs multiple environments. REGION: "us-west-2" AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: >- arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f @@ -20,14 +20,15 @@ phases: build: commands: - pip install "tox < 4.0" + # Run non-MPL-specific tests with the MPL installed - tox -e py311-examples-mpl - # Assume special role + # Assume special role to access keystore - TMP_ROLE=$(aws sts assume-role --role-arn "arn:aws:iam::370957321024:role/GitHub-CI-Public-ESDK-Python-Role-us-west-2" --role-session-name "CB-Py311ExamplesMpl") - export TMP_ROLE - export AWS_ACCESS_KEY_ID=$(echo "${TMP_ROLE}" | jq -r '.Credentials.AccessKeyId') - export AWS_SECRET_ACCESS_KEY=$(echo "${TMP_ROLE}" | jq -r '.Credentials.SecretAccessKey') - export AWS_SESSION_TOKEN=$(echo "${TMP_ROLE}" | jq -r '.Credentials.SessionToken') - aws sts get-caller-identity - # Run special role-specific examples + # Run MPL-specific tests with special role - tox -e py311-mplexamples-mpl diff --git a/codebuild/py312/awses_local_mpl.yml b/codebuild/py312/awses_local_mpl.yml index db25f4f57..1d0f80319 100644 --- a/codebuild/py312/awses_local_mpl.yml +++ b/codebuild/py312/awses_local_mpl.yml @@ -1,3 +1,5 @@ +# Runs the same tests as awses_local in an environment with the MPL installed. +# This asserts existing tests continue to pass with the MPL installed. version: 0.2 env: diff --git a/codebuild/py312/examples_mpl.yml b/codebuild/py312/examples_mpl.yml index ba0660024..366222441 100644 --- a/codebuild/py312/examples_mpl.yml +++ b/codebuild/py312/examples_mpl.yml @@ -1,8 +1,11 @@ +# Runs the same tests as examples in an environment with the MPL installed +# to assert existing tests continue to pass with the MPL installed. +# Then, run MPL-specific tests. version: 0.2 env: variables: - # No TOXENV; examples using the MPL switch envs + # No TOXENV. This runs multiple environments. REGION: "us-west-2" AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID: >- arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f @@ -25,13 +28,14 @@ phases: - pip install --upgrade pip - pip install setuptools - pip install "tox < 4.0" + # Run non-MPL-specific tests with the MPL installed - tox -e py312-examples-mpl - # Assume special role + # Assume special role to access keystore - TMP_ROLE=$(aws sts assume-role --role-arn "arn:aws:iam::370957321024:role/GitHub-CI-Public-ESDK-Python-Role-us-west-2" --role-session-name "CB-Py311ExamplesMpl") - export TMP_ROLE - export AWS_ACCESS_KEY_ID=$(echo "${TMP_ROLE}" | jq -r '.Credentials.AccessKeyId') - export AWS_SECRET_ACCESS_KEY=$(echo "${TMP_ROLE}" | jq -r '.Credentials.SecretAccessKey') - export AWS_SESSION_TOKEN=$(echo "${TMP_ROLE}" | jq -r '.Credentials.SessionToken') - aws sts get-caller-identity - # Run special role-specific examples + # Run MPL-specific tests with special role - tox -e py312-mplexamples-mpl diff --git a/codebuild/py312/integ_mpl.yml b/codebuild/py312/integ_mpl.yml index 553f41e8a..e292acc57 100644 --- a/codebuild/py312/integ_mpl.yml +++ b/codebuild/py312/integ_mpl.yml @@ -1,3 +1,5 @@ +# Runs the same tests as integ in an environment with the MPL installed. +# This asserts existing tests continue to pass with the MPL installed. version: 0.2 env: diff --git a/tox.ini b/tox.ini index fa3e8530f..e5a585ca5 100644 --- a/tox.ini +++ b/tox.ini @@ -15,7 +15,7 @@ envlist = # These must be separate from the above target. # These require the `-mpl` suffix so tox installs the MPL. # The `mpl` prefix runs only MPL-specific tests - py{311,312}-mpl{local,examples}-mpl + py{311,312}-mpl{local,integ,accept,examples}-mpl nocmk, bandit, doc8, readme, docs, {flake8,pylint}{,-tests,-examples}, @@ -56,7 +56,7 @@ envlist = # coverage :: Runs code coverage, failing the build if coverage is below the configured threshold [testenv:base-command] -commands = pytest --basetemp={envtmpdir} -l {posargs} -s -v +commands = pytest --basetemp={envtmpdir} -l {posargs} [testenv] ; passenv = AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID,AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2,AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_1,AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_2,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKEN,AWS_CONTAINER_CREDENTIALS_RELATIVE_URI,AWS_PROFILE,PIP_CONFIG_FILE @@ -87,11 +87,16 @@ commands = # MPL unit tests require the MPL to be installed mpllocal: {[testenv:base-command]commands} test/unit/mpl/ -m local integ: {[testenv:base-command]commands} test/ -m integ --ignore test/unit/mpl/ + # MPL integ tests require the MPL to be installed + mplinteg: {[testenv:base-command]commands} test/unit/mpl -m integ accept: {[testenv:base-command]commands} test/ -m accept --ignore test/unit/mpl/ + # MPL accept tests require the MPL to be installed + mplaccept: {[testenv:base-command]commands} test/unit/mpl -m accept examples: {[testenv:base-command]commands} examples/test/ -m examples --ignore examples/test/keyrings/ # MPL keyring examples require a special IAM role; run these separately under a separate set of permissions mplexamples: {[testenv:base-command]commands} examples/test/keyrings -m examples - all: {[testenv:base-command]commands} test/ examples/test/ --ignore test/unit/mpl/ + all: {[testenv:base-command]commands} test/ examples/test/ --ignore test/unit/mpl/ --ignore examples/test/keyrings/ + mplall: {[testenv:base-command]commands} test/unit/mpl/ examples/test/keyrings/ manual: {[testenv:base-command]commands} # Run code coverage on the unit tests From 1ee69cefbb3f0d3486c2d0e873a1ac6c0a4bf8ff Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 11:11:05 -0800 Subject: [PATCH 090/184] expand testing --- .github/workflows/ci_tests.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index 58f9b1b11..daa108060 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -72,9 +72,9 @@ jobs: optional_mpl_dependency: -mpl # mpllocal and mplaccept require the MPL to be installed - python: mpllocal - optional_mpl_dependency: + optional_mpl_dependency: "" - python: mplaccept - optional_mpl_dependency: + optional_mpl_dependency: "" steps: # Support long Dafny filenames (used in MPL and DBESDK repos) - name: Support longpaths From b33f2f706fb1dbb9f98d64b06a0c7ad6e97d08b2 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 11:12:01 -0800 Subject: [PATCH 091/184] expand testing --- .github/workflows/ci_tests.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index daa108060..495192a81 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -71,9 +71,9 @@ jobs: - python: 3.10 optional_mpl_dependency: -mpl # mpllocal and mplaccept require the MPL to be installed - - python: mpllocal + - category: mpllocal optional_mpl_dependency: "" - - python: mplaccept + - category: mplaccept optional_mpl_dependency: "" steps: # Support long Dafny filenames (used in MPL and DBESDK repos) From c582888a3f064ab1ab258f9c8bcb8fac80df6a36 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 11:16:22 -0800 Subject: [PATCH 092/184] expand testing --- tox.ini | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tox.ini b/tox.ini index e5a585ca5..cee1e35a4 100644 --- a/tox.ini +++ b/tox.ini @@ -15,7 +15,7 @@ envlist = # These must be separate from the above target. # These require the `-mpl` suffix so tox installs the MPL. # The `mpl` prefix runs only MPL-specific tests - py{311,312}-mpl{local,integ,accept,examples}-mpl + py{311,312}-mpl{local,examples}-mpl nocmk, bandit, doc8, readme, docs, {flake8,pylint}{,-tests,-examples}, @@ -87,11 +87,9 @@ commands = # MPL unit tests require the MPL to be installed mpllocal: {[testenv:base-command]commands} test/unit/mpl/ -m local integ: {[testenv:base-command]commands} test/ -m integ --ignore test/unit/mpl/ - # MPL integ tests require the MPL to be installed - mplinteg: {[testenv:base-command]commands} test/unit/mpl -m integ + # No MPL-specific integ tests accept: {[testenv:base-command]commands} test/ -m accept --ignore test/unit/mpl/ - # MPL accept tests require the MPL to be installed - mplaccept: {[testenv:base-command]commands} test/unit/mpl -m accept + # No MPL-specific accept tests examples: {[testenv:base-command]commands} examples/test/ -m examples --ignore examples/test/keyrings/ # MPL keyring examples require a special IAM role; run these separately under a separate set of permissions mplexamples: {[testenv:base-command]commands} examples/test/keyrings -m examples From 5ae44f5a077a240c1a9cd49bd49b08d165a4ad6c Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 11:19:22 -0800 Subject: [PATCH 093/184] expand testing --- .github/workflows/ci_tests.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index 495192a81..803d4741e 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -45,7 +45,6 @@ jobs: - local - accept - mpllocal - - mplaccept # These require credentials. # Enable them once we sort how to provide them. # - integ @@ -70,11 +69,9 @@ jobs: optional_mpl_dependency: -mpl - python: 3.10 optional_mpl_dependency: -mpl - # mpllocal and mplaccept require the MPL to be installed + # mpllocal requires the MPL to be installed - category: mpllocal optional_mpl_dependency: "" - - category: mplaccept - optional_mpl_dependency: "" steps: # Support long Dafny filenames (used in MPL and DBESDK repos) - name: Support longpaths From cb7e3d1c8c8a58548ef03cdbc22f663d676c179f Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 11:45:49 -0800 Subject: [PATCH 094/184] cleanup --- codebuild/coverage/coverage_mpl.yml | 2 +- examples/src/keyrings/hierarchical_keyring.py | 60 ++++++------------- examples/src/keyrings/module_.py | 2 +- examples/src/module_.py | 2 +- .../unit/test_crypto_authentication_signer.py | 6 +- test_vector_handlers/tox.ini | 4 +- tox.ini | 3 +- 7 files changed, 27 insertions(+), 52 deletions(-) diff --git a/codebuild/coverage/coverage_mpl.yml b/codebuild/coverage/coverage_mpl.yml index 5dcc65382..922705569 100644 --- a/codebuild/coverage/coverage_mpl.yml +++ b/codebuild/coverage/coverage_mpl.yml @@ -7,7 +7,7 @@ env: phases: install: runtime-versions: - python: latest + python: 3.11 build: commands: - pip install "tox < 4.0" diff --git a/examples/src/keyrings/hierarchical_keyring.py b/examples/src/keyrings/hierarchical_keyring.py index 50f620456..8f8707013 100644 --- a/examples/src/keyrings/hierarchical_keyring.py +++ b/examples/src/keyrings/hierarchical_keyring.py @@ -15,8 +15,6 @@ CacheTypeDefault, CreateAwsKmsHierarchicalKeyringInput, DefaultCache, - GetBranchKeyIdInput, - GetBranchKeyIdOutput, ) from aws_cryptographic_materialproviders.mpl.references import IBranchKeyIdSupplier, IKeyring from typing import Dict @@ -25,6 +23,8 @@ from aws_encryption_sdk import CommitmentPolicy from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError +from .example_branch_key_id_supplier import ExampleBranchKeyIdSupplier + module_root_dir = '/'.join(__file__.split("/")[:-1]) sys.path.append(module_root_dir) @@ -73,39 +73,6 @@ def encrypt_and_decrypt_with_keyring( branch_key_id_A: str = keystore.create_key(input=CreateKeyInput()).branch_key_identifier branch_key_id_B: str = keystore.create_key(input=CreateKeyInput()).branch_key_identifier - class ExampleBranchKeyIdSupplier(IBranchKeyIdSupplier): - """Example implementation of a branch key ID supplier.""" - - branch_key_id_for_tenant_A: str - branch_key_id_for_tenant_B: str - - def __init__(self, tenant_1_id, tenant_2_id): - self.branch_key_id_for_tenant_A = tenant_1_id - self.branch_key_id_for_tenant_B = tenant_2_id - - def get_branch_key_id( - self, - # Change this to `native_input` - input: GetBranchKeyIdInput # noqa pylint: disable=redefined-builtin - ) -> GetBranchKeyIdOutput: - """Returns branch key ID from the tenant ID in input's encryption context.""" - encryption_context: Dict[str, str] = input.encryption_context - - if b"tenant" not in encryption_context: - raise ValueError("EncryptionContext invalid, does not contain expected tenant key value pair.") - - tenant_key_id: str = encryption_context.get(b"tenant") - branch_key_id: str - - if tenant_key_id == b"TenantA": - branch_key_id = self.branch_key_id_for_tenant_A - elif tenant_key_id == b"TenantB": - branch_key_id = self.branch_key_id_for_tenant_B - else: - raise ValueError(f"Item does not contain valid tenant ID: {tenant_key_id=}") - - return GetBranchKeyIdOutput(branch_key_id=branch_key_id) - # 5. Create a branch key supplier that maps the branch key id to a more readable format branch_key_id_supplier: IBranchKeyIdSupplier = ExampleBranchKeyIdSupplier( tenant_1_id=branch_key_id_A, @@ -132,8 +99,10 @@ def get_branch_key_id( input=keyring_input ) - # The Branch Key Id supplier uses the encryption context to determine which branch key id will - # be used to encrypt data. + # 7. Create encryption context for both tenants. + # The Branch Key Id supplier uses the encryption context to determine which branch key id will + # be used to encrypt data. + # Create encryption context for TenantA encryption_context_A: Dict[str, str] = { "tenant": "TenantA", @@ -154,7 +123,7 @@ def get_branch_key_id( "the data you are handling": "is what you think it is", } - # Encrypt the data for encryptionContextA & encryptionContextB + # 8. Encrypt the data for encryptionContextA & encryptionContextB ciphertext_A, _ = client.encrypt( source=EXAMPLE_DATA, keyring=hierarchical_keyring, @@ -166,8 +135,8 @@ def get_branch_key_id( encryption_context=encryption_context_B ) - # To attest that TenantKeyB cannot decrypt a message written by TenantKeyA - # let's construct more restrictive hierarchical keyrings. + # 9. To attest that TenantKeyB cannot decrypt a message written by TenantKeyA, + # let's construct more restrictive hierarchical keyrings. keyring_input_A: CreateAwsKmsHierarchicalKeyringInput = CreateAwsKmsHierarchicalKeyringInput( key_store=keystore, branch_key_id=branch_key_id_A, @@ -198,6 +167,11 @@ def get_branch_key_id( input=keyring_input_B ) + # 10. Demonstrate that data encrypted by one tenant's key + # cannot be decrypted with by a keyring specific to another tenant. + + # Keyring with tenant B's branch key cannot decrypt data encrypted with tenant A's branch key + # This will fail and raise a AWSEncryptionSDKClientError, which we swallow ONLY for demonstration purposes. try: client.decrypt( source=ciphertext_A, @@ -206,7 +180,8 @@ def get_branch_key_id( except AWSEncryptionSDKClientError: pass - # This should fail + # Keyring with tenant A's branch key cannot decrypt data encrypted with tenant B's branch key. + # This will fail and raise a AWSEncryptionSDKClientError, which we swallow ONLY for demonstration purposes. try: client.decrypt( source=ciphertext_B, @@ -215,7 +190,8 @@ def get_branch_key_id( except AWSEncryptionSDKClientError: pass - # These should succeed + # 10. Demonstrate that data encrypted by one tenant's branch key can be decrypted by that tenant, + # and that the decrypted data matches the input data. plaintext_bytes_A, _ = client.decrypt( source=ciphertext_A, keyring=hierarchical_keyring_A diff --git a/examples/src/keyrings/module_.py b/examples/src/keyrings/module_.py index d9a8c058f..3e8d3062a 100644 --- a/examples/src/keyrings/module_.py +++ b/examples/src/keyrings/module_.py @@ -1 +1 @@ -"""Should remove this.""" +"""Should remove this once PYTHONPATH issues are resolved by adding doo files.""" diff --git a/examples/src/module_.py b/examples/src/module_.py index d9a8c058f..3e8d3062a 100644 --- a/examples/src/module_.py +++ b/examples/src/module_.py @@ -1 +1 @@ -"""Should remove this.""" +"""Should remove this once PYTHONPATH issues are resolved by adding doo files.""" diff --git a/test/unit/test_crypto_authentication_signer.py b/test/unit/test_crypto_authentication_signer.py index 2e5f5a4fd..bd7227fd3 100644 --- a/test/unit/test_crypto_authentication_signer.py +++ b/test/unit/test_crypto_authentication_signer.py @@ -81,8 +81,10 @@ def test_signer_from_key_bytes(patch_default_backend, patch_serialization, patch mock_algorithm_info = MagicMock(return_value=sentinel.algorithm_info, spec=patch_ec.EllipticCurve) _algorithm = MagicMock(signing_algorithm_info=mock_algorithm_info) - # signer = Signer.from_key_bytes(algorithm=_algorithm, key_bytes=sentinel.key_bytes) - + # Explicitly pass in patched serialization module. + # Patching the module introduces namespace issues + # which causes the method's `isinstance` checks to fail + # by changing the namespace from `serialization.Encoding.DER` to `Encoding.DER`. signer = Signer.from_key_bytes( algorithm=_algorithm, key_bytes=sentinel.key_bytes, diff --git a/test_vector_handlers/tox.ini b/test_vector_handlers/tox.ini index 7004080e3..df2707f6a 100644 --- a/test_vector_handlers/tox.ini +++ b/test_vector_handlers/tox.ini @@ -2,7 +2,7 @@ envlist = # The test vectors depend on new features now, # so until release we can only effectively test the local version of the ESDK. - py{37,38,39,310}-awses_local{,-mpl}, + py{37,38,39,310}-awses_local # 1.2.0 and 1.2.max are being difficult because of attrs bandit, doc8, readme, {flake8,pylint}{,-tests}, @@ -48,8 +48,6 @@ passenv = sitepackages = False deps = -rtest/requirements.txt - # install the MPL if in environment - mpl: -r../requirements_mpl.txt .. commands = {[testenv:base-command]commands} diff --git a/tox.ini b/tox.ini index cee1e35a4..ae30f3122 100644 --- a/tox.ini +++ b/tox.ini @@ -59,7 +59,6 @@ envlist = commands = pytest --basetemp={envtmpdir} -l {posargs} [testenv] -; passenv = AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID,AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2,AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_1,AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_2,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKEN,AWS_CONTAINER_CREDENTIALS_RELATIVE_URI,AWS_PROFILE,PIP_CONFIG_FILE passenv = # Identifies AWS KMS key id to use in integration tests AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID \ @@ -80,7 +79,7 @@ passenv = sitepackages = False deps = -rdev_requirements/test-requirements.txt - # install the MPL if in environment + # install the MPL requirements if the `-mpl` suffix is present mpl: -rrequirements_mpl.txt commands = local: {[testenv:base-command]commands} test/ -m local --ignore test/unit/mpl/ From b026b532b59b177cfb63be0019c508d829b41aea Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 11:53:41 -0800 Subject: [PATCH 095/184] cleanup --- .../internal/mpl/cmm_handler.py | 1 - .../internal/mpl/materials_handlers.py | 6 --- .../internal/utils/__init__.py | 18 +++++++++ src/aws_encryption_sdk/streaming_client.py | 37 +++++++------------ 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/aws_encryption_sdk/internal/mpl/cmm_handler.py b/src/aws_encryption_sdk/internal/mpl/cmm_handler.py index 9789651e5..1575e0187 100644 --- a/src/aws_encryption_sdk/internal/mpl/cmm_handler.py +++ b/src/aws_encryption_sdk/internal/mpl/cmm_handler.py @@ -29,7 +29,6 @@ from aws_encryption_sdk.structures import EncryptedDataKey as Native_EncryptedDataKey -# TODO-MPL Should this implement interface...? seems like yes since it implements all of interface methods class CMMHandler(CryptoMaterialsManager): """ In instances where encryption materials may be provided by either diff --git a/src/aws_encryption_sdk/internal/mpl/materials_handlers.py b/src/aws_encryption_sdk/internal/mpl/materials_handlers.py index bf32c2718..79312f863 100644 --- a/src/aws_encryption_sdk/internal/mpl/materials_handlers.py +++ b/src/aws_encryption_sdk/internal/mpl/materials_handlers.py @@ -93,9 +93,6 @@ def data_encryption_key(self) -> DataKey: if hasattr(self, "native_materials"): return self.native_materials.data_encryption_key else: - # TODO-MPL This impl is probably wrong, but works for for now - # If this works for all features, great! Remove this comment before launch. - # Otherwise, fix the implementation. mpl_dek = self.mpl_materials.plaintext_data_key return DataKey( # key_provider is unused, but the return type is DataKey @@ -149,9 +146,6 @@ def data_key(self) -> DataKey: if hasattr(self, "native_materials"): return self.native_materials.data_key else: - # TODO-MPL This impl is probably wrong, but works for for now - # If this works for all features, great! Remove this comment before launch. - # Otherwise, fix the implementation. return DataKey( key_provider=MasterKeyInfo( provider_id="", diff --git a/src/aws_encryption_sdk/internal/utils/__init__.py b/src/aws_encryption_sdk/internal/utils/__init__.py index dac38ac73..b65f6df0f 100644 --- a/src/aws_encryption_sdk/internal/utils/__init__.py +++ b/src/aws_encryption_sdk/internal/utils/__init__.py @@ -163,3 +163,21 @@ def source_data_key_length_check(source_data_key, algorithm): actual=len(source_data_key.data_key), required=algorithm.kdf_input_len ) ) + +def exactly_one_arg_is_not_none(*args): + """ + Helper function for internal ESDK logic. + Returns `True` if exactly one item in the list is not `None`. + Returns `False` otherwise. + """ + # Have not found any `not None` + found_one = False + for arg in args: + if arg is not None: + if found_one is False: + # Have not already found a `not None`, found a `not None` => only one `not None` (so far) + found_one = True + else: + # Already found a `not None`, found another `not None` => not exactly one `not None` + return False + return found_one \ No newline at end of file diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index ad998088c..72f18c117 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -59,6 +59,7 @@ serialize_non_framed_open, ) from aws_encryption_sdk.internal.mpl.cmm_handler import CMMHandler +from aws_encryption_sdk.internal.utils import exactly_one_arg_is_not_none from aws_encryption_sdk.internal.utils.commitment import ( validate_commitment_policy_on_decrypt, validate_commitment_policy_on_encrypt, @@ -84,25 +85,6 @@ _LOGGER = logging.getLogger(__name__) -def _exactly_one_arg_is_not_none(*args): - """ - Private helper function. - Returns `True` if exactly one item in the list is not `None`. - Returns `False` otherwise. - """ - # Have not found any `not None` - found_one = False - for arg in args: - if arg is not None: - if found_one is False: - # Have not already found a `not None`, found a `not None` => only one `not None` (so far) - found_one = True - else: - # Already found a `not None`, found another `not None` => not exactly one `not None` - return False - return found_one - - @attr.s(hash=True) # pylint: disable=too-many-instance-attributes @six.add_metaclass(abc.ABCMeta) class _ClientConfig(object): # pylint: disable=too-many-instance-attributes @@ -147,6 +129,7 @@ class _ClientConfig(object): # pylint: disable=too-many-instance-attributes hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(MasterKeyProvider)) ) if _HAS_MPL: + # Keyrings are only available if the MPL is installed in the runtime keyring = attr.ib( hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(IKeyring)) ) @@ -158,10 +141,13 @@ class _ClientConfig(object): # pylint: disable=too-many-instance-attributes ) # DEPRECATED: Value is no longer configurable here. Parameter left here to avoid breaking consumers. def _has_mpl_attrs_post_init(self): + """If the MPL is present in the runtime, perform MPL-specific post-init logic + to validate the new object has a valid state. + """ if not hasattr(self, "keyring"): self._no_mpl_attrs_post_init() return - if not _exactly_one_arg_is_not_none(self.materials_manager, self.key_provider, self.keyring): + if not exactly_one_arg_is_not_none(self.materials_manager, self.key_provider, self.keyring): raise TypeError("Exactly one of keyring, materials_manager, or key_provider must be provided") if self.materials_manager is None: if self.key_provider is not None: @@ -187,6 +173,9 @@ def _has_mpl_attrs_post_init(self): self.materials_manager = cmm_handler def _no_mpl_attrs_post_init(self): + """If the MPL is NOT present in the runtime, perform post-init logic + to validate the new object has a valid state. + """ both_cmm_and_mkp_defined = self.materials_manager is not None and self.key_provider is not None neither_cmm_nor_mkp_defined = self.materials_manager is None and self.key_provider is None @@ -560,8 +549,8 @@ def _prep_message(self): if self._encryption_materials.signing_key is None: self.signer = None else: - # MPL verification key is NOT key bytes, it is bytes of the compressed point - # TODO-MPL: clean this up, least-privilege violation. + # MPL verification key is PEM bytes, not DER bytes. + # If the underlying CMM is from the MPL, load PEM bytes. if (isinstance(self.config.materials_manager, CMMHandler) and hasattr(self.config.materials_manager, "mpl_cmm")): self.signer = Signer.from_key_bytes( @@ -928,8 +917,8 @@ def _read_header(self): if decryption_materials.verification_key is None: self.verifier = None else: - # MPL verification key is NOT key bytes, it is bytes of the compressed point - # TODO-MPL: clean this up, least-privilege violation. + # MPL verification key is NOT key bytes; it is bytes of the compressed point. + # If the underlying CMM is from the MPL, load PEM bytes. if (isinstance(self.config.materials_manager, CMMHandler) and hasattr(self.config.materials_manager, "mpl_cmm")): self.verifier = Verifier.from_encoded_point( From 50afa3ade9d8c5f2712392b925fe3b621e97ba4d Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 11:54:25 -0800 Subject: [PATCH 096/184] cleanup --- src/aws_encryption_sdk/streaming_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 72f18c117..032ed7d15 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -918,7 +918,7 @@ def _read_header(self): self.verifier = None else: # MPL verification key is NOT key bytes; it is bytes of the compressed point. - # If the underlying CMM is from the MPL, load PEM bytes. + # If the underlying CMM is from the MPL, load bytes from encoded point. if (isinstance(self.config.materials_manager, CMMHandler) and hasattr(self.config.materials_manager, "mpl_cmm")): self.verifier = Verifier.from_encoded_point( From 1c612a0b9e9cc85bae1f71a7d1027d2901c5de82 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 12:01:11 -0800 Subject: [PATCH 097/184] cleanup --- examples/src/keyrings/hierarchical_keyring.py | 2 +- src/aws_encryption_sdk/internal/utils/__init__.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/src/keyrings/hierarchical_keyring.py b/examples/src/keyrings/hierarchical_keyring.py index 8f8707013..c71719346 100644 --- a/examples/src/keyrings/hierarchical_keyring.py +++ b/examples/src/keyrings/hierarchical_keyring.py @@ -169,7 +169,7 @@ def encrypt_and_decrypt_with_keyring( # 10. Demonstrate that data encrypted by one tenant's key # cannot be decrypted with by a keyring specific to another tenant. - + # Keyring with tenant B's branch key cannot decrypt data encrypted with tenant A's branch key # This will fail and raise a AWSEncryptionSDKClientError, which we swallow ONLY for demonstration purposes. try: diff --git a/src/aws_encryption_sdk/internal/utils/__init__.py b/src/aws_encryption_sdk/internal/utils/__init__.py index b65f6df0f..b08121281 100644 --- a/src/aws_encryption_sdk/internal/utils/__init__.py +++ b/src/aws_encryption_sdk/internal/utils/__init__.py @@ -164,11 +164,15 @@ def source_data_key_length_check(source_data_key, algorithm): ) ) + def exactly_one_arg_is_not_none(*args): """ Helper function for internal ESDK logic. - Returns `True` if exactly one item in the list is not `None`. + Returns `True` if exactly one item in the provided arguments is not `None`. Returns `False` otherwise. + + :param args: Input arguments to check + :returns: `True` if exactly one item in the provided arguments is not `None`; `False` otherwise """ # Have not found any `not None` found_one = False @@ -180,4 +184,4 @@ def exactly_one_arg_is_not_none(*args): else: # Already found a `not None`, found another `not None` => not exactly one `not None` return False - return found_one \ No newline at end of file + return found_one From bcdb4ba37f189d8fe6407c66f3ee3ccb1dbc7ebe Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 12:19:22 -0800 Subject: [PATCH 098/184] add missing file --- .../example_branch_key_id_supplier.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 examples/src/keyrings/example_branch_key_id_supplier.py diff --git a/examples/src/keyrings/example_branch_key_id_supplier.py b/examples/src/keyrings/example_branch_key_id_supplier.py new file mode 100644 index 000000000..a3ef0df6f --- /dev/null +++ b/examples/src/keyrings/example_branch_key_id_supplier.py @@ -0,0 +1,37 @@ +from aws_cryptographic_materialproviders.mpl.models import GetBranchKeyIdInput, GetBranchKeyIdOutput +from aws_cryptographic_materialproviders.mpl.references import IBranchKeyIdSupplier +from typing import Dict + + +class ExampleBranchKeyIdSupplier(IBranchKeyIdSupplier): + """Example implementation of a branch key ID supplier.""" + + branch_key_id_for_tenant_A: str + branch_key_id_for_tenant_B: str + + def __init__(self, tenant_1_id, tenant_2_id): + self.branch_key_id_for_tenant_A = tenant_1_id + self.branch_key_id_for_tenant_B = tenant_2_id + + def get_branch_key_id( + self, + # Change this to `native_input` + input: GetBranchKeyIdInput # noqa pylint: disable=redefined-builtin + ) -> GetBranchKeyIdOutput: + """Returns branch key ID from the tenant ID in input's encryption context.""" + encryption_context: Dict[str, str] = input.encryption_context + + if b"tenant" not in encryption_context: + raise ValueError("EncryptionContext invalid, does not contain expected tenant key value pair.") + + tenant_key_id: str = encryption_context.get(b"tenant") + branch_key_id: str + + if tenant_key_id == b"TenantA": + branch_key_id = self.branch_key_id_for_tenant_A + elif tenant_key_id == b"TenantB": + branch_key_id = self.branch_key_id_for_tenant_B + else: + raise ValueError(f"Item does not contain valid tenant ID: {tenant_key_id=}") + + return GetBranchKeyIdOutput(branch_key_id=branch_key_id) \ No newline at end of file From 41fe2f9facf04bbbdf0ccf0168c20aa9e27e059c Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 12:26:47 -0800 Subject: [PATCH 099/184] add missing file --- test_vector_handlers/tox.ini | 3 +++ tox.ini | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/test_vector_handlers/tox.ini b/test_vector_handlers/tox.ini index df2707f6a..e5e467d8a 100644 --- a/test_vector_handlers/tox.ini +++ b/test_vector_handlers/tox.ini @@ -3,6 +3,7 @@ envlist = # The test vectors depend on new features now, # so until release we can only effectively test the local version of the ESDK. py{37,38,39,310}-awses_local + py{311,312}-awses_local{,-mpl} # 1.2.0 and 1.2.max are being difficult because of attrs bandit, doc8, readme, {flake8,pylint}{,-tests}, @@ -48,6 +49,8 @@ passenv = sitepackages = False deps = -rtest/requirements.txt + # Install the MPL requirements if the `-mpl` suffix is present + mpl: -rrequirements_mpl.txt .. commands = {[testenv:base-command]commands} diff --git a/tox.ini b/tox.ini index ae30f3122..72e8ec9fa 100644 --- a/tox.ini +++ b/tox.ini @@ -79,7 +79,7 @@ passenv = sitepackages = False deps = -rdev_requirements/test-requirements.txt - # install the MPL requirements if the `-mpl` suffix is present + # Install the MPL requirements if the `-mpl` suffix is present mpl: -rrequirements_mpl.txt commands = local: {[testenv:base-command]commands} test/ -m local --ignore test/unit/mpl/ From 1ba857e74a1e7e117a8208ac21e235d1c5d2e18a Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 12:31:48 -0800 Subject: [PATCH 100/184] add missing file --- test_vector_handlers/tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_vector_handlers/tox.ini b/test_vector_handlers/tox.ini index e5e467d8a..580b641e0 100644 --- a/test_vector_handlers/tox.ini +++ b/test_vector_handlers/tox.ini @@ -50,7 +50,7 @@ sitepackages = False deps = -rtest/requirements.txt # Install the MPL requirements if the `-mpl` suffix is present - mpl: -rrequirements_mpl.txt + mpl: -r../requirements_mpl.txt .. commands = {[testenv:base-command]commands} From 74bfe127d6ea86de4637b1dfea3621f703bff0cd Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 21 Feb 2024 12:38:32 -0800 Subject: [PATCH 101/184] cleanup --- examples/src/keyrings/example_branch_key_id_supplier.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/src/keyrings/example_branch_key_id_supplier.py b/examples/src/keyrings/example_branch_key_id_supplier.py index a3ef0df6f..a06280fa1 100644 --- a/examples/src/keyrings/example_branch_key_id_supplier.py +++ b/examples/src/keyrings/example_branch_key_id_supplier.py @@ -1,3 +1,5 @@ +"""Example implementation of a branch key ID supplier.""" + from aws_cryptographic_materialproviders.mpl.models import GetBranchKeyIdInput, GetBranchKeyIdOutput from aws_cryptographic_materialproviders.mpl.references import IBranchKeyIdSupplier from typing import Dict @@ -10,12 +12,13 @@ class ExampleBranchKeyIdSupplier(IBranchKeyIdSupplier): branch_key_id_for_tenant_B: str def __init__(self, tenant_1_id, tenant_2_id): + """Example constructor for a branch key ID supplier.""" self.branch_key_id_for_tenant_A = tenant_1_id self.branch_key_id_for_tenant_B = tenant_2_id def get_branch_key_id( self, - # Change this to `native_input` + # TODO-MPL: Change this to `native_input` in Smithy-Dafny input: GetBranchKeyIdInput # noqa pylint: disable=redefined-builtin ) -> GetBranchKeyIdOutput: """Returns branch key ID from the tenant ID in input's encryption context.""" @@ -34,4 +37,4 @@ def get_branch_key_id( else: raise ValueError(f"Item does not contain valid tenant ID: {tenant_key_id=}") - return GetBranchKeyIdOutput(branch_key_id=branch_key_id) \ No newline at end of file + return GetBranchKeyIdOutput(branch_key_id=branch_key_id) From b3b9a0ffd82d962ef4ccea15813b1ba09d6aac3d Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Thu, 22 Feb 2024 12:07:24 -0800 Subject: [PATCH 102/184] refactor --- .github/workflows/ci_tests.yaml | 1 + .../internal/mpl/cmm_handler.py | 157 ----------------- .../internal/mpl/materials_handlers.py | 164 ------------------ src/aws_encryption_sdk/streaming_client.py | 10 +- test/unit/mpl/README.md | 1 - test/unit/mpl/test_cmm_handler.py | 97 ----------- 6 files changed, 5 insertions(+), 425 deletions(-) delete mode 100644 src/aws_encryption_sdk/internal/mpl/cmm_handler.py delete mode 100644 src/aws_encryption_sdk/internal/mpl/materials_handlers.py delete mode 100644 test/unit/mpl/README.md delete mode 100644 test/unit/mpl/test_cmm_handler.py diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index 803d4741e..3a6b16d45 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -27,6 +27,7 @@ jobs: - ubuntu-latest # Windows fails due to "No module named 'Wrappers'" # This SHOULD be fixed once Dafny generates fully-qualified import statements + # (i.e. doo files, per-package module names) # Disable for now # - windows-latest - macos-latest diff --git a/src/aws_encryption_sdk/internal/mpl/cmm_handler.py b/src/aws_encryption_sdk/internal/mpl/cmm_handler.py deleted file mode 100644 index 1575e0187..000000000 --- a/src/aws_encryption_sdk/internal/mpl/cmm_handler.py +++ /dev/null @@ -1,157 +0,0 @@ -"""Retrieves encryption/decryption materials from an underlying materials provider.""" - -# These dependencies are only loaded if you install the MPL. -try: - # pylint seems to struggle with this conditional import - # pylint: disable=unused-import - from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException - from aws_cryptographic_materialproviders.mpl.models import ( - AlgorithmSuiteIdESDK, - CommitmentPolicyESDK, - DecryptMaterialsInput, - DecryptMaterialsOutput, - EncryptedDataKey as MPL_EncryptedDataKey, - GetEncryptionMaterialsInput, - GetEncryptionMaterialsOutput, - ) - from aws_cryptographic_materialproviders.mpl.references import ICryptographicMaterialsManager - -except ImportError: - pass - -from typing import List - -from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError -from aws_encryption_sdk.identifiers import CommitmentPolicy -from aws_encryption_sdk.internal.mpl.materials_handlers import DecryptionMaterialsHandler, EncryptionMaterialsHandler -from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest -from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager -from aws_encryption_sdk.structures import EncryptedDataKey as Native_EncryptedDataKey - - -class CMMHandler(CryptoMaterialsManager): - """ - In instances where encryption materials may be provided by either - an implementation of the native - `aws_encryption_sdk.materials_managers.base.CryptoMaterialsManager` - or an implementation of the MPL's - `aws_cryptographic_materialproviders.mpl.references.ICryptographicMaterialsManager`, - this provides the correct materials based on the underlying materials manager. - """ - - native_cmm: CryptoMaterialsManager - mpl_cmm: 'ICryptographicMaterialsManager' - - def _is_using_native_cmm(self): - return hasattr(self, "native_cmm") and not hasattr(self, "mpl_cmm") - - def __init__( - self, - cmm: 'CryptoMaterialsManager | ICryptographicMaterialsManager' - ): - """ - Create DecryptionMaterialsHandler. - :param cmm: Underlying cryptographic materials manager - """ - if isinstance(cmm, CryptoMaterialsManager): - self.native_cmm = cmm - elif isinstance(cmm, ICryptographicMaterialsManager): - self.mpl_cmm = cmm - else: - raise ValueError(f"Invalid CMM passed to CMMHandler. cmm: {cmm}") - - def get_encryption_materials( - self, - request: EncryptionMaterialsRequest - ) -> EncryptionMaterialsHandler: - """ - Returns an EncryptionMaterialsHandler for the configured CMM. - :param request: Request for encryption materials - """ - if self._is_using_native_cmm(): - return EncryptionMaterialsHandler(self.native_cmm.get_encryption_materials(request)) - else: - try: - mpl_input: GetEncryptionMaterialsInput = CMMHandler._native_to_mpl_get_encryption_materials( - request - ) - mpl_output: GetEncryptionMaterialsOutput = self.mpl_cmm.get_encryption_materials(mpl_input) - return EncryptionMaterialsHandler(mpl_output.encryption_materials) - except AwsCryptographicMaterialProvidersException as mpl_exception: - # Wrap MPL error into the ESDK error type - # so customers only have to catch ESDK error types. - raise AWSEncryptionSDKClientError(mpl_exception) - - @staticmethod - def _native_to_mpl_get_encryption_materials( - request: EncryptionMaterialsRequest - ) -> 'GetEncryptionMaterialsInput': - output: GetEncryptionMaterialsInput = GetEncryptionMaterialsInput( - encryption_context=request.encryption_context, - commitment_policy=CMMHandler._native_to_mpl_commmitment_policy( - request.commitment_policy - ), - max_plaintext_length=request.plaintext_length, - ) - return output - - @staticmethod - def _native_to_mpl_commmitment_policy( - native_commitment_policy: CommitmentPolicy - ) -> 'CommitmentPolicyESDK': - if native_commitment_policy == CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT: - return CommitmentPolicyESDK(value="FORBID_ENCRYPT_ALLOW_DECRYPT") - elif native_commitment_policy == CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT: - return CommitmentPolicyESDK(value="REQUIRE_ENCRYPT_ALLOW_DECRYPT") - elif native_commitment_policy == CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT: - return CommitmentPolicyESDK(value="REQUIRE_ENCRYPT_REQUIRE_DECRYPT") - else: - raise ValueError(f"Invalid native_commitment_policy: {native_commitment_policy}") - - def decrypt_materials( - self, - request: DecryptionMaterialsRequest - ) -> DecryptionMaterialsHandler: - """ - Returns a DecryptionMaterialsHandler for the configured CMM. - :param request: Request for decryption materials - """ - if self._is_using_native_cmm(): - return DecryptionMaterialsHandler(self.native_cmm.decrypt_materials(request)) - else: - try: - mpl_input: 'DecryptMaterialsInput' = \ - CMMHandler._create_mpl_decrypt_materials_input_from_request(request) - mpl_output: 'DecryptMaterialsOutput' = self.mpl_cmm.decrypt_materials(mpl_input) - return DecryptionMaterialsHandler(mpl_output.decryption_materials) - except AwsCryptographicMaterialProvidersException as mpl_exception: - # Wrap MPL error into the ESDK error type - # so customers only have to catch ESDK error types. - raise AWSEncryptionSDKClientError(mpl_exception) - - @staticmethod - def _native_algorithm_id_to_mpl_algorithm_id(native_algorithm_id: str) -> 'AlgorithmSuiteIdESDK': - # MPL algorithm suite ID = hexstr(native_algorithm_id) padded to 4 digits post-`x`. - return AlgorithmSuiteIdESDK(f"{native_algorithm_id:#0{6}x}") - - @staticmethod - def _create_mpl_decrypt_materials_input_from_request( - request: DecryptionMaterialsRequest - ) -> 'DecryptMaterialsInput': - key_blob_list: List[Native_EncryptedDataKey] = request.encrypted_data_keys - list_edks = [MPL_EncryptedDataKey( - key_provider_id=key_blob.key_provider.provider_id, - key_provider_info=key_blob.key_provider.key_info, - ciphertext=key_blob.encrypted_data_key, - ) for key_blob in key_blob_list] - output: DecryptMaterialsInput = DecryptMaterialsInput( - algorithm_suite_id=CMMHandler._native_algorithm_id_to_mpl_algorithm_id( - request.algorithm.algorithm_id - ), - commitment_policy=CMMHandler._native_to_mpl_commmitment_policy( - request.commitment_policy - ), - encrypted_data_keys=list_edks, - encryption_context=request.encryption_context, - ) - return output diff --git a/src/aws_encryption_sdk/internal/mpl/materials_handlers.py b/src/aws_encryption_sdk/internal/mpl/materials_handlers.py deleted file mode 100644 index 79312f863..000000000 --- a/src/aws_encryption_sdk/internal/mpl/materials_handlers.py +++ /dev/null @@ -1,164 +0,0 @@ -"""Provides encryption/decryption materials from an underlying materials provider.""" -# These dependencies are only loaded if you install the MPL. -try: - from aws_cryptographic_materialproviders.mpl.models import ( - DecryptionMaterials as MPL_DecryptionMaterials, - EncryptedDataKey as MPL_EncryptedDataKey, - EncryptionMaterials as MPL_EncryptionMaterials, - ) -except ImportError: - pass - -from typing import Dict, List, Set - -from aws_encryption_sdk.identifiers import Algorithm, AlgorithmSuite -from aws_encryption_sdk.materials_managers import ( - DecryptionMaterials as Native_DecryptionMaterials, - EncryptionMaterials as Native_EncryptionMaterials, -) -from aws_encryption_sdk.structures import DataKey, EncryptedDataKey as Native_EncryptedDataKey, MasterKeyInfo - - -def _mpl_algorithm_id_to_native_algorithm_id(mpl_algorithm_id: str): - # MPL algorithm suite ID == hex(native algorithm suite ID) - return int(mpl_algorithm_id, 16) - - -class EncryptionMaterialsHandler: - """ - In instances where encryption materials may be provided by either - the native `aws_encryption_sdk.materials_managers.EncryptionMaterials` - or the MPL's `aws_cryptographic_materialproviders.mpl.models.EncryptionMaterials`, - this provides the correct materials based on the configured materials provider. - """ - - native_materials: Native_EncryptionMaterials - mpl_materials: 'MPL_EncryptionMaterials' - - def __init__( - self, - materials: 'Native_EncryptionMaterials | MPL_EncryptionMaterials' - ): - """ - Create EncryptionMaterialsHandler. - :param materials: Underlying encryption materials - """ - if isinstance(materials, Native_EncryptionMaterials): - self.native_materials = materials - elif isinstance(materials, MPL_EncryptionMaterials): - self.mpl_materials = materials - else: - raise ValueError("Invalid EncryptionMaterials passed to EncryptionMaterialsHandler. " - f"materials: {materials}") - - @property - def algorithm(self) -> Algorithm: - """Materials' native Algorithm.""" - if hasattr(self, "native_materials"): - return self.native_materials.algorithm - else: - return AlgorithmSuite.get_by_id( - _mpl_algorithm_id_to_native_algorithm_id( - self.mpl_materials.algorithm_suite.id.value - ) - ) - - @property - def encryption_context(self) -> Dict[str, str]: - """Materials' encryption context.""" - if hasattr(self, "native_materials"): - return self.native_materials.encryption_context - else: - return self.mpl_materials.encryption_context - - @property - def encrypted_data_keys(self) -> List[Native_EncryptedDataKey]: - """Materials' encrypted data keys.""" - if hasattr(self, "native_materials"): - return self.native_materials.encrypted_data_keys - else: - mpl_edk_list: List[MPL_EncryptedDataKey] = self.mpl_materials.encrypted_data_keys - key_blob_list: Set[Native_EncryptedDataKey] = {Native_EncryptedDataKey( - key_provider=MasterKeyInfo( - provider_id=mpl_edk.key_provider_id, - key_info=mpl_edk.key_provider_info, - ), - encrypted_data_key=mpl_edk.ciphertext, - ) for mpl_edk in mpl_edk_list} - return key_blob_list - - @property - def data_encryption_key(self) -> DataKey: - """Materials' data encryption key.""" - if hasattr(self, "native_materials"): - return self.native_materials.data_encryption_key - else: - mpl_dek = self.mpl_materials.plaintext_data_key - return DataKey( - # key_provider is unused, but the return type is DataKey - key_provider=MasterKeyInfo( - provider_id="", - key_info=b'' - ), - data_key=mpl_dek, - encrypted_data_key=b'', # No encrypted DEK - ) - - @property - def signing_key(self) -> bytes: - """Materials' signing key.""" - if hasattr(self, "native_materials"): - return self.native_materials.signing_key - else: - return self.mpl_materials.signing_key - - -class DecryptionMaterialsHandler: - """ - In instances where decryption materials may be provided by either - the native `aws_encryption_sdk.materials_managers.DecryptionMaterials` - or the MPL's `aws_cryptographic_materialproviders.mpl.models.DecryptionMaterials`, - this provides the correct materials based on the configured materials provider. - """ - - native_materials: Native_DecryptionMaterials - mpl_materials: 'MPL_DecryptionMaterials' - - def __init__( - self, - materials: 'Native_DecryptionMaterials | MPL_DecryptionMaterials' - ): - """ - Create DecryptionMaterialsHandler. - :param materials: Underlying decryption materials - """ - if isinstance(materials, Native_DecryptionMaterials): - self.native_materials = materials - elif isinstance(materials, MPL_DecryptionMaterials): - self.mpl_materials = materials - else: - raise ValueError(f"Invalid DecryptionMaterials passed to DecryptionMaterialsHandler.\ - materials: {materials}") - - @property - def data_key(self) -> DataKey: - """Materials' data key.""" - if hasattr(self, "native_materials"): - return self.native_materials.data_key - else: - return DataKey( - key_provider=MasterKeyInfo( - provider_id="", - key_info=b'' - ), - data_key=self.mpl_materials.plaintext_data_key, - encrypted_data_key=b'', - ) - - @property - def verification_key(self) -> bytes: - """Materials' verification key.""" - if hasattr(self, "native_materials"): - return self.native_materials.verification_key - else: - return self.mpl_materials.verification_key diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 032ed7d15..61f2f88c6 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -58,7 +58,7 @@ serialize_non_framed_close, serialize_non_framed_open, ) -from aws_encryption_sdk.internal.mpl.cmm_handler import CMMHandler +from aws_encryption_sdk.materials_managers.mpl.cmm import MPLCMMHandler from aws_encryption_sdk.internal.utils import exactly_one_arg_is_not_none from aws_encryption_sdk.internal.utils.commitment import ( validate_commitment_policy_on_decrypt, @@ -169,7 +169,7 @@ def _has_mpl_attrs_post_init(self): keyring=self.keyring ) ) - cmm_handler: CryptoMaterialsManager = CMMHandler(cmm) + cmm_handler: CryptoMaterialsManager = MPLCMMHandler(cmm) self.materials_manager = cmm_handler def _no_mpl_attrs_post_init(self): @@ -551,8 +551,7 @@ def _prep_message(self): else: # MPL verification key is PEM bytes, not DER bytes. # If the underlying CMM is from the MPL, load PEM bytes. - if (isinstance(self.config.materials_manager, CMMHandler) - and hasattr(self.config.materials_manager, "mpl_cmm")): + if (isinstance(self.config.materials_manager, MPLCMMHandler)): self.signer = Signer.from_key_bytes( algorithm=self._encryption_materials.algorithm, key_bytes=self._encryption_materials.signing_key, encoding=serialization.Encoding.PEM, @@ -919,8 +918,7 @@ def _read_header(self): else: # MPL verification key is NOT key bytes; it is bytes of the compressed point. # If the underlying CMM is from the MPL, load bytes from encoded point. - if (isinstance(self.config.materials_manager, CMMHandler) - and hasattr(self.config.materials_manager, "mpl_cmm")): + if (isinstance(self.config.materials_manager, MPLCMMHandler)): self.verifier = Verifier.from_encoded_point( algorithm=header.algorithm, encoded_point=base64.b64encode(decryption_materials.verification_key) diff --git a/test/unit/mpl/README.md b/test/unit/mpl/README.md deleted file mode 100644 index 839feb7a2..000000000 --- a/test/unit/mpl/README.md +++ /dev/null @@ -1 +0,0 @@ -Tests in this file REQUIRE the aws-cryptographic-material-providers module to be installed in order to run. \ No newline at end of file diff --git a/test/unit/mpl/test_cmm_handler.py b/test/unit/mpl/test_cmm_handler.py deleted file mode 100644 index d16374899..000000000 --- a/test/unit/mpl/test_cmm_handler.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. -"""Test suite to verify the cmm_handler module delegates correctly.""" -import pytest -from aws_cryptographic_materialproviders.mpl.models import ( - EncryptionMaterials as MPL_EncryptionMaterials, - GetEncryptionMaterialsInput, - GetEncryptionMaterialsOutput, -) -from aws_cryptographic_materialproviders.mpl.references import ICryptographicMaterialsManager -from mock import MagicMock, patch - -from aws_encryption_sdk.internal.mpl.cmm_handler import CMMHandler -from aws_encryption_sdk.internal.mpl.materials_handlers import EncryptionMaterialsHandler -from aws_encryption_sdk.materials_managers import ( - EncryptionMaterials as Native_EncryptionMaterials, - EncryptionMaterialsRequest, -) -from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager - -mock_native_cmm = MagicMock(__class__=CryptoMaterialsManager) -mock_mpl_cmm = MagicMock(__class__=ICryptographicMaterialsManager) -mock_encryption_materials_request = MagicMock(__class__=EncryptionMaterialsRequest) -mock_encryption_materials_handler = MagicMock(__class__=EncryptionMaterialsHandler) -mock_native_encryption_materials = MagicMock(__class__=Native_EncryptionMaterials) -mock_mpl_encryption_materials = MagicMock(__class__=MPL_EncryptionMaterials) - -pytestmark = [pytest.mark.unit, pytest.mark.local] - - -def test_GIVEN_native_CMM_WHEN_create_CMMHandler_THEN_is_using_native_cmm_returns_True(): - cmm_handler = CMMHandler(cmm=mock_native_cmm) - assert cmm_handler._is_using_native_cmm() - - -def test_GIVEN_mpl_CMM_WHEN_create_CMMHandler_THEN_is_using_native_cmm_returns_False(): - cmm_handler = CMMHandler(cmm=mock_mpl_cmm) - assert not cmm_handler._is_using_native_cmm() - - -def test_GIVEN_unknown_CMM_WHEN_create_CMMHandler_THEN_raise_ValueError(): - with pytest.raises(ValueError): - CMMHandler(cmm="not a CMM") - - -@patch.object(mock_native_cmm, "get_encryption_materials") -def test_GIVEN_native_CMM_WHEN_get_encryption_materials_THEN_return_native_encryption_materials( - mock_get_encryption_materials -): - # Mock: native_cmm.get_encryption_materials returns mock native encryption materials - mock_get_encryption_materials.return_value = mock_native_encryption_materials - - cmm_handler = CMMHandler(cmm=mock_native_cmm) - test = cmm_handler.get_encryption_materials(mock_encryption_materials_request) - - # Verify cmm_handler returns EncryptionMaterialsHandler - assert isinstance(test, EncryptionMaterialsHandler) - # Verify returned EncryptionMaterialsHandler uses the output of `get_encryption_materials` - assert test.native_materials == mock_native_encryption_materials - # Verify we actually called `get_encryption_materials` - mock_native_cmm.get_encryption_materials.assert_called_once_with(mock_encryption_materials_request) - - -@patch.object(mock_mpl_cmm, "get_encryption_materials") -@patch("aws_encryption_sdk.internal.mpl.cmm_handler.CMMHandler._native_to_mpl_get_encryption_materials") -def test_GIVEN_mpl_CMM_WHEN_get_encryption_materials_THEN_return_mpl_encryption_materials( - mock_native_to_mpl_get_encryption_materials, - mock_get_encryption_materials, -): - # Mock: mpl_cmm.get_encryption_materials returns mock MPL encryption materials - mock_get_encryption_materials_output = MagicMock(__class__=GetEncryptionMaterialsOutput) - mock_get_encryption_materials_output.encryption_materials = mock_mpl_encryption_materials - mock_get_encryption_materials.return_value = mock_get_encryption_materials_output - - # Mock: CMMHandler._native_to_mpl_get_encryption_materials creates a GetEncryptionMaterialsInput - mock_get_encryption_materials_input = MagicMock(__class__=GetEncryptionMaterialsInput) - mock_native_to_mpl_get_encryption_materials.return_value = mock_get_encryption_materials_input - - cmm_handler = CMMHandler(cmm=mock_mpl_cmm) - test = cmm_handler.get_encryption_materials(mock_encryption_materials_request) - - # Verify cmm_handler returns EncryptionMaterialsHandler - assert isinstance(test, EncryptionMaterialsHandler) - # Verify returned EncryptionMaterialsHandler uses the output of `get_encryption_materials` - assert test.mpl_materials == mock_mpl_encryption_materials - # Verify we actually called `get_encryption_materials` - mock_mpl_cmm.get_encryption_materials.assert_called_once_with(mock_get_encryption_materials_input) From a594125a635c0741e121d87bf367940b22f7610e Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Thu, 22 Feb 2024 12:07:33 -0800 Subject: [PATCH 103/184] refactor --- .../materials_managers/mpl/__init__.py | 13 ++ .../materials_managers/mpl/cmm.py | 142 ++++++++++++++++++ .../materials_managers/mpl/materials.py | 135 +++++++++++++++++ 3 files changed, 290 insertions(+) create mode 100644 src/aws_encryption_sdk/materials_managers/mpl/__init__.py create mode 100644 src/aws_encryption_sdk/materials_managers/mpl/cmm.py create mode 100644 src/aws_encryption_sdk/materials_managers/mpl/materials.py diff --git a/src/aws_encryption_sdk/materials_managers/mpl/__init__.py b/src/aws_encryption_sdk/materials_managers/mpl/__init__.py new file mode 100644 index 000000000..295400d76 --- /dev/null +++ b/src/aws_encryption_sdk/materials_managers/mpl/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Modules related to the MPL's materials managers interfaces.""" diff --git a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py new file mode 100644 index 000000000..e16b49d51 --- /dev/null +++ b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py @@ -0,0 +1,142 @@ +"""Retrieves encryption/decryption materials from the MPL.""" + +# These dependencies are only loaded if you install the MPL. +try: + # pylint seems to struggle with this conditional import + # pylint: disable=unused-import + from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException + from aws_cryptographic_materialproviders.mpl.models import ( + AlgorithmSuiteIdESDK, + CommitmentPolicyESDK, + DecryptMaterialsInput, + DecryptMaterialsOutput, + EncryptedDataKey as MPL_EncryptedDataKey, + GetEncryptionMaterialsInput, + GetEncryptionMaterialsOutput, + ) + from aws_cryptographic_materialproviders.mpl.references import ICryptographicMaterialsManager + +except ImportError: + pass + +from typing import List + +from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError +from aws_encryption_sdk.identifiers import CommitmentPolicy +from aws_encryption_sdk.materials_managers.mpl.materials import MPLEncryptionMaterials, MPLDecryptionMaterials +from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest +from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager +from aws_encryption_sdk.structures import EncryptedDataKey as Native_EncryptedDataKey + + +class MPLCMMHandler(CryptoMaterialsManager): + """ + In instances where encryption materials are provided by an implementation of the MPL's + `aws_cryptographic_materialproviders.mpl.references.ICryptographicMaterialsManager`, + this maps the ESDK CMM interfaces to the MPL CMM. + """ + + mpl_cmm: 'ICryptographicMaterialsManager' + + def __init__( + self, + mpl_cmm: 'ICryptographicMaterialsManager' + ): + """ + Create DecryptionMaterialsHandler. + :param cmm: Underlying cryptographic materials manager + """ + if isinstance(mpl_cmm, ICryptographicMaterialsManager): + self.mpl_cmm = mpl_cmm + else: + raise ValueError(f"Invalid CMM passed to MPLCMMHandler. cmm: {mpl_cmm}") + + def get_encryption_materials( + self, + request: EncryptionMaterialsRequest + ) -> MPLEncryptionMaterials: + """ + Returns an EncryptionMaterialsHandler for the configured CMM. + :param request: Request for encryption materials + """ + try: + mpl_input: GetEncryptionMaterialsInput = MPLCMMHandler._native_to_mpl_get_encryption_materials( + request + ) + mpl_output: GetEncryptionMaterialsOutput = self.mpl_cmm.get_encryption_materials(mpl_input) + return MPLEncryptionMaterials(mpl_output.encryption_materials) + except AwsCryptographicMaterialProvidersException as mpl_exception: + # Wrap MPL error into the ESDK error type + # so customers only have to catch ESDK error types. + raise AWSEncryptionSDKClientError(mpl_exception) + + @staticmethod + def _native_to_mpl_get_encryption_materials( + request: EncryptionMaterialsRequest + ) -> 'GetEncryptionMaterialsInput': + output: GetEncryptionMaterialsInput = GetEncryptionMaterialsInput( + encryption_context=request.encryption_context, + commitment_policy=MPLCMMHandler._native_to_mpl_commmitment_policy( + request.commitment_policy + ), + max_plaintext_length=request.plaintext_length, + ) + return output + + @staticmethod + def _native_to_mpl_commmitment_policy( + native_commitment_policy: CommitmentPolicy + ) -> 'CommitmentPolicyESDK': + if native_commitment_policy == CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT: + return CommitmentPolicyESDK(value="FORBID_ENCRYPT_ALLOW_DECRYPT") + elif native_commitment_policy == CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT: + return CommitmentPolicyESDK(value="REQUIRE_ENCRYPT_ALLOW_DECRYPT") + elif native_commitment_policy == CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT: + return CommitmentPolicyESDK(value="REQUIRE_ENCRYPT_REQUIRE_DECRYPT") + else: + raise ValueError(f"Invalid native_commitment_policy: {native_commitment_policy}") + + def decrypt_materials( + self, + request: DecryptionMaterialsRequest + ) -> MPLDecryptionMaterials: + """ + Returns a DecryptionMaterialsHandler for the configured CMM. + :param request: Request for decryption materials + """ + try: + mpl_input: 'DecryptMaterialsInput' = \ + MPLCMMHandler._create_mpl_decrypt_materials_input_from_request(request) + mpl_output: 'DecryptMaterialsOutput' = self.mpl_cmm.decrypt_materials(mpl_input) + return MPLDecryptionMaterials(mpl_output.decryption_materials) + except AwsCryptographicMaterialProvidersException as mpl_exception: + # Wrap MPL error into the ESDK error type + # so customers only have to catch ESDK error types. + raise AWSEncryptionSDKClientError(mpl_exception) + + @staticmethod + def _native_algorithm_id_to_mpl_algorithm_id(native_algorithm_id: str) -> 'AlgorithmSuiteIdESDK': + # MPL algorithm suite ID = hexstr(native_algorithm_id) padded to 4 digits post-`x`. + return AlgorithmSuiteIdESDK(f"{native_algorithm_id:#0{6}x}") + + @staticmethod + def _create_mpl_decrypt_materials_input_from_request( + request: DecryptionMaterialsRequest + ) -> 'DecryptMaterialsInput': + key_blob_list: List[Native_EncryptedDataKey] = request.encrypted_data_keys + list_edks = [MPL_EncryptedDataKey( + key_provider_id=key_blob.key_provider.provider_id, + key_provider_info=key_blob.key_provider.key_info, + ciphertext=key_blob.encrypted_data_key, + ) for key_blob in key_blob_list] + output: DecryptMaterialsInput = DecryptMaterialsInput( + algorithm_suite_id=MPLCMMHandler._native_algorithm_id_to_mpl_algorithm_id( + request.algorithm.algorithm_id + ), + commitment_policy=MPLCMMHandler._native_to_mpl_commmitment_policy( + request.commitment_policy + ), + encrypted_data_keys=list_edks, + encryption_context=request.encryption_context, + ) + return output diff --git a/src/aws_encryption_sdk/materials_managers/mpl/materials.py b/src/aws_encryption_sdk/materials_managers/mpl/materials.py new file mode 100644 index 000000000..fdcf2ec06 --- /dev/null +++ b/src/aws_encryption_sdk/materials_managers/mpl/materials.py @@ -0,0 +1,135 @@ +"""Provides encryption/decryption materials from an underlying materials provider.""" +# These dependencies are only loaded if you install the MPL. +try: + from aws_cryptographic_materialproviders.mpl.models import ( + DecryptionMaterials as MPL_DecryptionMaterials, + EncryptedDataKey as MPL_EncryptedDataKey, + EncryptionMaterials as MPL_EncryptionMaterials, + ) +except ImportError: + pass + +from typing import Dict, List, Set + +from aws_encryption_sdk.identifiers import Algorithm, AlgorithmSuite +from aws_encryption_sdk.materials_managers import ( + DecryptionMaterials as Native_DecryptionMaterials, + EncryptionMaterials as Native_EncryptionMaterials, +) +from aws_encryption_sdk.structures import DataKey, EncryptedDataKey as Native_EncryptedDataKey, MasterKeyInfo + + +def _mpl_algorithm_id_to_native_algorithm_id(mpl_algorithm_id: str) -> int: + # MPL algorithm suite ID == hex(native algorithm suite ID) + return int(mpl_algorithm_id, 16) + + +class MPLEncryptionMaterials(Native_EncryptionMaterials): + """ + In instances where encryption materials are be provided by + the MPL's `aws_cryptographic_materialproviders.mpl.models.EncryptionMaterials`, + this maps the ESDK interfaces to the underlying MPL materials. + """ + + mpl_materials: 'MPL_EncryptionMaterials' + + def __init__( + self, + materials: 'MPL_EncryptionMaterials' + ): + """ + Create MPLEncryptionMaterialsHandler. + :param materials: Underlying encryption materials + """ + if isinstance(materials, MPL_EncryptionMaterials): + self.mpl_materials = materials + else: + raise ValueError("Invalid EncryptionMaterials passed to EncryptionMaterialsHandler. " + f"materials: {materials}") + + @property + def algorithm(self) -> Algorithm: + """Materials' native Algorithm.""" + return AlgorithmSuite.get_by_id( + _mpl_algorithm_id_to_native_algorithm_id( + self.mpl_materials.algorithm_suite.id.value + ) + ) + + @property + def encryption_context(self) -> Dict[str, str]: + """Materials' encryption context.""" + return self.mpl_materials.encryption_context + + @property + def encrypted_data_keys(self) -> List[Native_EncryptedDataKey]: + """Materials' encrypted data keys.""" + mpl_edk_list: List[MPL_EncryptedDataKey] = self.mpl_materials.encrypted_data_keys + key_blob_list: Set[Native_EncryptedDataKey] = {Native_EncryptedDataKey( + key_provider=MasterKeyInfo( + provider_id=mpl_edk.key_provider_id, + key_info=mpl_edk.key_provider_info, + ), + encrypted_data_key=mpl_edk.ciphertext, + ) for mpl_edk in mpl_edk_list} + return key_blob_list + + @property + def data_encryption_key(self) -> DataKey: + """Materials' data encryption key.""" + mpl_dek = self.mpl_materials.plaintext_data_key + return DataKey( + # key_provider is unused, but the return type is DataKey + key_provider=MasterKeyInfo( + provider_id="", + key_info=b'' + ), + data_key=mpl_dek, + encrypted_data_key=b'', # No encrypted DEK + ) + + @property + def signing_key(self) -> bytes: + """Materials' signing key.""" + return self.mpl_materials.signing_key + + +class MPLDecryptionMaterials(Native_DecryptionMaterials): + """ + In instances where decryption materials are be provided by + the MPL's `aws_cryptographic_materialproviders.mpl.models.DecryptionMaterials`, + this maps the ESDK interfaces to the underlying MPL materials. + """ + + mpl_materials: 'MPL_DecryptionMaterials' + + def __init__( + self, + materials: 'MPL_DecryptionMaterials' + ): + """ + Create DecryptionMaterialsHandler. + :param materials: Underlying decryption materials + """ + if isinstance(materials, MPL_DecryptionMaterials): + self.mpl_materials = materials + else: + raise ValueError(f"Invalid DecryptionMaterials passed to DecryptionMaterialsHandler.\ + materials: {materials}") + + @property + def data_key(self) -> DataKey: + """Materials' data key.""" + return DataKey( + key_provider=MasterKeyInfo( + provider_id="", + key_info=b'' + ), + data_key=self.mpl_materials.plaintext_data_key, + encrypted_data_key=b'', + ) + + @property + def verification_key(self) -> bytes: + """Materials' verification key.""" + return self.mpl_materials.verification_key From fdd2eda60f42aeea832b7851db1487dfb2784882 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 10:03:34 -0800 Subject: [PATCH 104/184] unit tests --- .../internal/mpl/__init__.py | 13 -------- .../materials_managers/mpl/cmm.py | 20 ++++++----- .../materials_managers/mpl/materials.py | 33 +++++++++++-------- 3 files changed, 32 insertions(+), 34 deletions(-) delete mode 100644 src/aws_encryption_sdk/internal/mpl/__init__.py diff --git a/src/aws_encryption_sdk/internal/mpl/__init__.py b/src/aws_encryption_sdk/internal/mpl/__init__.py deleted file mode 100644 index 41497cc20..000000000 --- a/src/aws_encryption_sdk/internal/mpl/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. -"""Modules related to the MPL.""" diff --git a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py index e16b49d51..cd789b994 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py @@ -15,9 +15,9 @@ GetEncryptionMaterialsOutput, ) from aws_cryptographic_materialproviders.mpl.references import ICryptographicMaterialsManager - + _HAS_MPL = True except ImportError: - pass + _HAS_MPL = False from typing import List @@ -43,9 +43,12 @@ def __init__( mpl_cmm: 'ICryptographicMaterialsManager' ): """ - Create DecryptionMaterialsHandler. - :param cmm: Underlying cryptographic materials manager + Create MPLCMMHandler. + :param mpl_cmm: Underlying MPL cryptographic materials manager """ + if not _HAS_MPL: + raise ImportError("You MUST install the aws-cryptographic-material-providers " + f"library to create an instance of {MPLCMMHandler}") if isinstance(mpl_cmm, ICryptographicMaterialsManager): self.mpl_cmm = mpl_cmm else: @@ -74,11 +77,12 @@ def get_encryption_materials( def _native_to_mpl_get_encryption_materials( request: EncryptionMaterialsRequest ) -> 'GetEncryptionMaterialsInput': + commitment_policy = MPLCMMHandler._native_to_mpl_commmitment_policy( + request.commitment_policy + ) output: GetEncryptionMaterialsInput = GetEncryptionMaterialsInput( encryption_context=request.encryption_context, - commitment_policy=MPLCMMHandler._native_to_mpl_commmitment_policy( - request.commitment_policy - ), + commitment_policy=commitment_policy, max_plaintext_length=request.plaintext_length, ) return output @@ -101,7 +105,7 @@ def decrypt_materials( request: DecryptionMaterialsRequest ) -> MPLDecryptionMaterials: """ - Returns a DecryptionMaterialsHandler for the configured CMM. + Returns a MPLDecryptionMaterials for the configured CMM. :param request: Request for decryption materials """ try: diff --git a/src/aws_encryption_sdk/materials_managers/mpl/materials.py b/src/aws_encryption_sdk/materials_managers/mpl/materials.py index fdcf2ec06..bd4b5f729 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/materials.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/materials.py @@ -6,8 +6,9 @@ EncryptedDataKey as MPL_EncryptedDataKey, EncryptionMaterials as MPL_EncryptionMaterials, ) + _HAS_MPL = True except ImportError: - pass + _HAS_MPL = False from typing import Dict, List, Set @@ -35,17 +36,20 @@ class MPLEncryptionMaterials(Native_EncryptionMaterials): def __init__( self, - materials: 'MPL_EncryptionMaterials' + mpl_materials: 'MPL_EncryptionMaterials' ): """ - Create MPLEncryptionMaterialsHandler. + Create MPLEncryptionMaterials. :param materials: Underlying encryption materials """ - if isinstance(materials, MPL_EncryptionMaterials): - self.mpl_materials = materials + if not _HAS_MPL: + raise ImportError("You MUST install the aws-cryptographic-material-providers " + f"library to create an instance of {MPLEncryptionMaterials}") + if isinstance(mpl_materials, MPL_EncryptionMaterials): + self.mpl_materials = mpl_materials else: - raise ValueError("Invalid EncryptionMaterials passed to EncryptionMaterialsHandler. " - f"materials: {materials}") + raise ValueError("Invalid EncryptionMaterials passed to MPLEncryptionMaterials. " + f"materials: {mpl_materials}") @property def algorithm(self) -> Algorithm: @@ -105,17 +109,20 @@ class MPLDecryptionMaterials(Native_DecryptionMaterials): def __init__( self, - materials: 'MPL_DecryptionMaterials' + mpl_materials: 'MPL_DecryptionMaterials' ): """ - Create DecryptionMaterialsHandler. + Create MPLDecryptionMaterials. :param materials: Underlying decryption materials """ - if isinstance(materials, MPL_DecryptionMaterials): - self.mpl_materials = materials + if not _HAS_MPL: + raise ImportError("You MUST install the aws-cryptographic-material-providers " + f"library to create an instance of {MPLDecryptionMaterials}") + if isinstance(mpl_materials, MPL_DecryptionMaterials): + self.mpl_materials = mpl_materials else: - raise ValueError(f"Invalid DecryptionMaterials passed to DecryptionMaterialsHandler.\ - materials: {materials}") + raise ValueError(f"Invalid DecryptionMaterials passed to MPLDecryptionMaterials.\ + materials: {mpl_materials}") @property def data_key(self) -> DataKey: From 0138f226a73bff13fe1e24d865b09ec7e2ff42b2 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 10:03:47 -0800 Subject: [PATCH 105/184] unit tests --- test/unit/test_material_managers_mpl_cmm.py | 278 ++++++++++++++++++ .../test_material_managers_mpl_materials.py | 221 ++++++++++++++ 2 files changed, 499 insertions(+) create mode 100644 test/unit/test_material_managers_mpl_cmm.py create mode 100644 test/unit/test_material_managers_mpl_materials.py diff --git a/test/unit/test_material_managers_mpl_cmm.py b/test/unit/test_material_managers_mpl_cmm.py new file mode 100644 index 000000000..77bf5502d --- /dev/null +++ b/test/unit/test_material_managers_mpl_cmm.py @@ -0,0 +1,278 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic.""" + +import pytest +from mock import MagicMock, patch + + +from aws_encryption_sdk.identifiers import CommitmentPolicy +import aws_encryption_sdk.materials_managers.mpl.cmm +from aws_encryption_sdk.materials_managers.mpl.cmm import MPLCMMHandler +from aws_encryption_sdk.materials_managers.mpl.materials import ( + MPLEncryptionMaterials, + MPLDecryptionMaterials, +) + +pytestmark = [pytest.mark.unit, pytest.mark.local] + + +# Check if MPL is installed, and skip tests based on its installation status +# Ideally, this logic would be based on mocking imports and testing logic, +# but doing that introduces errors that cause other tests to fail. +try: + from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException + from aws_cryptographic_materialproviders.mpl.models import ( + AlgorithmSuiteIdESDK, + CommitmentPolicyESDK, + DecryptMaterialsInput, + DecryptionMaterials as MPL_DecryptionMaterials, + EncryptionMaterials as MPL_EncryptionMaterials, + GetEncryptionMaterialsInput, + GetEncryptionMaterialsOutput, + ) + from aws_cryptographic_materialproviders.mpl.references import ( + ICryptographicMaterialsManager + ) + HAS_MPL = True + + mock_mpl_cmm = MagicMock(__class__=ICryptographicMaterialsManager) + mock_mpl_encryption_materials = MagicMock(__class__=MPL_EncryptionMaterials) + mock_mpl_decrypt_materials = MagicMock(__class__=MPL_DecryptionMaterials) + +except ImportError: + HAS_MPL = False + + # Ensure references to these mocks exist, even if they aren't used in a non-MPL context + mock_mpl_cmm = None + mock_mpl_encryption_materials = None + mock_mpl_decrypt_materials = None + +from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError +from aws_encryption_sdk.materials_managers import ( + EncryptionMaterialsRequest, + DecryptionMaterialsRequest, +) + + +mock_encryption_materials_request = MagicMock(__class__=EncryptionMaterialsRequest) +mock_encryption_materials_handler = MagicMock(__class__=MPLEncryptionMaterials) +mock_decryption_materials_request = MagicMock(__class__=DecryptionMaterialsRequest) + +@pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") +def test_GIVEN_test_has_mpl_is_False_THEN_cmm_has_mpl_is_False(): + """If the MPL IS NOT installed in the runtime environment, + assert the cmm has _HAS_MPL set to False""" + + assert hasattr(aws_encryption_sdk.materials_managers.mpl.cmm, "_HAS_MPL") + assert aws_encryption_sdk.materials_managers.mpl.cmm._HAS_MPL is False + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GIVEN_test_has_mpl_is_True_THEN_cmm_has_mpl_is_True(): + """If the MPL IS installed in the runtime environment, + assert the cmm has _HAS_MPL set to True""" + + assert hasattr(aws_encryption_sdk.materials_managers.mpl.cmm, "_HAS_MPL") + assert aws_encryption_sdk.materials_managers.mpl.cmm._HAS_MPL is True + + +@pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") +def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_THEN_raise_ImportError(): + with pytest.raises(ImportError): + MPLCMMHandler(mpl_cmm="doesn't matter") + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_valid_mpl_cmm_THEN_return_new_MPLCMMHandler(): + mpl_cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) + + assert mpl_cmm_handler.mpl_cmm == mock_mpl_cmm + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_invalid_mpl_cmm_THEN_raise_ValueError(): + with pytest.raises(ValueError): + MPLCMMHandler(mpl_cmm="not a valid mpl_cmm") + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +@patch.object(mock_mpl_cmm, "get_encryption_materials") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_get_encryption_materials") +def test_GIVEN_valid_request_WHEN_call_get_encryption_materials_THEN_return_MPLEncryptionMaterials( + mock_native_to_mpl_get_encryption_materials, + mock_get_encryption_materials, +): + + # Mock: mpl_cmm.get_encryption_materials returns mock MPL encryption materials + mock_get_encryption_materials_output = MagicMock(__class__=GetEncryptionMaterialsOutput) + mock_get_encryption_materials_output.encryption_materials = mock_mpl_encryption_materials + mock_get_encryption_materials.return_value = mock_get_encryption_materials_output + + # Mock: CMMHandler._native_to_mpl_get_encryption_materials creates a GetEncryptionMaterialsInput + mock_get_encryption_materials_input = MagicMock(__class__=GetEncryptionMaterialsInput) + mock_native_to_mpl_get_encryption_materials.return_value = mock_get_encryption_materials_input + + cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) + test = cmm_handler.get_encryption_materials(mock_encryption_materials_request) + + # Verify cmm_handler returns MPLEncryptionMaterials + assert isinstance(test, MPLEncryptionMaterials) + # Verify returned EncryptionMaterialsHandler uses the output of `get_encryption_materials` + assert test.mpl_materials == mock_mpl_encryption_materials + # Verify we actually called `get_encryption_materials` + mock_mpl_cmm.get_encryption_materials.assert_called_once_with(mock_get_encryption_materials_input) + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_commmitment_policy") +def test_GIVEN_get_encryption_materials_raises_MPL_Exception_WHEN_call_get_encryption_materials_THEN_raise_ESDK_Exception( + _ +): + with pytest.raises(AWSEncryptionSDKClientError): + with patch.object(mock_mpl_cmm, "get_encryption_materials", + side_effect=AwsCryptographicMaterialProvidersException("any")): + + cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) + cmm_handler.get_encryption_materials(mock_encryption_materials_request) + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_commmitment_policy") +def test_GIVEN_native_to_mpl_commmitment_policy_returns_valid_policy_WHEN_call_native_to_mpl_get_encryption_materials_THEN_returns_GetEncryptionMaterialsInput( + mock_mpl_commitment_policy +): + mock_commitment_policy = MagicMock(__class__=CommitmentPolicyESDK) + mock_mpl_commitment_policy.return_value = mock_commitment_policy + + output = MPLCMMHandler._native_to_mpl_get_encryption_materials(mock_encryption_materials_request) + + # verify correctness of returned value + assert isinstance(output, GetEncryptionMaterialsInput) + assert output.encryption_context == mock_encryption_materials_request.encryption_context + assert output.commitment_policy == mock_commitment_policy + assert output.max_plaintext_length == mock_encryption_materials_request.plaintext_length + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GIVEN_CommitmentPolicy_FORBID_ENCRYPT_ALLOW_DECRYPT_WHEN_call_native_to_mpl_commmitment_policyTHEN_returns_CommitmentPolicyESDK_FORBID_ENCRYPT_ALLOW_DECRYPT(): + native_commitment_policy = CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT + + output = MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) + + assert isinstance(output, CommitmentPolicyESDK) + assert output.value == "FORBID_ENCRYPT_ALLOW_DECRYPT" + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_ALLOW_DECRYPT_WHEN_call_native_to_mpl_commmitment_policyTHEN_returns_CommitmentPolicyESDK_REQUIRE_ENCRYPT_ALLOW_DECRYPT(): + native_commitment_policy = CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT + + output = MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) + + assert isinstance(output, CommitmentPolicyESDK) + assert output.value == "REQUIRE_ENCRYPT_ALLOW_DECRYPT" + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_REQUIRE_DECRYPT_WHEN_call_native_to_mpl_commmitment_policyTHEN_returns_CommitmentPolicyESDK_REQUIRE_ENCRYPT_REQUIRE_DECRYPT(): + native_commitment_policy = CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT + + output = MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) + + assert isinstance(output, CommitmentPolicyESDK) + assert output.value == "REQUIRE_ENCRYPT_REQUIRE_DECRYPT" + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GIVEN_CommitmentPolicy_unrecognized_WHEN_call_native_to_mpl_commmitment_policyTHEN_raise_ValueError(): + native_commitment_policy = "not a commitment policy" + + with pytest.raises(ValueError): + MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +@patch.object(mock_mpl_cmm, "decrypt_materials") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._create_mpl_decrypt_materials_input_from_request") +def test_GIVEN_valid_request_WHEN_call_decrypt_materials_THEN_return_MPLDecryptionMaterials( + mock_native_to_mpl_decrypt_materials, + mock_get_encryption_materials, +): + + # Mock: mpl_cmm.get_decryption_materials returns mock MPL decryption materials + mock_decrypt_materials_output = MagicMock(__class__=GetEncryptionMaterialsOutput) + mock_decrypt_materials_output.decryption_materials = mock_mpl_decrypt_materials + mock_get_encryption_materials.return_value = mock_decrypt_materials_output + + # Mock: CMMHandler._create_mpl_decrypt_materials_input_from_request creates a DecryptMaterialsInput + mock_decrypt_materials_input = MagicMock(__class__=GetEncryptionMaterialsInput) + mock_native_to_mpl_decrypt_materials.return_value = mock_decrypt_materials_input + + cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) + output = cmm_handler.decrypt_materials(mock_decryption_materials_request) + + # Verify cmm_handler returns MPLDecryptionMaterials + assert isinstance(output, MPLDecryptionMaterials) + # Verify returned MPLDecryptionMaterials uses the output of `decrypt_materials` + assert output.mpl_materials == mock_mpl_decrypt_materials + # Verify we actually called `decrypt_materials` + mock_mpl_cmm.decrypt_materials.assert_called_once_with(mock_decrypt_materials_input) + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._create_mpl_decrypt_materials_input_from_request") +def test_GIVEN_decrypt_materials_raises_MPL_Exception_WHEN_call_decrypt_materials_THEN_raise_ESDK_Exception( + _ +): + with pytest.raises(AWSEncryptionSDKClientError): + with patch.object(mock_mpl_cmm, "decrypt_materials", + side_effect=AwsCryptographicMaterialProvidersException("any")): + + cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) + cmm_handler.decrypt_materials(mock_decryption_materials_request) + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_WHEN_call_native_algorithm_id_to_mpl_algorithm_id_THEN_returns_valid_AlgorithmSuiteIdESDK(): + some_native_algorithm_id = 0x0000 # Not a real algorithm ID, but fits the format + + mpl_output = MPLCMMHandler._native_algorithm_id_to_mpl_algorithm_id( + some_native_algorithm_id + ) + + assert isinstance(mpl_output, AlgorithmSuiteIdESDK) + assert mpl_output.value == "0x0000" + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_algorithm_id_to_mpl_algorithm_id") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_commmitment_policy") +def test__create_mpl_decrypt_materials_input_from_request( + mock_mpl_commitment_policy, + mock_mpl_algorithm_id, +): + mock_algorithm_id = "0x1234" # Some fake algorithm ID that fits the format + mock_mpl_algorithm_id.return_value = mock_algorithm_id + mock_commitment_policy = MagicMock(__class__=CommitmentPolicyESDK) + mock_mpl_commitment_policy.return_value = mock_commitment_policy + + # mock_decryption_materials_request.algorithm = + + output = MPLCMMHandler._create_mpl_decrypt_materials_input_from_request(mock_decryption_materials_request) + + assert isinstance(output, DecryptMaterialsInput) + assert output.algorithm_suite_id == mock_algorithm_id + assert output.commitment_policy == mock_commitment_policy + assert output.encryption_context == mock_decryption_materials_request.encryption_context + + assert len(output.encrypted_data_keys) == len(mock_decryption_materials_request.encrypted_data_keys) + for i in range(len(output.encrypted_data_keys)): + # Assume input[i] == output[i], seems to work + output_edk = output.encrypted_data_keys[i] + input_edk = mock_decryption_materials_request[i] + assert output_edk.key_provider_id == input_edk.key_provider.provider_id + assert output_edk.key_provider_info == input_edk.key_provider.key_info + assert output_edk.ciphertext == input_edk.encrypted_data_key diff --git a/test/unit/test_material_managers_mpl_materials.py b/test/unit/test_material_managers_mpl_materials.py new file mode 100644 index 000000000..250efeb7e --- /dev/null +++ b/test/unit/test_material_managers_mpl_materials.py @@ -0,0 +1,221 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic.""" + +import pytest +from mock import MagicMock, patch, PropertyMock +from typing import Dict, List + +from aws_encryption_sdk.identifiers import CommitmentPolicy +import aws_encryption_sdk.materials_managers.mpl.materials +from aws_encryption_sdk.materials_managers.mpl.materials import ( + MPLEncryptionMaterials, + MPLDecryptionMaterials, +) +from aws_encryption_sdk.identifiers import Algorithm, AlgorithmSuite + +pytestmark = [pytest.mark.unit, pytest.mark.local] + + +# Check if MPL is installed, and skip tests based on its installation status +# Ideally, this logic would be based on mocking imports and testing logic, +# but doing that introduces errors that cause other tests to fail. +try: + from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException + from aws_cryptographic_materialproviders.mpl.models import ( + AlgorithmSuiteIdESDK, + CommitmentPolicyESDK, + DecryptMaterialsInput, + DecryptionMaterials as MPL_DecryptionMaterials, + EncryptedDataKey as MPL_EncryptedDataKey, + EncryptionMaterials as MPL_EncryptionMaterials, + GetEncryptionMaterialsInput, + GetEncryptionMaterialsOutput, + ) + from aws_cryptographic_materialproviders.mpl.references import ( + ICryptographicMaterialsManager + ) + HAS_MPL = True + + mock_mpl_encryption_materials = MagicMock(__class__=MPL_EncryptionMaterials) + mock_mpl_decrypt_materials = MagicMock(__class__=MPL_DecryptionMaterials) + +except ImportError: + HAS_MPL = False + + # Ensure references to these mocks exist, even if they aren't used in a non-MPL context + mock_mpl_cmm = None + mock_mpl_encryption_materials = None + mock_mpl_decrypt_materials = None + +from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError +from aws_encryption_sdk.materials_managers import ( + EncryptionMaterialsRequest, + DecryptionMaterialsRequest, +) + + +mock_encryption_materials_request = MagicMock(__class__=EncryptionMaterialsRequest) +mock_encryption_materials_handler = MagicMock(__class__=MPLEncryptionMaterials) +mock_decryption_materials_request = MagicMock(__class__=DecryptionMaterialsRequest) + +@pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") +def test_GIVEN_test_has_mpl_is_False_THEN_cmm_has_mpl_is_False(): + """If the MPL IS NOT installed in the runtime environment, + assert the cmm has _HAS_MPL set to False""" + + assert hasattr(aws_encryption_sdk.materials_managers.mpl.materials, "_HAS_MPL") + assert aws_encryption_sdk.materials_managers.mpl.materials._HAS_MPL is False + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GIVEN_test_has_mpl_is_True_THEN_cmm_has_mpl_is_True(): + """If the MPL IS installed in the runtime environment, + assert the cmm has _HAS_MPL set to True""" + + assert hasattr(aws_encryption_sdk.materials_managers.mpl.materials, "_HAS_MPL") + assert aws_encryption_sdk.materials_managers.mpl.materials._HAS_MPL is True + + +@pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") +def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_THEN_raise_ImportError(): + with pytest.raises(ImportError): + MPLEncryptionMaterials(mpl_materials="doesn't matter") + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_valid_mpl_cmm_THEN_return_new_MPLCMMHandler(): + mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + + assert mpl_encryption_materials.mpl_materials == mock_mpl_encryption_materials + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_invalid_mpl_cmm_THEN_raise_ValueError(): + with pytest.raises(ValueError): + MPLEncryptionMaterials(mpl_materials="not a valid mpl_materials") + +def test_mpl_to_native(): + some_mpl_algorithm_id = "0x1234" # Not a real algorithm ID, but fits the format + + native_output = aws_encryption_sdk.materials_managers.mpl.materials._mpl_algorithm_id_to_native_algorithm_id( + some_mpl_algorithm_id + ) + + assert native_output == 0x1234 + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +@patch("aws_encryption_sdk.materials_managers.mpl.materials._mpl_algorithm_id_to_native_algorithm_id") +@patch("aws_encryption_sdk.materials_managers.mpl.materials.AlgorithmSuite.get_by_id") +def test_GIVEN_valid_mpl_algorithm_id_WHEN_get_algorithm_THEN_valid_native_algorithm_id( + mock_algorithm, + mock_native_algorithm_id, +): + # Mock valid conversion from MPL to native algorithm ID + mock_native_algorithm_id.return_value = 0x1234 + + # Mock valid lookup in native AlgorithmSuite lookup + mock_algorithm.return_value = MagicMock(__class__=AlgorithmSuite) + + mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + output = mpl_encryption_materials.algorithm + assert output == mock_algorithm() # property calls automatically, we need to call the mock + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GecTHEN_valid_native_algorithm_id(): + mock_encryption_context = MagicMock(__class__=Dict[str, str]) + mock_mpl_encryption_materials.encryption_context = mock_encryption_context + + mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + output = mpl_encryption_materials.encryption_context + + assert output == mock_encryption_context + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GecTHEN_valid_nativefadsf_algorithm_id(): + mock_edk = MagicMock(__class__=MPL_EncryptedDataKey) + mock_mpl_key_provider_id = MagicMock(__class__=str) + mock_edk.key_provider_id = mock_mpl_key_provider_id + mock_mpl_key_provider_info = MagicMock(__class__=bytes) + mock_edk.key_provider_info = mock_mpl_key_provider_info + mock_mpl_ciphertext = MagicMock(__class__=bytes) + mock_edk.ciphertext = mock_mpl_ciphertext + + mock_edks = [ mock_edk ] + mock_mpl_encryption_materials.encrypted_data_keys = mock_edks + + mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + output = mpl_encryption_materials.encrypted_data_keys + output_as_list = list(output) + + assert len(output_as_list) == len(mock_edks) + for i in range(len(output_as_list)): + # assume output[i] corresponds to input[i] + native_edk = output_as_list[i] + mpl_edk = mock_edks[i] + + assert native_edk.encrypted_data_key == mpl_edk.ciphertext + assert native_edk.key_provider.provider_id == mpl_edk.key_provider_id + assert native_edk.key_provider.key_info == mpl_edk.key_provider_info + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GecTHEN_valid_nativefadsffadsfa_algorithm_id(): + mock_data_key = MagicMock(__class__=bytes) + mock_mpl_encryption_materials.plaintext_data_key = mock_data_key + + mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + output = mpl_encryption_materials.data_encryption_key + + assert output.key_provider.provider_id == "" + assert output.key_provider.key_info == b"" + assert output.data_key == mock_data_key + assert output.encrypted_data_key == b"" + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GecTHEN_valid_nativefasdfasdffadsf_algorithm_id(): + mock_signing_key = MagicMock(__class__=bytes) + mock_mpl_encryption_materials.signing_key = mock_signing_key + + mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + output = mpl_encryption_materials.signing_key + + assert output == mock_signing_key + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GecTHEN_valid_nativeffasdfasdadsffadsfa_algorithm_id(): + mock_data_key = MagicMock(__class__=bytes) + mock_mpl_decrypt_materials.plaintext_data_key = mock_data_key + + mpl_decryption_materials = MPLDecryptionMaterials(mpl_materials=mock_mpl_decrypt_materials) + output = mpl_decryption_materials.data_key + + assert output.key_provider.provider_id == "" + assert output.key_provider.key_info == b"" + assert output.data_key == mock_data_key + assert output.encrypted_data_key == b"" + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GecTHEN_validadsfasdf_nativefasdfasdffadsf_algorithm_id(): + mock_verification_key = MagicMock(__class__=bytes) + mock_mpl_decrypt_materials.verification_key = mock_verification_key + + mpl_decryption_materials = MPLDecryptionMaterials(mpl_materials=mock_mpl_decrypt_materials) + output = mpl_decryption_materials.verification_key + + assert output == mock_verification_key From f213e1912c4c87ead95eb92734c959d5ea91a388 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 10:14:13 -0800 Subject: [PATCH 106/184] upgrade image --- buildspec.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/buildspec.yml b/buildspec.yml index 3d70c144d..5dbd3f2b8 100644 --- a/buildspec.yml +++ b/buildspec.yml @@ -108,6 +108,8 @@ batch: buildspec: codebuild/coverage/coverage.yml - identifier: code_coverage_mpl buildspec: codebuild/coverage/coverage_mpl.yml + env: + image: aws/codebuild/standard:7.0 - identifier: compliance buildspec: codebuild/compliance/compliance.yml From d55f2963b82270f1a3377ff524a55ae663b5675a Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 10:23:56 -0800 Subject: [PATCH 107/184] refactor tests --- test/unit/mpl/__init__.py | 12 + .../mpl/test_material_managers_mpl_cmm.py | 278 ++++++++++++++++++ .../test_material_managers_mpl_materials.py | 221 ++++++++++++++ tox.ini | 12 +- 4 files changed, 516 insertions(+), 7 deletions(-) create mode 100644 test/unit/mpl/__init__.py create mode 100644 test/unit/mpl/test_material_managers_mpl_cmm.py create mode 100644 test/unit/mpl/test_material_managers_mpl_materials.py diff --git a/test/unit/mpl/__init__.py b/test/unit/mpl/__init__.py new file mode 100644 index 000000000..53a960891 --- /dev/null +++ b/test/unit/mpl/__init__.py @@ -0,0 +1,12 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. diff --git a/test/unit/mpl/test_material_managers_mpl_cmm.py b/test/unit/mpl/test_material_managers_mpl_cmm.py new file mode 100644 index 000000000..77bf5502d --- /dev/null +++ b/test/unit/mpl/test_material_managers_mpl_cmm.py @@ -0,0 +1,278 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic.""" + +import pytest +from mock import MagicMock, patch + + +from aws_encryption_sdk.identifiers import CommitmentPolicy +import aws_encryption_sdk.materials_managers.mpl.cmm +from aws_encryption_sdk.materials_managers.mpl.cmm import MPLCMMHandler +from aws_encryption_sdk.materials_managers.mpl.materials import ( + MPLEncryptionMaterials, + MPLDecryptionMaterials, +) + +pytestmark = [pytest.mark.unit, pytest.mark.local] + + +# Check if MPL is installed, and skip tests based on its installation status +# Ideally, this logic would be based on mocking imports and testing logic, +# but doing that introduces errors that cause other tests to fail. +try: + from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException + from aws_cryptographic_materialproviders.mpl.models import ( + AlgorithmSuiteIdESDK, + CommitmentPolicyESDK, + DecryptMaterialsInput, + DecryptionMaterials as MPL_DecryptionMaterials, + EncryptionMaterials as MPL_EncryptionMaterials, + GetEncryptionMaterialsInput, + GetEncryptionMaterialsOutput, + ) + from aws_cryptographic_materialproviders.mpl.references import ( + ICryptographicMaterialsManager + ) + HAS_MPL = True + + mock_mpl_cmm = MagicMock(__class__=ICryptographicMaterialsManager) + mock_mpl_encryption_materials = MagicMock(__class__=MPL_EncryptionMaterials) + mock_mpl_decrypt_materials = MagicMock(__class__=MPL_DecryptionMaterials) + +except ImportError: + HAS_MPL = False + + # Ensure references to these mocks exist, even if they aren't used in a non-MPL context + mock_mpl_cmm = None + mock_mpl_encryption_materials = None + mock_mpl_decrypt_materials = None + +from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError +from aws_encryption_sdk.materials_managers import ( + EncryptionMaterialsRequest, + DecryptionMaterialsRequest, +) + + +mock_encryption_materials_request = MagicMock(__class__=EncryptionMaterialsRequest) +mock_encryption_materials_handler = MagicMock(__class__=MPLEncryptionMaterials) +mock_decryption_materials_request = MagicMock(__class__=DecryptionMaterialsRequest) + +@pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") +def test_GIVEN_test_has_mpl_is_False_THEN_cmm_has_mpl_is_False(): + """If the MPL IS NOT installed in the runtime environment, + assert the cmm has _HAS_MPL set to False""" + + assert hasattr(aws_encryption_sdk.materials_managers.mpl.cmm, "_HAS_MPL") + assert aws_encryption_sdk.materials_managers.mpl.cmm._HAS_MPL is False + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GIVEN_test_has_mpl_is_True_THEN_cmm_has_mpl_is_True(): + """If the MPL IS installed in the runtime environment, + assert the cmm has _HAS_MPL set to True""" + + assert hasattr(aws_encryption_sdk.materials_managers.mpl.cmm, "_HAS_MPL") + assert aws_encryption_sdk.materials_managers.mpl.cmm._HAS_MPL is True + + +@pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") +def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_THEN_raise_ImportError(): + with pytest.raises(ImportError): + MPLCMMHandler(mpl_cmm="doesn't matter") + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_valid_mpl_cmm_THEN_return_new_MPLCMMHandler(): + mpl_cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) + + assert mpl_cmm_handler.mpl_cmm == mock_mpl_cmm + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_invalid_mpl_cmm_THEN_raise_ValueError(): + with pytest.raises(ValueError): + MPLCMMHandler(mpl_cmm="not a valid mpl_cmm") + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +@patch.object(mock_mpl_cmm, "get_encryption_materials") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_get_encryption_materials") +def test_GIVEN_valid_request_WHEN_call_get_encryption_materials_THEN_return_MPLEncryptionMaterials( + mock_native_to_mpl_get_encryption_materials, + mock_get_encryption_materials, +): + + # Mock: mpl_cmm.get_encryption_materials returns mock MPL encryption materials + mock_get_encryption_materials_output = MagicMock(__class__=GetEncryptionMaterialsOutput) + mock_get_encryption_materials_output.encryption_materials = mock_mpl_encryption_materials + mock_get_encryption_materials.return_value = mock_get_encryption_materials_output + + # Mock: CMMHandler._native_to_mpl_get_encryption_materials creates a GetEncryptionMaterialsInput + mock_get_encryption_materials_input = MagicMock(__class__=GetEncryptionMaterialsInput) + mock_native_to_mpl_get_encryption_materials.return_value = mock_get_encryption_materials_input + + cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) + test = cmm_handler.get_encryption_materials(mock_encryption_materials_request) + + # Verify cmm_handler returns MPLEncryptionMaterials + assert isinstance(test, MPLEncryptionMaterials) + # Verify returned EncryptionMaterialsHandler uses the output of `get_encryption_materials` + assert test.mpl_materials == mock_mpl_encryption_materials + # Verify we actually called `get_encryption_materials` + mock_mpl_cmm.get_encryption_materials.assert_called_once_with(mock_get_encryption_materials_input) + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_commmitment_policy") +def test_GIVEN_get_encryption_materials_raises_MPL_Exception_WHEN_call_get_encryption_materials_THEN_raise_ESDK_Exception( + _ +): + with pytest.raises(AWSEncryptionSDKClientError): + with patch.object(mock_mpl_cmm, "get_encryption_materials", + side_effect=AwsCryptographicMaterialProvidersException("any")): + + cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) + cmm_handler.get_encryption_materials(mock_encryption_materials_request) + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_commmitment_policy") +def test_GIVEN_native_to_mpl_commmitment_policy_returns_valid_policy_WHEN_call_native_to_mpl_get_encryption_materials_THEN_returns_GetEncryptionMaterialsInput( + mock_mpl_commitment_policy +): + mock_commitment_policy = MagicMock(__class__=CommitmentPolicyESDK) + mock_mpl_commitment_policy.return_value = mock_commitment_policy + + output = MPLCMMHandler._native_to_mpl_get_encryption_materials(mock_encryption_materials_request) + + # verify correctness of returned value + assert isinstance(output, GetEncryptionMaterialsInput) + assert output.encryption_context == mock_encryption_materials_request.encryption_context + assert output.commitment_policy == mock_commitment_policy + assert output.max_plaintext_length == mock_encryption_materials_request.plaintext_length + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GIVEN_CommitmentPolicy_FORBID_ENCRYPT_ALLOW_DECRYPT_WHEN_call_native_to_mpl_commmitment_policyTHEN_returns_CommitmentPolicyESDK_FORBID_ENCRYPT_ALLOW_DECRYPT(): + native_commitment_policy = CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT + + output = MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) + + assert isinstance(output, CommitmentPolicyESDK) + assert output.value == "FORBID_ENCRYPT_ALLOW_DECRYPT" + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_ALLOW_DECRYPT_WHEN_call_native_to_mpl_commmitment_policyTHEN_returns_CommitmentPolicyESDK_REQUIRE_ENCRYPT_ALLOW_DECRYPT(): + native_commitment_policy = CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT + + output = MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) + + assert isinstance(output, CommitmentPolicyESDK) + assert output.value == "REQUIRE_ENCRYPT_ALLOW_DECRYPT" + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_REQUIRE_DECRYPT_WHEN_call_native_to_mpl_commmitment_policyTHEN_returns_CommitmentPolicyESDK_REQUIRE_ENCRYPT_REQUIRE_DECRYPT(): + native_commitment_policy = CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT + + output = MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) + + assert isinstance(output, CommitmentPolicyESDK) + assert output.value == "REQUIRE_ENCRYPT_REQUIRE_DECRYPT" + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GIVEN_CommitmentPolicy_unrecognized_WHEN_call_native_to_mpl_commmitment_policyTHEN_raise_ValueError(): + native_commitment_policy = "not a commitment policy" + + with pytest.raises(ValueError): + MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +@patch.object(mock_mpl_cmm, "decrypt_materials") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._create_mpl_decrypt_materials_input_from_request") +def test_GIVEN_valid_request_WHEN_call_decrypt_materials_THEN_return_MPLDecryptionMaterials( + mock_native_to_mpl_decrypt_materials, + mock_get_encryption_materials, +): + + # Mock: mpl_cmm.get_decryption_materials returns mock MPL decryption materials + mock_decrypt_materials_output = MagicMock(__class__=GetEncryptionMaterialsOutput) + mock_decrypt_materials_output.decryption_materials = mock_mpl_decrypt_materials + mock_get_encryption_materials.return_value = mock_decrypt_materials_output + + # Mock: CMMHandler._create_mpl_decrypt_materials_input_from_request creates a DecryptMaterialsInput + mock_decrypt_materials_input = MagicMock(__class__=GetEncryptionMaterialsInput) + mock_native_to_mpl_decrypt_materials.return_value = mock_decrypt_materials_input + + cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) + output = cmm_handler.decrypt_materials(mock_decryption_materials_request) + + # Verify cmm_handler returns MPLDecryptionMaterials + assert isinstance(output, MPLDecryptionMaterials) + # Verify returned MPLDecryptionMaterials uses the output of `decrypt_materials` + assert output.mpl_materials == mock_mpl_decrypt_materials + # Verify we actually called `decrypt_materials` + mock_mpl_cmm.decrypt_materials.assert_called_once_with(mock_decrypt_materials_input) + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._create_mpl_decrypt_materials_input_from_request") +def test_GIVEN_decrypt_materials_raises_MPL_Exception_WHEN_call_decrypt_materials_THEN_raise_ESDK_Exception( + _ +): + with pytest.raises(AWSEncryptionSDKClientError): + with patch.object(mock_mpl_cmm, "decrypt_materials", + side_effect=AwsCryptographicMaterialProvidersException("any")): + + cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) + cmm_handler.decrypt_materials(mock_decryption_materials_request) + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_WHEN_call_native_algorithm_id_to_mpl_algorithm_id_THEN_returns_valid_AlgorithmSuiteIdESDK(): + some_native_algorithm_id = 0x0000 # Not a real algorithm ID, but fits the format + + mpl_output = MPLCMMHandler._native_algorithm_id_to_mpl_algorithm_id( + some_native_algorithm_id + ) + + assert isinstance(mpl_output, AlgorithmSuiteIdESDK) + assert mpl_output.value == "0x0000" + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_algorithm_id_to_mpl_algorithm_id") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_commmitment_policy") +def test__create_mpl_decrypt_materials_input_from_request( + mock_mpl_commitment_policy, + mock_mpl_algorithm_id, +): + mock_algorithm_id = "0x1234" # Some fake algorithm ID that fits the format + mock_mpl_algorithm_id.return_value = mock_algorithm_id + mock_commitment_policy = MagicMock(__class__=CommitmentPolicyESDK) + mock_mpl_commitment_policy.return_value = mock_commitment_policy + + # mock_decryption_materials_request.algorithm = + + output = MPLCMMHandler._create_mpl_decrypt_materials_input_from_request(mock_decryption_materials_request) + + assert isinstance(output, DecryptMaterialsInput) + assert output.algorithm_suite_id == mock_algorithm_id + assert output.commitment_policy == mock_commitment_policy + assert output.encryption_context == mock_decryption_materials_request.encryption_context + + assert len(output.encrypted_data_keys) == len(mock_decryption_materials_request.encrypted_data_keys) + for i in range(len(output.encrypted_data_keys)): + # Assume input[i] == output[i], seems to work + output_edk = output.encrypted_data_keys[i] + input_edk = mock_decryption_materials_request[i] + assert output_edk.key_provider_id == input_edk.key_provider.provider_id + assert output_edk.key_provider_info == input_edk.key_provider.key_info + assert output_edk.ciphertext == input_edk.encrypted_data_key diff --git a/test/unit/mpl/test_material_managers_mpl_materials.py b/test/unit/mpl/test_material_managers_mpl_materials.py new file mode 100644 index 000000000..250efeb7e --- /dev/null +++ b/test/unit/mpl/test_material_managers_mpl_materials.py @@ -0,0 +1,221 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic.""" + +import pytest +from mock import MagicMock, patch, PropertyMock +from typing import Dict, List + +from aws_encryption_sdk.identifiers import CommitmentPolicy +import aws_encryption_sdk.materials_managers.mpl.materials +from aws_encryption_sdk.materials_managers.mpl.materials import ( + MPLEncryptionMaterials, + MPLDecryptionMaterials, +) +from aws_encryption_sdk.identifiers import Algorithm, AlgorithmSuite + +pytestmark = [pytest.mark.unit, pytest.mark.local] + + +# Check if MPL is installed, and skip tests based on its installation status +# Ideally, this logic would be based on mocking imports and testing logic, +# but doing that introduces errors that cause other tests to fail. +try: + from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException + from aws_cryptographic_materialproviders.mpl.models import ( + AlgorithmSuiteIdESDK, + CommitmentPolicyESDK, + DecryptMaterialsInput, + DecryptionMaterials as MPL_DecryptionMaterials, + EncryptedDataKey as MPL_EncryptedDataKey, + EncryptionMaterials as MPL_EncryptionMaterials, + GetEncryptionMaterialsInput, + GetEncryptionMaterialsOutput, + ) + from aws_cryptographic_materialproviders.mpl.references import ( + ICryptographicMaterialsManager + ) + HAS_MPL = True + + mock_mpl_encryption_materials = MagicMock(__class__=MPL_EncryptionMaterials) + mock_mpl_decrypt_materials = MagicMock(__class__=MPL_DecryptionMaterials) + +except ImportError: + HAS_MPL = False + + # Ensure references to these mocks exist, even if they aren't used in a non-MPL context + mock_mpl_cmm = None + mock_mpl_encryption_materials = None + mock_mpl_decrypt_materials = None + +from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError +from aws_encryption_sdk.materials_managers import ( + EncryptionMaterialsRequest, + DecryptionMaterialsRequest, +) + + +mock_encryption_materials_request = MagicMock(__class__=EncryptionMaterialsRequest) +mock_encryption_materials_handler = MagicMock(__class__=MPLEncryptionMaterials) +mock_decryption_materials_request = MagicMock(__class__=DecryptionMaterialsRequest) + +@pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") +def test_GIVEN_test_has_mpl_is_False_THEN_cmm_has_mpl_is_False(): + """If the MPL IS NOT installed in the runtime environment, + assert the cmm has _HAS_MPL set to False""" + + assert hasattr(aws_encryption_sdk.materials_managers.mpl.materials, "_HAS_MPL") + assert aws_encryption_sdk.materials_managers.mpl.materials._HAS_MPL is False + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GIVEN_test_has_mpl_is_True_THEN_cmm_has_mpl_is_True(): + """If the MPL IS installed in the runtime environment, + assert the cmm has _HAS_MPL set to True""" + + assert hasattr(aws_encryption_sdk.materials_managers.mpl.materials, "_HAS_MPL") + assert aws_encryption_sdk.materials_managers.mpl.materials._HAS_MPL is True + + +@pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") +def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_THEN_raise_ImportError(): + with pytest.raises(ImportError): + MPLEncryptionMaterials(mpl_materials="doesn't matter") + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_valid_mpl_cmm_THEN_return_new_MPLCMMHandler(): + mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + + assert mpl_encryption_materials.mpl_materials == mock_mpl_encryption_materials + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_invalid_mpl_cmm_THEN_raise_ValueError(): + with pytest.raises(ValueError): + MPLEncryptionMaterials(mpl_materials="not a valid mpl_materials") + +def test_mpl_to_native(): + some_mpl_algorithm_id = "0x1234" # Not a real algorithm ID, but fits the format + + native_output = aws_encryption_sdk.materials_managers.mpl.materials._mpl_algorithm_id_to_native_algorithm_id( + some_mpl_algorithm_id + ) + + assert native_output == 0x1234 + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +@patch("aws_encryption_sdk.materials_managers.mpl.materials._mpl_algorithm_id_to_native_algorithm_id") +@patch("aws_encryption_sdk.materials_managers.mpl.materials.AlgorithmSuite.get_by_id") +def test_GIVEN_valid_mpl_algorithm_id_WHEN_get_algorithm_THEN_valid_native_algorithm_id( + mock_algorithm, + mock_native_algorithm_id, +): + # Mock valid conversion from MPL to native algorithm ID + mock_native_algorithm_id.return_value = 0x1234 + + # Mock valid lookup in native AlgorithmSuite lookup + mock_algorithm.return_value = MagicMock(__class__=AlgorithmSuite) + + mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + output = mpl_encryption_materials.algorithm + assert output == mock_algorithm() # property calls automatically, we need to call the mock + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GecTHEN_valid_native_algorithm_id(): + mock_encryption_context = MagicMock(__class__=Dict[str, str]) + mock_mpl_encryption_materials.encryption_context = mock_encryption_context + + mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + output = mpl_encryption_materials.encryption_context + + assert output == mock_encryption_context + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GecTHEN_valid_nativefadsf_algorithm_id(): + mock_edk = MagicMock(__class__=MPL_EncryptedDataKey) + mock_mpl_key_provider_id = MagicMock(__class__=str) + mock_edk.key_provider_id = mock_mpl_key_provider_id + mock_mpl_key_provider_info = MagicMock(__class__=bytes) + mock_edk.key_provider_info = mock_mpl_key_provider_info + mock_mpl_ciphertext = MagicMock(__class__=bytes) + mock_edk.ciphertext = mock_mpl_ciphertext + + mock_edks = [ mock_edk ] + mock_mpl_encryption_materials.encrypted_data_keys = mock_edks + + mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + output = mpl_encryption_materials.encrypted_data_keys + output_as_list = list(output) + + assert len(output_as_list) == len(mock_edks) + for i in range(len(output_as_list)): + # assume output[i] corresponds to input[i] + native_edk = output_as_list[i] + mpl_edk = mock_edks[i] + + assert native_edk.encrypted_data_key == mpl_edk.ciphertext + assert native_edk.key_provider.provider_id == mpl_edk.key_provider_id + assert native_edk.key_provider.key_info == mpl_edk.key_provider_info + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GecTHEN_valid_nativefadsffadsfa_algorithm_id(): + mock_data_key = MagicMock(__class__=bytes) + mock_mpl_encryption_materials.plaintext_data_key = mock_data_key + + mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + output = mpl_encryption_materials.data_encryption_key + + assert output.key_provider.provider_id == "" + assert output.key_provider.key_info == b"" + assert output.data_key == mock_data_key + assert output.encrypted_data_key == b"" + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GecTHEN_valid_nativefasdfasdffadsf_algorithm_id(): + mock_signing_key = MagicMock(__class__=bytes) + mock_mpl_encryption_materials.signing_key = mock_signing_key + + mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + output = mpl_encryption_materials.signing_key + + assert output == mock_signing_key + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GecTHEN_valid_nativeffasdfasdadsffadsfa_algorithm_id(): + mock_data_key = MagicMock(__class__=bytes) + mock_mpl_decrypt_materials.plaintext_data_key = mock_data_key + + mpl_decryption_materials = MPLDecryptionMaterials(mpl_materials=mock_mpl_decrypt_materials) + output = mpl_decryption_materials.data_key + + assert output.key_provider.provider_id == "" + assert output.key_provider.key_info == b"" + assert output.data_key == mock_data_key + assert output.encrypted_data_key == b"" + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_GecTHEN_validadsfasdf_nativefasdfasdffadsf_algorithm_id(): + mock_verification_key = MagicMock(__class__=bytes) + mock_mpl_decrypt_materials.verification_key = mock_verification_key + + mpl_decryption_materials = MPLDecryptionMaterials(mpl_materials=mock_mpl_decrypt_materials) + output = mpl_decryption_materials.verification_key + + assert output == mock_verification_key diff --git a/tox.ini b/tox.ini index 72e8ec9fa..346e4fae0 100644 --- a/tox.ini +++ b/tox.ini @@ -84,23 +84,21 @@ deps = commands = local: {[testenv:base-command]commands} test/ -m local --ignore test/unit/mpl/ # MPL unit tests require the MPL to be installed - mpllocal: {[testenv:base-command]commands} test/unit/mpl/ -m local - integ: {[testenv:base-command]commands} test/ -m integ --ignore test/unit/mpl/ - # No MPL-specific integ tests - accept: {[testenv:base-command]commands} test/ -m accept --ignore test/unit/mpl/ - # No MPL-specific accept tests + mpllocal: {[testenv:base-command]commands} test/ -m local + integ: {[testenv:base-command]commands} test/ -m integ + accept: {[testenv:base-command]commands} test/ -m accept examples: {[testenv:base-command]commands} examples/test/ -m examples --ignore examples/test/keyrings/ # MPL keyring examples require a special IAM role; run these separately under a separate set of permissions mplexamples: {[testenv:base-command]commands} examples/test/keyrings -m examples all: {[testenv:base-command]commands} test/ examples/test/ --ignore test/unit/mpl/ --ignore examples/test/keyrings/ - mplall: {[testenv:base-command]commands} test/unit/mpl/ examples/test/keyrings/ + mplall: {[testenv:base-command]commands} test/ examples/test/ manual: {[testenv:base-command]commands} # Run code coverage on the unit tests [testenv:coverage] commands = {[testenv:base-command]commands} --cov aws_encryption_sdk test/ -m local --ignore test/unit/mpl/ [testenv:mplcoverage-mpl] -commands = {[testenv:base-command]commands} --cov aws_encryption_sdk test/unit/mpl/ -m local +commands = {[testenv:base-command]commands} --cov aws_encryption_sdk test/ -m local # Verify that local tests work without environment variables present [testenv:nocmk] From 5ec46687b47ab907cc4a53ca9cc18a4f677c65e6 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 10:33:22 -0800 Subject: [PATCH 108/184] refactor tests --- .../materials_managers/mpl/cmm.py | 32 +- .../materials_managers/mpl/materials.py | 22 +- test/unit/mpl/__init__.py | 12 - .../mpl/test_material_managers_mpl_cmm.py | 278 ------------------ .../test_material_managers_mpl_materials.py | 221 -------------- test/unit/test_material_managers_mpl_cmm.py | 278 ------------------ .../test_material_managers_mpl_materials.py | 221 -------------- tox.ini | 6 +- 8 files changed, 20 insertions(+), 1050 deletions(-) delete mode 100644 test/unit/mpl/__init__.py delete mode 100644 test/unit/mpl/test_material_managers_mpl_cmm.py delete mode 100644 test/unit/mpl/test_material_managers_mpl_materials.py delete mode 100644 test/unit/test_material_managers_mpl_cmm.py delete mode 100644 test/unit/test_material_managers_mpl_materials.py diff --git a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py index cd789b994..1e3e3fb34 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py @@ -1,23 +1,16 @@ """Retrieves encryption/decryption materials from the MPL.""" -# These dependencies are only loaded if you install the MPL. -try: - # pylint seems to struggle with this conditional import - # pylint: disable=unused-import - from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException - from aws_cryptographic_materialproviders.mpl.models import ( - AlgorithmSuiteIdESDK, - CommitmentPolicyESDK, - DecryptMaterialsInput, - DecryptMaterialsOutput, - EncryptedDataKey as MPL_EncryptedDataKey, - GetEncryptionMaterialsInput, - GetEncryptionMaterialsOutput, - ) - from aws_cryptographic_materialproviders.mpl.references import ICryptographicMaterialsManager - _HAS_MPL = True -except ImportError: - _HAS_MPL = False +from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException +from aws_cryptographic_materialproviders.mpl.models import ( + AlgorithmSuiteIdESDK, + CommitmentPolicyESDK, + DecryptMaterialsInput, + DecryptMaterialsOutput, + EncryptedDataKey as MPL_EncryptedDataKey, + GetEncryptionMaterialsInput, + GetEncryptionMaterialsOutput, +) +from aws_cryptographic_materialproviders.mpl.references import ICryptographicMaterialsManager from typing import List @@ -46,9 +39,6 @@ def __init__( Create MPLCMMHandler. :param mpl_cmm: Underlying MPL cryptographic materials manager """ - if not _HAS_MPL: - raise ImportError("You MUST install the aws-cryptographic-material-providers " - f"library to create an instance of {MPLCMMHandler}") if isinstance(mpl_cmm, ICryptographicMaterialsManager): self.mpl_cmm = mpl_cmm else: diff --git a/src/aws_encryption_sdk/materials_managers/mpl/materials.py b/src/aws_encryption_sdk/materials_managers/mpl/materials.py index bd4b5f729..1ea2a199d 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/materials.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/materials.py @@ -1,14 +1,10 @@ """Provides encryption/decryption materials from an underlying materials provider.""" -# These dependencies are only loaded if you install the MPL. -try: - from aws_cryptographic_materialproviders.mpl.models import ( - DecryptionMaterials as MPL_DecryptionMaterials, - EncryptedDataKey as MPL_EncryptedDataKey, - EncryptionMaterials as MPL_EncryptionMaterials, - ) - _HAS_MPL = True -except ImportError: - _HAS_MPL = False + +from aws_cryptographic_materialproviders.mpl.models import ( + DecryptionMaterials as MPL_DecryptionMaterials, + EncryptedDataKey as MPL_EncryptedDataKey, + EncryptionMaterials as MPL_EncryptionMaterials, +) from typing import Dict, List, Set @@ -42,9 +38,6 @@ def __init__( Create MPLEncryptionMaterials. :param materials: Underlying encryption materials """ - if not _HAS_MPL: - raise ImportError("You MUST install the aws-cryptographic-material-providers " - f"library to create an instance of {MPLEncryptionMaterials}") if isinstance(mpl_materials, MPL_EncryptionMaterials): self.mpl_materials = mpl_materials else: @@ -115,9 +108,6 @@ def __init__( Create MPLDecryptionMaterials. :param materials: Underlying decryption materials """ - if not _HAS_MPL: - raise ImportError("You MUST install the aws-cryptographic-material-providers " - f"library to create an instance of {MPLDecryptionMaterials}") if isinstance(mpl_materials, MPL_DecryptionMaterials): self.mpl_materials = mpl_materials else: diff --git a/test/unit/mpl/__init__.py b/test/unit/mpl/__init__.py deleted file mode 100644 index 53a960891..000000000 --- a/test/unit/mpl/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. diff --git a/test/unit/mpl/test_material_managers_mpl_cmm.py b/test/unit/mpl/test_material_managers_mpl_cmm.py deleted file mode 100644 index 77bf5502d..000000000 --- a/test/unit/mpl/test_material_managers_mpl_cmm.py +++ /dev/null @@ -1,278 +0,0 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. -"""Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic.""" - -import pytest -from mock import MagicMock, patch - - -from aws_encryption_sdk.identifiers import CommitmentPolicy -import aws_encryption_sdk.materials_managers.mpl.cmm -from aws_encryption_sdk.materials_managers.mpl.cmm import MPLCMMHandler -from aws_encryption_sdk.materials_managers.mpl.materials import ( - MPLEncryptionMaterials, - MPLDecryptionMaterials, -) - -pytestmark = [pytest.mark.unit, pytest.mark.local] - - -# Check if MPL is installed, and skip tests based on its installation status -# Ideally, this logic would be based on mocking imports and testing logic, -# but doing that introduces errors that cause other tests to fail. -try: - from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException - from aws_cryptographic_materialproviders.mpl.models import ( - AlgorithmSuiteIdESDK, - CommitmentPolicyESDK, - DecryptMaterialsInput, - DecryptionMaterials as MPL_DecryptionMaterials, - EncryptionMaterials as MPL_EncryptionMaterials, - GetEncryptionMaterialsInput, - GetEncryptionMaterialsOutput, - ) - from aws_cryptographic_materialproviders.mpl.references import ( - ICryptographicMaterialsManager - ) - HAS_MPL = True - - mock_mpl_cmm = MagicMock(__class__=ICryptographicMaterialsManager) - mock_mpl_encryption_materials = MagicMock(__class__=MPL_EncryptionMaterials) - mock_mpl_decrypt_materials = MagicMock(__class__=MPL_DecryptionMaterials) - -except ImportError: - HAS_MPL = False - - # Ensure references to these mocks exist, even if they aren't used in a non-MPL context - mock_mpl_cmm = None - mock_mpl_encryption_materials = None - mock_mpl_decrypt_materials = None - -from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError -from aws_encryption_sdk.materials_managers import ( - EncryptionMaterialsRequest, - DecryptionMaterialsRequest, -) - - -mock_encryption_materials_request = MagicMock(__class__=EncryptionMaterialsRequest) -mock_encryption_materials_handler = MagicMock(__class__=MPLEncryptionMaterials) -mock_decryption_materials_request = MagicMock(__class__=DecryptionMaterialsRequest) - -@pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") -def test_GIVEN_test_has_mpl_is_False_THEN_cmm_has_mpl_is_False(): - """If the MPL IS NOT installed in the runtime environment, - assert the cmm has _HAS_MPL set to False""" - - assert hasattr(aws_encryption_sdk.materials_managers.mpl.cmm, "_HAS_MPL") - assert aws_encryption_sdk.materials_managers.mpl.cmm._HAS_MPL is False - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GIVEN_test_has_mpl_is_True_THEN_cmm_has_mpl_is_True(): - """If the MPL IS installed in the runtime environment, - assert the cmm has _HAS_MPL set to True""" - - assert hasattr(aws_encryption_sdk.materials_managers.mpl.cmm, "_HAS_MPL") - assert aws_encryption_sdk.materials_managers.mpl.cmm._HAS_MPL is True - - -@pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") -def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_THEN_raise_ImportError(): - with pytest.raises(ImportError): - MPLCMMHandler(mpl_cmm="doesn't matter") - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_valid_mpl_cmm_THEN_return_new_MPLCMMHandler(): - mpl_cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) - - assert mpl_cmm_handler.mpl_cmm == mock_mpl_cmm - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_invalid_mpl_cmm_THEN_raise_ValueError(): - with pytest.raises(ValueError): - MPLCMMHandler(mpl_cmm="not a valid mpl_cmm") - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -@patch.object(mock_mpl_cmm, "get_encryption_materials") -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_get_encryption_materials") -def test_GIVEN_valid_request_WHEN_call_get_encryption_materials_THEN_return_MPLEncryptionMaterials( - mock_native_to_mpl_get_encryption_materials, - mock_get_encryption_materials, -): - - # Mock: mpl_cmm.get_encryption_materials returns mock MPL encryption materials - mock_get_encryption_materials_output = MagicMock(__class__=GetEncryptionMaterialsOutput) - mock_get_encryption_materials_output.encryption_materials = mock_mpl_encryption_materials - mock_get_encryption_materials.return_value = mock_get_encryption_materials_output - - # Mock: CMMHandler._native_to_mpl_get_encryption_materials creates a GetEncryptionMaterialsInput - mock_get_encryption_materials_input = MagicMock(__class__=GetEncryptionMaterialsInput) - mock_native_to_mpl_get_encryption_materials.return_value = mock_get_encryption_materials_input - - cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) - test = cmm_handler.get_encryption_materials(mock_encryption_materials_request) - - # Verify cmm_handler returns MPLEncryptionMaterials - assert isinstance(test, MPLEncryptionMaterials) - # Verify returned EncryptionMaterialsHandler uses the output of `get_encryption_materials` - assert test.mpl_materials == mock_mpl_encryption_materials - # Verify we actually called `get_encryption_materials` - mock_mpl_cmm.get_encryption_materials.assert_called_once_with(mock_get_encryption_materials_input) - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_commmitment_policy") -def test_GIVEN_get_encryption_materials_raises_MPL_Exception_WHEN_call_get_encryption_materials_THEN_raise_ESDK_Exception( - _ -): - with pytest.raises(AWSEncryptionSDKClientError): - with patch.object(mock_mpl_cmm, "get_encryption_materials", - side_effect=AwsCryptographicMaterialProvidersException("any")): - - cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) - cmm_handler.get_encryption_materials(mock_encryption_materials_request) - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_commmitment_policy") -def test_GIVEN_native_to_mpl_commmitment_policy_returns_valid_policy_WHEN_call_native_to_mpl_get_encryption_materials_THEN_returns_GetEncryptionMaterialsInput( - mock_mpl_commitment_policy -): - mock_commitment_policy = MagicMock(__class__=CommitmentPolicyESDK) - mock_mpl_commitment_policy.return_value = mock_commitment_policy - - output = MPLCMMHandler._native_to_mpl_get_encryption_materials(mock_encryption_materials_request) - - # verify correctness of returned value - assert isinstance(output, GetEncryptionMaterialsInput) - assert output.encryption_context == mock_encryption_materials_request.encryption_context - assert output.commitment_policy == mock_commitment_policy - assert output.max_plaintext_length == mock_encryption_materials_request.plaintext_length - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GIVEN_CommitmentPolicy_FORBID_ENCRYPT_ALLOW_DECRYPT_WHEN_call_native_to_mpl_commmitment_policyTHEN_returns_CommitmentPolicyESDK_FORBID_ENCRYPT_ALLOW_DECRYPT(): - native_commitment_policy = CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT - - output = MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) - - assert isinstance(output, CommitmentPolicyESDK) - assert output.value == "FORBID_ENCRYPT_ALLOW_DECRYPT" - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_ALLOW_DECRYPT_WHEN_call_native_to_mpl_commmitment_policyTHEN_returns_CommitmentPolicyESDK_REQUIRE_ENCRYPT_ALLOW_DECRYPT(): - native_commitment_policy = CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT - - output = MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) - - assert isinstance(output, CommitmentPolicyESDK) - assert output.value == "REQUIRE_ENCRYPT_ALLOW_DECRYPT" - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_REQUIRE_DECRYPT_WHEN_call_native_to_mpl_commmitment_policyTHEN_returns_CommitmentPolicyESDK_REQUIRE_ENCRYPT_REQUIRE_DECRYPT(): - native_commitment_policy = CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT - - output = MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) - - assert isinstance(output, CommitmentPolicyESDK) - assert output.value == "REQUIRE_ENCRYPT_REQUIRE_DECRYPT" - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GIVEN_CommitmentPolicy_unrecognized_WHEN_call_native_to_mpl_commmitment_policyTHEN_raise_ValueError(): - native_commitment_policy = "not a commitment policy" - - with pytest.raises(ValueError): - MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -@patch.object(mock_mpl_cmm, "decrypt_materials") -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._create_mpl_decrypt_materials_input_from_request") -def test_GIVEN_valid_request_WHEN_call_decrypt_materials_THEN_return_MPLDecryptionMaterials( - mock_native_to_mpl_decrypt_materials, - mock_get_encryption_materials, -): - - # Mock: mpl_cmm.get_decryption_materials returns mock MPL decryption materials - mock_decrypt_materials_output = MagicMock(__class__=GetEncryptionMaterialsOutput) - mock_decrypt_materials_output.decryption_materials = mock_mpl_decrypt_materials - mock_get_encryption_materials.return_value = mock_decrypt_materials_output - - # Mock: CMMHandler._create_mpl_decrypt_materials_input_from_request creates a DecryptMaterialsInput - mock_decrypt_materials_input = MagicMock(__class__=GetEncryptionMaterialsInput) - mock_native_to_mpl_decrypt_materials.return_value = mock_decrypt_materials_input - - cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) - output = cmm_handler.decrypt_materials(mock_decryption_materials_request) - - # Verify cmm_handler returns MPLDecryptionMaterials - assert isinstance(output, MPLDecryptionMaterials) - # Verify returned MPLDecryptionMaterials uses the output of `decrypt_materials` - assert output.mpl_materials == mock_mpl_decrypt_materials - # Verify we actually called `decrypt_materials` - mock_mpl_cmm.decrypt_materials.assert_called_once_with(mock_decrypt_materials_input) - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._create_mpl_decrypt_materials_input_from_request") -def test_GIVEN_decrypt_materials_raises_MPL_Exception_WHEN_call_decrypt_materials_THEN_raise_ESDK_Exception( - _ -): - with pytest.raises(AWSEncryptionSDKClientError): - with patch.object(mock_mpl_cmm, "decrypt_materials", - side_effect=AwsCryptographicMaterialProvidersException("any")): - - cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) - cmm_handler.decrypt_materials(mock_decryption_materials_request) - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_WHEN_call_native_algorithm_id_to_mpl_algorithm_id_THEN_returns_valid_AlgorithmSuiteIdESDK(): - some_native_algorithm_id = 0x0000 # Not a real algorithm ID, but fits the format - - mpl_output = MPLCMMHandler._native_algorithm_id_to_mpl_algorithm_id( - some_native_algorithm_id - ) - - assert isinstance(mpl_output, AlgorithmSuiteIdESDK) - assert mpl_output.value == "0x0000" - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_algorithm_id_to_mpl_algorithm_id") -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_commmitment_policy") -def test__create_mpl_decrypt_materials_input_from_request( - mock_mpl_commitment_policy, - mock_mpl_algorithm_id, -): - mock_algorithm_id = "0x1234" # Some fake algorithm ID that fits the format - mock_mpl_algorithm_id.return_value = mock_algorithm_id - mock_commitment_policy = MagicMock(__class__=CommitmentPolicyESDK) - mock_mpl_commitment_policy.return_value = mock_commitment_policy - - # mock_decryption_materials_request.algorithm = - - output = MPLCMMHandler._create_mpl_decrypt_materials_input_from_request(mock_decryption_materials_request) - - assert isinstance(output, DecryptMaterialsInput) - assert output.algorithm_suite_id == mock_algorithm_id - assert output.commitment_policy == mock_commitment_policy - assert output.encryption_context == mock_decryption_materials_request.encryption_context - - assert len(output.encrypted_data_keys) == len(mock_decryption_materials_request.encrypted_data_keys) - for i in range(len(output.encrypted_data_keys)): - # Assume input[i] == output[i], seems to work - output_edk = output.encrypted_data_keys[i] - input_edk = mock_decryption_materials_request[i] - assert output_edk.key_provider_id == input_edk.key_provider.provider_id - assert output_edk.key_provider_info == input_edk.key_provider.key_info - assert output_edk.ciphertext == input_edk.encrypted_data_key diff --git a/test/unit/mpl/test_material_managers_mpl_materials.py b/test/unit/mpl/test_material_managers_mpl_materials.py deleted file mode 100644 index 250efeb7e..000000000 --- a/test/unit/mpl/test_material_managers_mpl_materials.py +++ /dev/null @@ -1,221 +0,0 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. -"""Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic.""" - -import pytest -from mock import MagicMock, patch, PropertyMock -from typing import Dict, List - -from aws_encryption_sdk.identifiers import CommitmentPolicy -import aws_encryption_sdk.materials_managers.mpl.materials -from aws_encryption_sdk.materials_managers.mpl.materials import ( - MPLEncryptionMaterials, - MPLDecryptionMaterials, -) -from aws_encryption_sdk.identifiers import Algorithm, AlgorithmSuite - -pytestmark = [pytest.mark.unit, pytest.mark.local] - - -# Check if MPL is installed, and skip tests based on its installation status -# Ideally, this logic would be based on mocking imports and testing logic, -# but doing that introduces errors that cause other tests to fail. -try: - from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException - from aws_cryptographic_materialproviders.mpl.models import ( - AlgorithmSuiteIdESDK, - CommitmentPolicyESDK, - DecryptMaterialsInput, - DecryptionMaterials as MPL_DecryptionMaterials, - EncryptedDataKey as MPL_EncryptedDataKey, - EncryptionMaterials as MPL_EncryptionMaterials, - GetEncryptionMaterialsInput, - GetEncryptionMaterialsOutput, - ) - from aws_cryptographic_materialproviders.mpl.references import ( - ICryptographicMaterialsManager - ) - HAS_MPL = True - - mock_mpl_encryption_materials = MagicMock(__class__=MPL_EncryptionMaterials) - mock_mpl_decrypt_materials = MagicMock(__class__=MPL_DecryptionMaterials) - -except ImportError: - HAS_MPL = False - - # Ensure references to these mocks exist, even if they aren't used in a non-MPL context - mock_mpl_cmm = None - mock_mpl_encryption_materials = None - mock_mpl_decrypt_materials = None - -from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError -from aws_encryption_sdk.materials_managers import ( - EncryptionMaterialsRequest, - DecryptionMaterialsRequest, -) - - -mock_encryption_materials_request = MagicMock(__class__=EncryptionMaterialsRequest) -mock_encryption_materials_handler = MagicMock(__class__=MPLEncryptionMaterials) -mock_decryption_materials_request = MagicMock(__class__=DecryptionMaterialsRequest) - -@pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") -def test_GIVEN_test_has_mpl_is_False_THEN_cmm_has_mpl_is_False(): - """If the MPL IS NOT installed in the runtime environment, - assert the cmm has _HAS_MPL set to False""" - - assert hasattr(aws_encryption_sdk.materials_managers.mpl.materials, "_HAS_MPL") - assert aws_encryption_sdk.materials_managers.mpl.materials._HAS_MPL is False - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GIVEN_test_has_mpl_is_True_THEN_cmm_has_mpl_is_True(): - """If the MPL IS installed in the runtime environment, - assert the cmm has _HAS_MPL set to True""" - - assert hasattr(aws_encryption_sdk.materials_managers.mpl.materials, "_HAS_MPL") - assert aws_encryption_sdk.materials_managers.mpl.materials._HAS_MPL is True - - -@pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") -def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_THEN_raise_ImportError(): - with pytest.raises(ImportError): - MPLEncryptionMaterials(mpl_materials="doesn't matter") - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_valid_mpl_cmm_THEN_return_new_MPLCMMHandler(): - mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) - - assert mpl_encryption_materials.mpl_materials == mock_mpl_encryption_materials - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_invalid_mpl_cmm_THEN_raise_ValueError(): - with pytest.raises(ValueError): - MPLEncryptionMaterials(mpl_materials="not a valid mpl_materials") - -def test_mpl_to_native(): - some_mpl_algorithm_id = "0x1234" # Not a real algorithm ID, but fits the format - - native_output = aws_encryption_sdk.materials_managers.mpl.materials._mpl_algorithm_id_to_native_algorithm_id( - some_mpl_algorithm_id - ) - - assert native_output == 0x1234 - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -@patch("aws_encryption_sdk.materials_managers.mpl.materials._mpl_algorithm_id_to_native_algorithm_id") -@patch("aws_encryption_sdk.materials_managers.mpl.materials.AlgorithmSuite.get_by_id") -def test_GIVEN_valid_mpl_algorithm_id_WHEN_get_algorithm_THEN_valid_native_algorithm_id( - mock_algorithm, - mock_native_algorithm_id, -): - # Mock valid conversion from MPL to native algorithm ID - mock_native_algorithm_id.return_value = 0x1234 - - # Mock valid lookup in native AlgorithmSuite lookup - mock_algorithm.return_value = MagicMock(__class__=AlgorithmSuite) - - mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) - output = mpl_encryption_materials.algorithm - assert output == mock_algorithm() # property calls automatically, we need to call the mock - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GecTHEN_valid_native_algorithm_id(): - mock_encryption_context = MagicMock(__class__=Dict[str, str]) - mock_mpl_encryption_materials.encryption_context = mock_encryption_context - - mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) - output = mpl_encryption_materials.encryption_context - - assert output == mock_encryption_context - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GecTHEN_valid_nativefadsf_algorithm_id(): - mock_edk = MagicMock(__class__=MPL_EncryptedDataKey) - mock_mpl_key_provider_id = MagicMock(__class__=str) - mock_edk.key_provider_id = mock_mpl_key_provider_id - mock_mpl_key_provider_info = MagicMock(__class__=bytes) - mock_edk.key_provider_info = mock_mpl_key_provider_info - mock_mpl_ciphertext = MagicMock(__class__=bytes) - mock_edk.ciphertext = mock_mpl_ciphertext - - mock_edks = [ mock_edk ] - mock_mpl_encryption_materials.encrypted_data_keys = mock_edks - - mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) - output = mpl_encryption_materials.encrypted_data_keys - output_as_list = list(output) - - assert len(output_as_list) == len(mock_edks) - for i in range(len(output_as_list)): - # assume output[i] corresponds to input[i] - native_edk = output_as_list[i] - mpl_edk = mock_edks[i] - - assert native_edk.encrypted_data_key == mpl_edk.ciphertext - assert native_edk.key_provider.provider_id == mpl_edk.key_provider_id - assert native_edk.key_provider.key_info == mpl_edk.key_provider_info - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GecTHEN_valid_nativefadsffadsfa_algorithm_id(): - mock_data_key = MagicMock(__class__=bytes) - mock_mpl_encryption_materials.plaintext_data_key = mock_data_key - - mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) - output = mpl_encryption_materials.data_encryption_key - - assert output.key_provider.provider_id == "" - assert output.key_provider.key_info == b"" - assert output.data_key == mock_data_key - assert output.encrypted_data_key == b"" - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GecTHEN_valid_nativefasdfasdffadsf_algorithm_id(): - mock_signing_key = MagicMock(__class__=bytes) - mock_mpl_encryption_materials.signing_key = mock_signing_key - - mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) - output = mpl_encryption_materials.signing_key - - assert output == mock_signing_key - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GecTHEN_valid_nativeffasdfasdadsffadsfa_algorithm_id(): - mock_data_key = MagicMock(__class__=bytes) - mock_mpl_decrypt_materials.plaintext_data_key = mock_data_key - - mpl_decryption_materials = MPLDecryptionMaterials(mpl_materials=mock_mpl_decrypt_materials) - output = mpl_decryption_materials.data_key - - assert output.key_provider.provider_id == "" - assert output.key_provider.key_info == b"" - assert output.data_key == mock_data_key - assert output.encrypted_data_key == b"" - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GecTHEN_validadsfasdf_nativefasdfasdffadsf_algorithm_id(): - mock_verification_key = MagicMock(__class__=bytes) - mock_mpl_decrypt_materials.verification_key = mock_verification_key - - mpl_decryption_materials = MPLDecryptionMaterials(mpl_materials=mock_mpl_decrypt_materials) - output = mpl_decryption_materials.verification_key - - assert output == mock_verification_key diff --git a/test/unit/test_material_managers_mpl_cmm.py b/test/unit/test_material_managers_mpl_cmm.py deleted file mode 100644 index 77bf5502d..000000000 --- a/test/unit/test_material_managers_mpl_cmm.py +++ /dev/null @@ -1,278 +0,0 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. -"""Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic.""" - -import pytest -from mock import MagicMock, patch - - -from aws_encryption_sdk.identifiers import CommitmentPolicy -import aws_encryption_sdk.materials_managers.mpl.cmm -from aws_encryption_sdk.materials_managers.mpl.cmm import MPLCMMHandler -from aws_encryption_sdk.materials_managers.mpl.materials import ( - MPLEncryptionMaterials, - MPLDecryptionMaterials, -) - -pytestmark = [pytest.mark.unit, pytest.mark.local] - - -# Check if MPL is installed, and skip tests based on its installation status -# Ideally, this logic would be based on mocking imports and testing logic, -# but doing that introduces errors that cause other tests to fail. -try: - from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException - from aws_cryptographic_materialproviders.mpl.models import ( - AlgorithmSuiteIdESDK, - CommitmentPolicyESDK, - DecryptMaterialsInput, - DecryptionMaterials as MPL_DecryptionMaterials, - EncryptionMaterials as MPL_EncryptionMaterials, - GetEncryptionMaterialsInput, - GetEncryptionMaterialsOutput, - ) - from aws_cryptographic_materialproviders.mpl.references import ( - ICryptographicMaterialsManager - ) - HAS_MPL = True - - mock_mpl_cmm = MagicMock(__class__=ICryptographicMaterialsManager) - mock_mpl_encryption_materials = MagicMock(__class__=MPL_EncryptionMaterials) - mock_mpl_decrypt_materials = MagicMock(__class__=MPL_DecryptionMaterials) - -except ImportError: - HAS_MPL = False - - # Ensure references to these mocks exist, even if they aren't used in a non-MPL context - mock_mpl_cmm = None - mock_mpl_encryption_materials = None - mock_mpl_decrypt_materials = None - -from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError -from aws_encryption_sdk.materials_managers import ( - EncryptionMaterialsRequest, - DecryptionMaterialsRequest, -) - - -mock_encryption_materials_request = MagicMock(__class__=EncryptionMaterialsRequest) -mock_encryption_materials_handler = MagicMock(__class__=MPLEncryptionMaterials) -mock_decryption_materials_request = MagicMock(__class__=DecryptionMaterialsRequest) - -@pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") -def test_GIVEN_test_has_mpl_is_False_THEN_cmm_has_mpl_is_False(): - """If the MPL IS NOT installed in the runtime environment, - assert the cmm has _HAS_MPL set to False""" - - assert hasattr(aws_encryption_sdk.materials_managers.mpl.cmm, "_HAS_MPL") - assert aws_encryption_sdk.materials_managers.mpl.cmm._HAS_MPL is False - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GIVEN_test_has_mpl_is_True_THEN_cmm_has_mpl_is_True(): - """If the MPL IS installed in the runtime environment, - assert the cmm has _HAS_MPL set to True""" - - assert hasattr(aws_encryption_sdk.materials_managers.mpl.cmm, "_HAS_MPL") - assert aws_encryption_sdk.materials_managers.mpl.cmm._HAS_MPL is True - - -@pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") -def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_THEN_raise_ImportError(): - with pytest.raises(ImportError): - MPLCMMHandler(mpl_cmm="doesn't matter") - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_valid_mpl_cmm_THEN_return_new_MPLCMMHandler(): - mpl_cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) - - assert mpl_cmm_handler.mpl_cmm == mock_mpl_cmm - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_invalid_mpl_cmm_THEN_raise_ValueError(): - with pytest.raises(ValueError): - MPLCMMHandler(mpl_cmm="not a valid mpl_cmm") - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -@patch.object(mock_mpl_cmm, "get_encryption_materials") -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_get_encryption_materials") -def test_GIVEN_valid_request_WHEN_call_get_encryption_materials_THEN_return_MPLEncryptionMaterials( - mock_native_to_mpl_get_encryption_materials, - mock_get_encryption_materials, -): - - # Mock: mpl_cmm.get_encryption_materials returns mock MPL encryption materials - mock_get_encryption_materials_output = MagicMock(__class__=GetEncryptionMaterialsOutput) - mock_get_encryption_materials_output.encryption_materials = mock_mpl_encryption_materials - mock_get_encryption_materials.return_value = mock_get_encryption_materials_output - - # Mock: CMMHandler._native_to_mpl_get_encryption_materials creates a GetEncryptionMaterialsInput - mock_get_encryption_materials_input = MagicMock(__class__=GetEncryptionMaterialsInput) - mock_native_to_mpl_get_encryption_materials.return_value = mock_get_encryption_materials_input - - cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) - test = cmm_handler.get_encryption_materials(mock_encryption_materials_request) - - # Verify cmm_handler returns MPLEncryptionMaterials - assert isinstance(test, MPLEncryptionMaterials) - # Verify returned EncryptionMaterialsHandler uses the output of `get_encryption_materials` - assert test.mpl_materials == mock_mpl_encryption_materials - # Verify we actually called `get_encryption_materials` - mock_mpl_cmm.get_encryption_materials.assert_called_once_with(mock_get_encryption_materials_input) - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_commmitment_policy") -def test_GIVEN_get_encryption_materials_raises_MPL_Exception_WHEN_call_get_encryption_materials_THEN_raise_ESDK_Exception( - _ -): - with pytest.raises(AWSEncryptionSDKClientError): - with patch.object(mock_mpl_cmm, "get_encryption_materials", - side_effect=AwsCryptographicMaterialProvidersException("any")): - - cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) - cmm_handler.get_encryption_materials(mock_encryption_materials_request) - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_commmitment_policy") -def test_GIVEN_native_to_mpl_commmitment_policy_returns_valid_policy_WHEN_call_native_to_mpl_get_encryption_materials_THEN_returns_GetEncryptionMaterialsInput( - mock_mpl_commitment_policy -): - mock_commitment_policy = MagicMock(__class__=CommitmentPolicyESDK) - mock_mpl_commitment_policy.return_value = mock_commitment_policy - - output = MPLCMMHandler._native_to_mpl_get_encryption_materials(mock_encryption_materials_request) - - # verify correctness of returned value - assert isinstance(output, GetEncryptionMaterialsInput) - assert output.encryption_context == mock_encryption_materials_request.encryption_context - assert output.commitment_policy == mock_commitment_policy - assert output.max_plaintext_length == mock_encryption_materials_request.plaintext_length - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GIVEN_CommitmentPolicy_FORBID_ENCRYPT_ALLOW_DECRYPT_WHEN_call_native_to_mpl_commmitment_policyTHEN_returns_CommitmentPolicyESDK_FORBID_ENCRYPT_ALLOW_DECRYPT(): - native_commitment_policy = CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT - - output = MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) - - assert isinstance(output, CommitmentPolicyESDK) - assert output.value == "FORBID_ENCRYPT_ALLOW_DECRYPT" - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_ALLOW_DECRYPT_WHEN_call_native_to_mpl_commmitment_policyTHEN_returns_CommitmentPolicyESDK_REQUIRE_ENCRYPT_ALLOW_DECRYPT(): - native_commitment_policy = CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT - - output = MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) - - assert isinstance(output, CommitmentPolicyESDK) - assert output.value == "REQUIRE_ENCRYPT_ALLOW_DECRYPT" - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_REQUIRE_DECRYPT_WHEN_call_native_to_mpl_commmitment_policyTHEN_returns_CommitmentPolicyESDK_REQUIRE_ENCRYPT_REQUIRE_DECRYPT(): - native_commitment_policy = CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT - - output = MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) - - assert isinstance(output, CommitmentPolicyESDK) - assert output.value == "REQUIRE_ENCRYPT_REQUIRE_DECRYPT" - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GIVEN_CommitmentPolicy_unrecognized_WHEN_call_native_to_mpl_commmitment_policyTHEN_raise_ValueError(): - native_commitment_policy = "not a commitment policy" - - with pytest.raises(ValueError): - MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -@patch.object(mock_mpl_cmm, "decrypt_materials") -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._create_mpl_decrypt_materials_input_from_request") -def test_GIVEN_valid_request_WHEN_call_decrypt_materials_THEN_return_MPLDecryptionMaterials( - mock_native_to_mpl_decrypt_materials, - mock_get_encryption_materials, -): - - # Mock: mpl_cmm.get_decryption_materials returns mock MPL decryption materials - mock_decrypt_materials_output = MagicMock(__class__=GetEncryptionMaterialsOutput) - mock_decrypt_materials_output.decryption_materials = mock_mpl_decrypt_materials - mock_get_encryption_materials.return_value = mock_decrypt_materials_output - - # Mock: CMMHandler._create_mpl_decrypt_materials_input_from_request creates a DecryptMaterialsInput - mock_decrypt_materials_input = MagicMock(__class__=GetEncryptionMaterialsInput) - mock_native_to_mpl_decrypt_materials.return_value = mock_decrypt_materials_input - - cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) - output = cmm_handler.decrypt_materials(mock_decryption_materials_request) - - # Verify cmm_handler returns MPLDecryptionMaterials - assert isinstance(output, MPLDecryptionMaterials) - # Verify returned MPLDecryptionMaterials uses the output of `decrypt_materials` - assert output.mpl_materials == mock_mpl_decrypt_materials - # Verify we actually called `decrypt_materials` - mock_mpl_cmm.decrypt_materials.assert_called_once_with(mock_decrypt_materials_input) - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._create_mpl_decrypt_materials_input_from_request") -def test_GIVEN_decrypt_materials_raises_MPL_Exception_WHEN_call_decrypt_materials_THEN_raise_ESDK_Exception( - _ -): - with pytest.raises(AWSEncryptionSDKClientError): - with patch.object(mock_mpl_cmm, "decrypt_materials", - side_effect=AwsCryptographicMaterialProvidersException("any")): - - cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) - cmm_handler.decrypt_materials(mock_decryption_materials_request) - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_WHEN_call_native_algorithm_id_to_mpl_algorithm_id_THEN_returns_valid_AlgorithmSuiteIdESDK(): - some_native_algorithm_id = 0x0000 # Not a real algorithm ID, but fits the format - - mpl_output = MPLCMMHandler._native_algorithm_id_to_mpl_algorithm_id( - some_native_algorithm_id - ) - - assert isinstance(mpl_output, AlgorithmSuiteIdESDK) - assert mpl_output.value == "0x0000" - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_algorithm_id_to_mpl_algorithm_id") -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_commmitment_policy") -def test__create_mpl_decrypt_materials_input_from_request( - mock_mpl_commitment_policy, - mock_mpl_algorithm_id, -): - mock_algorithm_id = "0x1234" # Some fake algorithm ID that fits the format - mock_mpl_algorithm_id.return_value = mock_algorithm_id - mock_commitment_policy = MagicMock(__class__=CommitmentPolicyESDK) - mock_mpl_commitment_policy.return_value = mock_commitment_policy - - # mock_decryption_materials_request.algorithm = - - output = MPLCMMHandler._create_mpl_decrypt_materials_input_from_request(mock_decryption_materials_request) - - assert isinstance(output, DecryptMaterialsInput) - assert output.algorithm_suite_id == mock_algorithm_id - assert output.commitment_policy == mock_commitment_policy - assert output.encryption_context == mock_decryption_materials_request.encryption_context - - assert len(output.encrypted_data_keys) == len(mock_decryption_materials_request.encrypted_data_keys) - for i in range(len(output.encrypted_data_keys)): - # Assume input[i] == output[i], seems to work - output_edk = output.encrypted_data_keys[i] - input_edk = mock_decryption_materials_request[i] - assert output_edk.key_provider_id == input_edk.key_provider.provider_id - assert output_edk.key_provider_info == input_edk.key_provider.key_info - assert output_edk.ciphertext == input_edk.encrypted_data_key diff --git a/test/unit/test_material_managers_mpl_materials.py b/test/unit/test_material_managers_mpl_materials.py deleted file mode 100644 index 250efeb7e..000000000 --- a/test/unit/test_material_managers_mpl_materials.py +++ /dev/null @@ -1,221 +0,0 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. -"""Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic.""" - -import pytest -from mock import MagicMock, patch, PropertyMock -from typing import Dict, List - -from aws_encryption_sdk.identifiers import CommitmentPolicy -import aws_encryption_sdk.materials_managers.mpl.materials -from aws_encryption_sdk.materials_managers.mpl.materials import ( - MPLEncryptionMaterials, - MPLDecryptionMaterials, -) -from aws_encryption_sdk.identifiers import Algorithm, AlgorithmSuite - -pytestmark = [pytest.mark.unit, pytest.mark.local] - - -# Check if MPL is installed, and skip tests based on its installation status -# Ideally, this logic would be based on mocking imports and testing logic, -# but doing that introduces errors that cause other tests to fail. -try: - from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException - from aws_cryptographic_materialproviders.mpl.models import ( - AlgorithmSuiteIdESDK, - CommitmentPolicyESDK, - DecryptMaterialsInput, - DecryptionMaterials as MPL_DecryptionMaterials, - EncryptedDataKey as MPL_EncryptedDataKey, - EncryptionMaterials as MPL_EncryptionMaterials, - GetEncryptionMaterialsInput, - GetEncryptionMaterialsOutput, - ) - from aws_cryptographic_materialproviders.mpl.references import ( - ICryptographicMaterialsManager - ) - HAS_MPL = True - - mock_mpl_encryption_materials = MagicMock(__class__=MPL_EncryptionMaterials) - mock_mpl_decrypt_materials = MagicMock(__class__=MPL_DecryptionMaterials) - -except ImportError: - HAS_MPL = False - - # Ensure references to these mocks exist, even if they aren't used in a non-MPL context - mock_mpl_cmm = None - mock_mpl_encryption_materials = None - mock_mpl_decrypt_materials = None - -from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError -from aws_encryption_sdk.materials_managers import ( - EncryptionMaterialsRequest, - DecryptionMaterialsRequest, -) - - -mock_encryption_materials_request = MagicMock(__class__=EncryptionMaterialsRequest) -mock_encryption_materials_handler = MagicMock(__class__=MPLEncryptionMaterials) -mock_decryption_materials_request = MagicMock(__class__=DecryptionMaterialsRequest) - -@pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") -def test_GIVEN_test_has_mpl_is_False_THEN_cmm_has_mpl_is_False(): - """If the MPL IS NOT installed in the runtime environment, - assert the cmm has _HAS_MPL set to False""" - - assert hasattr(aws_encryption_sdk.materials_managers.mpl.materials, "_HAS_MPL") - assert aws_encryption_sdk.materials_managers.mpl.materials._HAS_MPL is False - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GIVEN_test_has_mpl_is_True_THEN_cmm_has_mpl_is_True(): - """If the MPL IS installed in the runtime environment, - assert the cmm has _HAS_MPL set to True""" - - assert hasattr(aws_encryption_sdk.materials_managers.mpl.materials, "_HAS_MPL") - assert aws_encryption_sdk.materials_managers.mpl.materials._HAS_MPL is True - - -@pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") -def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_THEN_raise_ImportError(): - with pytest.raises(ImportError): - MPLEncryptionMaterials(mpl_materials="doesn't matter") - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_valid_mpl_cmm_THEN_return_new_MPLCMMHandler(): - mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) - - assert mpl_encryption_materials.mpl_materials == mock_mpl_encryption_materials - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_invalid_mpl_cmm_THEN_raise_ValueError(): - with pytest.raises(ValueError): - MPLEncryptionMaterials(mpl_materials="not a valid mpl_materials") - -def test_mpl_to_native(): - some_mpl_algorithm_id = "0x1234" # Not a real algorithm ID, but fits the format - - native_output = aws_encryption_sdk.materials_managers.mpl.materials._mpl_algorithm_id_to_native_algorithm_id( - some_mpl_algorithm_id - ) - - assert native_output == 0x1234 - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -@patch("aws_encryption_sdk.materials_managers.mpl.materials._mpl_algorithm_id_to_native_algorithm_id") -@patch("aws_encryption_sdk.materials_managers.mpl.materials.AlgorithmSuite.get_by_id") -def test_GIVEN_valid_mpl_algorithm_id_WHEN_get_algorithm_THEN_valid_native_algorithm_id( - mock_algorithm, - mock_native_algorithm_id, -): - # Mock valid conversion from MPL to native algorithm ID - mock_native_algorithm_id.return_value = 0x1234 - - # Mock valid lookup in native AlgorithmSuite lookup - mock_algorithm.return_value = MagicMock(__class__=AlgorithmSuite) - - mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) - output = mpl_encryption_materials.algorithm - assert output == mock_algorithm() # property calls automatically, we need to call the mock - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GecTHEN_valid_native_algorithm_id(): - mock_encryption_context = MagicMock(__class__=Dict[str, str]) - mock_mpl_encryption_materials.encryption_context = mock_encryption_context - - mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) - output = mpl_encryption_materials.encryption_context - - assert output == mock_encryption_context - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GecTHEN_valid_nativefadsf_algorithm_id(): - mock_edk = MagicMock(__class__=MPL_EncryptedDataKey) - mock_mpl_key_provider_id = MagicMock(__class__=str) - mock_edk.key_provider_id = mock_mpl_key_provider_id - mock_mpl_key_provider_info = MagicMock(__class__=bytes) - mock_edk.key_provider_info = mock_mpl_key_provider_info - mock_mpl_ciphertext = MagicMock(__class__=bytes) - mock_edk.ciphertext = mock_mpl_ciphertext - - mock_edks = [ mock_edk ] - mock_mpl_encryption_materials.encrypted_data_keys = mock_edks - - mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) - output = mpl_encryption_materials.encrypted_data_keys - output_as_list = list(output) - - assert len(output_as_list) == len(mock_edks) - for i in range(len(output_as_list)): - # assume output[i] corresponds to input[i] - native_edk = output_as_list[i] - mpl_edk = mock_edks[i] - - assert native_edk.encrypted_data_key == mpl_edk.ciphertext - assert native_edk.key_provider.provider_id == mpl_edk.key_provider_id - assert native_edk.key_provider.key_info == mpl_edk.key_provider_info - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GecTHEN_valid_nativefadsffadsfa_algorithm_id(): - mock_data_key = MagicMock(__class__=bytes) - mock_mpl_encryption_materials.plaintext_data_key = mock_data_key - - mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) - output = mpl_encryption_materials.data_encryption_key - - assert output.key_provider.provider_id == "" - assert output.key_provider.key_info == b"" - assert output.data_key == mock_data_key - assert output.encrypted_data_key == b"" - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GecTHEN_valid_nativefasdfasdffadsf_algorithm_id(): - mock_signing_key = MagicMock(__class__=bytes) - mock_mpl_encryption_materials.signing_key = mock_signing_key - - mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) - output = mpl_encryption_materials.signing_key - - assert output == mock_signing_key - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GecTHEN_valid_nativeffasdfasdadsffadsfa_algorithm_id(): - mock_data_key = MagicMock(__class__=bytes) - mock_mpl_decrypt_materials.plaintext_data_key = mock_data_key - - mpl_decryption_materials = MPLDecryptionMaterials(mpl_materials=mock_mpl_decrypt_materials) - output = mpl_decryption_materials.data_key - - assert output.key_provider.provider_id == "" - assert output.key_provider.key_info == b"" - assert output.data_key == mock_data_key - assert output.encrypted_data_key == b"" - - -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") -def test_GecTHEN_validadsfasdf_nativefasdfasdffadsf_algorithm_id(): - mock_verification_key = MagicMock(__class__=bytes) - mock_mpl_decrypt_materials.verification_key = mock_verification_key - - mpl_decryption_materials = MPLDecryptionMaterials(mpl_materials=mock_mpl_decrypt_materials) - output = mpl_decryption_materials.verification_key - - assert output == mock_verification_key diff --git a/tox.ini b/tox.ini index 346e4fae0..e48f9f3b5 100644 --- a/tox.ini +++ b/tox.ini @@ -82,7 +82,7 @@ deps = # Install the MPL requirements if the `-mpl` suffix is present mpl: -rrequirements_mpl.txt commands = - local: {[testenv:base-command]commands} test/ -m local --ignore test/unit/mpl/ + local: {[testenv:base-command]commands} test/ -m local --ignore test/mpl/ # MPL unit tests require the MPL to be installed mpllocal: {[testenv:base-command]commands} test/ -m local integ: {[testenv:base-command]commands} test/ -m integ @@ -90,13 +90,13 @@ commands = examples: {[testenv:base-command]commands} examples/test/ -m examples --ignore examples/test/keyrings/ # MPL keyring examples require a special IAM role; run these separately under a separate set of permissions mplexamples: {[testenv:base-command]commands} examples/test/keyrings -m examples - all: {[testenv:base-command]commands} test/ examples/test/ --ignore test/unit/mpl/ --ignore examples/test/keyrings/ + all: {[testenv:base-command]commands} test/ examples/test/ --ignore test/mpl/ --ignore examples/test/keyrings/ mplall: {[testenv:base-command]commands} test/ examples/test/ manual: {[testenv:base-command]commands} # Run code coverage on the unit tests [testenv:coverage] -commands = {[testenv:base-command]commands} --cov aws_encryption_sdk test/ -m local --ignore test/unit/mpl/ +commands = {[testenv:base-command]commands} --cov aws_encryption_sdk test/ -m local --ignore test/mpl/ [testenv:mplcoverage-mpl] commands = {[testenv:base-command]commands} --cov aws_encryption_sdk test/ -m local From 61ba4dec6bdc404a13bb245fa5bbb2078d014edc Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 10:36:39 -0800 Subject: [PATCH 109/184] refactor tests --- src/aws_encryption_sdk/streaming_client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 61f2f88c6..01d4ca5ac 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -58,7 +58,6 @@ serialize_non_framed_close, serialize_non_framed_open, ) -from aws_encryption_sdk.materials_managers.mpl.cmm import MPLCMMHandler from aws_encryption_sdk.internal.utils import exactly_one_arg_is_not_none from aws_encryption_sdk.internal.utils.commitment import ( validate_commitment_policy_on_decrypt, @@ -79,6 +78,10 @@ from aws_cryptographic_materialproviders.mpl.models import CreateDefaultCryptographicMaterialsManagerInput from aws_cryptographic_materialproviders.mpl.references import IKeyring _HAS_MPL = True + + # Import internal ESDK modules that depend on the MPL + from aws_encryption_sdk.materials_managers.mpl.cmm import MPLCMMHandler + except ImportError: _HAS_MPL = False From 95c5be6bfb7fea0ef31ebcc8e5ddec0ac07df9fd Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 10:37:17 -0800 Subject: [PATCH 110/184] refactor tests --- src/aws_encryption_sdk/streaming_client.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 01d4ca5ac..a3c05bbb7 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -554,7 +554,8 @@ def _prep_message(self): else: # MPL verification key is PEM bytes, not DER bytes. # If the underlying CMM is from the MPL, load PEM bytes. - if (isinstance(self.config.materials_manager, MPLCMMHandler)): + if (_HAS_MPL + and isinstance(self.config.materials_manager, MPLCMMHandler)): self.signer = Signer.from_key_bytes( algorithm=self._encryption_materials.algorithm, key_bytes=self._encryption_materials.signing_key, encoding=serialization.Encoding.PEM, @@ -921,7 +922,8 @@ def _read_header(self): else: # MPL verification key is NOT key bytes; it is bytes of the compressed point. # If the underlying CMM is from the MPL, load bytes from encoded point. - if (isinstance(self.config.materials_manager, MPLCMMHandler)): + if (_HAS_MPL + and isinstance(self.config.materials_manager, MPLCMMHandler)): self.verifier = Verifier.from_encoded_point( algorithm=header.algorithm, encoded_point=base64.b64encode(decryption_materials.verification_key) From 9566873a946acb70622962bdbcc8b2086e88e16d Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 10:51:45 -0800 Subject: [PATCH 111/184] refactor tests --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index e48f9f3b5..9e3fe2b98 100644 --- a/tox.ini +++ b/tox.ini @@ -96,7 +96,7 @@ commands = # Run code coverage on the unit tests [testenv:coverage] -commands = {[testenv:base-command]commands} --cov aws_encryption_sdk test/ -m local --ignore test/mpl/ +commands = {[testenv:base-command]commands} --cov aws_encryption_sdk --cov-config=.coveragerc test/ -m local --ignore test/mpl/ [testenv:mplcoverage-mpl] commands = {[testenv:base-command]commands} --cov aws_encryption_sdk test/ -m local From 66420832da4a4a8806254f0b8487ee03452a17d1 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 11:16:25 -0800 Subject: [PATCH 112/184] fix cov --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 9e3fe2b98..70ed4281f 100644 --- a/tox.ini +++ b/tox.ini @@ -96,9 +96,9 @@ commands = # Run code coverage on the unit tests [testenv:coverage] -commands = {[testenv:base-command]commands} --cov aws_encryption_sdk --cov-config=.coveragerc test/ -m local --ignore test/mpl/ +commands = {[testenv:base-command]commands} --cov aws_encryption_sdk test/ -m local --ignore test/mpl/ [testenv:mplcoverage-mpl] -commands = {[testenv:base-command]commands} --cov aws_encryption_sdk test/ -m local +commands = {[testenv:base-command]commands} --cov --cov-config=.coveragercmpl aws_encryption_sdk test/ -m local # Verify that local tests work without environment variables present [testenv:nocmk] From 51d2804343797d98848f7eea2b8f333f9f7f8a46 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 11:22:26 -0800 Subject: [PATCH 113/184] fix cov --- .coveragerc | 7 + .coveragercmpl | 1 + .gitignore | 5 +- test/mpl/README.md | 1 + test/mpl/__init__.py | 13 + .../unit/test_material_managers_mpl_cmm.py | 250 ++++++++++++++++++ .../test_material_managers_mpl_materials.py | 197 ++++++++++++++ 7 files changed, 472 insertions(+), 2 deletions(-) create mode 100644 .coveragerc create mode 100644 .coveragercmpl create mode 100644 test/mpl/README.md create mode 100644 test/mpl/__init__.py create mode 100644 test/mpl/unit/test_material_managers_mpl_cmm.py create mode 100644 test/mpl/unit/test_material_managers_mpl_materials.py diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..8957349aa --- /dev/null +++ b/.coveragerc @@ -0,0 +1,7 @@ +# .coveragerc file when running coverage WITHOUT coverage for the MPL +# This prevents the ESDK without the MPL from considering the MPL-specific modules as "missed" coverage +[run] +omit = */aws_encryption_sdk/materials_managers/mpl/* + +[report] +omit = */aws_encryption_sdk/materials_managers/mpl/* \ No newline at end of file diff --git a/.coveragercmpl b/.coveragercmpl new file mode 100644 index 000000000..31a7b4407 --- /dev/null +++ b/.coveragercmpl @@ -0,0 +1 @@ +# .coveragerc file when running coverage WITH coverage for the MPL diff --git a/.gitignore b/.gitignore index 63097dcba..fc224adc4 100644 --- a/.gitignore +++ b/.gitignore @@ -19,8 +19,9 @@ docs/build __pycache__ *.egg-info -# Coverage.py -.coverage* +# Coverage.py, NOT .coveragerc nor .coveragercmpl +.coverage +.coverage.py # MyPy .mypy_cache diff --git a/test/mpl/README.md b/test/mpl/README.md new file mode 100644 index 000000000..7ae7134d0 --- /dev/null +++ b/test/mpl/README.md @@ -0,0 +1 @@ +Tests in this directory REQUIRE the [aws-cryptographic-material-providers](https://github.com/aws/aws-cryptographic-material-providers-library) library to execute. \ No newline at end of file diff --git a/test/mpl/__init__.py b/test/mpl/__init__.py new file mode 100644 index 000000000..b976c1308 --- /dev/null +++ b/test/mpl/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Module containing tests that REQUIRE the aws-cryptographic-material-providers library to run.""" \ No newline at end of file diff --git a/test/mpl/unit/test_material_managers_mpl_cmm.py b/test/mpl/unit/test_material_managers_mpl_cmm.py new file mode 100644 index 000000000..b1589b1cf --- /dev/null +++ b/test/mpl/unit/test_material_managers_mpl_cmm.py @@ -0,0 +1,250 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic.""" + +import pytest +from mock import MagicMock, patch + + +from aws_encryption_sdk.identifiers import CommitmentPolicy +import aws_encryption_sdk.materials_managers.mpl.cmm +from aws_encryption_sdk.materials_managers.mpl.cmm import MPLCMMHandler +from aws_encryption_sdk.materials_managers.mpl.materials import ( + MPLEncryptionMaterials, + MPLDecryptionMaterials, +) + +pytestmark = [pytest.mark.unit, pytest.mark.local] + + +from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException +from aws_cryptographic_materialproviders.mpl.models import ( + AlgorithmSuiteIdESDK, + CommitmentPolicyESDK, + DecryptMaterialsInput, + DecryptionMaterials as MPL_DecryptionMaterials, + EncryptionMaterials as MPL_EncryptionMaterials, + GetEncryptionMaterialsInput, + GetEncryptionMaterialsOutput, +) +from aws_cryptographic_materialproviders.mpl.references import ( + ICryptographicMaterialsManager +) + +mock_mpl_cmm = MagicMock(__class__=ICryptographicMaterialsManager) +mock_mpl_encryption_materials = MagicMock(__class__=MPL_EncryptionMaterials) +mock_mpl_decrypt_materials = MagicMock(__class__=MPL_DecryptionMaterials) + + +from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError +from aws_encryption_sdk.materials_managers import ( + EncryptionMaterialsRequest, + DecryptionMaterialsRequest, +) + + +mock_encryption_materials_request = MagicMock(__class__=EncryptionMaterialsRequest) +mock_encryption_materials_handler = MagicMock(__class__=MPLEncryptionMaterials) +mock_decryption_materials_request = MagicMock(__class__=DecryptionMaterialsRequest) + +def test_GIVEN_test_has_mpl_is_False_THEN_cmm_has_mpl_is_False(): + """If the MPL IS NOT installed in the runtime environment, + assert the cmm has _HAS_MPL set to False""" + + assert hasattr(aws_encryption_sdk.materials_managers.mpl.cmm, "_HAS_MPL") + assert aws_encryption_sdk.materials_managers.mpl.cmm._HAS_MPL is False + + +def test_GIVEN_test_has_mpl_is_True_THEN_cmm_has_mpl_is_True(): + """If the MPL IS installed in the runtime environment, + assert the cmm has _HAS_MPL set to True""" + + assert hasattr(aws_encryption_sdk.materials_managers.mpl.cmm, "_HAS_MPL") + assert aws_encryption_sdk.materials_managers.mpl.cmm._HAS_MPL is True + + +def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_THEN_raise_ImportError(): + with pytest.raises(ImportError): + MPLCMMHandler(mpl_cmm="doesn't matter") + + +def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_valid_mpl_cmm_THEN_return_new_MPLCMMHandler(): + mpl_cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) + + assert mpl_cmm_handler.mpl_cmm == mock_mpl_cmm + + +def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_invalid_mpl_cmm_THEN_raise_ValueError(): + with pytest.raises(ValueError): + MPLCMMHandler(mpl_cmm="not a valid mpl_cmm") + + +@patch.object(mock_mpl_cmm, "get_encryption_materials") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_get_encryption_materials") +def test_GIVEN_valid_request_WHEN_call_get_encryption_materials_THEN_return_MPLEncryptionMaterials( + mock_native_to_mpl_get_encryption_materials, + mock_get_encryption_materials, +): + + # Mock: mpl_cmm.get_encryption_materials returns mock MPL encryption materials + mock_get_encryption_materials_output = MagicMock(__class__=GetEncryptionMaterialsOutput) + mock_get_encryption_materials_output.encryption_materials = mock_mpl_encryption_materials + mock_get_encryption_materials.return_value = mock_get_encryption_materials_output + + # Mock: CMMHandler._native_to_mpl_get_encryption_materials creates a GetEncryptionMaterialsInput + mock_get_encryption_materials_input = MagicMock(__class__=GetEncryptionMaterialsInput) + mock_native_to_mpl_get_encryption_materials.return_value = mock_get_encryption_materials_input + + cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) + test = cmm_handler.get_encryption_materials(mock_encryption_materials_request) + + # Verify cmm_handler returns MPLEncryptionMaterials + assert isinstance(test, MPLEncryptionMaterials) + # Verify returned EncryptionMaterialsHandler uses the output of `get_encryption_materials` + assert test.mpl_materials == mock_mpl_encryption_materials + # Verify we actually called `get_encryption_materials` + mock_mpl_cmm.get_encryption_materials.assert_called_once_with(mock_get_encryption_materials_input) + + +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_commmitment_policy") +def test_GIVEN_get_encryption_materials_raises_MPL_Exception_WHEN_call_get_encryption_materials_THEN_raise_ESDK_Exception( + _ +): + with pytest.raises(AWSEncryptionSDKClientError): + with patch.object(mock_mpl_cmm, "get_encryption_materials", + side_effect=AwsCryptographicMaterialProvidersException("any")): + + cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) + cmm_handler.get_encryption_materials(mock_encryption_materials_request) + +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_commmitment_policy") +def test_GIVEN_native_to_mpl_commmitment_policy_returns_valid_policy_WHEN_call_native_to_mpl_get_encryption_materials_THEN_returns_GetEncryptionMaterialsInput( + mock_mpl_commitment_policy +): + mock_commitment_policy = MagicMock(__class__=CommitmentPolicyESDK) + mock_mpl_commitment_policy.return_value = mock_commitment_policy + + output = MPLCMMHandler._native_to_mpl_get_encryption_materials(mock_encryption_materials_request) + + # verify correctness of returned value + assert isinstance(output, GetEncryptionMaterialsInput) + assert output.encryption_context == mock_encryption_materials_request.encryption_context + assert output.commitment_policy == mock_commitment_policy + assert output.max_plaintext_length == mock_encryption_materials_request.plaintext_length + + +def test_GIVEN_CommitmentPolicy_FORBID_ENCRYPT_ALLOW_DECRYPT_WHEN_call_native_to_mpl_commmitment_policyTHEN_returns_CommitmentPolicyESDK_FORBID_ENCRYPT_ALLOW_DECRYPT(): + native_commitment_policy = CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT + + output = MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) + + assert isinstance(output, CommitmentPolicyESDK) + assert output.value == "FORBID_ENCRYPT_ALLOW_DECRYPT" + +def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_ALLOW_DECRYPT_WHEN_call_native_to_mpl_commmitment_policyTHEN_returns_CommitmentPolicyESDK_REQUIRE_ENCRYPT_ALLOW_DECRYPT(): + native_commitment_policy = CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT + + output = MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) + + assert isinstance(output, CommitmentPolicyESDK) + assert output.value == "REQUIRE_ENCRYPT_ALLOW_DECRYPT" + +def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_REQUIRE_DECRYPT_WHEN_call_native_to_mpl_commmitment_policyTHEN_returns_CommitmentPolicyESDK_REQUIRE_ENCRYPT_REQUIRE_DECRYPT(): + native_commitment_policy = CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT + + output = MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) + + assert isinstance(output, CommitmentPolicyESDK) + assert output.value == "REQUIRE_ENCRYPT_REQUIRE_DECRYPT" + +def test_GIVEN_CommitmentPolicy_unrecognized_WHEN_call_native_to_mpl_commmitment_policyTHEN_raise_ValueError(): + native_commitment_policy = "not a commitment policy" + + with pytest.raises(ValueError): + MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) + +@patch.object(mock_mpl_cmm, "decrypt_materials") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._create_mpl_decrypt_materials_input_from_request") +def test_GIVEN_valid_request_WHEN_call_decrypt_materials_THEN_return_MPLDecryptionMaterials( + mock_native_to_mpl_decrypt_materials, + mock_get_encryption_materials, +): + + # Mock: mpl_cmm.get_decryption_materials returns mock MPL decryption materials + mock_decrypt_materials_output = MagicMock(__class__=GetEncryptionMaterialsOutput) + mock_decrypt_materials_output.decryption_materials = mock_mpl_decrypt_materials + mock_get_encryption_materials.return_value = mock_decrypt_materials_output + + # Mock: CMMHandler._create_mpl_decrypt_materials_input_from_request creates a DecryptMaterialsInput + mock_decrypt_materials_input = MagicMock(__class__=GetEncryptionMaterialsInput) + mock_native_to_mpl_decrypt_materials.return_value = mock_decrypt_materials_input + + cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) + output = cmm_handler.decrypt_materials(mock_decryption_materials_request) + + # Verify cmm_handler returns MPLDecryptionMaterials + assert isinstance(output, MPLDecryptionMaterials) + # Verify returned MPLDecryptionMaterials uses the output of `decrypt_materials` + assert output.mpl_materials == mock_mpl_decrypt_materials + # Verify we actually called `decrypt_materials` + mock_mpl_cmm.decrypt_materials.assert_called_once_with(mock_decrypt_materials_input) + +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._create_mpl_decrypt_materials_input_from_request") +def test_GIVEN_decrypt_materials_raises_MPL_Exception_WHEN_call_decrypt_materials_THEN_raise_ESDK_Exception( + _ +): + with pytest.raises(AWSEncryptionSDKClientError): + with patch.object(mock_mpl_cmm, "decrypt_materials", + side_effect=AwsCryptographicMaterialProvidersException("any")): + + cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) + cmm_handler.decrypt_materials(mock_decryption_materials_request) + +def test_WHEN_call_native_algorithm_id_to_mpl_algorithm_id_THEN_returns_valid_AlgorithmSuiteIdESDK(): + some_native_algorithm_id = 0x0000 # Not a real algorithm ID, but fits the format + + mpl_output = MPLCMMHandler._native_algorithm_id_to_mpl_algorithm_id( + some_native_algorithm_id + ) + + assert isinstance(mpl_output, AlgorithmSuiteIdESDK) + assert mpl_output.value == "0x0000" + +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_algorithm_id_to_mpl_algorithm_id") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_commmitment_policy") +def test__create_mpl_decrypt_materials_input_from_request( + mock_mpl_commitment_policy, + mock_mpl_algorithm_id, +): + mock_algorithm_id = "0x1234" # Some fake algorithm ID that fits the format + mock_mpl_algorithm_id.return_value = mock_algorithm_id + mock_commitment_policy = MagicMock(__class__=CommitmentPolicyESDK) + mock_mpl_commitment_policy.return_value = mock_commitment_policy + + # mock_decryption_materials_request.algorithm = + + output = MPLCMMHandler._create_mpl_decrypt_materials_input_from_request(mock_decryption_materials_request) + + assert isinstance(output, DecryptMaterialsInput) + assert output.algorithm_suite_id == mock_algorithm_id + assert output.commitment_policy == mock_commitment_policy + assert output.encryption_context == mock_decryption_materials_request.encryption_context + + assert len(output.encrypted_data_keys) == len(mock_decryption_materials_request.encrypted_data_keys) + for i in range(len(output.encrypted_data_keys)): + # Assume input[i] == output[i], seems to work + output_edk = output.encrypted_data_keys[i] + input_edk = mock_decryption_materials_request[i] + assert output_edk.key_provider_id == input_edk.key_provider.provider_id + assert output_edk.key_provider_info == input_edk.key_provider.key_info + assert output_edk.ciphertext == input_edk.encrypted_data_key diff --git a/test/mpl/unit/test_material_managers_mpl_materials.py b/test/mpl/unit/test_material_managers_mpl_materials.py new file mode 100644 index 000000000..dfd6b2769 --- /dev/null +++ b/test/mpl/unit/test_material_managers_mpl_materials.py @@ -0,0 +1,197 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic.""" + +import pytest +from mock import MagicMock, patch, PropertyMock +from typing import Dict, List + +from aws_encryption_sdk.identifiers import CommitmentPolicy +import aws_encryption_sdk.materials_managers.mpl.materials +from aws_encryption_sdk.materials_managers.mpl.materials import ( + MPLEncryptionMaterials, + MPLDecryptionMaterials, +) +from aws_encryption_sdk.identifiers import Algorithm, AlgorithmSuite + +pytestmark = [pytest.mark.unit, pytest.mark.local] + + +from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException +from aws_cryptographic_materialproviders.mpl.models import ( + AlgorithmSuiteIdESDK, + CommitmentPolicyESDK, + DecryptMaterialsInput, + DecryptionMaterials as MPL_DecryptionMaterials, + EncryptedDataKey as MPL_EncryptedDataKey, + EncryptionMaterials as MPL_EncryptionMaterials, + GetEncryptionMaterialsInput, + GetEncryptionMaterialsOutput, +) +from aws_cryptographic_materialproviders.mpl.references import ( + ICryptographicMaterialsManager +) + +mock_mpl_encryption_materials = MagicMock(__class__=MPL_EncryptionMaterials) +mock_mpl_decrypt_materials = MagicMock(__class__=MPL_DecryptionMaterials) + + +from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError +from aws_encryption_sdk.materials_managers import ( + EncryptionMaterialsRequest, + DecryptionMaterialsRequest, +) + + +mock_encryption_materials_request = MagicMock(__class__=EncryptionMaterialsRequest) +mock_encryption_materials_handler = MagicMock(__class__=MPLEncryptionMaterials) +mock_decryption_materials_request = MagicMock(__class__=DecryptionMaterialsRequest) + +def test_GIVEN_test_has_mpl_is_False_THEN_cmm_has_mpl_is_False(): + """If the MPL IS NOT installed in the runtime environment, + assert the cmm has _HAS_MPL set to False""" + + assert hasattr(aws_encryption_sdk.materials_managers.mpl.materials, "_HAS_MPL") + assert aws_encryption_sdk.materials_managers.mpl.materials._HAS_MPL is False + + +def test_GIVEN_test_has_mpl_is_True_THEN_cmm_has_mpl_is_True(): + """If the MPL IS installed in the runtime environment, + assert the cmm has _HAS_MPL set to True""" + + assert hasattr(aws_encryption_sdk.materials_managers.mpl.materials, "_HAS_MPL") + assert aws_encryption_sdk.materials_managers.mpl.materials._HAS_MPL is True + + +def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_THEN_raise_ImportError(): + with pytest.raises(ImportError): + MPLEncryptionMaterials(mpl_materials="doesn't matter") + + +def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_valid_mpl_cmm_THEN_return_new_MPLCMMHandler(): + mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + + assert mpl_encryption_materials.mpl_materials == mock_mpl_encryption_materials + + +def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_invalid_mpl_cmm_THEN_raise_ValueError(): + with pytest.raises(ValueError): + MPLEncryptionMaterials(mpl_materials="not a valid mpl_materials") + +def test_mpl_to_native(): + some_mpl_algorithm_id = "0x1234" # Not a real algorithm ID, but fits the format + + native_output = aws_encryption_sdk.materials_managers.mpl.materials._mpl_algorithm_id_to_native_algorithm_id( + some_mpl_algorithm_id + ) + + assert native_output == 0x1234 + + +@patch("aws_encryption_sdk.materials_managers.mpl.materials._mpl_algorithm_id_to_native_algorithm_id") +@patch("aws_encryption_sdk.materials_managers.mpl.materials.AlgorithmSuite.get_by_id") +def test_GIVEN_valid_mpl_algorithm_id_WHEN_get_algorithm_THEN_valid_native_algorithm_id( + mock_algorithm, + mock_native_algorithm_id, +): + # Mock valid conversion from MPL to native algorithm ID + mock_native_algorithm_id.return_value = 0x1234 + + # Mock valid lookup in native AlgorithmSuite lookup + mock_algorithm.return_value = MagicMock(__class__=AlgorithmSuite) + + mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + output = mpl_encryption_materials.algorithm + assert output == mock_algorithm() # property calls automatically, we need to call the mock + + +def test_GecTHEN_valid_native_algorithm_id(): + mock_encryption_context = MagicMock(__class__=Dict[str, str]) + mock_mpl_encryption_materials.encryption_context = mock_encryption_context + + mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + output = mpl_encryption_materials.encryption_context + + assert output == mock_encryption_context + + +def test_GecTHEN_valid_nativefadsf_algorithm_id(): + mock_edk = MagicMock(__class__=MPL_EncryptedDataKey) + mock_mpl_key_provider_id = MagicMock(__class__=str) + mock_edk.key_provider_id = mock_mpl_key_provider_id + mock_mpl_key_provider_info = MagicMock(__class__=bytes) + mock_edk.key_provider_info = mock_mpl_key_provider_info + mock_mpl_ciphertext = MagicMock(__class__=bytes) + mock_edk.ciphertext = mock_mpl_ciphertext + + mock_edks = [ mock_edk ] + mock_mpl_encryption_materials.encrypted_data_keys = mock_edks + + mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + output = mpl_encryption_materials.encrypted_data_keys + output_as_list = list(output) + + assert len(output_as_list) == len(mock_edks) + for i in range(len(output_as_list)): + # assume output[i] corresponds to input[i] + native_edk = output_as_list[i] + mpl_edk = mock_edks[i] + + assert native_edk.encrypted_data_key == mpl_edk.ciphertext + assert native_edk.key_provider.provider_id == mpl_edk.key_provider_id + assert native_edk.key_provider.key_info == mpl_edk.key_provider_info + +def test_GecTHEN_valid_nativefadsffadsfa_algorithm_id(): + mock_data_key = MagicMock(__class__=bytes) + mock_mpl_encryption_materials.plaintext_data_key = mock_data_key + + mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + output = mpl_encryption_materials.data_encryption_key + + assert output.key_provider.provider_id == "" + assert output.key_provider.key_info == b"" + assert output.data_key == mock_data_key + assert output.encrypted_data_key == b"" + + +def test_GecTHEN_valid_nativefasdfasdffadsf_algorithm_id(): + mock_signing_key = MagicMock(__class__=bytes) + mock_mpl_encryption_materials.signing_key = mock_signing_key + + mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + output = mpl_encryption_materials.signing_key + + assert output == mock_signing_key + + +def test_GecTHEN_valid_nativeffasdfasdadsffadsfa_algorithm_id(): + mock_data_key = MagicMock(__class__=bytes) + mock_mpl_decrypt_materials.plaintext_data_key = mock_data_key + + mpl_decryption_materials = MPLDecryptionMaterials(mpl_materials=mock_mpl_decrypt_materials) + output = mpl_decryption_materials.data_key + + assert output.key_provider.provider_id == "" + assert output.key_provider.key_info == b"" + assert output.data_key == mock_data_key + assert output.encrypted_data_key == b"" + + +def test_GecTHEN_validadsfasdf_nativefasdfasdffadsf_algorithm_id(): + mock_verification_key = MagicMock(__class__=bytes) + mock_mpl_decrypt_materials.verification_key = mock_verification_key + + mpl_decryption_materials = MPLDecryptionMaterials(mpl_materials=mock_mpl_decrypt_materials) + output = mpl_decryption_materials.verification_key + + assert output == mock_verification_key From 51e5db501c41d5c8e2351daa7b4331a21132f2b7 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 11:26:15 -0800 Subject: [PATCH 114/184] fix cov --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 70ed4281f..1cc78c45e 100644 --- a/tox.ini +++ b/tox.ini @@ -98,7 +98,7 @@ commands = [testenv:coverage] commands = {[testenv:base-command]commands} --cov aws_encryption_sdk test/ -m local --ignore test/mpl/ [testenv:mplcoverage-mpl] -commands = {[testenv:base-command]commands} --cov --cov-config=.coveragercmpl aws_encryption_sdk test/ -m local +commands = {[testenv:base-command]commands} --cov-config=.coveragercmpl --cov aws_encryption_sdk test/ -m local # Verify that local tests work without environment variables present [testenv:nocmk] From e2354613a6d9c14c8cd4116a399e27010010d6f4 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 11:29:58 -0800 Subject: [PATCH 115/184] fix cov --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 1cc78c45e..419188b54 100644 --- a/tox.ini +++ b/tox.ini @@ -85,8 +85,8 @@ commands = local: {[testenv:base-command]commands} test/ -m local --ignore test/mpl/ # MPL unit tests require the MPL to be installed mpllocal: {[testenv:base-command]commands} test/ -m local - integ: {[testenv:base-command]commands} test/ -m integ - accept: {[testenv:base-command]commands} test/ -m accept + integ: {[testenv:base-command]commands} test/ -m integ --ignore test/mpl/ + accept: {[testenv:base-command]commands} test/ -m accept --ignore test/mpl/ examples: {[testenv:base-command]commands} examples/test/ -m examples --ignore examples/test/keyrings/ # MPL keyring examples require a special IAM role; run these separately under a separate set of permissions mplexamples: {[testenv:base-command]commands} examples/test/keyrings -m examples From e7c745fbdb8e33d342a9027196519c53833fcba8 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 11:34:58 -0800 Subject: [PATCH 116/184] fix tests --- .../unit/test_material_managers_mpl_cmm.py | 20 ------------------- .../test_material_managers_mpl_materials.py | 20 ------------------- tox.ini | 8 +++++--- 3 files changed, 5 insertions(+), 43 deletions(-) diff --git a/test/mpl/unit/test_material_managers_mpl_cmm.py b/test/mpl/unit/test_material_managers_mpl_cmm.py index b1589b1cf..cae334722 100644 --- a/test/mpl/unit/test_material_managers_mpl_cmm.py +++ b/test/mpl/unit/test_material_managers_mpl_cmm.py @@ -57,26 +57,6 @@ mock_encryption_materials_handler = MagicMock(__class__=MPLEncryptionMaterials) mock_decryption_materials_request = MagicMock(__class__=DecryptionMaterialsRequest) -def test_GIVEN_test_has_mpl_is_False_THEN_cmm_has_mpl_is_False(): - """If the MPL IS NOT installed in the runtime environment, - assert the cmm has _HAS_MPL set to False""" - - assert hasattr(aws_encryption_sdk.materials_managers.mpl.cmm, "_HAS_MPL") - assert aws_encryption_sdk.materials_managers.mpl.cmm._HAS_MPL is False - - -def test_GIVEN_test_has_mpl_is_True_THEN_cmm_has_mpl_is_True(): - """If the MPL IS installed in the runtime environment, - assert the cmm has _HAS_MPL set to True""" - - assert hasattr(aws_encryption_sdk.materials_managers.mpl.cmm, "_HAS_MPL") - assert aws_encryption_sdk.materials_managers.mpl.cmm._HAS_MPL is True - - -def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_THEN_raise_ImportError(): - with pytest.raises(ImportError): - MPLCMMHandler(mpl_cmm="doesn't matter") - def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_valid_mpl_cmm_THEN_return_new_MPLCMMHandler(): mpl_cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) diff --git a/test/mpl/unit/test_material_managers_mpl_materials.py b/test/mpl/unit/test_material_managers_mpl_materials.py index dfd6b2769..bb83b89fd 100644 --- a/test/mpl/unit/test_material_managers_mpl_materials.py +++ b/test/mpl/unit/test_material_managers_mpl_materials.py @@ -57,26 +57,6 @@ mock_encryption_materials_handler = MagicMock(__class__=MPLEncryptionMaterials) mock_decryption_materials_request = MagicMock(__class__=DecryptionMaterialsRequest) -def test_GIVEN_test_has_mpl_is_False_THEN_cmm_has_mpl_is_False(): - """If the MPL IS NOT installed in the runtime environment, - assert the cmm has _HAS_MPL set to False""" - - assert hasattr(aws_encryption_sdk.materials_managers.mpl.materials, "_HAS_MPL") - assert aws_encryption_sdk.materials_managers.mpl.materials._HAS_MPL is False - - -def test_GIVEN_test_has_mpl_is_True_THEN_cmm_has_mpl_is_True(): - """If the MPL IS installed in the runtime environment, - assert the cmm has _HAS_MPL set to True""" - - assert hasattr(aws_encryption_sdk.materials_managers.mpl.materials, "_HAS_MPL") - assert aws_encryption_sdk.materials_managers.mpl.materials._HAS_MPL is True - - -def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_THEN_raise_ImportError(): - with pytest.raises(ImportError): - MPLEncryptionMaterials(mpl_materials="doesn't matter") - def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_valid_mpl_cmm_THEN_return_new_MPLCMMHandler(): mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) diff --git a/tox.ini b/tox.ini index 419188b54..3daa40e47 100644 --- a/tox.ini +++ b/tox.ini @@ -12,9 +12,11 @@ envlist = # tests in a test environment that also has the MPL. py{311,312}-{local,integ,accept,examples}{,-mpl}, # >=3.11: Run ONLY the MPL-specific tests. - # These must be separate from the above target. - # These require the `-mpl` suffix so tox installs the MPL. - # The `mpl` prefix runs only MPL-specific tests + # These must be separate from the above target, since + # these require the `-mpl` suffix. + # The `mpl` prefix specifies a separate target, + # i.e. `mpllocal` instead of `local`. + # `mplXXX` contains tests using MPL components. py{311,312}-mpl{local,examples}-mpl nocmk, bandit, doc8, readme, docs, From fee4f36f44c94bb357d10272190359fdf5d82e62 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 16:57:03 -0800 Subject: [PATCH 117/184] test cleanup --- .../example_branch_key_id_supplier.py | 5 +- .../materials_managers/mpl/cmm.py | 78 +++---- .../materials_managers/mpl/materials.py | 12 +- src/aws_encryption_sdk/streaming_client.py | 8 +- .../unit/test_material_managers_mpl_cmm.py | 207 +++++++++++------- .../test_material_managers_mpl_materials.py | 139 +++++++----- 6 files changed, 271 insertions(+), 178 deletions(-) diff --git a/examples/src/keyrings/example_branch_key_id_supplier.py b/examples/src/keyrings/example_branch_key_id_supplier.py index a06280fa1..ba9ae060c 100644 --- a/examples/src/keyrings/example_branch_key_id_supplier.py +++ b/examples/src/keyrings/example_branch_key_id_supplier.py @@ -18,11 +18,10 @@ def __init__(self, tenant_1_id, tenant_2_id): def get_branch_key_id( self, - # TODO-MPL: Change this to `native_input` in Smithy-Dafny - input: GetBranchKeyIdInput # noqa pylint: disable=redefined-builtin + param: GetBranchKeyIdInput ) -> GetBranchKeyIdOutput: """Returns branch key ID from the tenant ID in input's encryption context.""" - encryption_context: Dict[str, str] = input.encryption_context + encryption_context: Dict[str, str] = param.encryption_context if b"tenant" not in encryption_context: raise ValueError("EncryptionContext invalid, does not contain expected tenant key value pair.") diff --git a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py index 1e3e3fb34..c97c070f0 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py @@ -2,62 +2,64 @@ from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException from aws_cryptographic_materialproviders.mpl.models import ( - AlgorithmSuiteIdESDK, - CommitmentPolicyESDK, - DecryptMaterialsInput, - DecryptMaterialsOutput, + AlgorithmSuiteIdESDK as MPL_AlgorithmSuiteIdESDK, + CommitmentPolicyESDK as MPL_CommitmentPolicyESDK, + DecryptMaterialsInput as MPL_DecryptMaterialsInput, + DecryptMaterialsOutput as MPL_DecryptMaterialsOutput, EncryptedDataKey as MPL_EncryptedDataKey, - GetEncryptionMaterialsInput, - GetEncryptionMaterialsOutput, + GetEncryptionMaterialsInput as MPL_GetEncryptionMaterialsInput, + GetEncryptionMaterialsOutput as MPL_GetEncryptionMaterialsOutput, +) +from aws_cryptographic_materialproviders.mpl.references import ( + ICryptographicMaterialsManager as MPL_ICryptographicMaterialsManager ) -from aws_cryptographic_materialproviders.mpl.references import ICryptographicMaterialsManager from typing import List from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError from aws_encryption_sdk.identifiers import CommitmentPolicy -from aws_encryption_sdk.materials_managers.mpl.materials import MPLEncryptionMaterials, MPLDecryptionMaterials +from aws_encryption_sdk.materials_managers.mpl.materials import EncryptionMaterialsFromMPL, DecryptionMaterialsFromMPL from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager from aws_encryption_sdk.structures import EncryptedDataKey as Native_EncryptedDataKey -class MPLCMMHandler(CryptoMaterialsManager): +class CryptoMaterialsManagerFromMPL(CryptoMaterialsManager): """ In instances where encryption materials are provided by an implementation of the MPL's - `aws_cryptographic_materialproviders.mpl.references.ICryptographicMaterialsManager`, + `aws_cryptographic_materialproviders.mpl.references.MPL_ICryptographicMaterialsManager`, this maps the ESDK CMM interfaces to the MPL CMM. """ - mpl_cmm: 'ICryptographicMaterialsManager' + mpl_cmm: 'MPL_ICryptographicMaterialsManager' def __init__( self, - mpl_cmm: 'ICryptographicMaterialsManager' + mpl_cmm: 'MPL_ICryptographicMaterialsManager' ): """ - Create MPLCMMHandler. + Create CryptoMaterialsManagerFromMPL. :param mpl_cmm: Underlying MPL cryptographic materials manager """ - if isinstance(mpl_cmm, ICryptographicMaterialsManager): + if isinstance(mpl_cmm, MPL_ICryptographicMaterialsManager): self.mpl_cmm = mpl_cmm else: - raise ValueError(f"Invalid CMM passed to MPLCMMHandler. cmm: {mpl_cmm}") + raise ValueError(f"Invalid CMM passed to CryptoMaterialsManagerFromMPL. cmm: {mpl_cmm}") def get_encryption_materials( self, request: EncryptionMaterialsRequest - ) -> MPLEncryptionMaterials: + ) -> EncryptionMaterialsFromMPL: """ Returns an EncryptionMaterialsHandler for the configured CMM. :param request: Request for encryption materials """ try: - mpl_input: GetEncryptionMaterialsInput = MPLCMMHandler._native_to_mpl_get_encryption_materials( + mpl_input: MPL_GetEncryptionMaterialsInput = CryptoMaterialsManagerFromMPL._native_to_mpl_get_encryption_materials( request ) - mpl_output: GetEncryptionMaterialsOutput = self.mpl_cmm.get_encryption_materials(mpl_input) - return MPLEncryptionMaterials(mpl_output.encryption_materials) + mpl_output: MPL_GetEncryptionMaterialsOutput = self.mpl_cmm.get_encryption_materials(mpl_input) + return EncryptionMaterialsFromMPL(mpl_output.encryption_materials) except AwsCryptographicMaterialProvidersException as mpl_exception: # Wrap MPL error into the ESDK error type # so customers only have to catch ESDK error types. @@ -66,11 +68,11 @@ def get_encryption_materials( @staticmethod def _native_to_mpl_get_encryption_materials( request: EncryptionMaterialsRequest - ) -> 'GetEncryptionMaterialsInput': - commitment_policy = MPLCMMHandler._native_to_mpl_commmitment_policy( + ) -> 'MPL_GetEncryptionMaterialsInput': + commitment_policy = CryptoMaterialsManagerFromMPL._native_to_mpl_commmitment_policy( request.commitment_policy ) - output: GetEncryptionMaterialsInput = GetEncryptionMaterialsInput( + output: MPL_GetEncryptionMaterialsInput = MPL_GetEncryptionMaterialsInput( encryption_context=request.encryption_context, commitment_policy=commitment_policy, max_plaintext_length=request.plaintext_length, @@ -80,54 +82,54 @@ def _native_to_mpl_get_encryption_materials( @staticmethod def _native_to_mpl_commmitment_policy( native_commitment_policy: CommitmentPolicy - ) -> 'CommitmentPolicyESDK': + ) -> 'MPL_CommitmentPolicyESDK': if native_commitment_policy == CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT: - return CommitmentPolicyESDK(value="FORBID_ENCRYPT_ALLOW_DECRYPT") + return MPL_CommitmentPolicyESDK(value="FORBID_ENCRYPT_ALLOW_DECRYPT") elif native_commitment_policy == CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT: - return CommitmentPolicyESDK(value="REQUIRE_ENCRYPT_ALLOW_DECRYPT") + return MPL_CommitmentPolicyESDK(value="REQUIRE_ENCRYPT_ALLOW_DECRYPT") elif native_commitment_policy == CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT: - return CommitmentPolicyESDK(value="REQUIRE_ENCRYPT_REQUIRE_DECRYPT") + return MPL_CommitmentPolicyESDK(value="REQUIRE_ENCRYPT_REQUIRE_DECRYPT") else: raise ValueError(f"Invalid native_commitment_policy: {native_commitment_policy}") def decrypt_materials( self, request: DecryptionMaterialsRequest - ) -> MPLDecryptionMaterials: + ) -> DecryptionMaterialsFromMPL: """ - Returns a MPLDecryptionMaterials for the configured CMM. + Returns a DecryptionMaterialsFromMPL for the configured CMM. :param request: Request for decryption materials """ try: - mpl_input: 'DecryptMaterialsInput' = \ - MPLCMMHandler._create_mpl_decrypt_materials_input_from_request(request) - mpl_output: 'DecryptMaterialsOutput' = self.mpl_cmm.decrypt_materials(mpl_input) - return MPLDecryptionMaterials(mpl_output.decryption_materials) + mpl_input: 'MPL_DecryptMaterialsInput' = \ + CryptoMaterialsManagerFromMPL._create_mpl_decrypt_materials_input_from_request(request) + mpl_output: 'MPL_DecryptMaterialsOutput' = self.mpl_cmm.decrypt_materials(mpl_input) + return DecryptionMaterialsFromMPL(mpl_output.decryption_materials) except AwsCryptographicMaterialProvidersException as mpl_exception: # Wrap MPL error into the ESDK error type # so customers only have to catch ESDK error types. raise AWSEncryptionSDKClientError(mpl_exception) @staticmethod - def _native_algorithm_id_to_mpl_algorithm_id(native_algorithm_id: str) -> 'AlgorithmSuiteIdESDK': + def _native_algorithm_id_to_mpl_algorithm_id(native_algorithm_id: str) -> 'MPL_AlgorithmSuiteIdESDK': # MPL algorithm suite ID = hexstr(native_algorithm_id) padded to 4 digits post-`x`. - return AlgorithmSuiteIdESDK(f"{native_algorithm_id:#0{6}x}") + return MPL_AlgorithmSuiteIdESDK(f"{native_algorithm_id:#0{6}x}") @staticmethod def _create_mpl_decrypt_materials_input_from_request( request: DecryptionMaterialsRequest - ) -> 'DecryptMaterialsInput': + ) -> 'MPL_DecryptMaterialsInput': key_blob_list: List[Native_EncryptedDataKey] = request.encrypted_data_keys list_edks = [MPL_EncryptedDataKey( key_provider_id=key_blob.key_provider.provider_id, key_provider_info=key_blob.key_provider.key_info, ciphertext=key_blob.encrypted_data_key, ) for key_blob in key_blob_list] - output: DecryptMaterialsInput = DecryptMaterialsInput( - algorithm_suite_id=MPLCMMHandler._native_algorithm_id_to_mpl_algorithm_id( + output: MPL_DecryptMaterialsInput = MPL_DecryptMaterialsInput( + algorithm_suite_id=CryptoMaterialsManagerFromMPL._native_algorithm_id_to_mpl_algorithm_id( request.algorithm.algorithm_id ), - commitment_policy=MPLCMMHandler._native_to_mpl_commmitment_policy( + commitment_policy=CryptoMaterialsManagerFromMPL._native_to_mpl_commmitment_policy( request.commitment_policy ), encrypted_data_keys=list_edks, diff --git a/src/aws_encryption_sdk/materials_managers/mpl/materials.py b/src/aws_encryption_sdk/materials_managers/mpl/materials.py index 1ea2a199d..2bdf3f810 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/materials.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/materials.py @@ -21,7 +21,7 @@ def _mpl_algorithm_id_to_native_algorithm_id(mpl_algorithm_id: str) -> int: return int(mpl_algorithm_id, 16) -class MPLEncryptionMaterials(Native_EncryptionMaterials): +class EncryptionMaterialsFromMPL(Native_EncryptionMaterials): """ In instances where encryption materials are be provided by the MPL's `aws_cryptographic_materialproviders.mpl.models.EncryptionMaterials`, @@ -35,13 +35,13 @@ def __init__( mpl_materials: 'MPL_EncryptionMaterials' ): """ - Create MPLEncryptionMaterials. + Create EncryptionMaterialsFromMPL. :param materials: Underlying encryption materials """ if isinstance(mpl_materials, MPL_EncryptionMaterials): self.mpl_materials = mpl_materials else: - raise ValueError("Invalid EncryptionMaterials passed to MPLEncryptionMaterials. " + raise ValueError("Invalid EncryptionMaterials passed to EncryptionMaterialsFromMPL. " f"materials: {mpl_materials}") @property @@ -91,7 +91,7 @@ def signing_key(self) -> bytes: return self.mpl_materials.signing_key -class MPLDecryptionMaterials(Native_DecryptionMaterials): +class DecryptionMaterialsFromMPL(Native_DecryptionMaterials): """ In instances where decryption materials are be provided by the MPL's `aws_cryptographic_materialproviders.mpl.models.DecryptionMaterials`, @@ -105,13 +105,13 @@ def __init__( mpl_materials: 'MPL_DecryptionMaterials' ): """ - Create MPLDecryptionMaterials. + Create DecryptionMaterialsFromMPL. :param materials: Underlying decryption materials """ if isinstance(mpl_materials, MPL_DecryptionMaterials): self.mpl_materials = mpl_materials else: - raise ValueError(f"Invalid DecryptionMaterials passed to MPLDecryptionMaterials.\ + raise ValueError(f"Invalid DecryptionMaterials passed to DecryptionMaterialsFromMPL.\ materials: {mpl_materials}") @property diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index a3c05bbb7..72ed4efb7 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -80,7 +80,7 @@ _HAS_MPL = True # Import internal ESDK modules that depend on the MPL - from aws_encryption_sdk.materials_managers.mpl.cmm import MPLCMMHandler + from aws_encryption_sdk.materials_managers.mpl.cmm import CryptoMaterialsManagerFromMPL except ImportError: _HAS_MPL = False @@ -172,7 +172,7 @@ def _has_mpl_attrs_post_init(self): keyring=self.keyring ) ) - cmm_handler: CryptoMaterialsManager = MPLCMMHandler(cmm) + cmm_handler: CryptoMaterialsManager = CryptoMaterialsManagerFromMPL(cmm) self.materials_manager = cmm_handler def _no_mpl_attrs_post_init(self): @@ -555,7 +555,7 @@ def _prep_message(self): # MPL verification key is PEM bytes, not DER bytes. # If the underlying CMM is from the MPL, load PEM bytes. if (_HAS_MPL - and isinstance(self.config.materials_manager, MPLCMMHandler)): + and isinstance(self.config.materials_manager, CryptoMaterialsManagerFromMPL)): self.signer = Signer.from_key_bytes( algorithm=self._encryption_materials.algorithm, key_bytes=self._encryption_materials.signing_key, encoding=serialization.Encoding.PEM, @@ -923,7 +923,7 @@ def _read_header(self): # MPL verification key is NOT key bytes; it is bytes of the compressed point. # If the underlying CMM is from the MPL, load bytes from encoded point. if (_HAS_MPL - and isinstance(self.config.materials_manager, MPLCMMHandler)): + and isinstance(self.config.materials_manager, CryptoMaterialsManagerFromMPL)): self.verifier = Verifier.from_encoded_point( algorithm=header.algorithm, encoded_point=base64.b64encode(decryption_materials.verification_key) diff --git a/test/mpl/unit/test_material_managers_mpl_cmm.py b/test/mpl/unit/test_material_managers_mpl_cmm.py index cae334722..22f8bf63e 100644 --- a/test/mpl/unit/test_material_managers_mpl_cmm.py +++ b/test/mpl/unit/test_material_managers_mpl_cmm.py @@ -17,11 +17,10 @@ from aws_encryption_sdk.identifiers import CommitmentPolicy -import aws_encryption_sdk.materials_managers.mpl.cmm -from aws_encryption_sdk.materials_managers.mpl.cmm import MPLCMMHandler +from aws_encryption_sdk.materials_managers.mpl.cmm import CryptoMaterialsManagerFromMPL from aws_encryption_sdk.materials_managers.mpl.materials import ( - MPLEncryptionMaterials, - MPLDecryptionMaterials, + EncryptionMaterialsFromMPL, + DecryptionMaterialsFromMPL, ) pytestmark = [pytest.mark.unit, pytest.mark.local] @@ -51,180 +50,234 @@ EncryptionMaterialsRequest, DecryptionMaterialsRequest, ) +from aws_encryption_sdk.structures import EncryptedDataKey as Native_EncryptedDataKey mock_encryption_materials_request = MagicMock(__class__=EncryptionMaterialsRequest) -mock_encryption_materials_handler = MagicMock(__class__=MPLEncryptionMaterials) +mock_encryption_materials_handler = MagicMock(__class__=EncryptionMaterialsFromMPL) mock_decryption_materials_request = MagicMock(__class__=DecryptionMaterialsRequest) +mock_edk = MagicMock(__class__=Native_EncryptedDataKey) +mock_mpl_key_provider_id = MagicMock(__class__=str) +mock_edk.key_provider.provider_id = mock_mpl_key_provider_id +mock_mpl_key_provider_info = MagicMock(__class__=bytes) +mock_edk.key_provider.key_info = mock_mpl_key_provider_info +mock_mpl_encrypted_data_key = MagicMock(__class__=bytes) +mock_edk.encrypted_data_key = mock_mpl_encrypted_data_key -def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_valid_mpl_cmm_THEN_return_new_MPLCMMHandler(): - mpl_cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) - - assert mpl_cmm_handler.mpl_cmm == mock_mpl_cmm + +def test_GIVEN_valid_mpl_cmm_WHEN_create_CryptoMaterialsManagerFromMPL_THEN_return_new_CryptoMaterialsManagerFromMPL(): + # Given: valid mpl_cmm + # When: create new CryptoMaterialsManagerFromMPL + mpl_cmm = CryptoMaterialsManagerFromMPL(mpl_cmm=mock_mpl_cmm) + # Then: CryptoMaterialsManagerFromMPL is valid + assert mpl_cmm.mpl_cmm == mock_mpl_cmm -def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_invalid_mpl_cmm_THEN_raise_ValueError(): +def test_GIVEN_invalid_mpl_cmm_WHEN_create_CryptoMaterialsManagerFromMPL_THEN_raise_ValueError(): + # Then: raises ValueError with pytest.raises(ValueError): - MPLCMMHandler(mpl_cmm="not a valid mpl_cmm") + # Given: invalid mpl_cmm + # When: create new CryptoMaterialsManagerFromMPL + CryptoMaterialsManagerFromMPL(mpl_cmm="not a valid mpl_cmm") @patch.object(mock_mpl_cmm, "get_encryption_materials") -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_get_encryption_materials") -def test_GIVEN_valid_request_WHEN_call_get_encryption_materials_THEN_return_MPLEncryptionMaterials( +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.CryptoMaterialsManagerFromMPL._native_to_mpl_get_encryption_materials") +def test_GIVEN_valid_request_WHEN_get_encryption_materials_THEN_return_EncryptionMaterialsFromMPL( mock_native_to_mpl_get_encryption_materials, mock_get_encryption_materials, ): - # Mock: mpl_cmm.get_encryption_materials returns mock MPL encryption materials + # Given: _native_to_mpl_get_encryption_materials creates a GetEncryptionMaterialsInput + mock_get_encryption_materials_input = MagicMock(__class__=GetEncryptionMaterialsInput) + mock_native_to_mpl_get_encryption_materials.return_value = mock_get_encryption_materials_input + + # Given: mpl_cmm.get_encryption_materials returns mock MPL encryption materials mock_get_encryption_materials_output = MagicMock(__class__=GetEncryptionMaterialsOutput) mock_get_encryption_materials_output.encryption_materials = mock_mpl_encryption_materials mock_get_encryption_materials.return_value = mock_get_encryption_materials_output - # Mock: CMMHandler._native_to_mpl_get_encryption_materials creates a GetEncryptionMaterialsInput - mock_get_encryption_materials_input = MagicMock(__class__=GetEncryptionMaterialsInput) - mock_native_to_mpl_get_encryption_materials.return_value = mock_get_encryption_materials_input - - cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) - test = cmm_handler.get_encryption_materials(mock_encryption_materials_request) + # When: get_encryption_materials + cmm = CryptoMaterialsManagerFromMPL(mpl_cmm=mock_mpl_cmm) + output = cmm.get_encryption_materials(mock_encryption_materials_request) - # Verify cmm_handler returns MPLEncryptionMaterials - assert isinstance(test, MPLEncryptionMaterials) + # Then: + # Verify cmm returns EncryptionMaterialsFromMPL + assert isinstance(output, EncryptionMaterialsFromMPL) # Verify returned EncryptionMaterialsHandler uses the output of `get_encryption_materials` - assert test.mpl_materials == mock_mpl_encryption_materials + assert output.mpl_materials == mock_mpl_encryption_materials # Verify we actually called `get_encryption_materials` mock_mpl_cmm.get_encryption_materials.assert_called_once_with(mock_get_encryption_materials_input) -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_commmitment_policy") -def test_GIVEN_get_encryption_materials_raises_MPL_Exception_WHEN_call_get_encryption_materials_THEN_raise_ESDK_Exception( +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.CryptoMaterialsManagerFromMPL._native_to_mpl_commmitment_policy") +def test_GIVEN_mpl_cmm_raises_MPLException_WHEN_get_encryption_materials_THEN_raise_ESDKException( _ ): + # Then: Raises AWSEncryptionSDKClientError with pytest.raises(AWSEncryptionSDKClientError): + # Given: mpl_cmm.get_encryption_materials raises MPL exception with patch.object(mock_mpl_cmm, "get_encryption_materials", side_effect=AwsCryptographicMaterialProvidersException("any")): - - cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) - cmm_handler.get_encryption_materials(mock_encryption_materials_request) + # When: get_encryption_materials + cmm = CryptoMaterialsManagerFromMPL(mpl_cmm=mock_mpl_cmm) + cmm.get_encryption_materials(mock_encryption_materials_request) -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_commmitment_policy") -def test_GIVEN_native_to_mpl_commmitment_policy_returns_valid_policy_WHEN_call_native_to_mpl_get_encryption_materials_THEN_returns_GetEncryptionMaterialsInput( +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.CryptoMaterialsManagerFromMPL._native_to_mpl_commmitment_policy") +def test_GIVEN_valid_mpl_commitment_policy_WHEN_native_to_mpl_get_encryption_materials_THEN_returns_GetEncryptionMaterialsInput( mock_mpl_commitment_policy ): + # Given: commitment policy is some MPL ESDK commitment policy mock_commitment_policy = MagicMock(__class__=CommitmentPolicyESDK) mock_mpl_commitment_policy.return_value = mock_commitment_policy - output = MPLCMMHandler._native_to_mpl_get_encryption_materials(mock_encryption_materials_request) + # When: _native_to_mpl_get_encryption_materials + output = CryptoMaterialsManagerFromMPL._native_to_mpl_get_encryption_materials(mock_encryption_materials_request) - # verify correctness of returned value + # Then: returned GetEncryptionMaterialsInput is correct assert isinstance(output, GetEncryptionMaterialsInput) assert output.encryption_context == mock_encryption_materials_request.encryption_context assert output.commitment_policy == mock_commitment_policy assert output.max_plaintext_length == mock_encryption_materials_request.plaintext_length -def test_GIVEN_CommitmentPolicy_FORBID_ENCRYPT_ALLOW_DECRYPT_WHEN_call_native_to_mpl_commmitment_policyTHEN_returns_CommitmentPolicyESDK_FORBID_ENCRYPT_ALLOW_DECRYPT(): +def test_GIVEN_CommitmentPolicy_FORBID_ENCRYPT_ALLOW_DECRYPT_WHEN_native_to_mpl_commmitment_policy_THEN_returns_CommitmentPolicyESDK_FORBID_ENCRYPT_ALLOW_DECRYPT(): + # Given: native FORBID_ENCRYPT_ALLOW_DECRYPT native_commitment_policy = CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT - output = MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) + # When: _native_to_mpl_commmitment_policy + output = CryptoMaterialsManagerFromMPL._native_to_mpl_commmitment_policy(native_commitment_policy) + # Then: Returns MPL FORBID_ENCRYPT_ALLOW_DECRYPT assert isinstance(output, CommitmentPolicyESDK) assert output.value == "FORBID_ENCRYPT_ALLOW_DECRYPT" -def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_ALLOW_DECRYPT_WHEN_call_native_to_mpl_commmitment_policyTHEN_returns_CommitmentPolicyESDK_REQUIRE_ENCRYPT_ALLOW_DECRYPT(): +def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_ALLOW_DECRYPT_WHEN_native_to_mpl_commmitment_policy_THEN_returns_CommitmentPolicyESDK_REQUIRE_ENCRYPT_ALLOW_DECRYPT(): + # Given: native REQUIRE_ENCRYPT_ALLOW_DECRYPT native_commitment_policy = CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT - output = MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) + # When: _native_to_mpl_commmitment_policy + output = CryptoMaterialsManagerFromMPL._native_to_mpl_commmitment_policy(native_commitment_policy) + # Then: Returns MPL REQUIRE_ENCRYPT_ALLOW_DECRYPT assert isinstance(output, CommitmentPolicyESDK) assert output.value == "REQUIRE_ENCRYPT_ALLOW_DECRYPT" -def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_REQUIRE_DECRYPT_WHEN_call_native_to_mpl_commmitment_policyTHEN_returns_CommitmentPolicyESDK_REQUIRE_ENCRYPT_REQUIRE_DECRYPT(): +def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_REQUIRE_DECRYPT_WHEN_native_to_mpl_commmitment_policy_THEN_returns_CommitmentPolicyESDK_REQUIRE_ENCRYPT_REQUIRE_DECRYPT(): + # Given: native REQUIRE_ENCRYPT_REQUIRE_DECRYPT native_commitment_policy = CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT - output = MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) + # When: _native_to_mpl_commmitment_policy + output = CryptoMaterialsManagerFromMPL._native_to_mpl_commmitment_policy(native_commitment_policy) + # Then: Returns MPL REQUIRE_ENCRYPT_REQUIRE_DECRYPT assert isinstance(output, CommitmentPolicyESDK) assert output.value == "REQUIRE_ENCRYPT_REQUIRE_DECRYPT" -def test_GIVEN_CommitmentPolicy_unrecognized_WHEN_call_native_to_mpl_commmitment_policyTHEN_raise_ValueError(): +def test_GIVEN_CommitmentPolicy_unrecognized_WHEN_native_to_mpl_commmitment_policy_THEN_raise_ValueError(): + # Given: invalid native commitment policy native_commitment_policy = "not a commitment policy" + # Then: Raises ValueError with pytest.raises(ValueError): - MPLCMMHandler._native_to_mpl_commmitment_policy(native_commitment_policy) + # When: _native_to_mpl_commmitment_policy + CryptoMaterialsManagerFromMPL._native_to_mpl_commmitment_policy(native_commitment_policy) @patch.object(mock_mpl_cmm, "decrypt_materials") -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._create_mpl_decrypt_materials_input_from_request") -def test_GIVEN_valid_request_WHEN_call_decrypt_materials_THEN_return_MPLDecryptionMaterials( +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.CryptoMaterialsManagerFromMPL._create_mpl_decrypt_materials_input_from_request") +def test_GIVEN_valid_request_WHEN_decrypt_materials_THEN_return_DecryptionMaterialsFromMPL( mock_native_to_mpl_decrypt_materials, mock_get_encryption_materials, ): - # Mock: mpl_cmm.get_decryption_materials returns mock MPL decryption materials + # Given: mpl_cmm.get_decryption_materials returns mock MPL decryption materials mock_decrypt_materials_output = MagicMock(__class__=GetEncryptionMaterialsOutput) mock_decrypt_materials_output.decryption_materials = mock_mpl_decrypt_materials mock_get_encryption_materials.return_value = mock_decrypt_materials_output - # Mock: CMMHandler._create_mpl_decrypt_materials_input_from_request creates a DecryptMaterialsInput + # Given: CMMHandler._create_mpl_decrypt_materials_input_from_request creates a DecryptMaterialsInput mock_decrypt_materials_input = MagicMock(__class__=GetEncryptionMaterialsInput) mock_native_to_mpl_decrypt_materials.return_value = mock_decrypt_materials_input - cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) - output = cmm_handler.decrypt_materials(mock_decryption_materials_request) + # When: decrypt_materials + cmm = CryptoMaterialsManagerFromMPL(mpl_cmm=mock_mpl_cmm) + output = cmm.decrypt_materials(mock_decryption_materials_request) - # Verify cmm_handler returns MPLDecryptionMaterials - assert isinstance(output, MPLDecryptionMaterials) - # Verify returned MPLDecryptionMaterials uses the output of `decrypt_materials` + # Then: + # Verify cmm returns DecryptionMaterialsFromMPL + assert isinstance(output, DecryptionMaterialsFromMPL) + # Verify returned DecryptionMaterialsFromMPL uses the output of `decrypt_materials` assert output.mpl_materials == mock_mpl_decrypt_materials # Verify we actually called `decrypt_materials` mock_mpl_cmm.decrypt_materials.assert_called_once_with(mock_decrypt_materials_input) -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._create_mpl_decrypt_materials_input_from_request") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.CryptoMaterialsManagerFromMPL._create_mpl_decrypt_materials_input_from_request") def test_GIVEN_decrypt_materials_raises_MPL_Exception_WHEN_call_decrypt_materials_THEN_raise_ESDK_Exception( _ ): + # Then: Raises AWSEncryptionSDKClientError with pytest.raises(AWSEncryptionSDKClientError): + # Given: mpl_cmm.decrypt_materials raises MPL exception with patch.object(mock_mpl_cmm, "decrypt_materials", side_effect=AwsCryptographicMaterialProvidersException("any")): - - cmm_handler = MPLCMMHandler(mpl_cmm=mock_mpl_cmm) - cmm_handler.decrypt_materials(mock_decryption_materials_request) + # When: decrypt_materials + cmm = CryptoMaterialsManagerFromMPL(mpl_cmm=mock_mpl_cmm) + cmm.decrypt_materials(mock_decryption_materials_request) -def test_WHEN_call_native_algorithm_id_to_mpl_algorithm_id_THEN_returns_valid_AlgorithmSuiteIdESDK(): - some_native_algorithm_id = 0x0000 # Not a real algorithm ID, but fits the format +def test_GIVEN_valid_native_algorithm_id_WHEN_native_algorithm_id_to_mpl_algorithm_id_THEN_returns_valid_AlgorithmSuiteIdESDK(): + # Given: any native algorithm ID + some_native_algorithm_id = 0x1234 # Not a real algorithm ID, but fits the format - mpl_output = MPLCMMHandler._native_algorithm_id_to_mpl_algorithm_id( + # When: _native_algorithm_id_to_mpl_algorithm_id + mpl_output = CryptoMaterialsManagerFromMPL._native_algorithm_id_to_mpl_algorithm_id( some_native_algorithm_id ) + # Then: returns valid MPL algorithm ID assert isinstance(mpl_output, AlgorithmSuiteIdESDK) - assert mpl_output.value == "0x0000" + assert mpl_output.value == "0x1234" -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_algorithm_id_to_mpl_algorithm_id") -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.MPLCMMHandler._native_to_mpl_commmitment_policy") -def test__create_mpl_decrypt_materials_input_from_request( +@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") +def test_GIVEN_valid_request_WHEN_create_mpl_decrypt_materials_input_from_request_THEN_returns_MPL_DecryptMaterialsInput( 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: _native_to_mpl_commmitment_policy returns some MPL commitment policy mock_commitment_policy = MagicMock(__class__=CommitmentPolicyESDK) mock_mpl_commitment_policy.return_value = mock_commitment_policy - # mock_decryption_materials_request.algorithm = - - output = MPLCMMHandler._create_mpl_decrypt_materials_input_from_request(mock_decryption_materials_request) - - assert isinstance(output, DecryptMaterialsInput) - assert output.algorithm_suite_id == mock_algorithm_id - assert output.commitment_policy == mock_commitment_policy - assert output.encryption_context == mock_decryption_materials_request.encryption_context - - assert len(output.encrypted_data_keys) == len(mock_decryption_materials_request.encrypted_data_keys) - for i in range(len(output.encrypted_data_keys)): - # Assume input[i] == output[i], seems to work - output_edk = output.encrypted_data_keys[i] - input_edk = mock_decryption_materials_request[i] - assert output_edk.key_provider_id == input_edk.key_provider.provider_id - assert output_edk.key_provider_info == input_edk.key_provider.key_info - assert output_edk.ciphertext == input_edk.encrypted_data_key + no_mock_edks = [ mock_edk ] + one_mock_edk = [ mock_edk ] + two_mock_edks = [ mock_edk, mock_edk ] + + # Given: ESK lists of various lengths + for mock_edks in [ no_mock_edks, one_mock_edk, two_mock_edks ]: + + mock_decryption_materials_request.encrypted_data_keys = mock_edks + + # When: _create_mpl_decrypt_materials_input_from_request + output = CryptoMaterialsManagerFromMPL._create_mpl_decrypt_materials_input_from_request(mock_decryption_materials_request) + + # Then: + # Verify general correctness of output structure + assert isinstance(output, DecryptMaterialsInput) + assert output.algorithm_suite_id == mock_algorithm_id + assert output.commitment_policy == mock_commitment_policy + assert output.encryption_context == mock_decryption_materials_request.encryption_context + + assert len(output.encrypted_data_keys) == len(mock_edks) + for i in range(len(output.encrypted_data_keys)): + # Assume input[i] == output[i] to make validation easier + # This is how the src is implemented but is not a requirement. + # If this assumption breaks, we should enhance this test. + output_edk = output.encrypted_data_keys[i] + input_edk = mock_edks[i] + assert output_edk.key_provider_id == input_edk.key_provider.provider_id + assert output_edk.key_provider_info == input_edk.key_provider.key_info + assert output_edk.ciphertext == input_edk.encrypted_data_key \ No newline at end of file diff --git a/test/mpl/unit/test_material_managers_mpl_materials.py b/test/mpl/unit/test_material_managers_mpl_materials.py index bb83b89fd..b39e9bc8d 100644 --- a/test/mpl/unit/test_material_managers_mpl_materials.py +++ b/test/mpl/unit/test_material_managers_mpl_materials.py @@ -14,13 +14,12 @@ import pytest from mock import MagicMock, patch, PropertyMock -from typing import Dict, List +from typing import Dict, List, Set -from aws_encryption_sdk.identifiers import CommitmentPolicy import aws_encryption_sdk.materials_managers.mpl.materials from aws_encryption_sdk.materials_managers.mpl.materials import ( - MPLEncryptionMaterials, - MPLDecryptionMaterials, + EncryptionMaterialsFromMPL, + DecryptionMaterialsFromMPL, ) from aws_encryption_sdk.identifiers import Algorithm, AlgorithmSuite @@ -54,124 +53,164 @@ mock_encryption_materials_request = MagicMock(__class__=EncryptionMaterialsRequest) -mock_encryption_materials_handler = MagicMock(__class__=MPLEncryptionMaterials) +mock_encryption_materials_handler = MagicMock(__class__=EncryptionMaterialsFromMPL) mock_decryption_materials_request = MagicMock(__class__=DecryptionMaterialsRequest) +mock_edk = MagicMock(__class__=MPL_EncryptedDataKey) +mock_mpl_key_provider_id = MagicMock(__class__=str) +mock_edk.key_provider_id = mock_mpl_key_provider_id +mock_mpl_key_provider_info = MagicMock(__class__=bytes) +mock_edk.key_provider_info = mock_mpl_key_provider_info +mock_mpl_ciphertext = MagicMock(__class__=bytes) +mock_edk.ciphertext = mock_mpl_ciphertext -def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_valid_mpl_cmm_THEN_return_new_MPLCMMHandler(): - mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + +def test_GIVEN_valid_mpl_materials_WHEN_create_EncryptionMaterialsFromMPL_THEN_return_new_CryptoMaterialsManagerFromMPL(): + # Given: valid mpl_materials + # When: create EncryptionMaterialsFromMPL + mpl_encryption_materials = EncryptionMaterialsFromMPL(mpl_materials=mock_mpl_encryption_materials) + # Then: EncryptionMaterialsFromMPL is valid assert mpl_encryption_materials.mpl_materials == mock_mpl_encryption_materials -def test_GIVEN_test_has_mpl_is_False_WHEN_create_MPLCMMHandler_with_invalid_mpl_cmm_THEN_raise_ValueError(): +def test_GIVEN_invalid_mpl_materials_WHEN_create_EncryptionMaterialsFromMPL_THEN_raise_ValueError(): + # Then: Raise ValueError with pytest.raises(ValueError): - MPLEncryptionMaterials(mpl_materials="not a valid mpl_materials") + # Given: invalid mpl_materials + # When: create EncryptionMaterialsFromMPL + EncryptionMaterialsFromMPL(mpl_materials="not a valid mpl_materials") + -def test_mpl_to_native(): +def test_GIVEN_valid_mpl_algorithm_id_WHEN_mpl_algorithm_id_to_native_algorithm_id_THEN_valid_native_output(): + # Given: any valid MPL algorithm ID some_mpl_algorithm_id = "0x1234" # Not a real algorithm ID, but fits the format + # When: _mpl_algorithm_id_to_native_algorithm_id native_output = aws_encryption_sdk.materials_managers.mpl.materials._mpl_algorithm_id_to_native_algorithm_id( some_mpl_algorithm_id ) + # Then: valid native algorithm ID assert native_output == 0x1234 @patch("aws_encryption_sdk.materials_managers.mpl.materials._mpl_algorithm_id_to_native_algorithm_id") @patch("aws_encryption_sdk.materials_managers.mpl.materials.AlgorithmSuite.get_by_id") -def test_GIVEN_valid_mpl_algorithm_id_WHEN_get_algorithm_THEN_valid_native_algorithm_id( +def test_GIVEN_valid_mpl_algorithm_id_WHEN_EncryptionMaterials_get_algorithm_THEN_valid_native_algorithm_id( mock_algorithm, mock_native_algorithm_id, ): - # Mock valid conversion from MPL to native algorithm ID + # Given: _mpl_algorithm_id_to_native_algorithm_id returns a valid native algorithm ID mock_native_algorithm_id.return_value = 0x1234 - # Mock valid lookup in native AlgorithmSuite lookup + # Given: get_by_id returns a valid native AlgorithmSuite by looking up an ID mock_algorithm.return_value = MagicMock(__class__=AlgorithmSuite) - mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + # When: Get algorithm + mpl_encryption_materials = EncryptionMaterialsFromMPL(mpl_materials=mock_mpl_encryption_materials) output = mpl_encryption_materials.algorithm + + # Then: output is valid assert output == mock_algorithm() # property calls automatically, we need to call the mock -def test_GecTHEN_valid_native_algorithm_id(): +def test_GIVEN_valid_encryption_context_WHEN_EncryptionMaterials_get_encryption_context_THEN_valid_encryption_context(): + # Given: valid encryption context mock_encryption_context = MagicMock(__class__=Dict[str, str]) mock_mpl_encryption_materials.encryption_context = mock_encryption_context - mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + # When: get encryption context + mpl_encryption_materials = EncryptionMaterialsFromMPL(mpl_materials=mock_mpl_encryption_materials) output = mpl_encryption_materials.encryption_context + # Then: returns valid encryption context assert output == mock_encryption_context -def test_GecTHEN_valid_nativefadsf_algorithm_id(): - mock_edk = MagicMock(__class__=MPL_EncryptedDataKey) - mock_mpl_key_provider_id = MagicMock(__class__=str) - mock_edk.key_provider_id = mock_mpl_key_provider_id - mock_mpl_key_provider_info = MagicMock(__class__=bytes) - mock_edk.key_provider_info = mock_mpl_key_provider_info - mock_mpl_ciphertext = MagicMock(__class__=bytes) - mock_edk.ciphertext = mock_mpl_ciphertext - - mock_edks = [ mock_edk ] - mock_mpl_encryption_materials.encrypted_data_keys = mock_edks - - mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) - output = mpl_encryption_materials.encrypted_data_keys - output_as_list = list(output) - - assert len(output_as_list) == len(mock_edks) - for i in range(len(output_as_list)): - # assume output[i] corresponds to input[i] - native_edk = output_as_list[i] - mpl_edk = mock_edks[i] - - assert native_edk.encrypted_data_key == mpl_edk.ciphertext - assert native_edk.key_provider.provider_id == mpl_edk.key_provider_id - assert native_edk.key_provider.key_info == mpl_edk.key_provider_info - -def test_GecTHEN_valid_nativefadsffadsfa_algorithm_id(): +def test_GIVEN_valid_edks_WHEN_EncryptionMaterials_get_edks_THEN_returns_edks(): + + # Given: lists of mocked EDKs of various lengths + no_mock_edks = [] + one_mock_edk = [ mock_edk ] + two_mocked_edks = [ mock_edk, mock_edk ] + for mock_edks in [ no_mock_edks, one_mock_edk, two_mocked_edks ]: + mock_mpl_encryption_materials.encrypted_data_keys = mock_edks + + # When: get EDKs + mpl_encryption_materials = EncryptionMaterialsFromMPL(mpl_materials=mock_mpl_encryption_materials) + output = mpl_encryption_materials.encrypted_data_keys + + # Then: returns EDKs + output_as_list = list(output) + # Native ESDK Python types the EDKs as a set; + # Ensure the MPL's list is collapsed into a set correctly + assert len(output_as_list) == len(set(mock_edks)) + for i in range(len(output_as_list)): + # Assume input[i] == output[i] to make validation easier + # This is how the src is implemented but is not a requirement. + # If this assumption breaks, we should enhance this test. + native_edk = output_as_list[i] + mpl_edk = mock_edks[i] + + assert native_edk.encrypted_data_key == mpl_edk.ciphertext + assert native_edk.key_provider.provider_id == mpl_edk.key_provider_id + assert native_edk.key_provider.key_info == mpl_edk.key_provider_info + + +def test_GIVEN_valid_data_key_WHEN_EncryptionMaterials_get_data_key_THEN_returns_data_key(): + # Given: Valid MPL data key mock_data_key = MagicMock(__class__=bytes) mock_mpl_encryption_materials.plaintext_data_key = mock_data_key - mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + # When: get data key + mpl_encryption_materials = EncryptionMaterialsFromMPL(mpl_materials=mock_mpl_encryption_materials) output = mpl_encryption_materials.data_encryption_key + # Then: Returns native data key assert output.key_provider.provider_id == "" assert output.key_provider.key_info == b"" assert output.data_key == mock_data_key assert output.encrypted_data_key == b"" -def test_GecTHEN_valid_nativefasdfasdffadsf_algorithm_id(): +def test_GIVEN_valid_signing_key_WHEN_EncryptionMaterials_get_signing_key_THEN_returns_signing_key(): + # Given: valid signing key mock_signing_key = MagicMock(__class__=bytes) mock_mpl_encryption_materials.signing_key = mock_signing_key - mpl_encryption_materials = MPLEncryptionMaterials(mpl_materials=mock_mpl_encryption_materials) + # When: get signing key + mpl_encryption_materials = EncryptionMaterialsFromMPL(mpl_materials=mock_mpl_encryption_materials) output = mpl_encryption_materials.signing_key + # Then: returns signing key assert output == mock_signing_key -def test_GecTHEN_valid_nativeffasdfasdadsffadsfa_algorithm_id(): +def test_GIVEN_valid_data_key_WHEN_DecryptionMaterials_get_data_key_THEN_returns_data_key(): + # Given: valid MPL data key mock_data_key = MagicMock(__class__=bytes) mock_mpl_decrypt_materials.plaintext_data_key = mock_data_key - mpl_decryption_materials = MPLDecryptionMaterials(mpl_materials=mock_mpl_decrypt_materials) + # When: get data key + mpl_decryption_materials = DecryptionMaterialsFromMPL(mpl_materials=mock_mpl_decrypt_materials) output = mpl_decryption_materials.data_key + # Then: returns valid native data key assert output.key_provider.provider_id == "" assert output.key_provider.key_info == b"" assert output.data_key == mock_data_key assert output.encrypted_data_key == b"" -def test_GecTHEN_validadsfasdf_nativefasdfasdffadsf_algorithm_id(): +def test_GIVEN_valid_verification_key_WHEN_DecryptionMaterials_get_verification_key_THEN_returns_verification_key(): + # Given: valid verification key mock_verification_key = MagicMock(__class__=bytes) mock_mpl_decrypt_materials.verification_key = mock_verification_key - mpl_decryption_materials = MPLDecryptionMaterials(mpl_materials=mock_mpl_decrypt_materials) + # When: get verification key + mpl_decryption_materials = DecryptionMaterialsFromMPL(mpl_materials=mock_mpl_decrypt_materials) output = mpl_decryption_materials.verification_key + # Then: returns verification key assert output == mock_verification_key From ac6471a921407df409bceb9dc1dafbb5e697544c Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 17:00:31 -0800 Subject: [PATCH 118/184] test cleanup --- examples/src/keyrings/hierarchical_keyring.py | 6 +- .../unit/test_material_managers_mpl_cmm.py | 56 +++++++++---------- .../test_material_managers_mpl_materials.py | 11 ---- 3 files changed, 31 insertions(+), 42 deletions(-) diff --git a/examples/src/keyrings/hierarchical_keyring.py b/examples/src/keyrings/hierarchical_keyring.py index c71719346..aa87485f9 100644 --- a/examples/src/keyrings/hierarchical_keyring.py +++ b/examples/src/keyrings/hierarchical_keyring.py @@ -4,12 +4,12 @@ import sys import boto3 -# ignore missing MPL for pylint, but the MPL is required for this example +# Ignore missing MPL for pylint, but the MPL is required for this example # noqa pylint: disable=import-error -from aws_cryptographic_materialproviders.keystore.client import KeyStore +from aws_cryptographic_materialproviders.keystore import KeyStore from aws_cryptographic_materialproviders.keystore.config import KeyStoreConfig from aws_cryptographic_materialproviders.keystore.models import CreateKeyInput, KMSConfigurationKmsKeyArn -from aws_cryptographic_materialproviders.mpl.client import AwsCryptographicMaterialProviders +from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig from aws_cryptographic_materialproviders.mpl.models import ( CacheTypeDefault, diff --git a/test/mpl/unit/test_material_managers_mpl_cmm.py b/test/mpl/unit/test_material_managers_mpl_cmm.py index 22f8bf63e..52a4b333c 100644 --- a/test/mpl/unit/test_material_managers_mpl_cmm.py +++ b/test/mpl/unit/test_material_managers_mpl_cmm.py @@ -28,19 +28,19 @@ from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException from aws_cryptographic_materialproviders.mpl.models import ( - AlgorithmSuiteIdESDK, - CommitmentPolicyESDK, - DecryptMaterialsInput, + AlgorithmSuiteIdESDK as MPL_AlgorithmSuiteIdESDK, + CommitmentPolicyESDK as MPL_CommitmentPolicyESDK, + DecryptMaterialsInput as MPL_DecryptMaterialsInput, DecryptionMaterials as MPL_DecryptionMaterials, EncryptionMaterials as MPL_EncryptionMaterials, - GetEncryptionMaterialsInput, - GetEncryptionMaterialsOutput, + GetEncryptionMaterialsInput as MPL_GetEncryptionMaterialsInput, + GetEncryptionMaterialsOutput as MPL_GetEncryptionMaterialsOutput, ) from aws_cryptographic_materialproviders.mpl.references import ( - ICryptographicMaterialsManager + ICryptographicMaterialsManager as MPL_ICryptographicMaterialsManager, ) -mock_mpl_cmm = MagicMock(__class__=ICryptographicMaterialsManager) +mock_mpl_cmm = MagicMock(__class__=MPL_ICryptographicMaterialsManager) mock_mpl_encryption_materials = MagicMock(__class__=MPL_EncryptionMaterials) mock_mpl_decrypt_materials = MagicMock(__class__=MPL_DecryptionMaterials) @@ -89,12 +89,12 @@ def test_GIVEN_valid_request_WHEN_get_encryption_materials_THEN_return_Encryptio mock_get_encryption_materials, ): - # Given: _native_to_mpl_get_encryption_materials creates a GetEncryptionMaterialsInput - mock_get_encryption_materials_input = MagicMock(__class__=GetEncryptionMaterialsInput) + # Given: _native_to_mpl_get_encryption_materials creates a MPL_GetEncryptionMaterialsInput + mock_get_encryption_materials_input = MagicMock(__class__=MPL_GetEncryptionMaterialsInput) mock_native_to_mpl_get_encryption_materials.return_value = mock_get_encryption_materials_input # Given: mpl_cmm.get_encryption_materials returns mock MPL encryption materials - mock_get_encryption_materials_output = MagicMock(__class__=GetEncryptionMaterialsOutput) + mock_get_encryption_materials_output = MagicMock(__class__=MPL_GetEncryptionMaterialsOutput) mock_get_encryption_materials_output.encryption_materials = mock_mpl_encryption_materials mock_get_encryption_materials.return_value = mock_get_encryption_materials_output @@ -125,24 +125,24 @@ def test_GIVEN_mpl_cmm_raises_MPLException_WHEN_get_encryption_materials_THEN_ra cmm.get_encryption_materials(mock_encryption_materials_request) @patch("aws_encryption_sdk.materials_managers.mpl.cmm.CryptoMaterialsManagerFromMPL._native_to_mpl_commmitment_policy") -def test_GIVEN_valid_mpl_commitment_policy_WHEN_native_to_mpl_get_encryption_materials_THEN_returns_GetEncryptionMaterialsInput( +def test_GIVEN_valid_mpl_commitment_policy_WHEN_native_to_mpl_get_encryption_materials_THEN_returns_MPL_GetEncryptionMaterialsInput( mock_mpl_commitment_policy ): # Given: commitment policy is some MPL ESDK commitment policy - mock_commitment_policy = MagicMock(__class__=CommitmentPolicyESDK) + mock_commitment_policy = MagicMock(__class__=MPL_CommitmentPolicyESDK) mock_mpl_commitment_policy.return_value = mock_commitment_policy # When: _native_to_mpl_get_encryption_materials output = CryptoMaterialsManagerFromMPL._native_to_mpl_get_encryption_materials(mock_encryption_materials_request) - # Then: returned GetEncryptionMaterialsInput is correct - assert isinstance(output, GetEncryptionMaterialsInput) + # Then: returned MPL_GetEncryptionMaterialsInput is correct + assert isinstance(output, MPL_GetEncryptionMaterialsInput) assert output.encryption_context == mock_encryption_materials_request.encryption_context assert output.commitment_policy == mock_commitment_policy 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_CommitmentPolicyESDK_FORBID_ENCRYPT_ALLOW_DECRYPT(): +def test_GIVEN_CommitmentPolicy_FORBID_ENCRYPT_ALLOW_DECRYPT_WHEN_native_to_mpl_commmitment_policy_THEN_returns_MPL_CommitmentPolicyESDK_FORBID_ENCRYPT_ALLOW_DECRYPT(): # Given: native FORBID_ENCRYPT_ALLOW_DECRYPT native_commitment_policy = CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT @@ -150,10 +150,10 @@ def test_GIVEN_CommitmentPolicy_FORBID_ENCRYPT_ALLOW_DECRYPT_WHEN_native_to_mpl_ output = CryptoMaterialsManagerFromMPL._native_to_mpl_commmitment_policy(native_commitment_policy) # Then: Returns MPL FORBID_ENCRYPT_ALLOW_DECRYPT - assert isinstance(output, CommitmentPolicyESDK) + 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_CommitmentPolicyESDK_REQUIRE_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(): # Given: native REQUIRE_ENCRYPT_ALLOW_DECRYPT native_commitment_policy = CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT @@ -161,10 +161,10 @@ def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_ALLOW_DECRYPT_WHEN_native_to_mpl output = CryptoMaterialsManagerFromMPL._native_to_mpl_commmitment_policy(native_commitment_policy) # Then: Returns MPL REQUIRE_ENCRYPT_ALLOW_DECRYPT - assert isinstance(output, CommitmentPolicyESDK) + 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_CommitmentPolicyESDK_REQUIRE_ENCRYPT_REQUIRE_DECRYPT(): +def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_REQUIRE_DECRYPT_WHEN_native_to_mpl_commmitment_policy_THEN_returns_MPL_CommitmentPolicyESDK_REQUIRE_ENCRYPT_REQUIRE_DECRYPT(): # Given: native REQUIRE_ENCRYPT_REQUIRE_DECRYPT native_commitment_policy = CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT @@ -172,7 +172,7 @@ def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_REQUIRE_DECRYPT_WHEN_native_to_m output = CryptoMaterialsManagerFromMPL._native_to_mpl_commmitment_policy(native_commitment_policy) # Then: Returns MPL REQUIRE_ENCRYPT_REQUIRE_DECRYPT - assert isinstance(output, CommitmentPolicyESDK) + 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(): @@ -192,12 +192,12 @@ def test_GIVEN_valid_request_WHEN_decrypt_materials_THEN_return_DecryptionMateri ): # Given: mpl_cmm.get_decryption_materials returns mock MPL decryption materials - mock_decrypt_materials_output = MagicMock(__class__=GetEncryptionMaterialsOutput) + mock_decrypt_materials_output = MagicMock(__class__=MPL_GetEncryptionMaterialsOutput) mock_decrypt_materials_output.decryption_materials = mock_mpl_decrypt_materials mock_get_encryption_materials.return_value = mock_decrypt_materials_output - # Given: CMMHandler._create_mpl_decrypt_materials_input_from_request creates a DecryptMaterialsInput - mock_decrypt_materials_input = MagicMock(__class__=GetEncryptionMaterialsInput) + # Given: CMMHandler._create_mpl_decrypt_materials_input_from_request creates a MPL_DecryptMaterialsInput + mock_decrypt_materials_input = MagicMock(__class__=MPL_GetEncryptionMaterialsInput) mock_native_to_mpl_decrypt_materials.return_value = mock_decrypt_materials_input # When: decrypt_materials @@ -225,7 +225,7 @@ def test_GIVEN_decrypt_materials_raises_MPL_Exception_WHEN_call_decrypt_material cmm = CryptoMaterialsManagerFromMPL(mpl_cmm=mock_mpl_cmm) cmm.decrypt_materials(mock_decryption_materials_request) -def test_GIVEN_valid_native_algorithm_id_WHEN_native_algorithm_id_to_mpl_algorithm_id_THEN_returns_valid_AlgorithmSuiteIdESDK(): +def test_GIVEN_valid_native_algorithm_id_WHEN_native_algorithm_id_to_mpl_algorithm_id_THEN_returns_valid_MPL_AlgorithmSuiteIdESDK(): # Given: any native algorithm ID some_native_algorithm_id = 0x1234 # Not a real algorithm ID, but fits the format @@ -235,12 +235,12 @@ def test_GIVEN_valid_native_algorithm_id_WHEN_native_algorithm_id_to_mpl_algorit ) # Then: returns valid MPL algorithm ID - assert isinstance(mpl_output, AlgorithmSuiteIdESDK) + assert isinstance(mpl_output, MPL_AlgorithmSuiteIdESDK) assert mpl_output.value == "0x1234" @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") -def test_GIVEN_valid_request_WHEN_create_mpl_decrypt_materials_input_from_request_THEN_returns_MPL_DecryptMaterialsInput( +def test_GIVEN_valid_request_WHEN_create_mpl_decrypt_materials_input_from_request_THEN_returns_MPL_MPL_DecryptMaterialsInput( mock_mpl_commitment_policy, mock_mpl_algorithm_id, ): @@ -249,7 +249,7 @@ def test_GIVEN_valid_request_WHEN_create_mpl_decrypt_materials_input_from_reques mock_mpl_algorithm_id.return_value = mock_algorithm_id # Given: _native_to_mpl_commmitment_policy returns some MPL commitment policy - mock_commitment_policy = MagicMock(__class__=CommitmentPolicyESDK) + mock_commitment_policy = MagicMock(__class__=MPL_CommitmentPolicyESDK) mock_mpl_commitment_policy.return_value = mock_commitment_policy no_mock_edks = [ mock_edk ] @@ -266,7 +266,7 @@ def test_GIVEN_valid_request_WHEN_create_mpl_decrypt_materials_input_from_reques # Then: # Verify general correctness of output structure - assert isinstance(output, DecryptMaterialsInput) + assert isinstance(output, MPL_DecryptMaterialsInput) assert output.algorithm_suite_id == mock_algorithm_id assert output.commitment_policy == mock_commitment_policy assert output.encryption_context == mock_decryption_materials_request.encryption_context diff --git a/test/mpl/unit/test_material_managers_mpl_materials.py b/test/mpl/unit/test_material_managers_mpl_materials.py index b39e9bc8d..92a8c95df 100644 --- a/test/mpl/unit/test_material_managers_mpl_materials.py +++ b/test/mpl/unit/test_material_managers_mpl_materials.py @@ -26,32 +26,21 @@ pytestmark = [pytest.mark.unit, pytest.mark.local] -from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException from aws_cryptographic_materialproviders.mpl.models import ( - AlgorithmSuiteIdESDK, - CommitmentPolicyESDK, - DecryptMaterialsInput, DecryptionMaterials as MPL_DecryptionMaterials, EncryptedDataKey as MPL_EncryptedDataKey, EncryptionMaterials as MPL_EncryptionMaterials, - GetEncryptionMaterialsInput, - GetEncryptionMaterialsOutput, -) -from aws_cryptographic_materialproviders.mpl.references import ( - ICryptographicMaterialsManager ) mock_mpl_encryption_materials = MagicMock(__class__=MPL_EncryptionMaterials) mock_mpl_decrypt_materials = MagicMock(__class__=MPL_DecryptionMaterials) -from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError from aws_encryption_sdk.materials_managers import ( EncryptionMaterialsRequest, DecryptionMaterialsRequest, ) - mock_encryption_materials_request = MagicMock(__class__=EncryptionMaterialsRequest) mock_encryption_materials_handler = MagicMock(__class__=EncryptionMaterialsFromMPL) mock_decryption_materials_request = MagicMock(__class__=DecryptionMaterialsRequest) From a5ebc19c479f4b6de3874dc4735ca0e27ffbbc38 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 17:18:50 -0800 Subject: [PATCH 119/184] isort --- .../materials_managers/mpl/cmm.py | 5 ++--- .../materials_managers/mpl/materials.py | 1 - test/mpl/unit/test_material_managers_mpl_cmm.py | 14 +++----------- .../unit/test_material_managers_mpl_materials.py | 12 +++--------- 4 files changed, 8 insertions(+), 24 deletions(-) diff --git a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py index c97c070f0..1bbd7c89a 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py @@ -11,16 +11,15 @@ GetEncryptionMaterialsOutput as MPL_GetEncryptionMaterialsOutput, ) from aws_cryptographic_materialproviders.mpl.references import ( - ICryptographicMaterialsManager as MPL_ICryptographicMaterialsManager + ICryptographicMaterialsManager as MPL_ICryptographicMaterialsManager, ) - from typing import List from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError from aws_encryption_sdk.identifiers import CommitmentPolicy -from aws_encryption_sdk.materials_managers.mpl.materials import EncryptionMaterialsFromMPL, DecryptionMaterialsFromMPL from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager +from aws_encryption_sdk.materials_managers.mpl.materials import DecryptionMaterialsFromMPL, EncryptionMaterialsFromMPL from aws_encryption_sdk.structures import EncryptedDataKey as Native_EncryptedDataKey diff --git a/src/aws_encryption_sdk/materials_managers/mpl/materials.py b/src/aws_encryption_sdk/materials_managers/mpl/materials.py index 2bdf3f810..31f7d2a65 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/materials.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/materials.py @@ -5,7 +5,6 @@ EncryptedDataKey as MPL_EncryptedDataKey, EncryptionMaterials as MPL_EncryptionMaterials, ) - from typing import Dict, List, Set from aws_encryption_sdk.identifiers import Algorithm, AlgorithmSuite diff --git a/test/mpl/unit/test_material_managers_mpl_cmm.py b/test/mpl/unit/test_material_managers_mpl_cmm.py index 52a4b333c..eb795b7c2 100644 --- a/test/mpl/unit/test_material_managers_mpl_cmm.py +++ b/test/mpl/unit/test_material_managers_mpl_cmm.py @@ -15,13 +15,9 @@ import pytest from mock import MagicMock, patch - from aws_encryption_sdk.identifiers import CommitmentPolicy from aws_encryption_sdk.materials_managers.mpl.cmm import CryptoMaterialsManagerFromMPL -from aws_encryption_sdk.materials_managers.mpl.materials import ( - EncryptionMaterialsFromMPL, - DecryptionMaterialsFromMPL, -) +from aws_encryption_sdk.materials_managers.mpl.materials import DecryptionMaterialsFromMPL, EncryptionMaterialsFromMPL pytestmark = [pytest.mark.unit, pytest.mark.local] @@ -30,8 +26,8 @@ from aws_cryptographic_materialproviders.mpl.models import ( AlgorithmSuiteIdESDK as MPL_AlgorithmSuiteIdESDK, CommitmentPolicyESDK as MPL_CommitmentPolicyESDK, - DecryptMaterialsInput as MPL_DecryptMaterialsInput, DecryptionMaterials as MPL_DecryptionMaterials, + DecryptMaterialsInput as MPL_DecryptMaterialsInput, EncryptionMaterials as MPL_EncryptionMaterials, GetEncryptionMaterialsInput as MPL_GetEncryptionMaterialsInput, GetEncryptionMaterialsOutput as MPL_GetEncryptionMaterialsOutput, @@ -46,13 +42,9 @@ from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError -from aws_encryption_sdk.materials_managers import ( - EncryptionMaterialsRequest, - DecryptionMaterialsRequest, -) +from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest from aws_encryption_sdk.structures import EncryptedDataKey as Native_EncryptedDataKey - mock_encryption_materials_request = MagicMock(__class__=EncryptionMaterialsRequest) mock_encryption_materials_handler = MagicMock(__class__=EncryptionMaterialsFromMPL) mock_decryption_materials_request = MagicMock(__class__=DecryptionMaterialsRequest) diff --git a/test/mpl/unit/test_material_managers_mpl_materials.py b/test/mpl/unit/test_material_managers_mpl_materials.py index 92a8c95df..96237998a 100644 --- a/test/mpl/unit/test_material_managers_mpl_materials.py +++ b/test/mpl/unit/test_material_managers_mpl_materials.py @@ -13,15 +13,12 @@ """Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic.""" import pytest -from mock import MagicMock, patch, PropertyMock +from mock import MagicMock, PropertyMock, patch from typing import Dict, List, Set import aws_encryption_sdk.materials_managers.mpl.materials -from aws_encryption_sdk.materials_managers.mpl.materials import ( - EncryptionMaterialsFromMPL, - DecryptionMaterialsFromMPL, -) from aws_encryption_sdk.identifiers import Algorithm, AlgorithmSuite +from aws_encryption_sdk.materials_managers.mpl.materials import DecryptionMaterialsFromMPL, EncryptionMaterialsFromMPL pytestmark = [pytest.mark.unit, pytest.mark.local] @@ -36,10 +33,7 @@ mock_mpl_decrypt_materials = MagicMock(__class__=MPL_DecryptionMaterials) -from aws_encryption_sdk.materials_managers import ( - EncryptionMaterialsRequest, - DecryptionMaterialsRequest, -) +from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest mock_encryption_materials_request = MagicMock(__class__=EncryptionMaterialsRequest) mock_encryption_materials_handler = MagicMock(__class__=EncryptionMaterialsFromMPL) From 21f361462ec2542056ffcd25ae08bffdb21a7a8d Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 17:21:52 -0800 Subject: [PATCH 120/184] fixes --- test/mpl/__init__.py | 2 +- .../unit/test_material_managers_mpl_cmm.py | 28 +++++++++---------- .../test_material_managers_mpl_materials.py | 15 ++++------ 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/test/mpl/__init__.py b/test/mpl/__init__.py index b976c1308..2a6c71715 100644 --- a/test/mpl/__init__.py +++ b/test/mpl/__init__.py @@ -10,4 +10,4 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -"""Module containing tests that REQUIRE the aws-cryptographic-material-providers library to run.""" \ No newline at end of file +"""Module containing tests that REQUIRE the aws-cryptographic-material-providers library to run.""" diff --git a/test/mpl/unit/test_material_managers_mpl_cmm.py b/test/mpl/unit/test_material_managers_mpl_cmm.py index eb795b7c2..a67c3e5c5 100644 --- a/test/mpl/unit/test_material_managers_mpl_cmm.py +++ b/test/mpl/unit/test_material_managers_mpl_cmm.py @@ -13,15 +13,6 @@ """Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic.""" import pytest -from mock import MagicMock, patch - -from aws_encryption_sdk.identifiers import CommitmentPolicy -from aws_encryption_sdk.materials_managers.mpl.cmm import CryptoMaterialsManagerFromMPL -from aws_encryption_sdk.materials_managers.mpl.materials import DecryptionMaterialsFromMPL, EncryptionMaterialsFromMPL - -pytestmark = [pytest.mark.unit, pytest.mark.local] - - from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException from aws_cryptographic_materialproviders.mpl.models import ( AlgorithmSuiteIdESDK as MPL_AlgorithmSuiteIdESDK, @@ -35,20 +26,27 @@ from aws_cryptographic_materialproviders.mpl.references import ( ICryptographicMaterialsManager as MPL_ICryptographicMaterialsManager, ) - -mock_mpl_cmm = MagicMock(__class__=MPL_ICryptographicMaterialsManager) -mock_mpl_encryption_materials = MagicMock(__class__=MPL_EncryptionMaterials) -mock_mpl_decrypt_materials = MagicMock(__class__=MPL_DecryptionMaterials) - +from mock import MagicMock, patch from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError +from aws_encryption_sdk.identifiers import CommitmentPolicy from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest +from aws_encryption_sdk.materials_managers.mpl.cmm import CryptoMaterialsManagerFromMPL +from aws_encryption_sdk.materials_managers.mpl.materials import DecryptionMaterialsFromMPL, EncryptionMaterialsFromMPL from aws_encryption_sdk.structures import EncryptedDataKey as Native_EncryptedDataKey +pytestmark = [pytest.mark.unit, pytest.mark.local] + + mock_encryption_materials_request = MagicMock(__class__=EncryptionMaterialsRequest) -mock_encryption_materials_handler = MagicMock(__class__=EncryptionMaterialsFromMPL) mock_decryption_materials_request = MagicMock(__class__=DecryptionMaterialsRequest) + +mock_mpl_cmm = MagicMock(__class__=MPL_ICryptographicMaterialsManager) +mock_mpl_encryption_materials = MagicMock(__class__=MPL_EncryptionMaterials) +mock_mpl_decrypt_materials = MagicMock(__class__=MPL_DecryptionMaterials) + + mock_edk = MagicMock(__class__=Native_EncryptedDataKey) mock_mpl_key_provider_id = MagicMock(__class__=str) mock_edk.key_provider.provider_id = mock_mpl_key_provider_id diff --git a/test/mpl/unit/test_material_managers_mpl_materials.py b/test/mpl/unit/test_material_managers_mpl_materials.py index 96237998a..cb3ca7397 100644 --- a/test/mpl/unit/test_material_managers_mpl_materials.py +++ b/test/mpl/unit/test_material_managers_mpl_materials.py @@ -13,28 +13,25 @@ """Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic.""" import pytest +from aws_cryptographic_materialproviders.mpl.models import ( + DecryptionMaterials as MPL_DecryptionMaterials, + EncryptedDataKey as MPL_EncryptedDataKey, + EncryptionMaterials as MPL_EncryptionMaterials, +) from mock import MagicMock, PropertyMock, patch from typing import Dict, List, Set import aws_encryption_sdk.materials_managers.mpl.materials from aws_encryption_sdk.identifiers import Algorithm, AlgorithmSuite +from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest from aws_encryption_sdk.materials_managers.mpl.materials import DecryptionMaterialsFromMPL, EncryptionMaterialsFromMPL pytestmark = [pytest.mark.unit, pytest.mark.local] -from aws_cryptographic_materialproviders.mpl.models import ( - DecryptionMaterials as MPL_DecryptionMaterials, - EncryptedDataKey as MPL_EncryptedDataKey, - EncryptionMaterials as MPL_EncryptionMaterials, -) - mock_mpl_encryption_materials = MagicMock(__class__=MPL_EncryptionMaterials) mock_mpl_decrypt_materials = MagicMock(__class__=MPL_DecryptionMaterials) - -from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest - mock_encryption_materials_request = MagicMock(__class__=EncryptionMaterialsRequest) mock_encryption_materials_handler = MagicMock(__class__=EncryptionMaterialsFromMPL) mock_decryption_materials_request = MagicMock(__class__=DecryptionMaterialsRequest) From 22eabb64dec94e8eaf6ccfb7a3fb14a29fcd09eb Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 17:27:38 -0800 Subject: [PATCH 121/184] fix --- decrypt_oracle/.chalice/pipeline.py | 2 +- .../src/aws_encryption_sdk_decrypt_oracle/app.py | 3 ++- .../test/integration/integration_test_utils.py | 3 ++- decrypt_oracle/test/test_n_generate_test_vectors.py | 7 ++++--- examples/test/examples_test_utils.py | 2 +- examples/test/test_i_basic_encryption.py | 1 - ...basic_file_encryption_with_multiple_providers.py | 4 +--- ...i_basic_file_encryption_with_raw_key_provider.py | 1 - examples/test/test_i_data_key_caching_basic.py | 1 - examples/test/test_i_discovery_kms_provider.py | 4 +--- examples/test/test_i_mrk_aware_kms_provider.py | 4 +--- examples/test/test_i_multiple_kms_cmk.py | 4 +--- examples/test/test_i_one_kms_cmk.py | 4 +--- examples/test/test_i_one_kms_cmk_streaming_data.py | 1 - examples/test/test_i_one_kms_cmk_unsigned.py | 4 +--- examples/test/test_i_set_commitment.py | 4 +--- .../materials_managers/mpl/__init__.py | 5 ++++- .../materials_managers/mpl/cmm.py | 12 ++++++++---- .../materials_managers/mpl/materials.py | 5 ++++- src/aws_encryption_sdk/streaming_client.py | 4 ++-- test/mpl/__init__.py | 5 ++++- test/mpl/unit/test_material_managers_mpl_cmm.py | 5 ++++- .../unit/test_material_managers_mpl_materials.py | 7 +++++-- .../src/awses_test_vectors/internal/aws_kms.py | 5 +++-- .../src/awses_test_vectors/internal/util.py | 3 +-- .../manifests/full_message/decrypt.py | 9 ++++----- .../manifests/full_message/decrypt_generation.py | 13 ++++++------- .../manifests/full_message/encrypt.py | 7 +++---- .../src/awses_test_vectors/manifests/keys.py | 4 +--- .../src/awses_test_vectors/manifests/master_key.py | 11 +++++------ .../commands/test_i_full_message_encrypt.py | 1 - 31 files changed, 71 insertions(+), 74 deletions(-) diff --git a/decrypt_oracle/.chalice/pipeline.py b/decrypt_oracle/.chalice/pipeline.py index 9d5573646..c05df6739 100644 --- a/decrypt_oracle/.chalice/pipeline.py +++ b/decrypt_oracle/.chalice/pipeline.py @@ -2,7 +2,6 @@ import argparse import getpass import logging -from typing import Iterable import boto3 import troposphere @@ -20,6 +19,7 @@ ) from botocore.exceptions import ClientError from troposphere import GetAtt, Ref, Sub, Template, codebuild, codepipeline, iam, s3 +from typing import Iterable APPLICATION_NAME = "AwsEncryptionSdkDecryptOraclePython" PIPELINE_STACK_NAME = "{}DeployPipeline".format(APPLICATION_NAME) diff --git a/decrypt_oracle/src/aws_encryption_sdk_decrypt_oracle/app.py b/decrypt_oracle/src/aws_encryption_sdk_decrypt_oracle/app.py index 820b9e015..e250bb3c8 100644 --- a/decrypt_oracle/src/aws_encryption_sdk_decrypt_oracle/app.py +++ b/decrypt_oracle/src/aws_encryption_sdk_decrypt_oracle/app.py @@ -15,10 +15,11 @@ import logging import os +from chalice import Chalice, Response + import aws_encryption_sdk from aws_encryption_sdk.identifiers import CommitmentPolicy from aws_encryption_sdk.key_providers.kms import DiscoveryAwsKmsMasterKeyProvider -from chalice import Chalice, Response from .key_providers.counting import CountingMasterKey from .key_providers.null import NullMasterKey diff --git a/decrypt_oracle/test/integration/integration_test_utils.py b/decrypt_oracle/test/integration/integration_test_utils.py index c03b7f440..9849f1ecc 100644 --- a/decrypt_oracle/test/integration/integration_test_utils.py +++ b/decrypt_oracle/test/integration/integration_test_utils.py @@ -15,10 +15,11 @@ import json import os from collections import namedtuple + +import pytest from typing import Any, Callable, Iterable, Optional, Text import aws_encryption_sdk -import pytest from aws_encryption_sdk.identifiers import CommitmentPolicy from aws_encryption_sdk.key_providers.kms import StrictAwsKmsMasterKeyProvider diff --git a/decrypt_oracle/test/test_n_generate_test_vectors.py b/decrypt_oracle/test/test_n_generate_test_vectors.py index deb3f7c4d..ae9bb7d7d 100644 --- a/decrypt_oracle/test/test_n_generate_test_vectors.py +++ b/decrypt_oracle/test/test_n_generate_test_vectors.py @@ -15,14 +15,15 @@ import binascii import json import os + +import pytest +from aws_encryption_sdk_decrypt_oracle.key_providers.counting import CountingMasterKey +from aws_encryption_sdk_decrypt_oracle.key_providers.null import NullMasterKey from typing import Dict, Iterable, Text import aws_encryption_sdk -import pytest from aws_encryption_sdk.key_providers.base import MasterKeyProvider from aws_encryption_sdk.key_providers.kms import KMSMasterKey -from aws_encryption_sdk_decrypt_oracle.key_providers.counting import CountingMasterKey -from aws_encryption_sdk_decrypt_oracle.key_providers.null import NullMasterKey from .integration.integration_test_utils import test_vectors_filename diff --git a/examples/test/examples_test_utils.py b/examples/test/examples_test_utils.py index 8a51f21c8..08e8cf2f5 100644 --- a/examples/test/examples_test_utils.py +++ b/examples/test/examples_test_utils.py @@ -49,7 +49,7 @@ from integration_test_utils import ( # noqa pylint: disable=unused-import,import-error get_cmk_arn, - get_second_cmk_arn, get_mrk_arn, + get_second_cmk_arn, get_second_mrk_arn, ) diff --git a/examples/test/test_i_basic_encryption.py b/examples/test/test_i_basic_encryption.py index f2a4fab51..aa32d61fa 100644 --- a/examples/test/test_i_basic_encryption.py +++ b/examples/test/test_i_basic_encryption.py @@ -17,7 +17,6 @@ from ..src.basic_encryption import cycle_string from .examples_test_utils import get_cmk_arn, static_plaintext - pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_basic_file_encryption_with_multiple_providers.py b/examples/test/test_i_basic_file_encryption_with_multiple_providers.py index 282a272ab..0792f4958 100644 --- a/examples/test/test_i_basic_file_encryption_with_multiple_providers.py +++ b/examples/test/test_i_basic_file_encryption_with_multiple_providers.py @@ -18,9 +18,7 @@ import pytest from ..src.basic_file_encryption_with_multiple_providers import cycle_file -from .examples_test_utils import get_cmk_arn -from .examples_test_utils import static_plaintext - +from .examples_test_utils import get_cmk_arn, static_plaintext pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py b/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py index 710c0ccac..046b7f964 100644 --- a/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py +++ b/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py @@ -19,7 +19,6 @@ from ..src.basic_file_encryption_with_raw_key_provider import cycle_file from .examples_test_utils import static_plaintext - pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_data_key_caching_basic.py b/examples/test/test_i_data_key_caching_basic.py index 734c35692..7a30f4e53 100644 --- a/examples/test/test_i_data_key_caching_basic.py +++ b/examples/test/test_i_data_key_caching_basic.py @@ -16,7 +16,6 @@ from ..src.data_key_caching_basic import encrypt_with_caching from .examples_test_utils import get_cmk_arn - pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_discovery_kms_provider.py b/examples/test/test_i_discovery_kms_provider.py index e9a1c6e71..0f64cbf59 100644 --- a/examples/test/test_i_discovery_kms_provider.py +++ b/examples/test/test_i_discovery_kms_provider.py @@ -16,9 +16,7 @@ import pytest from ..src.discovery_kms_provider import encrypt_decrypt -from .examples_test_utils import get_cmk_arn -from .examples_test_utils import static_plaintext - +from .examples_test_utils import get_cmk_arn, static_plaintext pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_mrk_aware_kms_provider.py b/examples/test/test_i_mrk_aware_kms_provider.py index 8e7a003f8..a90101fa8 100644 --- a/examples/test/test_i_mrk_aware_kms_provider.py +++ b/examples/test/test_i_mrk_aware_kms_provider.py @@ -15,9 +15,7 @@ import pytest from ..src.mrk_aware_kms_provider import encrypt_decrypt -from .examples_test_utils import get_mrk_arn, get_second_mrk_arn -from .examples_test_utils import static_plaintext - +from .examples_test_utils import get_mrk_arn, get_second_mrk_arn, static_plaintext pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_multiple_kms_cmk.py b/examples/test/test_i_multiple_kms_cmk.py index 39369cbc6..2915a0fd7 100644 --- a/examples/test/test_i_multiple_kms_cmk.py +++ b/examples/test/test_i_multiple_kms_cmk.py @@ -16,9 +16,7 @@ import pytest from ..src.multiple_kms_cmk import encrypt_decrypt -from .examples_test_utils import get_cmk_arn, get_second_cmk_arn -from .examples_test_utils import static_plaintext - +from .examples_test_utils import get_cmk_arn, get_second_cmk_arn, static_plaintext pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_one_kms_cmk.py b/examples/test/test_i_one_kms_cmk.py index 71ce74d3d..96dd48dae 100644 --- a/examples/test/test_i_one_kms_cmk.py +++ b/examples/test/test_i_one_kms_cmk.py @@ -16,9 +16,7 @@ import pytest from ..src.one_kms_cmk import encrypt_decrypt -from .examples_test_utils import get_cmk_arn -from .examples_test_utils import static_plaintext - +from .examples_test_utils import get_cmk_arn, static_plaintext pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_one_kms_cmk_streaming_data.py b/examples/test/test_i_one_kms_cmk_streaming_data.py index b22fa4232..f0a3094d0 100644 --- a/examples/test/test_i_one_kms_cmk_streaming_data.py +++ b/examples/test/test_i_one_kms_cmk_streaming_data.py @@ -20,7 +20,6 @@ from ..src.one_kms_cmk_streaming_data import encrypt_decrypt_stream from .examples_test_utils import get_cmk_arn, static_plaintext - pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_one_kms_cmk_unsigned.py b/examples/test/test_i_one_kms_cmk_unsigned.py index 8a2758c96..41f16473d 100644 --- a/examples/test/test_i_one_kms_cmk_unsigned.py +++ b/examples/test/test_i_one_kms_cmk_unsigned.py @@ -16,9 +16,7 @@ import pytest from ..src.one_kms_cmk_unsigned import encrypt_decrypt -from .examples_test_utils import get_cmk_arn -from .examples_test_utils import static_plaintext - +from .examples_test_utils import get_cmk_arn, static_plaintext pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_set_commitment.py b/examples/test/test_i_set_commitment.py index 96247334b..c14a379bf 100644 --- a/examples/test/test_i_set_commitment.py +++ b/examples/test/test_i_set_commitment.py @@ -16,9 +16,7 @@ import pytest from ..src.set_commitment import encrypt_decrypt -from .examples_test_utils import get_cmk_arn -from .examples_test_utils import static_plaintext - +from .examples_test_utils import get_cmk_arn, static_plaintext pytestmark = [pytest.mark.examples] diff --git a/src/aws_encryption_sdk/materials_managers/mpl/__init__.py b/src/aws_encryption_sdk/materials_managers/mpl/__init__.py index 295400d76..7593a3300 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/__init__.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/__init__.py @@ -10,4 +10,7 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -"""Modules related to the MPL's materials managers interfaces.""" +"""Modules related to the MPL's materials managers interfaces. + +The aws-cryptographic-materials-library MUST be installed to use these modules. +""" diff --git a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py index 1bbd7c89a..24a10139f 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py @@ -1,4 +1,7 @@ -"""Retrieves encryption/decryption materials from the MPL.""" +"""Retrieves encryption/decryption materials from the MPL and interfaces them to EDK components. + +The aws-cryptographic-materials-library MUST be installed to use this module. +""" from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException from aws_cryptographic_materialproviders.mpl.models import ( @@ -54,9 +57,10 @@ def get_encryption_materials( :param request: Request for encryption materials """ try: - mpl_input: MPL_GetEncryptionMaterialsInput = CryptoMaterialsManagerFromMPL._native_to_mpl_get_encryption_materials( - request - ) + mpl_input: MPL_GetEncryptionMaterialsInput = \ + CryptoMaterialsManagerFromMPL._native_to_mpl_get_encryption_materials( + request + ) mpl_output: MPL_GetEncryptionMaterialsOutput = self.mpl_cmm.get_encryption_materials(mpl_input) return EncryptionMaterialsFromMPL(mpl_output.encryption_materials) except AwsCryptographicMaterialProvidersException as mpl_exception: diff --git a/src/aws_encryption_sdk/materials_managers/mpl/materials.py b/src/aws_encryption_sdk/materials_managers/mpl/materials.py index 31f7d2a65..c23e2b038 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/materials.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/materials.py @@ -1,4 +1,7 @@ -"""Provides encryption/decryption materials from an underlying materials provider.""" +"""Provides encryption/decryption materials from an underlying materials provider from the MPL. + +The aws-cryptographic-materials-library MUST be installed to use this module. +""" from aws_cryptographic_materialproviders.mpl.models import ( DecryptionMaterials as MPL_DecryptionMaterials, diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 72ed4efb7..959b5ff0b 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -555,7 +555,7 @@ def _prep_message(self): # MPL verification key is PEM bytes, not DER bytes. # If the underlying CMM is from the MPL, load PEM bytes. if (_HAS_MPL - and isinstance(self.config.materials_manager, CryptoMaterialsManagerFromMPL)): + and isinstance(self.config.materials_manager, CryptoMaterialsManagerFromMPL)): self.signer = Signer.from_key_bytes( algorithm=self._encryption_materials.algorithm, key_bytes=self._encryption_materials.signing_key, encoding=serialization.Encoding.PEM, @@ -923,7 +923,7 @@ def _read_header(self): # MPL verification key is NOT key bytes; it is bytes of the compressed point. # If the underlying CMM is from the MPL, load bytes from encoded point. if (_HAS_MPL - and isinstance(self.config.materials_manager, CryptoMaterialsManagerFromMPL)): + and isinstance(self.config.materials_manager, CryptoMaterialsManagerFromMPL)): self.verifier = Verifier.from_encoded_point( algorithm=header.algorithm, encoded_point=base64.b64encode(decryption_materials.verification_key) diff --git a/test/mpl/__init__.py b/test/mpl/__init__.py index 2a6c71715..37f482e0b 100644 --- a/test/mpl/__init__.py +++ b/test/mpl/__init__.py @@ -10,4 +10,7 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -"""Module containing tests that REQUIRE the aws-cryptographic-material-providers library to run.""" +"""Module testing components that use the MPL. + +The aws-cryptographic-materials-library MUST be installed to run tests in this module. +""" diff --git a/test/mpl/unit/test_material_managers_mpl_cmm.py b/test/mpl/unit/test_material_managers_mpl_cmm.py index a67c3e5c5..fa8f76410 100644 --- a/test/mpl/unit/test_material_managers_mpl_cmm.py +++ b/test/mpl/unit/test_material_managers_mpl_cmm.py @@ -10,7 +10,10 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -"""Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic.""" +"""Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic. + +The aws-cryptographic-materials-library MUST be installed to run tests in this module. +""" import pytest from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException diff --git a/test/mpl/unit/test_material_managers_mpl_materials.py b/test/mpl/unit/test_material_managers_mpl_materials.py index cb3ca7397..60e12c634 100644 --- a/test/mpl/unit/test_material_managers_mpl_materials.py +++ b/test/mpl/unit/test_material_managers_mpl_materials.py @@ -10,7 +10,10 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -"""Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic.""" +"""Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic. + +The aws-cryptographic-materials-library MUST be installed to run tests in this module. +""" import pytest from aws_cryptographic_materialproviders.mpl.models import ( @@ -18,7 +21,7 @@ EncryptedDataKey as MPL_EncryptedDataKey, EncryptionMaterials as MPL_EncryptionMaterials, ) -from mock import MagicMock, PropertyMock, patch +from mock import MagicMock, patch from typing import Dict, List, Set import aws_encryption_sdk.materials_managers.mpl.materials diff --git a/test_vector_handlers/src/awses_test_vectors/internal/aws_kms.py b/test_vector_handlers/src/awses_test_vectors/internal/aws_kms.py index 14c109e7d..3d2088a73 100644 --- a/test_vector_handlers/src/awses_test_vectors/internal/aws_kms.py +++ b/test_vector_handlers/src/awses_test_vectors/internal/aws_kms.py @@ -15,14 +15,15 @@ from aws_encryption_sdk.identifiers import AlgorithmSuite except ImportError: from aws_encryption_sdk.identifiers import Algorithm as AlgorithmSuite + +from awses_test_vectors.internal.defaults import ENCODING + from aws_encryption_sdk.key_providers.kms import ( DiscoveryAwsKmsMasterKeyProvider, MRKAwareDiscoveryAwsKmsMasterKeyProvider, StrictAwsKmsMasterKeyProvider, ) -from awses_test_vectors.internal.defaults import ENCODING - # This lets us easily use a single boto3 client per region for all KMS master keys. KMS_MASTER_KEY_PROVIDER = DiscoveryAwsKmsMasterKeyProvider() KMS_MRK_AWARE_MASTER_KEY_PROVIDER = MRKAwareDiscoveryAwsKmsMasterKeyProvider(discovery_region="us-west-2") diff --git a/test_vector_handlers/src/awses_test_vectors/internal/util.py b/test_vector_handlers/src/awses_test_vectors/internal/util.py index da5552f13..67d4ec67f 100644 --- a/test_vector_handlers/src/awses_test_vectors/internal/util.py +++ b/test_vector_handlers/src/awses_test_vectors/internal/util.py @@ -24,12 +24,11 @@ from aws_encryption_sdk.identifiers import Algorithm as AlgorithmSuite try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Any, Callable, Dict, Iterable, Type # noqa pylint: disable=unused-import - from awses_test_vectors.internal.mypy_types import ( # noqa pylint: disable=unused-import ISINSTANCE, MANIFEST_VERSION, ) + from typing import Any, Callable, Dict, Iterable, Type # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass diff --git a/test_vector_handlers/src/awses_test_vectors/manifests/full_message/decrypt.py b/test_vector_handlers/src/awses_test_vectors/manifests/full_message/decrypt.py index c94fd1452..a53f6cc5d 100644 --- a/test_vector_handlers/src/awses_test_vectors/manifests/full_message/decrypt.py +++ b/test_vector_handlers/src/awses_test_vectors/manifests/full_message/decrypt.py @@ -20,11 +20,8 @@ from enum import Enum import attr -import aws_encryption_sdk import pytest import six -from aws_encryption_sdk.identifiers import CommitmentPolicy - from awses_test_vectors.internal.defaults import ENCODING from awses_test_vectors.internal.util import ( dictionary_validator, @@ -35,14 +32,16 @@ from awses_test_vectors.manifests.keys import KeysManifest from awses_test_vectors.manifests.master_key import MasterKeySpec, master_key_provider_from_master_key_specs -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import IO, Callable, Dict, Iterable, Optional # noqa pylint: disable=unused-import +import aws_encryption_sdk +from aws_encryption_sdk.identifiers import CommitmentPolicy +try: # Python 3.5.0 and 3.5.1 have incompatible typing modules from awses_test_vectors.internal.mypy_types import ( # noqa pylint: disable=unused-import DECRYPT_SCENARIO_SPEC, FULL_MESSAGE_DECRYPT_MANIFEST, MASTER_KEY_SPEC, ) + from typing import IO, Callable, Dict, Iterable, Optional # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass diff --git a/test_vector_handlers/src/awses_test_vectors/manifests/full_message/decrypt_generation.py b/test_vector_handlers/src/awses_test_vectors/manifests/full_message/decrypt_generation.py index e407a1b65..48fc1a6b3 100644 --- a/test_vector_handlers/src/awses_test_vectors/manifests/full_message/decrypt_generation.py +++ b/test_vector_handlers/src/awses_test_vectors/manifests/full_message/decrypt_generation.py @@ -22,11 +22,6 @@ import attr import six -from aws_encryption_sdk.caches.local import LocalCryptoMaterialsCache -from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager -from aws_encryption_sdk.materials_managers.caching import CachingCryptoMaterialsManager -from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager - from awses_test_vectors.internal.defaults import ENCODING from awses_test_vectors.internal.util import ( dictionary_validator, @@ -45,6 +40,11 @@ from awses_test_vectors.manifests.full_message.encrypt import MessageEncryptionTestScenario from awses_test_vectors.manifests.keys import KeysManifest +from aws_encryption_sdk.caches.local import LocalCryptoMaterialsCache +from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager +from aws_encryption_sdk.materials_managers.caching import CachingCryptoMaterialsManager +from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager + try: from aws_encryption_sdk.identifiers import AlgorithmSuite except ImportError: @@ -53,12 +53,11 @@ from awses_test_vectors.manifests.master_key import MasterKeySpec, master_key_provider_from_master_key_specs try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import IO, Callable, Dict, Iterable, Optional # noqa pylint: disable=unused-import - from awses_test_vectors.internal.mypy_types import ( # noqa pylint: disable=unused-import ENCRYPT_SCENARIO_SPEC, PLAINTEXTS_SPEC, ) + from typing import IO, Callable, Dict, Iterable, Optional # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass diff --git a/test_vector_handlers/src/awses_test_vectors/manifests/full_message/encrypt.py b/test_vector_handlers/src/awses_test_vectors/manifests/full_message/encrypt.py index c77fed1ce..2e88c8a52 100644 --- a/test_vector_handlers/src/awses_test_vectors/manifests/full_message/encrypt.py +++ b/test_vector_handlers/src/awses_test_vectors/manifests/full_message/encrypt.py @@ -19,9 +19,7 @@ import os import attr -import aws_encryption_sdk import six - from awses_test_vectors.internal.defaults import ENCODING from awses_test_vectors.internal.util import ( algorithm_suite_from_string_id, @@ -34,6 +32,8 @@ from awses_test_vectors.manifests.keys import KeysManifest from awses_test_vectors.manifests.master_key import MasterKeySpec, master_key_provider_from_master_key_specs +import aws_encryption_sdk + try: from aws_encryption_sdk.identifiers import AlgorithmSuite, CommitmentPolicy except ImportError: @@ -41,12 +41,11 @@ try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import IO, Callable, Dict, Iterable, Optional # noqa pylint: disable=unused-import - from awses_test_vectors.internal.mypy_types import ( # noqa pylint: disable=unused-import ENCRYPT_SCENARIO_SPEC, PLAINTEXTS_SPEC, ) + from typing import IO, Callable, Dict, Iterable, Optional # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass diff --git a/test_vector_handlers/src/awses_test_vectors/manifests/keys.py b/test_vector_handlers/src/awses_test_vectors/manifests/keys.py index cba6b7e25..546dbb489 100644 --- a/test_vector_handlers/src/awses_test_vectors/manifests/keys.py +++ b/test_vector_handlers/src/awses_test_vectors/manifests/keys.py @@ -19,14 +19,11 @@ import attr import six - from awses_test_vectors.internal.aws_kms import arn_from_key_id from awses_test_vectors.internal.defaults import ENCODING from awses_test_vectors.internal.util import dictionary_validator, membership_validator, validate_manifest_type try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Dict, Iterable, Optional, cast # noqa pylint: disable=unused-import - from awses_test_vectors.internal.mypy_types import ( # noqa pylint: disable=unused-import AWS_KMS_KEY_SPEC, KEY_SPEC, @@ -34,6 +31,7 @@ MANIFEST_VERSION, MANUAL_KEY_SPEC, ) + from typing import Dict, Iterable, Optional, cast # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass diff --git a/test_vector_handlers/src/awses_test_vectors/manifests/master_key.py b/test_vector_handlers/src/awses_test_vectors/manifests/master_key.py index a1a7ae4af..8b00870f1 100644 --- a/test_vector_handlers/src/awses_test_vectors/manifests/master_key.py +++ b/test_vector_handlers/src/awses_test_vectors/manifests/master_key.py @@ -17,6 +17,10 @@ """ import attr import six +from awses_test_vectors.internal.aws_kms import KMS_MASTER_KEY_PROVIDER, KMS_MRK_AWARE_MASTER_KEY_PROVIDER +from awses_test_vectors.internal.util import membership_validator +from awses_test_vectors.manifests.keys import KeysManifest, KeySpec # noqa pylint: disable=unused-import + from aws_encryption_sdk.identifiers import EncryptionKeyType, WrappingAlgorithm from aws_encryption_sdk.key_providers.base import MasterKeyProvider # noqa pylint: disable=unused-import from aws_encryption_sdk.key_providers.kms import ( # noqa pylint: disable=unused-import @@ -26,10 +30,6 @@ ) from aws_encryption_sdk.key_providers.raw import RawMasterKey -from awses_test_vectors.internal.aws_kms import KMS_MASTER_KEY_PROVIDER, KMS_MRK_AWARE_MASTER_KEY_PROVIDER -from awses_test_vectors.internal.util import membership_validator -from awses_test_vectors.manifests.keys import KeysManifest, KeySpec # noqa pylint: disable=unused-import - try: from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey except ImportError: @@ -37,9 +37,8 @@ try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Iterable # noqa pylint: disable=unused-import - from awses_test_vectors.internal.mypy_types import MASTER_KEY_SPEC # noqa pylint: disable=unused-import + from typing import Iterable # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass diff --git a/test_vector_handlers/test/integration/commands/test_i_full_message_encrypt.py b/test_vector_handlers/test/integration/commands/test_i_full_message_encrypt.py index 6305a15da..6928caeba 100644 --- a/test_vector_handlers/test/integration/commands/test_i_full_message_encrypt.py +++ b/test_vector_handlers/test/integration/commands/test_i_full_message_encrypt.py @@ -14,7 +14,6 @@ Integration tests for ``awses_test_vectors.commands``. """ import pytest - from awses_test_vectors.commands import full_message_decrypt, full_message_decrypt_generate, full_message_encrypt from ..integration_test_utils import ( # noqa pylint: disable=unused-import From ac0ceb3e60d9b4b8ff3c3ae44b7ff2b9b0a50af2 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 17:29:19 -0800 Subject: [PATCH 122/184] fix --- src/aws_encryption_sdk/materials_managers/mpl/cmm.py | 4 +++- src/aws_encryption_sdk/materials_managers/mpl/materials.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py index 24a10139f..1913505c4 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py @@ -2,7 +2,8 @@ The aws-cryptographic-materials-library MUST be installed to use this module. """ - +# pylint should pass even if the MPL isn't installed +# noqa pylint: disable=import-error from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException from aws_cryptographic_materialproviders.mpl.models import ( AlgorithmSuiteIdESDK as MPL_AlgorithmSuiteIdESDK, @@ -16,6 +17,7 @@ from aws_cryptographic_materialproviders.mpl.references import ( ICryptographicMaterialsManager as MPL_ICryptographicMaterialsManager, ) +# noqa pylint: enable=import-error from typing import List from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError diff --git a/src/aws_encryption_sdk/materials_managers/mpl/materials.py b/src/aws_encryption_sdk/materials_managers/mpl/materials.py index c23e2b038..faa47cb46 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/materials.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/materials.py @@ -2,12 +2,14 @@ The aws-cryptographic-materials-library MUST be installed to use this module. """ - +# pylint should pass even if the MPL isn't installed +# noqa pylint: disable=import-error from aws_cryptographic_materialproviders.mpl.models import ( DecryptionMaterials as MPL_DecryptionMaterials, EncryptedDataKey as MPL_EncryptedDataKey, EncryptionMaterials as MPL_EncryptionMaterials, ) +# noqa pylint: enable=import-error from typing import Dict, List, Set from aws_encryption_sdk.identifiers import Algorithm, AlgorithmSuite From 2fd88584be77ed1be902e56ff473208e674b2d1d Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 17:32:01 -0800 Subject: [PATCH 123/184] oops --- src/aws_encryption_sdk/materials_managers/mpl/cmm.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py index 1913505c4..760808bfe 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py @@ -3,7 +3,8 @@ The aws-cryptographic-materials-library MUST be installed to use this module. """ # pylint should pass even if the MPL isn't installed -# noqa pylint: disable=import-error +# Also thinks these imports aren't used if it can't import them +# noqa pylint: disable=import-error,unused-import from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException from aws_cryptographic_materialproviders.mpl.models import ( AlgorithmSuiteIdESDK as MPL_AlgorithmSuiteIdESDK, @@ -17,7 +18,7 @@ from aws_cryptographic_materialproviders.mpl.references import ( ICryptographicMaterialsManager as MPL_ICryptographicMaterialsManager, ) -# noqa pylint: enable=import-error +# noqa pylint: enable=import-error,unused-import from typing import List from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError From 51c6a9caad271e1288ae06e07355c8b2cc6c0b85 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 17:33:06 -0800 Subject: [PATCH 124/184] revert --- decrypt_oracle/.chalice/pipeline.py | 2 +- .../src/aws_encryption_sdk_decrypt_oracle/app.py | 3 +-- .../test/integration/integration_test_utils.py | 3 +-- decrypt_oracle/test/test_n_generate_test_vectors.py | 7 +++---- examples/test/examples_test_utils.py | 2 +- examples/test/test_i_basic_encryption.py | 1 + ...basic_file_encryption_with_multiple_providers.py | 4 +++- ...i_basic_file_encryption_with_raw_key_provider.py | 1 + examples/test/test_i_data_key_caching_basic.py | 1 + examples/test/test_i_discovery_kms_provider.py | 4 +++- examples/test/test_i_mrk_aware_kms_provider.py | 4 +++- examples/test/test_i_multiple_kms_cmk.py | 4 +++- examples/test/test_i_one_kms_cmk.py | 4 +++- examples/test/test_i_one_kms_cmk_streaming_data.py | 1 + examples/test/test_i_one_kms_cmk_unsigned.py | 4 +++- examples/test/test_i_set_commitment.py | 4 +++- .../materials_managers/mpl/__init__.py | 5 +---- .../materials_managers/mpl/cmm.py | 7 +++---- .../materials_managers/mpl/materials.py | 5 +++++ src/aws_encryption_sdk/streaming_client.py | 4 ++-- test/mpl/__init__.py | 5 +---- test/mpl/unit/test_material_managers_mpl_cmm.py | 5 +---- .../unit/test_material_managers_mpl_materials.py | 7 ++----- .../src/awses_test_vectors/internal/aws_kms.py | 5 ++--- .../src/awses_test_vectors/internal/util.py | 3 ++- .../manifests/full_message/decrypt.py | 9 +++++---- .../manifests/full_message/decrypt_generation.py | 13 +++++++------ .../manifests/full_message/encrypt.py | 7 ++++--- .../src/awses_test_vectors/manifests/keys.py | 4 +++- .../src/awses_test_vectors/manifests/master_key.py | 11 ++++++----- .../commands/test_i_full_message_encrypt.py | 1 + 31 files changed, 77 insertions(+), 63 deletions(-) diff --git a/decrypt_oracle/.chalice/pipeline.py b/decrypt_oracle/.chalice/pipeline.py index c05df6739..9d5573646 100644 --- a/decrypt_oracle/.chalice/pipeline.py +++ b/decrypt_oracle/.chalice/pipeline.py @@ -2,6 +2,7 @@ import argparse import getpass import logging +from typing import Iterable import boto3 import troposphere @@ -19,7 +20,6 @@ ) from botocore.exceptions import ClientError from troposphere import GetAtt, Ref, Sub, Template, codebuild, codepipeline, iam, s3 -from typing import Iterable APPLICATION_NAME = "AwsEncryptionSdkDecryptOraclePython" PIPELINE_STACK_NAME = "{}DeployPipeline".format(APPLICATION_NAME) diff --git a/decrypt_oracle/src/aws_encryption_sdk_decrypt_oracle/app.py b/decrypt_oracle/src/aws_encryption_sdk_decrypt_oracle/app.py index e250bb3c8..820b9e015 100644 --- a/decrypt_oracle/src/aws_encryption_sdk_decrypt_oracle/app.py +++ b/decrypt_oracle/src/aws_encryption_sdk_decrypt_oracle/app.py @@ -15,11 +15,10 @@ import logging import os -from chalice import Chalice, Response - import aws_encryption_sdk from aws_encryption_sdk.identifiers import CommitmentPolicy from aws_encryption_sdk.key_providers.kms import DiscoveryAwsKmsMasterKeyProvider +from chalice import Chalice, Response from .key_providers.counting import CountingMasterKey from .key_providers.null import NullMasterKey diff --git a/decrypt_oracle/test/integration/integration_test_utils.py b/decrypt_oracle/test/integration/integration_test_utils.py index 9849f1ecc..c03b7f440 100644 --- a/decrypt_oracle/test/integration/integration_test_utils.py +++ b/decrypt_oracle/test/integration/integration_test_utils.py @@ -15,11 +15,10 @@ import json import os from collections import namedtuple - -import pytest from typing import Any, Callable, Iterable, Optional, Text import aws_encryption_sdk +import pytest from aws_encryption_sdk.identifiers import CommitmentPolicy from aws_encryption_sdk.key_providers.kms import StrictAwsKmsMasterKeyProvider diff --git a/decrypt_oracle/test/test_n_generate_test_vectors.py b/decrypt_oracle/test/test_n_generate_test_vectors.py index ae9bb7d7d..deb3f7c4d 100644 --- a/decrypt_oracle/test/test_n_generate_test_vectors.py +++ b/decrypt_oracle/test/test_n_generate_test_vectors.py @@ -15,15 +15,14 @@ import binascii import json import os - -import pytest -from aws_encryption_sdk_decrypt_oracle.key_providers.counting import CountingMasterKey -from aws_encryption_sdk_decrypt_oracle.key_providers.null import NullMasterKey from typing import Dict, Iterable, Text import aws_encryption_sdk +import pytest from aws_encryption_sdk.key_providers.base import MasterKeyProvider from aws_encryption_sdk.key_providers.kms import KMSMasterKey +from aws_encryption_sdk_decrypt_oracle.key_providers.counting import CountingMasterKey +from aws_encryption_sdk_decrypt_oracle.key_providers.null import NullMasterKey from .integration.integration_test_utils import test_vectors_filename diff --git a/examples/test/examples_test_utils.py b/examples/test/examples_test_utils.py index 08e8cf2f5..8a51f21c8 100644 --- a/examples/test/examples_test_utils.py +++ b/examples/test/examples_test_utils.py @@ -49,7 +49,7 @@ from integration_test_utils import ( # noqa pylint: disable=unused-import,import-error get_cmk_arn, - get_mrk_arn, get_second_cmk_arn, + get_mrk_arn, get_second_mrk_arn, ) diff --git a/examples/test/test_i_basic_encryption.py b/examples/test/test_i_basic_encryption.py index aa32d61fa..f2a4fab51 100644 --- a/examples/test/test_i_basic_encryption.py +++ b/examples/test/test_i_basic_encryption.py @@ -17,6 +17,7 @@ from ..src.basic_encryption import cycle_string from .examples_test_utils import get_cmk_arn, static_plaintext + pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_basic_file_encryption_with_multiple_providers.py b/examples/test/test_i_basic_file_encryption_with_multiple_providers.py index 0792f4958..282a272ab 100644 --- a/examples/test/test_i_basic_file_encryption_with_multiple_providers.py +++ b/examples/test/test_i_basic_file_encryption_with_multiple_providers.py @@ -18,7 +18,9 @@ import pytest from ..src.basic_file_encryption_with_multiple_providers import cycle_file -from .examples_test_utils import get_cmk_arn, static_plaintext +from .examples_test_utils import get_cmk_arn +from .examples_test_utils import static_plaintext + pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py b/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py index 046b7f964..710c0ccac 100644 --- a/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py +++ b/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py @@ -19,6 +19,7 @@ from ..src.basic_file_encryption_with_raw_key_provider import cycle_file from .examples_test_utils import static_plaintext + pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_data_key_caching_basic.py b/examples/test/test_i_data_key_caching_basic.py index 7a30f4e53..734c35692 100644 --- a/examples/test/test_i_data_key_caching_basic.py +++ b/examples/test/test_i_data_key_caching_basic.py @@ -16,6 +16,7 @@ from ..src.data_key_caching_basic import encrypt_with_caching from .examples_test_utils import get_cmk_arn + pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_discovery_kms_provider.py b/examples/test/test_i_discovery_kms_provider.py index 0f64cbf59..e9a1c6e71 100644 --- a/examples/test/test_i_discovery_kms_provider.py +++ b/examples/test/test_i_discovery_kms_provider.py @@ -16,7 +16,9 @@ import pytest from ..src.discovery_kms_provider import encrypt_decrypt -from .examples_test_utils import get_cmk_arn, static_plaintext +from .examples_test_utils import get_cmk_arn +from .examples_test_utils import static_plaintext + pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_mrk_aware_kms_provider.py b/examples/test/test_i_mrk_aware_kms_provider.py index a90101fa8..8e7a003f8 100644 --- a/examples/test/test_i_mrk_aware_kms_provider.py +++ b/examples/test/test_i_mrk_aware_kms_provider.py @@ -15,7 +15,9 @@ import pytest from ..src.mrk_aware_kms_provider import encrypt_decrypt -from .examples_test_utils import get_mrk_arn, get_second_mrk_arn, static_plaintext +from .examples_test_utils import get_mrk_arn, get_second_mrk_arn +from .examples_test_utils import static_plaintext + pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_multiple_kms_cmk.py b/examples/test/test_i_multiple_kms_cmk.py index 2915a0fd7..39369cbc6 100644 --- a/examples/test/test_i_multiple_kms_cmk.py +++ b/examples/test/test_i_multiple_kms_cmk.py @@ -16,7 +16,9 @@ import pytest from ..src.multiple_kms_cmk import encrypt_decrypt -from .examples_test_utils import get_cmk_arn, get_second_cmk_arn, static_plaintext +from .examples_test_utils import get_cmk_arn, get_second_cmk_arn +from .examples_test_utils import static_plaintext + pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_one_kms_cmk.py b/examples/test/test_i_one_kms_cmk.py index 96dd48dae..71ce74d3d 100644 --- a/examples/test/test_i_one_kms_cmk.py +++ b/examples/test/test_i_one_kms_cmk.py @@ -16,7 +16,9 @@ import pytest from ..src.one_kms_cmk import encrypt_decrypt -from .examples_test_utils import get_cmk_arn, static_plaintext +from .examples_test_utils import get_cmk_arn +from .examples_test_utils import static_plaintext + pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_one_kms_cmk_streaming_data.py b/examples/test/test_i_one_kms_cmk_streaming_data.py index f0a3094d0..b22fa4232 100644 --- a/examples/test/test_i_one_kms_cmk_streaming_data.py +++ b/examples/test/test_i_one_kms_cmk_streaming_data.py @@ -20,6 +20,7 @@ from ..src.one_kms_cmk_streaming_data import encrypt_decrypt_stream from .examples_test_utils import get_cmk_arn, static_plaintext + pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_one_kms_cmk_unsigned.py b/examples/test/test_i_one_kms_cmk_unsigned.py index 41f16473d..8a2758c96 100644 --- a/examples/test/test_i_one_kms_cmk_unsigned.py +++ b/examples/test/test_i_one_kms_cmk_unsigned.py @@ -16,7 +16,9 @@ import pytest from ..src.one_kms_cmk_unsigned import encrypt_decrypt -from .examples_test_utils import get_cmk_arn, static_plaintext +from .examples_test_utils import get_cmk_arn +from .examples_test_utils import static_plaintext + pytestmark = [pytest.mark.examples] diff --git a/examples/test/test_i_set_commitment.py b/examples/test/test_i_set_commitment.py index c14a379bf..96247334b 100644 --- a/examples/test/test_i_set_commitment.py +++ b/examples/test/test_i_set_commitment.py @@ -16,7 +16,9 @@ import pytest from ..src.set_commitment import encrypt_decrypt -from .examples_test_utils import get_cmk_arn, static_plaintext +from .examples_test_utils import get_cmk_arn +from .examples_test_utils import static_plaintext + pytestmark = [pytest.mark.examples] diff --git a/src/aws_encryption_sdk/materials_managers/mpl/__init__.py b/src/aws_encryption_sdk/materials_managers/mpl/__init__.py index 7593a3300..295400d76 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/__init__.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/__init__.py @@ -10,7 +10,4 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -"""Modules related to the MPL's materials managers interfaces. - -The aws-cryptographic-materials-library MUST be installed to use these modules. -""" +"""Modules related to the MPL's materials managers interfaces.""" diff --git a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py index 760808bfe..ead7c48f1 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py @@ -60,10 +60,9 @@ def get_encryption_materials( :param request: Request for encryption materials """ try: - mpl_input: MPL_GetEncryptionMaterialsInput = \ - CryptoMaterialsManagerFromMPL._native_to_mpl_get_encryption_materials( - request - ) + mpl_input: MPL_GetEncryptionMaterialsInput = CryptoMaterialsManagerFromMPL._native_to_mpl_get_encryption_materials( + request + ) mpl_output: MPL_GetEncryptionMaterialsOutput = self.mpl_cmm.get_encryption_materials(mpl_input) return EncryptionMaterialsFromMPL(mpl_output.encryption_materials) except AwsCryptographicMaterialProvidersException as mpl_exception: diff --git a/src/aws_encryption_sdk/materials_managers/mpl/materials.py b/src/aws_encryption_sdk/materials_managers/mpl/materials.py index faa47cb46..a82a3c372 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/materials.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/materials.py @@ -1,9 +1,14 @@ +<<<<<<< HEAD """Provides encryption/decryption materials from an underlying materials provider from the MPL. The aws-cryptographic-materials-library MUST be installed to use this module. """ # pylint should pass even if the MPL isn't installed # noqa pylint: disable=import-error +======= +"""Provides encryption/decryption materials from an underlying materials provider.""" + +>>>>>>> parent of 22eabb6 (fix) from aws_cryptographic_materialproviders.mpl.models import ( DecryptionMaterials as MPL_DecryptionMaterials, EncryptedDataKey as MPL_EncryptedDataKey, diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 959b5ff0b..72ed4efb7 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -555,7 +555,7 @@ def _prep_message(self): # MPL verification key is PEM bytes, not DER bytes. # If the underlying CMM is from the MPL, load PEM bytes. if (_HAS_MPL - and isinstance(self.config.materials_manager, CryptoMaterialsManagerFromMPL)): + and isinstance(self.config.materials_manager, CryptoMaterialsManagerFromMPL)): self.signer = Signer.from_key_bytes( algorithm=self._encryption_materials.algorithm, key_bytes=self._encryption_materials.signing_key, encoding=serialization.Encoding.PEM, @@ -923,7 +923,7 @@ def _read_header(self): # MPL verification key is NOT key bytes; it is bytes of the compressed point. # If the underlying CMM is from the MPL, load bytes from encoded point. if (_HAS_MPL - and isinstance(self.config.materials_manager, CryptoMaterialsManagerFromMPL)): + and isinstance(self.config.materials_manager, CryptoMaterialsManagerFromMPL)): self.verifier = Verifier.from_encoded_point( algorithm=header.algorithm, encoded_point=base64.b64encode(decryption_materials.verification_key) diff --git a/test/mpl/__init__.py b/test/mpl/__init__.py index 37f482e0b..2a6c71715 100644 --- a/test/mpl/__init__.py +++ b/test/mpl/__init__.py @@ -10,7 +10,4 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -"""Module testing components that use the MPL. - -The aws-cryptographic-materials-library MUST be installed to run tests in this module. -""" +"""Module containing tests that REQUIRE the aws-cryptographic-material-providers library to run.""" diff --git a/test/mpl/unit/test_material_managers_mpl_cmm.py b/test/mpl/unit/test_material_managers_mpl_cmm.py index fa8f76410..a67c3e5c5 100644 --- a/test/mpl/unit/test_material_managers_mpl_cmm.py +++ b/test/mpl/unit/test_material_managers_mpl_cmm.py @@ -10,10 +10,7 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -"""Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic. - -The aws-cryptographic-materials-library MUST be installed to run tests in this module. -""" +"""Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic.""" import pytest from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException diff --git a/test/mpl/unit/test_material_managers_mpl_materials.py b/test/mpl/unit/test_material_managers_mpl_materials.py index 60e12c634..cb3ca7397 100644 --- a/test/mpl/unit/test_material_managers_mpl_materials.py +++ b/test/mpl/unit/test_material_managers_mpl_materials.py @@ -10,10 +10,7 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -"""Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic. - -The aws-cryptographic-materials-library MUST be installed to run tests in this module. -""" +"""Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic.""" import pytest from aws_cryptographic_materialproviders.mpl.models import ( @@ -21,7 +18,7 @@ EncryptedDataKey as MPL_EncryptedDataKey, EncryptionMaterials as MPL_EncryptionMaterials, ) -from mock import MagicMock, patch +from mock import MagicMock, PropertyMock, patch from typing import Dict, List, Set import aws_encryption_sdk.materials_managers.mpl.materials diff --git a/test_vector_handlers/src/awses_test_vectors/internal/aws_kms.py b/test_vector_handlers/src/awses_test_vectors/internal/aws_kms.py index 3d2088a73..14c109e7d 100644 --- a/test_vector_handlers/src/awses_test_vectors/internal/aws_kms.py +++ b/test_vector_handlers/src/awses_test_vectors/internal/aws_kms.py @@ -15,15 +15,14 @@ from aws_encryption_sdk.identifiers import AlgorithmSuite except ImportError: from aws_encryption_sdk.identifiers import Algorithm as AlgorithmSuite - -from awses_test_vectors.internal.defaults import ENCODING - from aws_encryption_sdk.key_providers.kms import ( DiscoveryAwsKmsMasterKeyProvider, MRKAwareDiscoveryAwsKmsMasterKeyProvider, StrictAwsKmsMasterKeyProvider, ) +from awses_test_vectors.internal.defaults import ENCODING + # This lets us easily use a single boto3 client per region for all KMS master keys. KMS_MASTER_KEY_PROVIDER = DiscoveryAwsKmsMasterKeyProvider() KMS_MRK_AWARE_MASTER_KEY_PROVIDER = MRKAwareDiscoveryAwsKmsMasterKeyProvider(discovery_region="us-west-2") diff --git a/test_vector_handlers/src/awses_test_vectors/internal/util.py b/test_vector_handlers/src/awses_test_vectors/internal/util.py index 67d4ec67f..da5552f13 100644 --- a/test_vector_handlers/src/awses_test_vectors/internal/util.py +++ b/test_vector_handlers/src/awses_test_vectors/internal/util.py @@ -24,11 +24,12 @@ from aws_encryption_sdk.identifiers import Algorithm as AlgorithmSuite try: # Python 3.5.0 and 3.5.1 have incompatible typing modules + from typing import Any, Callable, Dict, Iterable, Type # noqa pylint: disable=unused-import + from awses_test_vectors.internal.mypy_types import ( # noqa pylint: disable=unused-import ISINSTANCE, MANIFEST_VERSION, ) - from typing import Any, Callable, Dict, Iterable, Type # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass diff --git a/test_vector_handlers/src/awses_test_vectors/manifests/full_message/decrypt.py b/test_vector_handlers/src/awses_test_vectors/manifests/full_message/decrypt.py index a53f6cc5d..c94fd1452 100644 --- a/test_vector_handlers/src/awses_test_vectors/manifests/full_message/decrypt.py +++ b/test_vector_handlers/src/awses_test_vectors/manifests/full_message/decrypt.py @@ -20,8 +20,11 @@ from enum import Enum import attr +import aws_encryption_sdk import pytest import six +from aws_encryption_sdk.identifiers import CommitmentPolicy + from awses_test_vectors.internal.defaults import ENCODING from awses_test_vectors.internal.util import ( dictionary_validator, @@ -32,16 +35,14 @@ from awses_test_vectors.manifests.keys import KeysManifest from awses_test_vectors.manifests.master_key import MasterKeySpec, master_key_provider_from_master_key_specs -import aws_encryption_sdk -from aws_encryption_sdk.identifiers import CommitmentPolicy - try: # Python 3.5.0 and 3.5.1 have incompatible typing modules + from typing import IO, Callable, Dict, Iterable, Optional # noqa pylint: disable=unused-import + from awses_test_vectors.internal.mypy_types import ( # noqa pylint: disable=unused-import DECRYPT_SCENARIO_SPEC, FULL_MESSAGE_DECRYPT_MANIFEST, MASTER_KEY_SPEC, ) - from typing import IO, Callable, Dict, Iterable, Optional # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass diff --git a/test_vector_handlers/src/awses_test_vectors/manifests/full_message/decrypt_generation.py b/test_vector_handlers/src/awses_test_vectors/manifests/full_message/decrypt_generation.py index 48fc1a6b3..e407a1b65 100644 --- a/test_vector_handlers/src/awses_test_vectors/manifests/full_message/decrypt_generation.py +++ b/test_vector_handlers/src/awses_test_vectors/manifests/full_message/decrypt_generation.py @@ -22,6 +22,11 @@ import attr import six +from aws_encryption_sdk.caches.local import LocalCryptoMaterialsCache +from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager +from aws_encryption_sdk.materials_managers.caching import CachingCryptoMaterialsManager +from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager + from awses_test_vectors.internal.defaults import ENCODING from awses_test_vectors.internal.util import ( dictionary_validator, @@ -40,11 +45,6 @@ from awses_test_vectors.manifests.full_message.encrypt import MessageEncryptionTestScenario from awses_test_vectors.manifests.keys import KeysManifest -from aws_encryption_sdk.caches.local import LocalCryptoMaterialsCache -from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager -from aws_encryption_sdk.materials_managers.caching import CachingCryptoMaterialsManager -from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager - try: from aws_encryption_sdk.identifiers import AlgorithmSuite except ImportError: @@ -53,11 +53,12 @@ from awses_test_vectors.manifests.master_key import MasterKeySpec, master_key_provider_from_master_key_specs try: # Python 3.5.0 and 3.5.1 have incompatible typing modules + from typing import IO, Callable, Dict, Iterable, Optional # noqa pylint: disable=unused-import + from awses_test_vectors.internal.mypy_types import ( # noqa pylint: disable=unused-import ENCRYPT_SCENARIO_SPEC, PLAINTEXTS_SPEC, ) - from typing import IO, Callable, Dict, Iterable, Optional # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass diff --git a/test_vector_handlers/src/awses_test_vectors/manifests/full_message/encrypt.py b/test_vector_handlers/src/awses_test_vectors/manifests/full_message/encrypt.py index 2e88c8a52..c77fed1ce 100644 --- a/test_vector_handlers/src/awses_test_vectors/manifests/full_message/encrypt.py +++ b/test_vector_handlers/src/awses_test_vectors/manifests/full_message/encrypt.py @@ -19,7 +19,9 @@ import os import attr +import aws_encryption_sdk import six + from awses_test_vectors.internal.defaults import ENCODING from awses_test_vectors.internal.util import ( algorithm_suite_from_string_id, @@ -32,8 +34,6 @@ from awses_test_vectors.manifests.keys import KeysManifest from awses_test_vectors.manifests.master_key import MasterKeySpec, master_key_provider_from_master_key_specs -import aws_encryption_sdk - try: from aws_encryption_sdk.identifiers import AlgorithmSuite, CommitmentPolicy except ImportError: @@ -41,11 +41,12 @@ try: # Python 3.5.0 and 3.5.1 have incompatible typing modules + from typing import IO, Callable, Dict, Iterable, Optional # noqa pylint: disable=unused-import + from awses_test_vectors.internal.mypy_types import ( # noqa pylint: disable=unused-import ENCRYPT_SCENARIO_SPEC, PLAINTEXTS_SPEC, ) - from typing import IO, Callable, Dict, Iterable, Optional # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass diff --git a/test_vector_handlers/src/awses_test_vectors/manifests/keys.py b/test_vector_handlers/src/awses_test_vectors/manifests/keys.py index 546dbb489..cba6b7e25 100644 --- a/test_vector_handlers/src/awses_test_vectors/manifests/keys.py +++ b/test_vector_handlers/src/awses_test_vectors/manifests/keys.py @@ -19,11 +19,14 @@ import attr import six + from awses_test_vectors.internal.aws_kms import arn_from_key_id from awses_test_vectors.internal.defaults import ENCODING from awses_test_vectors.internal.util import dictionary_validator, membership_validator, validate_manifest_type try: # Python 3.5.0 and 3.5.1 have incompatible typing modules + from typing import Dict, Iterable, Optional, cast # noqa pylint: disable=unused-import + from awses_test_vectors.internal.mypy_types import ( # noqa pylint: disable=unused-import AWS_KMS_KEY_SPEC, KEY_SPEC, @@ -31,7 +34,6 @@ MANIFEST_VERSION, MANUAL_KEY_SPEC, ) - from typing import Dict, Iterable, Optional, cast # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass diff --git a/test_vector_handlers/src/awses_test_vectors/manifests/master_key.py b/test_vector_handlers/src/awses_test_vectors/manifests/master_key.py index 8b00870f1..a1a7ae4af 100644 --- a/test_vector_handlers/src/awses_test_vectors/manifests/master_key.py +++ b/test_vector_handlers/src/awses_test_vectors/manifests/master_key.py @@ -17,10 +17,6 @@ """ import attr import six -from awses_test_vectors.internal.aws_kms import KMS_MASTER_KEY_PROVIDER, KMS_MRK_AWARE_MASTER_KEY_PROVIDER -from awses_test_vectors.internal.util import membership_validator -from awses_test_vectors.manifests.keys import KeysManifest, KeySpec # noqa pylint: disable=unused-import - from aws_encryption_sdk.identifiers import EncryptionKeyType, WrappingAlgorithm from aws_encryption_sdk.key_providers.base import MasterKeyProvider # noqa pylint: disable=unused-import from aws_encryption_sdk.key_providers.kms import ( # noqa pylint: disable=unused-import @@ -30,6 +26,10 @@ ) from aws_encryption_sdk.key_providers.raw import RawMasterKey +from awses_test_vectors.internal.aws_kms import KMS_MASTER_KEY_PROVIDER, KMS_MRK_AWARE_MASTER_KEY_PROVIDER +from awses_test_vectors.internal.util import membership_validator +from awses_test_vectors.manifests.keys import KeysManifest, KeySpec # noqa pylint: disable=unused-import + try: from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey except ImportError: @@ -37,8 +37,9 @@ try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from awses_test_vectors.internal.mypy_types import MASTER_KEY_SPEC # noqa pylint: disable=unused-import from typing import Iterable # noqa pylint: disable=unused-import + + from awses_test_vectors.internal.mypy_types import MASTER_KEY_SPEC # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass diff --git a/test_vector_handlers/test/integration/commands/test_i_full_message_encrypt.py b/test_vector_handlers/test/integration/commands/test_i_full_message_encrypt.py index 6928caeba..6305a15da 100644 --- a/test_vector_handlers/test/integration/commands/test_i_full_message_encrypt.py +++ b/test_vector_handlers/test/integration/commands/test_i_full_message_encrypt.py @@ -14,6 +14,7 @@ Integration tests for ``awses_test_vectors.commands``. """ import pytest + from awses_test_vectors.commands import full_message_decrypt, full_message_decrypt_generate, full_message_encrypt from ..integration_test_utils import ( # noqa pylint: disable=unused-import From 800f9de0de601bb647ba2ab57e7df3d4eedfa795 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 17:33:18 -0800 Subject: [PATCH 125/184] revert --- src/aws_encryption_sdk/materials_managers/mpl/materials.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/aws_encryption_sdk/materials_managers/mpl/materials.py b/src/aws_encryption_sdk/materials_managers/mpl/materials.py index a82a3c372..faa47cb46 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/materials.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/materials.py @@ -1,14 +1,9 @@ -<<<<<<< HEAD """Provides encryption/decryption materials from an underlying materials provider from the MPL. The aws-cryptographic-materials-library MUST be installed to use this module. """ # pylint should pass even if the MPL isn't installed # noqa pylint: disable=import-error -======= -"""Provides encryption/decryption materials from an underlying materials provider.""" - ->>>>>>> parent of 22eabb6 (fix) from aws_cryptographic_materialproviders.mpl.models import ( DecryptionMaterials as MPL_DecryptionMaterials, EncryptedDataKey as MPL_EncryptedDataKey, From ebcb7590c472f82affa86ecc793a5e8a3e494a8b Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 17:35:14 -0800 Subject: [PATCH 126/184] fix --- src/aws_encryption_sdk/materials_managers/mpl/__init__.py | 5 ++++- src/aws_encryption_sdk/materials_managers/mpl/cmm.py | 7 ++++--- src/aws_encryption_sdk/streaming_client.py | 4 ++-- test/mpl/__init__.py | 5 ++++- test/mpl/unit/test_material_managers_mpl_cmm.py | 5 ++++- test/mpl/unit/test_material_managers_mpl_materials.py | 7 +++++-- 6 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/aws_encryption_sdk/materials_managers/mpl/__init__.py b/src/aws_encryption_sdk/materials_managers/mpl/__init__.py index 295400d76..7593a3300 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/__init__.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/__init__.py @@ -10,4 +10,7 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -"""Modules related to the MPL's materials managers interfaces.""" +"""Modules related to the MPL's materials managers interfaces. + +The aws-cryptographic-materials-library MUST be installed to use these modules. +""" diff --git a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py index ead7c48f1..760808bfe 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py @@ -60,9 +60,10 @@ def get_encryption_materials( :param request: Request for encryption materials """ try: - mpl_input: MPL_GetEncryptionMaterialsInput = CryptoMaterialsManagerFromMPL._native_to_mpl_get_encryption_materials( - request - ) + mpl_input: MPL_GetEncryptionMaterialsInput = \ + CryptoMaterialsManagerFromMPL._native_to_mpl_get_encryption_materials( + request + ) mpl_output: MPL_GetEncryptionMaterialsOutput = self.mpl_cmm.get_encryption_materials(mpl_input) return EncryptionMaterialsFromMPL(mpl_output.encryption_materials) except AwsCryptographicMaterialProvidersException as mpl_exception: diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 72ed4efb7..959b5ff0b 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -555,7 +555,7 @@ def _prep_message(self): # MPL verification key is PEM bytes, not DER bytes. # If the underlying CMM is from the MPL, load PEM bytes. if (_HAS_MPL - and isinstance(self.config.materials_manager, CryptoMaterialsManagerFromMPL)): + and isinstance(self.config.materials_manager, CryptoMaterialsManagerFromMPL)): self.signer = Signer.from_key_bytes( algorithm=self._encryption_materials.algorithm, key_bytes=self._encryption_materials.signing_key, encoding=serialization.Encoding.PEM, @@ -923,7 +923,7 @@ def _read_header(self): # MPL verification key is NOT key bytes; it is bytes of the compressed point. # If the underlying CMM is from the MPL, load bytes from encoded point. if (_HAS_MPL - and isinstance(self.config.materials_manager, CryptoMaterialsManagerFromMPL)): + and isinstance(self.config.materials_manager, CryptoMaterialsManagerFromMPL)): self.verifier = Verifier.from_encoded_point( algorithm=header.algorithm, encoded_point=base64.b64encode(decryption_materials.verification_key) diff --git a/test/mpl/__init__.py b/test/mpl/__init__.py index 2a6c71715..d3f78d0bf 100644 --- a/test/mpl/__init__.py +++ b/test/mpl/__init__.py @@ -10,4 +10,7 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -"""Module containing tests that REQUIRE the aws-cryptographic-material-providers library to run.""" +"""Module testing components that use the MPL. + +The aws-cryptographic-materials-library MUST be installed to run tests in this module. +""" \ No newline at end of file diff --git a/test/mpl/unit/test_material_managers_mpl_cmm.py b/test/mpl/unit/test_material_managers_mpl_cmm.py index a67c3e5c5..fa8f76410 100644 --- a/test/mpl/unit/test_material_managers_mpl_cmm.py +++ b/test/mpl/unit/test_material_managers_mpl_cmm.py @@ -10,7 +10,10 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -"""Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic.""" +"""Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic. + +The aws-cryptographic-materials-library MUST be installed to run tests in this module. +""" import pytest from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException diff --git a/test/mpl/unit/test_material_managers_mpl_materials.py b/test/mpl/unit/test_material_managers_mpl_materials.py index cb3ca7397..6c992ff24 100644 --- a/test/mpl/unit/test_material_managers_mpl_materials.py +++ b/test/mpl/unit/test_material_managers_mpl_materials.py @@ -10,7 +10,10 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -"""Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic.""" +"""Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.materials logic. + +The aws-cryptographic-materials-library MUST be installed to run tests in this module. +""" import pytest from aws_cryptographic_materialproviders.mpl.models import ( @@ -18,7 +21,7 @@ EncryptedDataKey as MPL_EncryptedDataKey, EncryptionMaterials as MPL_EncryptionMaterials, ) -from mock import MagicMock, PropertyMock, patch +from mock import MagicMock, patch from typing import Dict, List, Set import aws_encryption_sdk.materials_managers.mpl.materials From cf26ca3fdb0b2cccbe011d8c2071453fe06bbb09 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 17:45:15 -0800 Subject: [PATCH 127/184] fix --- .../materials_managers/mpl/cmm.py | 2 +- .../unit/test_material_managers_mpl_cmm.py | 68 ++++++++++++------- .../test_material_managers_mpl_materials.py | 18 ++--- 3 files changed, 53 insertions(+), 35 deletions(-) diff --git a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py index 760808bfe..e0879f3fb 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py @@ -2,6 +2,7 @@ The aws-cryptographic-materials-library MUST be installed to use this module. """ +from typing import List # pylint should pass even if the MPL isn't installed # Also thinks these imports aren't used if it can't import them # noqa pylint: disable=import-error,unused-import @@ -19,7 +20,6 @@ ICryptographicMaterialsManager as MPL_ICryptographicMaterialsManager, ) # noqa pylint: enable=import-error,unused-import -from typing import List from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError from aws_encryption_sdk.identifiers import CommitmentPolicy diff --git a/test/mpl/unit/test_material_managers_mpl_cmm.py b/test/mpl/unit/test_material_managers_mpl_cmm.py index fa8f76410..060f19f95 100644 --- a/test/mpl/unit/test_material_managers_mpl_cmm.py +++ b/test/mpl/unit/test_material_managers_mpl_cmm.py @@ -76,16 +76,17 @@ def test_GIVEN_invalid_mpl_cmm_WHEN_create_CryptoMaterialsManagerFromMPL_THEN_ra @patch.object(mock_mpl_cmm, "get_encryption_materials") -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.CryptoMaterialsManagerFromMPL._native_to_mpl_get_encryption_materials") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.CryptoMaterialsManagerFromMPL" + "._native_to_mpl_get_encryption_materials") def test_GIVEN_valid_request_WHEN_get_encryption_materials_THEN_return_EncryptionMaterialsFromMPL( mock_native_to_mpl_get_encryption_materials, mock_get_encryption_materials, ): - + # Given: _native_to_mpl_get_encryption_materials creates a MPL_GetEncryptionMaterialsInput mock_get_encryption_materials_input = MagicMock(__class__=MPL_GetEncryptionMaterialsInput) mock_native_to_mpl_get_encryption_materials.return_value = mock_get_encryption_materials_input - + # Given: mpl_cmm.get_encryption_materials returns mock MPL encryption materials mock_get_encryption_materials_output = MagicMock(__class__=MPL_GetEncryptionMaterialsOutput) mock_get_encryption_materials_output.encryption_materials = mock_mpl_encryption_materials @@ -104,7 +105,8 @@ def test_GIVEN_valid_request_WHEN_get_encryption_materials_THEN_return_Encryptio mock_mpl_cmm.get_encryption_materials.assert_called_once_with(mock_get_encryption_materials_input) -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.CryptoMaterialsManagerFromMPL._native_to_mpl_commmitment_policy") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.CryptoMaterialsManagerFromMPL" + "._native_to_mpl_commmitment_policy") def test_GIVEN_mpl_cmm_raises_MPLException_WHEN_get_encryption_materials_THEN_raise_ESDKException( _ ): @@ -112,13 +114,15 @@ def test_GIVEN_mpl_cmm_raises_MPLException_WHEN_get_encryption_materials_THEN_ra with pytest.raises(AWSEncryptionSDKClientError): # Given: mpl_cmm.get_encryption_materials raises MPL exception with patch.object(mock_mpl_cmm, "get_encryption_materials", - side_effect=AwsCryptographicMaterialProvidersException("any")): + side_effect=AwsCryptographicMaterialProvidersException("any")): # When: get_encryption_materials cmm = CryptoMaterialsManagerFromMPL(mpl_cmm=mock_mpl_cmm) cmm.get_encryption_materials(mock_encryption_materials_request) -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.CryptoMaterialsManagerFromMPL._native_to_mpl_commmitment_policy") -def test_GIVEN_valid_mpl_commitment_policy_WHEN_native_to_mpl_get_encryption_materials_THEN_returns_MPL_GetEncryptionMaterialsInput( + +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.CryptoMaterialsManagerFromMPL" + "._native_to_mpl_commmitment_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 ): # Given: commitment policy is some MPL ESDK commitment policy @@ -126,7 +130,9 @@ def test_GIVEN_valid_mpl_commitment_policy_WHEN_native_to_mpl_get_encryption_mat mock_mpl_commitment_policy.return_value = mock_commitment_policy # When: _native_to_mpl_get_encryption_materials - output = CryptoMaterialsManagerFromMPL._native_to_mpl_get_encryption_materials(mock_encryption_materials_request) + output = CryptoMaterialsManagerFromMPL._native_to_mpl_get_encryption_materials( + mock_encryption_materials_request + ) # Then: returned MPL_GetEncryptionMaterialsInput is correct assert isinstance(output, MPL_GetEncryptionMaterialsInput) @@ -135,7 +141,7 @@ 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(): +def test_GIVEN_CommitmentPolicy_FORBID_ENCRYPT_ALLOW_DECRYPT_WHEN_native_to_mpl_commmitment_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 @@ -146,7 +152,8 @@ def test_GIVEN_CommitmentPolicy_FORBID_ENCRYPT_ALLOW_DECRYPT_WHEN_native_to_mpl_ 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(): + +def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_ALLOW_DECRYPT_WHEN_native_to_mpl_commmitment_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 @@ -157,7 +164,8 @@ def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_ALLOW_DECRYPT_WHEN_native_to_mpl 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(): + +def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_REQUIRE_DECRYPT_WHEN_native_to_mpl_commmitment_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 @@ -168,6 +176,7 @@ def test_GIVEN_CommitmentPolicy_REQUIRE_ENCRYPT_REQUIRE_DECRYPT_WHEN_native_to_m 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(): # Given: invalid native commitment policy native_commitment_policy = "not a commitment policy" @@ -177,13 +186,14 @@ def test_GIVEN_CommitmentPolicy_unrecognized_WHEN_native_to_mpl_commmitment_poli # When: _native_to_mpl_commmitment_policy CryptoMaterialsManagerFromMPL._native_to_mpl_commmitment_policy(native_commitment_policy) + @patch.object(mock_mpl_cmm, "decrypt_materials") -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.CryptoMaterialsManagerFromMPL._create_mpl_decrypt_materials_input_from_request") +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.CryptoMaterialsManagerFromMPL" + "._create_mpl_decrypt_materials_input_from_request") def test_GIVEN_valid_request_WHEN_decrypt_materials_THEN_return_DecryptionMaterialsFromMPL( mock_native_to_mpl_decrypt_materials, mock_get_encryption_materials, ): - # Given: mpl_cmm.get_decryption_materials returns mock MPL decryption materials mock_decrypt_materials_output = MagicMock(__class__=MPL_GetEncryptionMaterialsOutput) mock_decrypt_materials_output.decryption_materials = mock_mpl_decrypt_materials @@ -205,7 +215,9 @@ def test_GIVEN_valid_request_WHEN_decrypt_materials_THEN_return_DecryptionMateri # Verify we actually called `decrypt_materials` mock_mpl_cmm.decrypt_materials.assert_called_once_with(mock_decrypt_materials_input) -@patch("aws_encryption_sdk.materials_managers.mpl.cmm.CryptoMaterialsManagerFromMPL._create_mpl_decrypt_materials_input_from_request") + +@patch("aws_encryption_sdk.materials_managers.mpl.cmm.CryptoMaterialsManagerFromMPL" + "._create_mpl_decrypt_materials_input_from_request") def test_GIVEN_decrypt_materials_raises_MPL_Exception_WHEN_call_decrypt_materials_THEN_raise_ESDK_Exception( _ ): @@ -213,12 +225,13 @@ def test_GIVEN_decrypt_materials_raises_MPL_Exception_WHEN_call_decrypt_material with pytest.raises(AWSEncryptionSDKClientError): # Given: mpl_cmm.decrypt_materials raises MPL exception with patch.object(mock_mpl_cmm, "decrypt_materials", - side_effect=AwsCryptographicMaterialProvidersException("any")): + side_effect=AwsCryptographicMaterialProvidersException("any")): # When: decrypt_materials cmm = CryptoMaterialsManagerFromMPL(mpl_cmm=mock_mpl_cmm) cmm.decrypt_materials(mock_decryption_materials_request) -def test_GIVEN_valid_native_algorithm_id_WHEN_native_algorithm_id_to_mpl_algorithm_id_THEN_returns_valid_MPL_AlgorithmSuiteIdESDK(): + +def test_GIVEN_valid_native_algorithm_id_WHEN_native_algorithm_id_to_mpl_algorithm_id_THEN_returns_valid_MPL_AlgorithmSuiteIdESDK(): # noqa: E501 # Given: any native algorithm ID some_native_algorithm_id = 0x1234 # Not a real algorithm ID, but fits the format @@ -231,9 +244,12 @@ def test_GIVEN_valid_native_algorithm_id_WHEN_native_algorithm_id_to_mpl_algorit assert isinstance(mpl_output, MPL_AlgorithmSuiteIdESDK) assert mpl_output.value == "0x1234" -@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") -def test_GIVEN_valid_request_WHEN_create_mpl_decrypt_materials_input_from_request_THEN_returns_MPL_MPL_DecryptMaterialsInput( + +@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") +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, ): @@ -245,17 +261,19 @@ def test_GIVEN_valid_request_WHEN_create_mpl_decrypt_materials_input_from_reques mock_commitment_policy = MagicMock(__class__=MPL_CommitmentPolicyESDK) mock_mpl_commitment_policy.return_value = mock_commitment_policy - no_mock_edks = [ mock_edk ] - one_mock_edk = [ mock_edk ] - two_mock_edks = [ mock_edk, mock_edk ] + no_mock_edks = [mock_edk] + one_mock_edk = [mock_edk] + two_mock_edks = [mock_edk, mock_edk] # Given: ESK lists of various lengths - for mock_edks in [ no_mock_edks, one_mock_edk, two_mock_edks ]: + for mock_edks in [no_mock_edks, one_mock_edk, two_mock_edks]: mock_decryption_materials_request.encrypted_data_keys = mock_edks # When: _create_mpl_decrypt_materials_input_from_request - output = CryptoMaterialsManagerFromMPL._create_mpl_decrypt_materials_input_from_request(mock_decryption_materials_request) + output = CryptoMaterialsManagerFromMPL._create_mpl_decrypt_materials_input_from_request( + mock_decryption_materials_request + ) # Then: # Verify general correctness of output structure @@ -273,4 +291,4 @@ def test_GIVEN_valid_request_WHEN_create_mpl_decrypt_materials_input_from_reques input_edk = mock_edks[i] assert output_edk.key_provider_id == input_edk.key_provider.provider_id assert output_edk.key_provider_info == input_edk.key_provider.key_info - assert output_edk.ciphertext == input_edk.encrypted_data_key \ No newline at end of file + assert output_edk.ciphertext == input_edk.encrypted_data_key diff --git a/test/mpl/unit/test_material_managers_mpl_materials.py b/test/mpl/unit/test_material_managers_mpl_materials.py index 6c992ff24..a2333f267 100644 --- a/test/mpl/unit/test_material_managers_mpl_materials.py +++ b/test/mpl/unit/test_material_managers_mpl_materials.py @@ -22,10 +22,10 @@ EncryptionMaterials as MPL_EncryptionMaterials, ) from mock import MagicMock, patch -from typing import Dict, List, Set +from typing import Dict import aws_encryption_sdk.materials_managers.mpl.materials -from aws_encryption_sdk.identifiers import Algorithm, AlgorithmSuite +from aws_encryption_sdk.identifiers import AlgorithmSuite from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest from aws_encryption_sdk.materials_managers.mpl.materials import DecryptionMaterialsFromMPL, EncryptionMaterialsFromMPL @@ -48,11 +48,11 @@ mock_edk.ciphertext = mock_mpl_ciphertext -def test_GIVEN_valid_mpl_materials_WHEN_create_EncryptionMaterialsFromMPL_THEN_return_new_CryptoMaterialsManagerFromMPL(): +def test_GIVEN_mpl_materials_WHEN_create_EncryptionMaterialsFromMPL_THEN_return_new_CryptoMaterialsManagerFromMPL(): # Given: valid mpl_materials # When: create EncryptionMaterialsFromMPL mpl_encryption_materials = EncryptionMaterialsFromMPL(mpl_materials=mock_mpl_encryption_materials) - + # Then: EncryptionMaterialsFromMPL is valid assert mpl_encryption_materials.mpl_materials == mock_mpl_encryption_materials @@ -93,7 +93,7 @@ def test_GIVEN_valid_mpl_algorithm_id_WHEN_EncryptionMaterials_get_algorithm_THE # When: Get algorithm mpl_encryption_materials = EncryptionMaterialsFromMPL(mpl_materials=mock_mpl_encryption_materials) output = mpl_encryption_materials.algorithm - + # Then: output is valid assert output == mock_algorithm() # property calls automatically, we need to call the mock @@ -112,12 +112,12 @@ def test_GIVEN_valid_encryption_context_WHEN_EncryptionMaterials_get_encryption_ def test_GIVEN_valid_edks_WHEN_EncryptionMaterials_get_edks_THEN_returns_edks(): - + # Given: lists of mocked EDKs of various lengths no_mock_edks = [] - one_mock_edk = [ mock_edk ] - two_mocked_edks = [ mock_edk, mock_edk ] - for mock_edks in [ no_mock_edks, one_mock_edk, two_mocked_edks ]: + one_mock_edk = [mock_edk] + two_mocked_edks = [mock_edk, mock_edk] + for mock_edks in [no_mock_edks, one_mock_edk, two_mocked_edks]: mock_mpl_encryption_materials.encrypted_data_keys = mock_edks # When: get EDKs From 7f27ebdb1cbe0a7b7039d8912ffb8deed032a5e2 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 17:46:38 -0800 Subject: [PATCH 128/184] fix --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 3daa40e47..952d3c24f 100644 --- a/tox.ini +++ b/tox.ini @@ -112,7 +112,7 @@ passenv = setenv = ######################################################### deps = -rdev_requirements/test-requirements.txt -commands = {[testenv:base-command]commands} test/ -m local --ignore test/unit/mpl/ +commands = {[testenv:base-command]commands} test/ -m local --ignore test/mpl/ # Collect requirements for use in upstream tests [testenv:freeze-upstream-requirements-base] @@ -144,7 +144,7 @@ commands = {[testenv:freeze-upstream-requirements-base]commands} test/upstream-r [testenv:test-upstream-requirements-base] sitepackages = False recreate = True -commands = {[testenv:base-command]commands} test/ -m local --ignore test/unit/mpl/ +commands = {[testenv:base-command]commands} test/ -m local --ignore --ignore test/mpl/ # Test frozen upstream requirements for Python 3.7 [testenv:test-upstream-requirements-py37] From 00f4721542d8529eb2ca6b0ac621b440940055be Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 17:48:24 -0800 Subject: [PATCH 129/184] fix --- src/aws_encryption_sdk/materials_managers/mpl/materials.py | 2 +- test/mpl/__init__.py | 2 +- tox.ini | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aws_encryption_sdk/materials_managers/mpl/materials.py b/src/aws_encryption_sdk/materials_managers/mpl/materials.py index faa47cb46..39aff2c3c 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/materials.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/materials.py @@ -2,6 +2,7 @@ The aws-cryptographic-materials-library MUST be installed to use this module. """ +from typing import Dict, List, Set # pylint should pass even if the MPL isn't installed # noqa pylint: disable=import-error from aws_cryptographic_materialproviders.mpl.models import ( @@ -10,7 +11,6 @@ EncryptionMaterials as MPL_EncryptionMaterials, ) # noqa pylint: enable=import-error -from typing import Dict, List, Set from aws_encryption_sdk.identifiers import Algorithm, AlgorithmSuite from aws_encryption_sdk.materials_managers import ( diff --git a/test/mpl/__init__.py b/test/mpl/__init__.py index d3f78d0bf..37f482e0b 100644 --- a/test/mpl/__init__.py +++ b/test/mpl/__init__.py @@ -13,4 +13,4 @@ """Module testing components that use the MPL. The aws-cryptographic-materials-library MUST be installed to run tests in this module. -""" \ No newline at end of file +""" diff --git a/tox.ini b/tox.ini index 952d3c24f..3644c973a 100644 --- a/tox.ini +++ b/tox.ini @@ -144,7 +144,7 @@ commands = {[testenv:freeze-upstream-requirements-base]commands} test/upstream-r [testenv:test-upstream-requirements-base] sitepackages = False recreate = True -commands = {[testenv:base-command]commands} test/ -m local --ignore --ignore test/mpl/ +commands = {[testenv:base-command]commands} test/ -m local --ignore test/mpl/ # Test frozen upstream requirements for Python 3.7 [testenv:test-upstream-requirements-py37] From 018b93f3ed2b5eae5bf6c2e4dc5d7837e38a34a3 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 17:55:46 -0800 Subject: [PATCH 130/184] fix --- src/aws_encryption_sdk/materials_managers/mpl/cmm.py | 7 ++++++- src/aws_encryption_sdk/materials_managers/mpl/materials.py | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py index e0879f3fb..c262cf7ce 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py @@ -2,7 +2,6 @@ The aws-cryptographic-materials-library MUST be installed to use this module. """ -from typing import List # pylint should pass even if the MPL isn't installed # Also thinks these imports aren't used if it can't import them # noqa pylint: disable=import-error,unused-import @@ -21,6 +20,9 @@ ) # noqa pylint: enable=import-error,unused-import +# pylint and isort disagree on where this should go. Choose isort and disable pylint for this. +from typing import List # noqa pylint: disable=wrong-import-order + from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError from aws_encryption_sdk.identifiers import CommitmentPolicy from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest @@ -28,6 +30,9 @@ from aws_encryption_sdk.materials_managers.mpl.materials import DecryptionMaterialsFromMPL, EncryptionMaterialsFromMPL from aws_encryption_sdk.structures import EncryptedDataKey as Native_EncryptedDataKey +# noqa pylint: enable=import-error,unused-import + + class CryptoMaterialsManagerFromMPL(CryptoMaterialsManager): """ diff --git a/src/aws_encryption_sdk/materials_managers/mpl/materials.py b/src/aws_encryption_sdk/materials_managers/mpl/materials.py index 39aff2c3c..43579fac6 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/materials.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/materials.py @@ -2,7 +2,6 @@ The aws-cryptographic-materials-library MUST be installed to use this module. """ -from typing import Dict, List, Set # pylint should pass even if the MPL isn't installed # noqa pylint: disable=import-error from aws_cryptographic_materialproviders.mpl.models import ( @@ -12,6 +11,9 @@ ) # noqa pylint: enable=import-error +# pylint and isort disagree on where this should go. Choose isort and disable pylint for this. +from typing import Dict, List, Set # noqa pylint: disable=wrong-import-order + from aws_encryption_sdk.identifiers import Algorithm, AlgorithmSuite from aws_encryption_sdk.materials_managers import ( DecryptionMaterials as Native_DecryptionMaterials, From d413b65024d398510f1811c8638109ae7c886336 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 18:00:04 -0800 Subject: [PATCH 131/184] fix --- src/aws_encryption_sdk/materials_managers/mpl/cmm.py | 4 ---- src/aws_encryption_sdk/materials_managers/mpl/materials.py | 2 -- 2 files changed, 6 deletions(-) diff --git a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py index c262cf7ce..a0119a588 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py @@ -19,7 +19,6 @@ ICryptographicMaterialsManager as MPL_ICryptographicMaterialsManager, ) # noqa pylint: enable=import-error,unused-import - # pylint and isort disagree on where this should go. Choose isort and disable pylint for this. from typing import List # noqa pylint: disable=wrong-import-order @@ -30,9 +29,6 @@ from aws_encryption_sdk.materials_managers.mpl.materials import DecryptionMaterialsFromMPL, EncryptionMaterialsFromMPL from aws_encryption_sdk.structures import EncryptedDataKey as Native_EncryptedDataKey -# noqa pylint: enable=import-error,unused-import - - class CryptoMaterialsManagerFromMPL(CryptoMaterialsManager): """ diff --git a/src/aws_encryption_sdk/materials_managers/mpl/materials.py b/src/aws_encryption_sdk/materials_managers/mpl/materials.py index 43579fac6..4508d5545 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/materials.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/materials.py @@ -9,8 +9,6 @@ EncryptedDataKey as MPL_EncryptedDataKey, EncryptionMaterials as MPL_EncryptionMaterials, ) -# noqa pylint: enable=import-error - # pylint and isort disagree on where this should go. Choose isort and disable pylint for this. from typing import Dict, List, Set # noqa pylint: disable=wrong-import-order From c4ca658d08efdac51f85c04b9aa9cd5cf37a3a60 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Fri, 23 Feb 2024 18:09:29 -0800 Subject: [PATCH 132/184] copyright --- examples/src/keyrings/__init__.py | 14 ++------------ .../src/keyrings/example_branch_key_id_supplier.py | 2 ++ examples/test/keyrings/__init__.py | 14 ++------------ .../test/keyrings/test_i_hierarchical_keyring.py | 2 ++ .../materials_managers/mpl/__init__.py | 14 ++------------ .../materials_managers/mpl/cmm.py | 2 ++ .../materials_managers/mpl/materials.py | 2 ++ test/mpl/__init__.py | 14 ++------------ test/mpl/unit/test_material_managers_mpl_cmm.py | 14 ++------------ .../unit/test_material_managers_mpl_materials.py | 14 ++------------ test/unit/test_streaming_client_mpl_import.py | 14 ++------------ 11 files changed, 22 insertions(+), 84 deletions(-) diff --git a/examples/src/keyrings/__init__.py b/examples/src/keyrings/__init__.py index e8fd618b1..120179eda 100644 --- a/examples/src/keyrings/__init__.py +++ b/examples/src/keyrings/__init__.py @@ -1,13 +1,3 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 """Stub module indicator to make linter configuration simpler.""" diff --git a/examples/src/keyrings/example_branch_key_id_supplier.py b/examples/src/keyrings/example_branch_key_id_supplier.py index ba9ae060c..7b390cdda 100644 --- a/examples/src/keyrings/example_branch_key_id_supplier.py +++ b/examples/src/keyrings/example_branch_key_id_supplier.py @@ -1,3 +1,5 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 """Example implementation of a branch key ID supplier.""" from aws_cryptographic_materialproviders.mpl.models import GetBranchKeyIdInput, GetBranchKeyIdOutput diff --git a/examples/test/keyrings/__init__.py b/examples/test/keyrings/__init__.py index e8fd618b1..120179eda 100644 --- a/examples/test/keyrings/__init__.py +++ b/examples/test/keyrings/__init__.py @@ -1,13 +1,3 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 """Stub module indicator to make linter configuration simpler.""" diff --git a/examples/test/keyrings/test_i_hierarchical_keyring.py b/examples/test/keyrings/test_i_hierarchical_keyring.py index d80bb565d..4cae478d7 100644 --- a/examples/test/keyrings/test_i_hierarchical_keyring.py +++ b/examples/test/keyrings/test_i_hierarchical_keyring.py @@ -1,3 +1,5 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 """Unit test suite for the hierarchical keyring example.""" import pytest diff --git a/src/aws_encryption_sdk/materials_managers/mpl/__init__.py b/src/aws_encryption_sdk/materials_managers/mpl/__init__.py index 7593a3300..be75f3566 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/__init__.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/__init__.py @@ -1,15 +1,5 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 """Modules related to the MPL's materials managers interfaces. The aws-cryptographic-materials-library MUST be installed to use these modules. diff --git a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py index a0119a588..53a4b3505 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py @@ -1,3 +1,5 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 """Retrieves encryption/decryption materials from the MPL and interfaces them to EDK components. The aws-cryptographic-materials-library MUST be installed to use this module. diff --git a/src/aws_encryption_sdk/materials_managers/mpl/materials.py b/src/aws_encryption_sdk/materials_managers/mpl/materials.py index 4508d5545..dfd1bd6fc 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/materials.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/materials.py @@ -1,3 +1,5 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 """Provides encryption/decryption materials from an underlying materials provider from the MPL. The aws-cryptographic-materials-library MUST be installed to use this module. diff --git a/test/mpl/__init__.py b/test/mpl/__init__.py index 37f482e0b..79522d342 100644 --- a/test/mpl/__init__.py +++ b/test/mpl/__init__.py @@ -1,15 +1,5 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 """Module testing components that use the MPL. The aws-cryptographic-materials-library MUST be installed to run tests in this module. diff --git a/test/mpl/unit/test_material_managers_mpl_cmm.py b/test/mpl/unit/test_material_managers_mpl_cmm.py index 060f19f95..80d6f00ee 100644 --- a/test/mpl/unit/test_material_managers_mpl_cmm.py +++ b/test/mpl/unit/test_material_managers_mpl_cmm.py @@ -1,15 +1,5 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 """Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.cmm logic. The aws-cryptographic-materials-library MUST be installed to run tests in this module. diff --git a/test/mpl/unit/test_material_managers_mpl_materials.py b/test/mpl/unit/test_material_managers_mpl_materials.py index a2333f267..9e76556a2 100644 --- a/test/mpl/unit/test_material_managers_mpl_materials.py +++ b/test/mpl/unit/test_material_managers_mpl_materials.py @@ -1,15 +1,5 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 """Unit test suite to validate aws_encryption_sdk.materials_managers.mpl.materials logic. The aws-cryptographic-materials-library MUST be installed to run tests in this module. diff --git a/test/unit/test_streaming_client_mpl_import.py b/test/unit/test_streaming_client_mpl_import.py index a4ca87e2a..638b04fd6 100644 --- a/test/unit/test_streaming_client_mpl_import.py +++ b/test/unit/test_streaming_client_mpl_import.py @@ -1,15 +1,5 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 """Unit test suite to validate aws_encryption_sdk.streaming_client MPL import logic.""" import pytest From d99b6667bb1a7d65a36af598889edeab2beecfc6 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Mon, 26 Feb 2024 12:40:24 -0800 Subject: [PATCH 133/184] more unit tests --- src/aws_encryption_sdk/streaming_client.py | 34 +++-- .../unit/test_crypto_authentication_signer.py | 63 +++++++-- test/unit/test_streaming_client_configs.py | 96 +++++++++++++ .../test_streaming_client_stream_decryptor.py | 132 +++++++++++++++++- .../test_streaming_client_stream_encryptor.py | 79 +++++++++++ test/unit/test_utils.py | 25 ++++ 6 files changed, 401 insertions(+), 28 deletions(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 959b5ff0b..2cfcc9a02 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -73,8 +73,9 @@ try: # pylint should pass even if the MPL isn't installed # noqa pylint: disable=import-error - from aws_cryptographic_materialproviders.mpl.client import AwsCryptographicMaterialProviders + from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig + from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException from aws_cryptographic_materialproviders.mpl.models import CreateDefaultCryptographicMaterialsManagerInput from aws_cryptographic_materialproviders.mpl.references import IKeyring _HAS_MPL = True @@ -147,9 +148,6 @@ def _has_mpl_attrs_post_init(self): """If the MPL is present in the runtime, perform MPL-specific post-init logic to validate the new object has a valid state. """ - if not hasattr(self, "keyring"): - self._no_mpl_attrs_post_init() - return if not exactly_one_arg_is_not_none(self.materials_manager, self.key_provider, self.keyring): raise TypeError("Exactly one of keyring, materials_manager, or key_provider must be provided") if self.materials_manager is None: @@ -159,21 +157,21 @@ def _has_mpl_attrs_post_init(self): master_key_provider=self.key_provider ) elif self.keyring is not None: - # No CMM, provided MPL keyring => create MPL's DefaultCryptographicMaterialsManager - if not isinstance(self.keyring, IKeyring): - raise ValueError(f"Argument provided to keyring MUST be a {IKeyring}. \ - Found {self.keyring.__class__.__name__}") - - mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( - config=MaterialProvidersConfig() - ) - cmm = mat_prov.create_default_cryptographic_materials_manager( - CreateDefaultCryptographicMaterialsManagerInput( - keyring=self.keyring + try: + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( + config=MaterialProvidersConfig() ) - ) - cmm_handler: CryptoMaterialsManager = CryptoMaterialsManagerFromMPL(cmm) - self.materials_manager = cmm_handler + cmm = mat_prov.create_default_cryptographic_materials_manager( + CreateDefaultCryptographicMaterialsManagerInput( + keyring=self.keyring + ) + ) + cmm_handler: CryptoMaterialsManager = CryptoMaterialsManagerFromMPL(cmm) + self.materials_manager = cmm_handler + except AwsCryptographicMaterialProvidersException as mpl_exception: + # Wrap MPL error into the ESDK error type + # so customers only have to catch ESDK error types. + raise AWSEncryptionSDKClientError(mpl_exception) def _no_mpl_attrs_post_init(self): """If the MPL is NOT present in the runtime, perform post-init logic diff --git a/test/unit/test_crypto_authentication_signer.py b/test/unit/test_crypto_authentication_signer.py index bd7227fd3..c37c97bde 100644 --- a/test/unit/test_crypto_authentication_signer.py +++ b/test/unit/test_crypto_authentication_signer.py @@ -12,7 +12,8 @@ # language governing permissions and limitations under the License. """Unit test suite for ``aws_encryption_sdk.internal.crypto.authentication.Signer``.""" import pytest -from mock import MagicMock, sentinel +from mock import MagicMock, sentinel, patch +import cryptography.hazmat.primitives.serialization from pytest_mock import mocker # noqa pylint: disable=unused-import import aws_encryption_sdk.internal.crypto.authentication @@ -75,28 +76,72 @@ def test_f_signer_from_key_bytes(): def test_f_signer_key_bytes(): test = Signer(algorithm=ALGORITHM, key=VALUES["ecc_private_key_prime"]) assert test.key_bytes() == VALUES["ecc_private_key_prime_private_bytes"] + +def test_GIVEN_no_encoding_WHEN_signer_from_key_bytes_THEN_load_der_private_key( + patch_default_backend, + patch_build_hasher, + patch_ec +): + mock_algorithm_info = MagicMock(return_value=sentinel.algorithm_info, spec=patch_ec.EllipticCurve) + _algorithm = MagicMock(signing_algorithm_info=mock_algorithm_info) -def test_signer_from_key_bytes(patch_default_backend, patch_serialization, patch_build_hasher, patch_ec): + # Make a new patched serialization module for this test. + # The default patch introduces serialization as `serialization.Encoding.DER` + # from within the src, but is `Encoding.DER` in the test. + # This namespace change causes the src's `isinstance` checks to fail. + # Mock the `serialization.Encoding.DER` + with patch.object(cryptography.hazmat.primitives, "serialization"): + # Mock the `serialization.load_der_private_key` + with patch.object(aws_encryption_sdk.internal.crypto.authentication.serialization, "load_der_private_key") as mock_der: + Signer.from_key_bytes( + algorithm=_algorithm, + key_bytes=sentinel.key_bytes, + ) + + mock_der.assert_called_once_with( + data=sentinel.key_bytes, password=None, backend=patch_default_backend.return_value + ) + + +def test_GIVEN_PEM_encoding_WHEN_signer_from_key_bytes_THEN_load_pem_private_key( + patch_default_backend, + patch_serialization, + patch_build_hasher, + patch_ec +): mock_algorithm_info = MagicMock(return_value=sentinel.algorithm_info, spec=patch_ec.EllipticCurve) _algorithm = MagicMock(signing_algorithm_info=mock_algorithm_info) - # Explicitly pass in patched serialization module. - # Patching the module introduces namespace issues - # which causes the method's `isinstance` checks to fail - # by changing the namespace from `serialization.Encoding.DER` to `Encoding.DER`. signer = Signer.from_key_bytes( algorithm=_algorithm, key_bytes=sentinel.key_bytes, - encoding=patch_serialization.Encoding.DER + encoding=patch_serialization.Encoding.PEM ) - patch_serialization.load_der_private_key.assert_called_once_with( + patch_serialization.load_pem_private_key.assert_called_once_with( data=sentinel.key_bytes, password=None, backend=patch_default_backend.return_value ) assert isinstance(signer, Signer) assert signer.algorithm is _algorithm - assert signer.key is patch_serialization.load_der_private_key.return_value + assert signer.key is patch_serialization.load_pem_private_key.return_value + + +def test_GIVEN_unrecognized_encoding_WHEN_signer_from_key_bytes_THEN_raise_ValueError( + patch_default_backend, + patch_serialization, + patch_build_hasher, + patch_ec +): + mock_algorithm_info = MagicMock(return_value=sentinel.algorithm_info, spec=patch_ec.EllipticCurve) + _algorithm = MagicMock(signing_algorithm_info=mock_algorithm_info) + + with pytest.raises(ValueError): + signer = Signer.from_key_bytes( + algorithm=_algorithm, + key_bytes=sentinel.key_bytes, + encoding="not an encoding" + ) def test_signer_key_bytes(patch_default_backend, patch_serialization, patch_build_hasher, patch_ec): diff --git a/test/unit/test_streaming_client_configs.py b/test/unit/test_streaming_client_configs.py index 426f8f85f..c76a64ea7 100644 --- a/test/unit/test_streaming_client_configs.py +++ b/test/unit/test_streaming_client_configs.py @@ -15,6 +15,7 @@ import pytest import six +from mock import patch from aws_encryption_sdk import CommitmentPolicy from aws_encryption_sdk.internal.defaults import ALGORITHM, FRAME_LENGTH, LINE_LENGTH @@ -28,6 +29,22 @@ pytestmark = [pytest.mark.unit, pytest.mark.local] +# Check if MPL is installed, and skip tests based on its installation status +# Ideally, this logic would be based on mocking imports and testing logic, +# but doing that introduces errors that cause other tests to fail. +try: + from aws_cryptographic_materialproviders.mpl.references import ( + IKeyring, + ) + HAS_MPL = True + + from aws_encryption_sdk.materials_managers.mpl.cmm import ( + CryptoMaterialsManagerFromMPL, + ) +except ImportError: + HAS_MPL = False + + class FakeCryptoMaterialsManager(CryptoMaterialsManager): def get_encryption_materials(self, request): return @@ -42,6 +59,14 @@ class FakeMasterKeyProvider(MasterKeyProvider): def _new_master_key(self, key_id): return + +if HAS_MPL: + class FakeKeyring(IKeyring): + def on_encrypt(self, param): + return + + def on_decrypt(self, param): + return BASE_KWARGS = dict( @@ -126,6 +151,18 @@ def test_client_config_defaults(): assert test.max_encrypted_data_keys is None +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_client_config_with_mpl_attr(): + test = _ClientConfig(**BASE_KWARGS) + assert hasattr(test, "keyring") + + +@pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") +def test_client_config_no_mpl(): + test = _ClientConfig(**BASE_KWARGS) + assert not hasattr(test, "keyring") + + def test_encryptor_config_defaults(): test = EncryptorConfig(**BASE_KWARGS) assert test.encryption_context == {} @@ -154,3 +191,62 @@ def test_client_config_converts(kwargs, stream_type): assert isinstance(test.source, stream_type) if test.key_provider is not None: assert isinstance(test.materials_manager, DefaultCryptoMaterialsManager) + + +@pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") +@patch.object(_ClientConfig, "_no_mpl_attrs_post_init") +def test_GIVEN_no_mpl_WHEN_attrs_post_init_THEN_calls_no_mpl_method( + mock_no_mpl_attrs_post_init, +): + _ClientConfig(**BASE_KWARGS) + mock_no_mpl_attrs_post_init.assert_called_once_with() + + +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +@patch.object(_ClientConfig, "_has_mpl_attrs_post_init") +def test_GIVEN_has_mpl_WHEN_attrs_post_init_THEN_calls_no_mpl_method( + _has_mpl_attrs_post_init, +): + _ClientConfig(**BASE_KWARGS) + _has_mpl_attrs_post_init.assert_called_once_with() + + +@pytest.mark.parametrize( + "kwargs, stream_type", + ( + (dict(source=b"", materials_manager=FakeCryptoMaterialsManager()), io.BytesIO), + (dict(source=b"", key_provider=FakeMasterKeyProvider()), io.BytesIO), + (dict(source="", materials_manager=FakeCryptoMaterialsManager()), io.BytesIO), + (dict(source=io.BytesIO(), materials_manager=FakeCryptoMaterialsManager()), io.BytesIO), + (dict(source=six.StringIO(), materials_manager=FakeCryptoMaterialsManager()), six.StringIO), + (dict(source=b"", keyring=FakeKeyring()), io.BytesIO), + ), +) +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_client_configs_with_mpl( + kwargs, + stream_type +): + kwargs["commitment_policy"] = CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT + + test = _ClientConfig(**kwargs) + + # In all cases, config should have a materials manager + assert test.materials_manager is not None + + # If materials manager was provided, it should be directly used + if hasattr(kwargs, "materials_manager"): + assert kwargs["materials_manager"] == test.materials_manager + + # If MPL keyring was provided, it should be wrapped in MPL materials manager + if hasattr(kwargs, "keyring"): + assert test.keyring is not None + assert test.keyring == kwargs["keyring"] + assert isinstance(test.keyring, IKeyring) + assert isinstance(test.materials_manager, CryptoMaterialsManagerFromMPL) + + # If native key_provider was provided, it should be wrapped in native materials manager + if hasattr(kwargs, "key_provider"): + assert test.key_provider is not None + assert test.key_provider == kwargs["key_provider"] + assert isinstance(test.materials_manager, DefaultCryptoMaterialsManager) diff --git a/test/unit/test_streaming_client_stream_decryptor.py b/test/unit/test_streaming_client_stream_decryptor.py index 157755094..c8a17e650 100644 --- a/test/unit/test_streaming_client_stream_decryptor.py +++ b/test/unit/test_streaming_client_stream_decryptor.py @@ -33,14 +33,36 @@ pytestmark = [pytest.mark.unit, pytest.mark.local] +# Check if MPL is installed, and skip tests based on its installation status +# Ideally, this logic would be based on mocking imports and testing logic, +# but doing that introduces errors that cause other tests to fail. +try: + from aws_cryptographic_materialproviders.mpl.references import ( + IKeyring, + ) + HAS_MPL = True + + from aws_encryption_sdk.materials_managers.mpl.cmm import ( + CryptoMaterialsManagerFromMPL, + ) +except ImportError: + HAS_MPL = False + + class TestStreamDecryptor(object): @pytest.fixture(autouse=True) def apply_fixtures(self): self.mock_key_provider = MagicMock(__class__=MasterKeyProvider) self.mock_materials_manager = MagicMock(__class__=CryptoMaterialsManager) - self.mock_materials_manager.decrypt_materials.return_value = MagicMock( + self.mock_decrypt_materials = MagicMock( data_key=VALUES["data_key_obj"], verification_key=sentinel.verification_key ) + self.mock_materials_manager.decrypt_materials.return_value = self.mock_decrypt_materials + + if HAS_MPL: + self.mock_mpl_materials_manager = MagicMock(__class__=CryptoMaterialsManagerFromMPL) + self.mock_mpl_materials_manager.decrypt_materials.return_value = self.mock_decrypt_materials + self.mock_header = MagicMock() self.mock_header.version = SerializationVersion.V1 self.mock_header.algorithm = MagicMock( @@ -213,6 +235,114 @@ def test_read_header(self, mock_derive_datakey, mock_decrypt_materials_request, assert test_header is self.mock_header assert test_header_auth is sentinel.header_auth + @patch("aws_encryption_sdk.streaming_client.DecryptionMaterialsRequest") + @patch("aws_encryption_sdk.streaming_client.derive_data_encryption_key") + @patch("aws_encryption_sdk.streaming_client.Verifier") + @pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") + def test_GIVEN_verification_key_AND_no_mpl_WHEN_read_header_THEN_calls_from_key_bytes( + self, + mock_verifier, + *_, + ): + mock_verifier_instance = MagicMock() + mock_verifier.from_key_bytes.return_value = mock_verifier_instance + ct_stream = io.BytesIO(VALUES["data_128"]) + mock_commitment_policy = MagicMock(__class__=CommitmentPolicy) + test_decryptor = StreamDecryptor( + materials_manager=self.mock_materials_manager, + source=ct_stream, + commitment_policy=mock_commitment_policy, + ) + test_decryptor.source_stream = ct_stream + test_decryptor._stream_length = len(VALUES["data_128"]) + + test_decryptor._read_header() + + mock_verifier.from_key_bytes.assert_called_once_with( + algorithm=self.mock_header.algorithm, key_bytes=sentinel.verification_key + ) + + @patch("aws_encryption_sdk.streaming_client.DecryptionMaterialsRequest") + @patch("aws_encryption_sdk.streaming_client.derive_data_encryption_key") + @patch("aws_encryption_sdk.streaming_client.Verifier") + @pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") + def test_GIVEN_verification_key_AND_has_mpl_AND_not_MPLCMM_WHEN_read_header_THEN_calls_from_key_bytes( + self, + mock_verifier, + *_, + ): + mock_verifier_instance = MagicMock() + mock_verifier.from_key_bytes.return_value = mock_verifier_instance + ct_stream = io.BytesIO(VALUES["data_128"]) + mock_commitment_policy = MagicMock(__class__=CommitmentPolicy) + test_decryptor = StreamDecryptor( + materials_manager=self.mock_materials_manager, + source=ct_stream, + commitment_policy=mock_commitment_policy, + ) + test_decryptor.source_stream = ct_stream + test_decryptor._stream_length = len(VALUES["data_128"]) + + test_decryptor._read_header() + + mock_verifier.from_key_bytes.assert_called_once_with( + algorithm=self.mock_header.algorithm, key_bytes=sentinel.verification_key + ) + + @patch("aws_encryption_sdk.streaming_client.DecryptionMaterialsRequest") + @patch("aws_encryption_sdk.streaming_client.derive_data_encryption_key") + @patch("aws_encryption_sdk.streaming_client.Verifier") + @patch("base64.b64encode") + @pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") + def test_GIVEN_verification_key_AND_has_mpl_AND_has_MPLCMM_WHEN_read_header_THEN_calls_from_encoded_point( + self, + mock_b64encoding, + mock_verifier, + *_, + ): + mock_verifier_instance = MagicMock() + mock_verifier.from_key_bytes.return_value = mock_verifier_instance + ct_stream = io.BytesIO(VALUES["data_128"]) + mock_commitment_policy = MagicMock(__class__=CommitmentPolicy) + test_decryptor = StreamDecryptor( + materials_manager=self.mock_mpl_materials_manager, + source=ct_stream, + commitment_policy=mock_commitment_policy, + ) + test_decryptor.source_stream = ct_stream + test_decryptor._stream_length = len(VALUES["data_128"]) + + test_decryptor._read_header() + + mock_verifier.from_encoded_point.assert_called_once_with( + algorithm=self.mock_header.algorithm, encoded_point=mock_b64encoding() + ) + + # @patch("aws_encryption_sdk.streaming_client.Verifier") + # @pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") + # def test_GIVEN_verification_key_AND_has_mpl_AND_not_MPLCMM_WHEN_read_header_THEN_calls_from_key_bytes( + # self, + # mock_verifier, + # ): + # mock_verifier_instance = MagicMock() + # mock_verifier.from_key_bytes.return_value = mock_verifier_instance + # ct_stream = io.BytesIO(VALUES["data_128"]) + # mock_commitment_policy = MagicMock(__class__=CommitmentPolicy) + # test_decryptor = StreamDecryptor( + # materials_manager=self.mock_materials_manager, + # source=ct_stream, + # commitment_policy=mock_commitment_policy, + # ) + # test_decryptor.source_stream = ct_stream + # test_decryptor._stream_length = len(VALUES["data_128"]) + + # test_decryptor._read_header() + + # mock_verifier.from_key_bytes.assert_called_once_with( + # algorithm=self.mock_header.algorithm, key_bytes=sentinel.verification_key + # ) + + @patch("aws_encryption_sdk.streaming_client.derive_data_encryption_key") def test_read_header_frame_too_large(self, mock_derive_datakey): self.mock_header.content_type = ContentType.FRAMED_DATA diff --git a/test/unit/test_streaming_client_stream_encryptor.py b/test/unit/test_streaming_client_stream_encryptor.py index 5bfd0c903..11664411a 100644 --- a/test/unit/test_streaming_client_stream_encryptor.py +++ b/test/unit/test_streaming_client_stream_encryptor.py @@ -13,6 +13,7 @@ """Unit test suite for aws_encryption_sdk.streaming_client.StreamEncryptor""" import io +from cryptography.hazmat.primitives import serialization import pytest import six from mock import MagicMock, call, patch, sentinel @@ -37,6 +38,22 @@ pytestmark = [pytest.mark.unit, pytest.mark.local] +# Check if MPL is installed, and skip tests based on its installation status +# Ideally, this logic would be based on mocking imports and testing logic, +# but doing that introduces errors that cause other tests to fail. +try: + from aws_cryptographic_materialproviders.mpl.references import ( + IKeyring, + ) + HAS_MPL = True + + from aws_encryption_sdk.materials_managers.mpl.cmm import ( + CryptoMaterialsManagerFromMPL, + ) +except ImportError: + HAS_MPL = False + + class TestStreamEncryptor(object): @pytest.fixture(autouse=True) def apply_fixtures(self): @@ -60,6 +77,10 @@ def apply_fixtures(self): self.mock_master_keys_set, ) + if HAS_MPL: + self.mock_mpl_materials_manager = MagicMock(__class__=CryptoMaterialsManagerFromMPL) + self.mock_mpl_materials_manager.get_encryption_materials.return_value = self.mock_encryption_materials + self.mock_master_key = MagicMock(__class__=MasterKey) self.mock_frame_length = MagicMock(__class__=int) @@ -366,6 +387,64 @@ def test_prep_message_non_framed_message(self, mock_write_header, mock_prep_non_ test_encryptor._prep_message() mock_prep_non_framed.assert_called_once_with() + @pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") + def test_GIVEN_no_mpl_AND_uses_signer_WHEN_prep_message_THEN_signer_uses_default_encoding(self): + self.mock_encryption_materials.algorithm = Algorithm.AES_128_GCM_IV12_TAG16 + test_encryptor = StreamEncryptor( + source=VALUES["data_128"], + materials_manager=self.mock_materials_manager, + frame_length=self.mock_frame_length, + algorithm=Algorithm.AES_128_GCM_IV12_TAG16, + commitment_policy=self.mock_commitment_policy, + signature_policy=self.mock_signature_policy, + ) + test_encryptor.content_type = ContentType.FRAMED_DATA + with patch.object(self.mock_signer, "from_key_bytes"): + test_encryptor._prep_message() + self.mock_signer.from_key_bytes.assert_called_once_with( + algorithm=self.mock_encryption_materials.algorithm, + key_bytes=self.mock_encryption_materials.signing_key + ) + + @pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") + def test_GIVEN_has_mpl_AND_not_MPLCMM_AND_uses_signer_WHEN_prep_message_THEN_signer_uses_default_encoding(self): + self.mock_encryption_materials.algorithm = Algorithm.AES_128_GCM_IV12_TAG16 + test_encryptor = StreamEncryptor( + source=VALUES["data_128"], + materials_manager=self.mock_materials_manager, + frame_length=self.mock_frame_length, + algorithm=Algorithm.AES_128_GCM_IV12_TAG16, + commitment_policy=self.mock_commitment_policy, + signature_policy=self.mock_signature_policy, + ) + test_encryptor.content_type = ContentType.FRAMED_DATA + with patch.object(self.mock_signer, "from_key_bytes"): + test_encryptor._prep_message() + self.mock_signer.from_key_bytes.assert_called_once_with( + algorithm=self.mock_encryption_materials.algorithm, + key_bytes=self.mock_encryption_materials.signing_key + ) + + @pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") + def test_GIVEN_has_mpl_AND_has_MPLCMM_AND_uses_signer_WHEN_prep_message_THEN_signer_uses_default_encoding(self): + self.mock_encryption_materials.algorithm = Algorithm.AES_128_GCM_IV12_TAG16 + test_encryptor = StreamEncryptor( + source=VALUES["data_128"], + materials_manager=self.mock_mpl_materials_manager, + frame_length=self.mock_frame_length, + algorithm=Algorithm.AES_128_GCM_IV12_TAG16, + commitment_policy=self.mock_commitment_policy, + signature_policy=self.mock_signature_policy, + ) + test_encryptor.content_type = ContentType.FRAMED_DATA + with patch.object(self.mock_signer, "from_key_bytes"): + test_encryptor._prep_message() + self.mock_signer.from_key_bytes.assert_called_once_with( + algorithm=self.mock_encryption_materials.algorithm, + key_bytes=self.mock_encryption_materials.signing_key, + encoding=serialization.Encoding.PEM + ) + def test_prep_message_no_signer(self): self.mock_encryption_materials.algorithm = Algorithm.AES_128_GCM_IV12_TAG16 test_encryptor = StreamEncryptor( diff --git a/test/unit/test_utils.py b/test/unit/test_utils.py index c6d565108..d717b51c7 100644 --- a/test/unit/test_utils.py +++ b/test/unit/test_utils.py @@ -265,3 +265,28 @@ def test_source_data_key_length_check_invalid(self): source_data_key=mock_data_key, algorithm=mock_algorithm ) excinfo.match("Invalid Source Data Key length 4 for algorithm required: 5") + + def test_exactly_one_arg_is_not_none(self): + # No args => no args are not None + assert aws_encryption_sdk.internal.utils.exactly_one_arg_is_not_none() is False + assert aws_encryption_sdk.internal.utils.exactly_one_arg_is_not_none( + None + ) is False + assert aws_encryption_sdk.internal.utils.exactly_one_arg_is_not_none( + "not None" + ) is True + assert aws_encryption_sdk.internal.utils.exactly_one_arg_is_not_none( + "not None", "also not None" + ) is False + assert aws_encryption_sdk.internal.utils.exactly_one_arg_is_not_none( + "not None", None + ) is True + assert aws_encryption_sdk.internal.utils.exactly_one_arg_is_not_none( + "not None", "also not None" + ) is False + assert aws_encryption_sdk.internal.utils.exactly_one_arg_is_not_none( + None, "not None" + ) is True + assert aws_encryption_sdk.internal.utils.exactly_one_arg_is_not_none( + None, None + ) is False \ No newline at end of file From 49cb7c8d8b57f125c22fcddd206f18a31347e7fc Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Mon, 26 Feb 2024 12:45:57 -0800 Subject: [PATCH 134/184] more unit tests --- test/unit/test_streaming_client_configs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/test_streaming_client_configs.py b/test/unit/test_streaming_client_configs.py index c76a64ea7..3e49e6747 100644 --- a/test/unit/test_streaming_client_configs.py +++ b/test/unit/test_streaming_client_configs.py @@ -211,6 +211,7 @@ def test_GIVEN_has_mpl_WHEN_attrs_post_init_THEN_calls_no_mpl_method( _has_mpl_attrs_post_init.assert_called_once_with() +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") @pytest.mark.parametrize( "kwargs, stream_type", ( @@ -222,7 +223,6 @@ def test_GIVEN_has_mpl_WHEN_attrs_post_init_THEN_calls_no_mpl_method( (dict(source=b"", keyring=FakeKeyring()), io.BytesIO), ), ) -@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") def test_client_configs_with_mpl( kwargs, stream_type From 705113a3ce2fcb2bda264c3f453125bd20db6a96 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Mon, 26 Feb 2024 12:54:24 -0800 Subject: [PATCH 135/184] more unit tests --- test/unit/test_streaming_client_configs.py | 38 ++++++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/test/unit/test_streaming_client_configs.py b/test/unit/test_streaming_client_configs.py index 3e49e6747..120931cb8 100644 --- a/test/unit/test_streaming_client_configs.py +++ b/test/unit/test_streaming_client_configs.py @@ -215,17 +215,15 @@ def test_GIVEN_has_mpl_WHEN_attrs_post_init_THEN_calls_no_mpl_method( @pytest.mark.parametrize( "kwargs, stream_type", ( - (dict(source=b"", materials_manager=FakeCryptoMaterialsManager()), io.BytesIO), - (dict(source=b"", key_provider=FakeMasterKeyProvider()), io.BytesIO), - (dict(source="", materials_manager=FakeCryptoMaterialsManager()), io.BytesIO), - (dict(source=io.BytesIO(), materials_manager=FakeCryptoMaterialsManager()), io.BytesIO), - (dict(source=six.StringIO(), materials_manager=FakeCryptoMaterialsManager()), six.StringIO), - (dict(source=b"", keyring=FakeKeyring()), io.BytesIO), + (dict(source=b"", materials_manager=FakeCryptoMaterialsManager())), + (dict(source=b"", key_provider=FakeMasterKeyProvider())), + (dict(source="", materials_manager=FakeCryptoMaterialsManager())), + (dict(source=io.BytesIO(), materials_manager=FakeCryptoMaterialsManager())), + (dict(source=six.StringIO(), materials_manager=FakeCryptoMaterialsManager())), ), ) def test_client_configs_with_mpl( kwargs, - stream_type ): kwargs["commitment_policy"] = CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT @@ -250,3 +248,29 @@ def test_client_configs_with_mpl( assert test.key_provider is not None assert test.key_provider == kwargs["key_provider"] assert isinstance(test.materials_manager, DefaultCryptoMaterialsManager) + + +# This needs its own test; pytest parametrize cannot use a conditionally-loaded type +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_keyring_client_config_with_mpl( +): + kwargs = { + "source": b"", + "keyring": FakeKeyring() + } + + test = _ClientConfig(**kwargs) + + # In all cases, config should have a materials manager + assert test.materials_manager is not None + + # If materials manager was provided, it should be directly used + if hasattr(kwargs, "materials_manager"): + assert kwargs["materials_manager"] == test.materials_manager + + # If MPL keyring was provided, it should be wrapped in MPL materials manager + if hasattr(kwargs, "keyring"): + assert test.keyring is not None + assert test.keyring == kwargs["keyring"] + assert isinstance(test.keyring, IKeyring) + assert isinstance(test.materials_manager, CryptoMaterialsManagerFromMPL) From f76d7f9f76c1eeaaf14aafbeade594680066515b Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Mon, 26 Feb 2024 12:56:49 -0800 Subject: [PATCH 136/184] more unit tests --- test/unit/test_streaming_client_configs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/test_streaming_client_configs.py b/test/unit/test_streaming_client_configs.py index 120931cb8..1a3fc89bc 100644 --- a/test/unit/test_streaming_client_configs.py +++ b/test/unit/test_streaming_client_configs.py @@ -213,7 +213,7 @@ def test_GIVEN_has_mpl_WHEN_attrs_post_init_THEN_calls_no_mpl_method( @pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") @pytest.mark.parametrize( - "kwargs, stream_type", + "kwargs", ( (dict(source=b"", materials_manager=FakeCryptoMaterialsManager())), (dict(source=b"", key_provider=FakeMasterKeyProvider())), From 0da2a4f2c7dee0c0bd333bb42872b83f322af6e9 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Mon, 26 Feb 2024 13:10:59 -0800 Subject: [PATCH 137/184] more unit tests --- .../unit/test_crypto_authentication_signer.py | 9 ++++ test/unit/test_streaming_client_configs.py | 10 +++- .../test_streaming_client_stream_decryptor.py | 46 +++++++------------ .../test_streaming_client_stream_encryptor.py | 20 +++++--- 4 files changed, 47 insertions(+), 38 deletions(-) diff --git a/test/unit/test_crypto_authentication_signer.py b/test/unit/test_crypto_authentication_signer.py index c37c97bde..58cad2a7e 100644 --- a/test/unit/test_crypto_authentication_signer.py +++ b/test/unit/test_crypto_authentication_signer.py @@ -94,11 +94,14 @@ def test_GIVEN_no_encoding_WHEN_signer_from_key_bytes_THEN_load_der_private_key( with patch.object(cryptography.hazmat.primitives, "serialization"): # Mock the `serialization.load_der_private_key` with patch.object(aws_encryption_sdk.internal.crypto.authentication.serialization, "load_der_private_key") as mock_der: + # When: from_key_bytes Signer.from_key_bytes( algorithm=_algorithm, key_bytes=sentinel.key_bytes, + # Given: No encoding provided => default arg ) + # Then: calls load_der_private_key mock_der.assert_called_once_with( data=sentinel.key_bytes, password=None, backend=patch_default_backend.return_value ) @@ -113,12 +116,15 @@ def test_GIVEN_PEM_encoding_WHEN_signer_from_key_bytes_THEN_load_pem_private_key mock_algorithm_info = MagicMock(return_value=sentinel.algorithm_info, spec=patch_ec.EllipticCurve) _algorithm = MagicMock(signing_algorithm_info=mock_algorithm_info) + # When: from_key_bytes signer = Signer.from_key_bytes( algorithm=_algorithm, key_bytes=sentinel.key_bytes, + # Given: PEM encoding encoding=patch_serialization.Encoding.PEM ) + # Then: calls load_pem_private_key patch_serialization.load_pem_private_key.assert_called_once_with( data=sentinel.key_bytes, password=None, backend=patch_default_backend.return_value ) @@ -136,10 +142,13 @@ def test_GIVEN_unrecognized_encoding_WHEN_signer_from_key_bytes_THEN_raise_Value mock_algorithm_info = MagicMock(return_value=sentinel.algorithm_info, spec=patch_ec.EllipticCurve) _algorithm = MagicMock(signing_algorithm_info=mock_algorithm_info) + # Then: Raises ValueError with pytest.raises(ValueError): + # When: from_key_bytes signer = Signer.from_key_bytes( algorithm=_algorithm, key_bytes=sentinel.key_bytes, + # Given: Invalid encoding encoding="not an encoding" ) diff --git a/test/unit/test_streaming_client_configs.py b/test/unit/test_streaming_client_configs.py index 1a3fc89bc..38f6de930 100644 --- a/test/unit/test_streaming_client_configs.py +++ b/test/unit/test_streaming_client_configs.py @@ -193,22 +193,28 @@ def test_client_config_converts(kwargs, stream_type): assert isinstance(test.materials_manager, DefaultCryptoMaterialsManager) +# Given: no MPL @pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") @patch.object(_ClientConfig, "_no_mpl_attrs_post_init") def test_GIVEN_no_mpl_WHEN_attrs_post_init_THEN_calls_no_mpl_method( mock_no_mpl_attrs_post_init, ): + # When: attrs_post_init _ClientConfig(**BASE_KWARGS) + # Then: calls _no_mpl_attrs_post_init mock_no_mpl_attrs_post_init.assert_called_once_with() +# Given: has MPL @pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") @patch.object(_ClientConfig, "_has_mpl_attrs_post_init") def test_GIVEN_has_mpl_WHEN_attrs_post_init_THEN_calls_no_mpl_method( - _has_mpl_attrs_post_init, + mock_has_mpl_attrs_post_init, ): + # When: attrs_post_init _ClientConfig(**BASE_KWARGS) - _has_mpl_attrs_post_init.assert_called_once_with() + # Then: calls _has_mpl_attrs_post_init + mock_has_mpl_attrs_post_init.assert_called_once_with() @pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") diff --git a/test/unit/test_streaming_client_stream_decryptor.py b/test/unit/test_streaming_client_stream_decryptor.py index c8a17e650..fc45cc393 100644 --- a/test/unit/test_streaming_client_stream_decryptor.py +++ b/test/unit/test_streaming_client_stream_decryptor.py @@ -37,14 +37,11 @@ # Ideally, this logic would be based on mocking imports and testing logic, # but doing that introduces errors that cause other tests to fail. try: - from aws_cryptographic_materialproviders.mpl.references import ( - IKeyring, - ) - HAS_MPL = True - from aws_encryption_sdk.materials_managers.mpl.cmm import ( CryptoMaterialsManagerFromMPL, ) + HAS_MPL = True + except ImportError: HAS_MPL = False @@ -238,12 +235,14 @@ def test_read_header(self, mock_derive_datakey, mock_decrypt_materials_request, @patch("aws_encryption_sdk.streaming_client.DecryptionMaterialsRequest") @patch("aws_encryption_sdk.streaming_client.derive_data_encryption_key") @patch("aws_encryption_sdk.streaming_client.Verifier") + # Given: no MPL @pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") def test_GIVEN_verification_key_AND_no_mpl_WHEN_read_header_THEN_calls_from_key_bytes( self, mock_verifier, *_, ): + # Given: verification key mock_verifier_instance = MagicMock() mock_verifier.from_key_bytes.return_value = mock_verifier_instance ct_stream = io.BytesIO(VALUES["data_128"]) @@ -256,8 +255,10 @@ def test_GIVEN_verification_key_AND_no_mpl_WHEN_read_header_THEN_calls_from_key_ test_decryptor.source_stream = ct_stream test_decryptor._stream_length = len(VALUES["data_128"]) + # When: read header test_decryptor._read_header() + # Then: calls from_key_bytes mock_verifier.from_key_bytes.assert_called_once_with( algorithm=self.mock_header.algorithm, key_bytes=sentinel.verification_key ) @@ -265,17 +266,20 @@ def test_GIVEN_verification_key_AND_no_mpl_WHEN_read_header_THEN_calls_from_key_ @patch("aws_encryption_sdk.streaming_client.DecryptionMaterialsRequest") @patch("aws_encryption_sdk.streaming_client.derive_data_encryption_key") @patch("aws_encryption_sdk.streaming_client.Verifier") + # Given: has MPL @pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") def test_GIVEN_verification_key_AND_has_mpl_AND_not_MPLCMM_WHEN_read_header_THEN_calls_from_key_bytes( self, mock_verifier, *_, ): + # Given: verification key mock_verifier_instance = MagicMock() mock_verifier.from_key_bytes.return_value = mock_verifier_instance ct_stream = io.BytesIO(VALUES["data_128"]) mock_commitment_policy = MagicMock(__class__=CommitmentPolicy) test_decryptor = StreamDecryptor( + # Given: native CMM materials_manager=self.mock_materials_manager, source=ct_stream, commitment_policy=mock_commitment_policy, @@ -283,8 +287,10 @@ def test_GIVEN_verification_key_AND_has_mpl_AND_not_MPLCMM_WHEN_read_header_THEN test_decryptor.source_stream = ct_stream test_decryptor._stream_length = len(VALUES["data_128"]) + # When: read_header test_decryptor._read_header() + # Then: calls from_key_bytess mock_verifier.from_key_bytes.assert_called_once_with( algorithm=self.mock_header.algorithm, key_bytes=sentinel.verification_key ) @@ -293,6 +299,7 @@ def test_GIVEN_verification_key_AND_has_mpl_AND_not_MPLCMM_WHEN_read_header_THEN @patch("aws_encryption_sdk.streaming_client.derive_data_encryption_key") @patch("aws_encryption_sdk.streaming_client.Verifier") @patch("base64.b64encode") + # Given: has MPL @pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") def test_GIVEN_verification_key_AND_has_mpl_AND_has_MPLCMM_WHEN_read_header_THEN_calls_from_encoded_point( self, @@ -300,11 +307,13 @@ def test_GIVEN_verification_key_AND_has_mpl_AND_has_MPLCMM_WHEN_read_header_THEN mock_verifier, *_, ): + # Given: Verification key mock_verifier_instance = MagicMock() mock_verifier.from_key_bytes.return_value = mock_verifier_instance ct_stream = io.BytesIO(VALUES["data_128"]) mock_commitment_policy = MagicMock(__class__=CommitmentPolicy) test_decryptor = StreamDecryptor( + # Given: MPL CMM materials_manager=self.mock_mpl_materials_manager, source=ct_stream, commitment_policy=mock_commitment_policy, @@ -312,37 +321,14 @@ def test_GIVEN_verification_key_AND_has_mpl_AND_has_MPLCMM_WHEN_read_header_THEN test_decryptor.source_stream = ct_stream test_decryptor._stream_length = len(VALUES["data_128"]) + # When: read header test_decryptor._read_header() + # Then: calls from_encoded_point mock_verifier.from_encoded_point.assert_called_once_with( algorithm=self.mock_header.algorithm, encoded_point=mock_b64encoding() ) - # @patch("aws_encryption_sdk.streaming_client.Verifier") - # @pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") - # def test_GIVEN_verification_key_AND_has_mpl_AND_not_MPLCMM_WHEN_read_header_THEN_calls_from_key_bytes( - # self, - # mock_verifier, - # ): - # mock_verifier_instance = MagicMock() - # mock_verifier.from_key_bytes.return_value = mock_verifier_instance - # ct_stream = io.BytesIO(VALUES["data_128"]) - # mock_commitment_policy = MagicMock(__class__=CommitmentPolicy) - # test_decryptor = StreamDecryptor( - # materials_manager=self.mock_materials_manager, - # source=ct_stream, - # commitment_policy=mock_commitment_policy, - # ) - # test_decryptor.source_stream = ct_stream - # test_decryptor._stream_length = len(VALUES["data_128"]) - - # test_decryptor._read_header() - - # mock_verifier.from_key_bytes.assert_called_once_with( - # algorithm=self.mock_header.algorithm, key_bytes=sentinel.verification_key - # ) - - @patch("aws_encryption_sdk.streaming_client.derive_data_encryption_key") def test_read_header_frame_too_large(self, mock_derive_datakey): self.mock_header.content_type = ContentType.FRAMED_DATA diff --git a/test/unit/test_streaming_client_stream_encryptor.py b/test/unit/test_streaming_client_stream_encryptor.py index 11664411a..bb4ba1c5e 100644 --- a/test/unit/test_streaming_client_stream_encryptor.py +++ b/test/unit/test_streaming_client_stream_encryptor.py @@ -42,14 +42,11 @@ # Ideally, this logic would be based on mocking imports and testing logic, # but doing that introduces errors that cause other tests to fail. try: - from aws_cryptographic_materialproviders.mpl.references import ( - IKeyring, - ) - HAS_MPL = True - from aws_encryption_sdk.materials_managers.mpl.cmm import ( CryptoMaterialsManagerFromMPL, ) + HAS_MPL = True + except ImportError: HAS_MPL = False @@ -387,6 +384,7 @@ def test_prep_message_non_framed_message(self, mock_write_header, mock_prep_non_ test_encryptor._prep_message() mock_prep_non_framed.assert_called_once_with() + # Given: no MPL @pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") def test_GIVEN_no_mpl_AND_uses_signer_WHEN_prep_message_THEN_signer_uses_default_encoding(self): self.mock_encryption_materials.algorithm = Algorithm.AES_128_GCM_IV12_TAG16 @@ -400,17 +398,21 @@ def test_GIVEN_no_mpl_AND_uses_signer_WHEN_prep_message_THEN_signer_uses_default ) test_encryptor.content_type = ContentType.FRAMED_DATA with patch.object(self.mock_signer, "from_key_bytes"): + # When: prep message test_encryptor._prep_message() + # Then: calls from_key_bytes with default encoding self.mock_signer.from_key_bytes.assert_called_once_with( algorithm=self.mock_encryption_materials.algorithm, key_bytes=self.mock_encryption_materials.signing_key ) + # Given: has MPL @pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") def test_GIVEN_has_mpl_AND_not_MPLCMM_AND_uses_signer_WHEN_prep_message_THEN_signer_uses_default_encoding(self): self.mock_encryption_materials.algorithm = Algorithm.AES_128_GCM_IV12_TAG16 test_encryptor = StreamEncryptor( source=VALUES["data_128"], + # Given: native CMM materials_manager=self.mock_materials_manager, frame_length=self.mock_frame_length, algorithm=Algorithm.AES_128_GCM_IV12_TAG16, @@ -419,17 +421,21 @@ def test_GIVEN_has_mpl_AND_not_MPLCMM_AND_uses_signer_WHEN_prep_message_THEN_sig ) test_encryptor.content_type = ContentType.FRAMED_DATA with patch.object(self.mock_signer, "from_key_bytes"): + # When: prep_message test_encryptor._prep_message() + # Then: calls from_key_bytes with default encoding self.mock_signer.from_key_bytes.assert_called_once_with( algorithm=self.mock_encryption_materials.algorithm, key_bytes=self.mock_encryption_materials.signing_key ) + # Given: has MPL @pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") - def test_GIVEN_has_mpl_AND_has_MPLCMM_AND_uses_signer_WHEN_prep_message_THEN_signer_uses_default_encoding(self): + def test_GIVEN_has_mpl_AND_has_MPLCMM_AND_uses_signer_WHEN_prep_message_THEN_signer_uses_PEM_encoding(self): self.mock_encryption_materials.algorithm = Algorithm.AES_128_GCM_IV12_TAG16 test_encryptor = StreamEncryptor( source=VALUES["data_128"], + # Given: MPL CMM materials_manager=self.mock_mpl_materials_manager, frame_length=self.mock_frame_length, algorithm=Algorithm.AES_128_GCM_IV12_TAG16, @@ -438,10 +444,12 @@ def test_GIVEN_has_mpl_AND_has_MPLCMM_AND_uses_signer_WHEN_prep_message_THEN_sig ) test_encryptor.content_type = ContentType.FRAMED_DATA with patch.object(self.mock_signer, "from_key_bytes"): + # When: prep_message test_encryptor._prep_message() self.mock_signer.from_key_bytes.assert_called_once_with( algorithm=self.mock_encryption_materials.algorithm, key_bytes=self.mock_encryption_materials.signing_key, + # Then: calls from_key_bytes with PEM encoding encoding=serialization.Encoding.PEM ) From 0040b2c67af302cf5e624838f2f54222d7aa85f3 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Mon, 26 Feb 2024 13:16:37 -0800 Subject: [PATCH 138/184] cleanup --- src/aws_encryption_sdk/streaming_client.py | 2 +- .../unit/test_crypto_authentication_signer.py | 15 ++++++----- test/unit/test_streaming_client_configs.py | 25 ++++++++----------- .../test_streaming_client_stream_decryptor.py | 8 +++--- .../test_streaming_client_stream_encryptor.py | 6 ++--- test/unit/test_utils.py | 16 ++++++------ 6 files changed, 34 insertions(+), 38 deletions(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 2cfcc9a02..cc9a6bb0f 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -171,7 +171,7 @@ def _has_mpl_attrs_post_init(self): except AwsCryptographicMaterialProvidersException as mpl_exception: # Wrap MPL error into the ESDK error type # so customers only have to catch ESDK error types. - raise AWSEncryptionSDKClientError(mpl_exception) + raise AWSEncryptionSDKClientError(mpl_exception) def _no_mpl_attrs_post_init(self): """If the MPL is NOT present in the runtime, perform post-init logic diff --git a/test/unit/test_crypto_authentication_signer.py b/test/unit/test_crypto_authentication_signer.py index 58cad2a7e..425f672ed 100644 --- a/test/unit/test_crypto_authentication_signer.py +++ b/test/unit/test_crypto_authentication_signer.py @@ -11,9 +11,9 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Unit test suite for ``aws_encryption_sdk.internal.crypto.authentication.Signer``.""" -import pytest -from mock import MagicMock, sentinel, patch import cryptography.hazmat.primitives.serialization +import pytest +from mock import MagicMock, patch, sentinel from pytest_mock import mocker # noqa pylint: disable=unused-import import aws_encryption_sdk.internal.crypto.authentication @@ -76,7 +76,7 @@ def test_f_signer_from_key_bytes(): def test_f_signer_key_bytes(): test = Signer(algorithm=ALGORITHM, key=VALUES["ecc_private_key_prime"]) assert test.key_bytes() == VALUES["ecc_private_key_prime_private_bytes"] - + def test_GIVEN_no_encoding_WHEN_signer_from_key_bytes_THEN_load_der_private_key( patch_default_backend, @@ -93,7 +93,10 @@ def test_GIVEN_no_encoding_WHEN_signer_from_key_bytes_THEN_load_der_private_key( # Mock the `serialization.Encoding.DER` with patch.object(cryptography.hazmat.primitives, "serialization"): # Mock the `serialization.load_der_private_key` - with patch.object(aws_encryption_sdk.internal.crypto.authentication.serialization, "load_der_private_key") as mock_der: + with patch.object( + aws_encryption_sdk.internal.crypto.authentication.serialization, + "load_der_private_key" + ) as mock_der: # When: from_key_bytes Signer.from_key_bytes( algorithm=_algorithm, @@ -106,7 +109,7 @@ def test_GIVEN_no_encoding_WHEN_signer_from_key_bytes_THEN_load_der_private_key( data=sentinel.key_bytes, password=None, backend=patch_default_backend.return_value ) - + def test_GIVEN_PEM_encoding_WHEN_signer_from_key_bytes_THEN_load_pem_private_key( patch_default_backend, patch_serialization, @@ -145,7 +148,7 @@ def test_GIVEN_unrecognized_encoding_WHEN_signer_from_key_bytes_THEN_raise_Value # Then: Raises ValueError with pytest.raises(ValueError): # When: from_key_bytes - signer = Signer.from_key_bytes( + Signer.from_key_bytes( algorithm=_algorithm, key_bytes=sentinel.key_bytes, # Given: Invalid encoding diff --git a/test/unit/test_streaming_client_configs.py b/test/unit/test_streaming_client_configs.py index 38f6de930..26ef86be8 100644 --- a/test/unit/test_streaming_client_configs.py +++ b/test/unit/test_streaming_client_configs.py @@ -33,14 +33,10 @@ # Ideally, this logic would be based on mocking imports and testing logic, # but doing that introduces errors that cause other tests to fail. try: - from aws_cryptographic_materialproviders.mpl.references import ( - IKeyring, - ) + from aws_cryptographic_materialproviders.mpl.references import IKeyring HAS_MPL = True - from aws_encryption_sdk.materials_managers.mpl.cmm import ( - CryptoMaterialsManagerFromMPL, - ) + from aws_encryption_sdk.materials_managers.mpl.cmm import CryptoMaterialsManagerFromMPL except ImportError: HAS_MPL = False @@ -59,14 +55,15 @@ class FakeMasterKeyProvider(MasterKeyProvider): def _new_master_key(self, key_id): return - + + if HAS_MPL: class FakeKeyring(IKeyring): def on_encrypt(self, param): - return - + return + def on_decrypt(self, param): - return + return BASE_KWARGS = dict( @@ -234,10 +231,10 @@ def test_client_configs_with_mpl( kwargs["commitment_policy"] = CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT test = _ClientConfig(**kwargs) - + # In all cases, config should have a materials manager assert test.materials_manager is not None - + # If materials manager was provided, it should be directly used if hasattr(kwargs, "materials_manager"): assert kwargs["materials_manager"] == test.materials_manager @@ -266,10 +263,10 @@ def test_keyring_client_config_with_mpl( } test = _ClientConfig(**kwargs) - + # In all cases, config should have a materials manager assert test.materials_manager is not None - + # If materials manager was provided, it should be directly used if hasattr(kwargs, "materials_manager"): assert kwargs["materials_manager"] == test.materials_manager diff --git a/test/unit/test_streaming_client_stream_decryptor.py b/test/unit/test_streaming_client_stream_decryptor.py index fc45cc393..e06cad308 100644 --- a/test/unit/test_streaming_client_stream_decryptor.py +++ b/test/unit/test_streaming_client_stream_decryptor.py @@ -37,9 +37,7 @@ # Ideally, this logic would be based on mocking imports and testing logic, # but doing that introduces errors that cause other tests to fail. try: - from aws_encryption_sdk.materials_managers.mpl.cmm import ( - CryptoMaterialsManagerFromMPL, - ) + from aws_encryption_sdk.materials_managers.mpl.cmm import CryptoMaterialsManagerFromMPL HAS_MPL = True except ImportError: @@ -55,7 +53,7 @@ def apply_fixtures(self): data_key=VALUES["data_key_obj"], verification_key=sentinel.verification_key ) self.mock_materials_manager.decrypt_materials.return_value = self.mock_decrypt_materials - + if HAS_MPL: self.mock_mpl_materials_manager = MagicMock(__class__=CryptoMaterialsManagerFromMPL) self.mock_mpl_materials_manager.decrypt_materials.return_value = self.mock_decrypt_materials @@ -258,7 +256,7 @@ def test_GIVEN_verification_key_AND_no_mpl_WHEN_read_header_THEN_calls_from_key_ # When: read header test_decryptor._read_header() - # Then: calls from_key_bytes + # Then: calls from_key_bytes mock_verifier.from_key_bytes.assert_called_once_with( algorithm=self.mock_header.algorithm, key_bytes=sentinel.verification_key ) diff --git a/test/unit/test_streaming_client_stream_encryptor.py b/test/unit/test_streaming_client_stream_encryptor.py index bb4ba1c5e..e43752689 100644 --- a/test/unit/test_streaming_client_stream_encryptor.py +++ b/test/unit/test_streaming_client_stream_encryptor.py @@ -13,9 +13,9 @@ """Unit test suite for aws_encryption_sdk.streaming_client.StreamEncryptor""" import io -from cryptography.hazmat.primitives import serialization import pytest import six +from cryptography.hazmat.primitives import serialization from mock import MagicMock, call, patch, sentinel import aws_encryption_sdk.internal.defaults @@ -42,9 +42,7 @@ # Ideally, this logic would be based on mocking imports and testing logic, # but doing that introduces errors that cause other tests to fail. try: - from aws_encryption_sdk.materials_managers.mpl.cmm import ( - CryptoMaterialsManagerFromMPL, - ) + from aws_encryption_sdk.materials_managers.mpl.cmm import CryptoMaterialsManagerFromMPL HAS_MPL = True except ImportError: diff --git a/test/unit/test_utils.py b/test/unit/test_utils.py index d717b51c7..69f9f060d 100644 --- a/test/unit/test_utils.py +++ b/test/unit/test_utils.py @@ -268,25 +268,25 @@ def test_source_data_key_length_check_invalid(self): def test_exactly_one_arg_is_not_none(self): # No args => no args are not None - assert aws_encryption_sdk.internal.utils.exactly_one_arg_is_not_none() is False + assert aws_encryption_sdk.internal.utils.exactly_one_arg_is_not_none() is False assert aws_encryption_sdk.internal.utils.exactly_one_arg_is_not_none( None - ) is False + ) is False assert aws_encryption_sdk.internal.utils.exactly_one_arg_is_not_none( "not None" - ) is True + ) is True assert aws_encryption_sdk.internal.utils.exactly_one_arg_is_not_none( "not None", "also not None" - ) is False + ) is False assert aws_encryption_sdk.internal.utils.exactly_one_arg_is_not_none( "not None", None - ) is True + ) is True assert aws_encryption_sdk.internal.utils.exactly_one_arg_is_not_none( "not None", "also not None" - ) is False + ) is False assert aws_encryption_sdk.internal.utils.exactly_one_arg_is_not_none( None, "not None" - ) is True + ) is True assert aws_encryption_sdk.internal.utils.exactly_one_arg_is_not_none( None, None - ) is False \ No newline at end of file + ) is False From 9131433f84f2f796a7e29f221e774020008aeeca Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Mon, 26 Feb 2024 13:20:17 -0800 Subject: [PATCH 139/184] cleanup --- src/aws_encryption_sdk/streaming_client.py | 2 +- test/unit/test_streaming_client_configs.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index cc9a6bb0f..5bf953244 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -171,7 +171,7 @@ def _has_mpl_attrs_post_init(self): except AwsCryptographicMaterialProvidersException as mpl_exception: # Wrap MPL error into the ESDK error type # so customers only have to catch ESDK error types. - raise AWSEncryptionSDKClientError(mpl_exception) + raise AWSEncryptionSDKClientError(mpl_exception) def _no_mpl_attrs_post_init(self): """If the MPL is NOT present in the runtime, perform post-init logic diff --git a/test/unit/test_streaming_client_configs.py b/test/unit/test_streaming_client_configs.py index 26ef86be8..18886f65b 100644 --- a/test/unit/test_streaming_client_configs.py +++ b/test/unit/test_streaming_client_configs.py @@ -259,7 +259,8 @@ def test_keyring_client_config_with_mpl( ): kwargs = { "source": b"", - "keyring": FakeKeyring() + "keyring": FakeKeyring(), + "commitment_policy": CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT } test = _ClientConfig(**kwargs) From e6826eb3fdc5773dc00f5bfb1113a7d8f0c67fd3 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 28 Feb 2024 12:20:56 -0800 Subject: [PATCH 140/184] poc impl --- src/aws_encryption_sdk/__init__.py | 1 + .../internal/formatting/serialize.py | 35 +++++-- .../materials_managers/mpl/cmm.py | 1 + .../materials_managers/mpl/materials.py | 11 +++ src/aws_encryption_sdk/streaming_client.py | 93 +++++++++++++++---- 5 files changed, 115 insertions(+), 26 deletions(-) diff --git a/src/aws_encryption_sdk/__init__.py b/src/aws_encryption_sdk/__init__.py index 661d41ee6..4b35e6744 100644 --- a/src/aws_encryption_sdk/__init__.py +++ b/src/aws_encryption_sdk/__init__.py @@ -185,6 +185,7 @@ def decrypt(self, **kwargs): If source_length is not provided and read() is called, will attempt to seek() to the end of the stream and tell() to find the length of source data. + :param dict encryption_context: Dictionary defining encryption context :param int max_body_length: Maximum frame size (or content length for non-framed messages) in bytes to read from ciphertext message. :returns: Tuple containing the decrypted plaintext and the message header object diff --git a/src/aws_encryption_sdk/internal/formatting/serialize.py b/src/aws_encryption_sdk/internal/formatting/serialize.py index b4d866099..718d4ad7d 100644 --- a/src/aws_encryption_sdk/internal/formatting/serialize.py +++ b/src/aws_encryption_sdk/internal/formatting/serialize.py @@ -218,7 +218,13 @@ def _serialize_header_auth_v1(algorithm, header, data_encryption_key, signer=Non return output -def _serialize_header_auth_v2(algorithm, header, data_encryption_key, signer=None): +def _serialize_header_auth_v2( + algorithm, + header, + data_encryption_key, + signer=None, + required_encryption_context_bytes=None + ): """Creates serialized header authentication data for messages in serialization version V2. :param algorithm: Algorithm to use for encryption @@ -230,13 +236,22 @@ def _serialize_header_auth_v2(algorithm, header, data_encryption_key, signer=Non :returns: Serialized header authentication data :rtype: bytes """ - header_auth = encrypt( - algorithm=algorithm, - key=data_encryption_key, - plaintext=b"", - associated_data=header, - iv=header_auth_iv(algorithm), - ) + if required_encryption_context_bytes is None: + header_auth = encrypt( + algorithm=algorithm, + key=data_encryption_key, + plaintext=b"", + associated_data=header, + iv=header_auth_iv(algorithm), + ) + else: + header_auth = encrypt( + algorithm=algorithm, + key=data_encryption_key, + plaintext=b"", + associated_data=header + required_encryption_context_bytes, + iv=header_auth_iv(algorithm), + ) output = struct.pack( ">{tag_len}s".format(tag_len=algorithm.tag_len), header_auth.tag, @@ -246,7 +261,7 @@ def _serialize_header_auth_v2(algorithm, header, data_encryption_key, signer=Non return output -def serialize_header_auth(version, algorithm, header, data_encryption_key, signer=None): +def serialize_header_auth(version, algorithm, header, data_encryption_key, signer=None, required_encryption_context_bytes=None): """Creates serialized header authentication data. :param version: The serialization version of the message @@ -263,7 +278,7 @@ def serialize_header_auth(version, algorithm, header, data_encryption_key, signe if version == SerializationVersion.V1: return _serialize_header_auth_v1(algorithm, header, data_encryption_key, signer) elif version == SerializationVersion.V2: - return _serialize_header_auth_v2(algorithm, header, data_encryption_key, signer) + return _serialize_header_auth_v2(algorithm, header, data_encryption_key, signer, required_encryption_context_bytes) else: raise SerializationError("Unrecognized message format version: {}".format(version)) diff --git a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py index 53a4b3505..8df42bf48 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py @@ -143,5 +143,6 @@ def _create_mpl_decrypt_materials_input_from_request( ), encrypted_data_keys=list_edks, encryption_context=request.encryption_context, + reproduced_encryption_context=request.reproduced_encryption_context, ) return output diff --git a/src/aws_encryption_sdk/materials_managers/mpl/materials.py b/src/aws_encryption_sdk/materials_managers/mpl/materials.py index dfd1bd6fc..d2abf182c 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/materials.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/materials.py @@ -95,6 +95,12 @@ def data_encryption_key(self) -> DataKey: def signing_key(self) -> bytes: """Materials' signing key.""" return self.mpl_materials.signing_key + + + @property + def required_encryption_context_keys(self) -> bytes: + """Materials' required encryption context keys.""" + return self.mpl_materials.required_encryption_context_keys class DecryptionMaterialsFromMPL(Native_DecryptionMaterials): @@ -136,3 +142,8 @@ def data_key(self) -> DataKey: def verification_key(self) -> bytes: """Materials' verification key.""" return self.mpl_materials.verification_key + + @property + def required_encryption_context_keys(self) -> bytes: + """Materials' required encryption context keys.""" + return self.mpl_materials.required_encryption_context_keys diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 5bf953244..6d779c79e 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -593,11 +593,23 @@ def generate_header(self, message_id): if self._encryption_materials.algorithm.message_format_version == 0x02: version = SerializationVersion.V2 + if hasattr(self._encryption_materials, "required_encryption_context_keys"): + self._required_encryption_context = {} + self._stored_encryption_context = {} + for (k, v) in self._encryption_materials.encryption_context: + if k in self._encryption_materials.required_encryption_context_keys: + self._required_encryption_context[k] = v + else: + self._stored_encryption_context[k] = v + else: + self._stored_encryption_context = self._encryption_materials.encryption_context, + self._required_encryption_context = None + kwargs = dict( version=version, algorithm=self._encryption_materials.algorithm, message_id=message_id, - encryption_context=self._encryption_materials.encryption_context, + encryption_context=self._stored_encryption_context, encrypted_data_keys=self._encryption_materials.encrypted_data_keys, content_type=self.content_type, frame_length=self.config.frame_length, @@ -621,13 +633,27 @@ def generate_header(self, message_id): def _write_header(self): """Builds the message header and writes it to the output stream.""" self.output_buffer += serialize_header(header=self._header, signer=self.signer) - self.output_buffer += serialize_header_auth( - version=self._header.version, - algorithm=self._encryption_materials.algorithm, - header=self.output_buffer, - data_encryption_key=self._derived_data_key, - signer=self.signer, - ) + + if self._required_encryption_context is not None: + required_ec_serialized = aws_encryption_sdk.internal.formatting.encryption_context.serialize_encryption_context( + self._required_encryption_context + ) + self.output_buffer += serialize_header_auth( + version=self._header.version, + algorithm=self._encryption_materials.algorithm, + header=self.output_buffer, + data_encryption_key=self._derived_data_key, + signer=self.signer, + required_encryption_context_bytes=required_ec_serialized, + ) + else: + self.output_buffer += serialize_header_auth( + version=self._header.version, + algorithm=self._encryption_materials.algorithm, + header=self.output_buffer, + data_encryption_key=self._derived_data_key, + signer=self.signer, + ) def _prep_non_framed(self): """Prepare the opening data for a non-framed message.""" @@ -907,14 +933,32 @@ def _read_header(self): found=header.frame_length, custom=self.config.max_body_length ) ) - - decrypt_materials_request = DecryptionMaterialsRequest( - encrypted_data_keys=header.encrypted_data_keys, - algorithm=header.algorithm, - encryption_context=header.encryption_context, - commitment_policy=self.config.commitment_policy, - ) + + if hasattr(self, "encryption_context"): + decrypt_materials_request = DecryptionMaterialsRequest( + encrypted_data_keys=header.encrypted_data_keys, + algorithm=header.algorithm, + encryption_context=header.encryption_context, + commitment_policy=self.config.commitment_policy, + reproduced_encryption_context=self.encryption_context + ) + else: + decrypt_materials_request = DecryptionMaterialsRequest( + encrypted_data_keys=header.encrypted_data_keys, + algorithm=header.algorithm, + encryption_context=header.encryption_context, + commitment_policy=self.config.commitment_policy, + ) decryption_materials = self.config.materials_manager.decrypt_materials(request=decrypt_materials_request) + + if hasattr(decryption_materials, "required_encryption_context_keys"): + self._required_encryption_context = {} + for (k, v) in self._encryption_materials.encryption_context: + if k in self._encryption_materials.required_encryption_context_keys: + self._required_encryption_context[k] = v + else: + self._required_encryption_context = None + if decryption_materials.verification_key is None: self.verifier = None else: @@ -953,7 +997,24 @@ def _read_header(self): "message. Halting processing of this message." ) - validate_header(header=header, header_auth=header_auth, raw_header=raw_header, data_key=self._derived_data_key) + if required_ec_serialized is not None: + required_ec_serialized = aws_encryption_sdk.internal.formatting.encryption_context.serialize_encryption_context( + self._required_encryption_context + ) + + validate_header( + header=header, + header_auth=header_auth, + raw_header=raw_header + required_ec_serialized, + data_key=self._derived_data_key + ) + else: + validate_header( + header=header, + header_auth=header_auth, + raw_header=raw_header, + data_key=self._derived_data_key + ) return header, header_auth From a9fa1a5579dde63c6b5556991452075738329608 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 28 Feb 2024 13:34:13 -0800 Subject: [PATCH 141/184] passing --- .../materials_managers/__init__.py | 5 ++ .../materials_managers/mpl/materials.py | 5 ++ src/aws_encryption_sdk/streaming_client.py | 63 +++++++++++++++---- 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/src/aws_encryption_sdk/materials_managers/__init__.py b/src/aws_encryption_sdk/materials_managers/__init__.py index 9db1dafae..f1eb30023 100644 --- a/src/aws_encryption_sdk/materials_managers/__init__.py +++ b/src/aws_encryption_sdk/materials_managers/__init__.py @@ -89,11 +89,16 @@ class DecryptionMaterialsRequest(object): :param encrypted_data_keys: Set of encrypted data keys :type encrypted_data_keys: set of `aws_encryption_sdk.structures.EncryptedDataKey` :param dict encryption_context: Encryption context to provide to master keys for underlying decrypt requests + :param dict reproduced_encryption_context: TODO """ algorithm = attr.ib(validator=attr.validators.instance_of(Algorithm)) encrypted_data_keys = attr.ib(validator=attr.validators.instance_of(set)) encryption_context = attr.ib(validator=attr.validators.instance_of(dict)) + reproduced_encryption_context = attr.ib( + default=None, + validator=attr.validators.optional(attr.validators.instance_of(dict)) + ) commitment_policy = attr.ib( default=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, validator=attr.validators.optional(attr.validators.instance_of(CommitmentPolicy)), diff --git a/src/aws_encryption_sdk/materials_managers/mpl/materials.py b/src/aws_encryption_sdk/materials_managers/mpl/materials.py index d2abf182c..5b066c7c7 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/materials.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/materials.py @@ -143,6 +143,11 @@ def verification_key(self) -> bytes: """Materials' verification key.""" return self.mpl_materials.verification_key + @property + def encryption_context(self) -> Dict[str, str]: + """Materials' encryption context.""" + return self.mpl_materials.encryption_context + @property def required_encryption_context_keys(self) -> bytes: """Materials' required encryption context keys.""" diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 6d779c79e..f678c4b77 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -76,8 +76,13 @@ from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException - from aws_cryptographic_materialproviders.mpl.models import CreateDefaultCryptographicMaterialsManagerInput - from aws_cryptographic_materialproviders.mpl.references import IKeyring + from aws_cryptographic_materialproviders.mpl.models import ( + CreateDefaultCryptographicMaterialsManagerInput, + ) + from aws_cryptographic_materialproviders.mpl.references import ( + ICryptographicMaterialsManager, + IKeyring, + ) _HAS_MPL = True # Import internal ESDK modules that depend on the MPL @@ -126,9 +131,30 @@ class _ClientConfig(object): # pylint: disable=too-many-instance-attributes max_encrypted_data_keys = attr.ib( hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(int)) ) - materials_manager = attr.ib( - hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(CryptoMaterialsManager)) - ) + if _HAS_MPL: + # With the MPL, the provided materials_manager can be an instance of + # either the native interface or an MPL interface. + # If it implements the MPL interface, this constructor will + # internally wrap it in a native interface. + materials_manager = attr.ib( + hash=True, + default=None, + validator=attr.validators.optional( + attr.validators.instance_of( + (CryptoMaterialsManager, ICryptographicMaterialsManager) + ) + ) + ) + else: + materials_manager = attr.ib( + hash=True, + default=None, + validator=attr.validators.optional( + attr.validators.instance_of( + CryptoMaterialsManager + ) + ) + ) key_provider = attr.ib( hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(MasterKeyProvider)) ) @@ -172,6 +198,12 @@ def _has_mpl_attrs_post_init(self): # Wrap MPL error into the ESDK error type # so customers only have to catch ESDK error types. raise AWSEncryptionSDKClientError(mpl_exception) + # TODO-MPL: MUST wrap MPL with native + elif (self.materials_manager is not None + and isinstance(self.materials_manager, ICryptographicMaterialsManager)): + # If the provided materials manager implements an MPL interface, + # wrap it in a native interface. + self.materials_manager = CryptoMaterialsManagerFromMPL(self.materials_manager) def _no_mpl_attrs_post_init(self): """If the MPL is NOT present in the runtime, perform post-init logic @@ -596,7 +628,8 @@ def generate_header(self, message_id): if hasattr(self._encryption_materials, "required_encryption_context_keys"): self._required_encryption_context = {} self._stored_encryption_context = {} - for (k, v) in self._encryption_materials.encryption_context: + print(f"{self._encryption_materials.encryption_context=}") + for (k, v) in self._encryption_materials.encryption_context.items(): if k in self._encryption_materials.required_encryption_context_keys: self._required_encryption_context[k] = v else: @@ -856,6 +889,11 @@ class DecryptorConfig(_ClientConfig): max_body_length = attr.ib( hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(six.integer_types)) ) + encryption_context = attr.ib( + hash=False, # dictionaries are not hashable + default=attr.Factory(dict), + validator=attr.validators.instance_of(dict), + ) class StreamDecryptor(_EncryptionStream): # pylint: disable=too-many-instance-attributes @@ -934,13 +972,15 @@ def _read_header(self): ) ) - if hasattr(self, "encryption_context"): + print(f"{self.config.encryption_context=}") + + if hasattr(self.config, "encryption_context"): decrypt_materials_request = DecryptionMaterialsRequest( encrypted_data_keys=header.encrypted_data_keys, algorithm=header.algorithm, encryption_context=header.encryption_context, commitment_policy=self.config.commitment_policy, - reproduced_encryption_context=self.encryption_context + reproduced_encryption_context=self.config.encryption_context ) else: decrypt_materials_request = DecryptionMaterialsRequest( @@ -949,12 +989,13 @@ def _read_header(self): encryption_context=header.encryption_context, commitment_policy=self.config.commitment_policy, ) + print(f"{decrypt_materials_request=}") decryption_materials = self.config.materials_manager.decrypt_materials(request=decrypt_materials_request) if hasattr(decryption_materials, "required_encryption_context_keys"): self._required_encryption_context = {} - for (k, v) in self._encryption_materials.encryption_context: - if k in self._encryption_materials.required_encryption_context_keys: + for (k, v) in decryption_materials.encryption_context.items(): + if k in decryption_materials.required_encryption_context_keys: self._required_encryption_context[k] = v else: self._required_encryption_context = None @@ -997,7 +1038,7 @@ def _read_header(self): "message. Halting processing of this message." ) - if required_ec_serialized is not None: + if self._required_encryption_context is not None: required_ec_serialized = aws_encryption_sdk.internal.formatting.encryption_context.serialize_encryption_context( self._required_encryption_context ) From 4eeb85889b498004eb865762e6503a6991bbee84 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 28 Feb 2024 14:03:10 -0800 Subject: [PATCH 142/184] cleanup --- src/aws_encryption_sdk/__init__.py | 4 +- .../internal/formatting/serialize.py | 37 +++++++++++++++---- .../materials_managers/__init__.py | 4 +- src/aws_encryption_sdk/streaming_client.py | 36 ++++++++++++++---- 4 files changed, 64 insertions(+), 17 deletions(-) diff --git a/src/aws_encryption_sdk/__init__.py b/src/aws_encryption_sdk/__init__.py index 4b35e6744..96898d446 100644 --- a/src/aws_encryption_sdk/__init__.py +++ b/src/aws_encryption_sdk/__init__.py @@ -185,7 +185,9 @@ def decrypt(self, **kwargs): If source_length is not provided and read() is called, will attempt to seek() to the end of the stream and tell() to find the length of source data. - :param dict encryption_context: Dictionary defining encryption context + :param dict encryption_context: Dictionary defining encryption context to validate + on decrypt. This is ONLY validated on decrypt if using the required encryption + context CMM from the aws-cryptographic-materialproviders library. :param int max_body_length: Maximum frame size (or content length for non-framed messages) in bytes to read from ciphertext message. :returns: Tuple containing the decrypted plaintext and the message header object diff --git a/src/aws_encryption_sdk/internal/formatting/serialize.py b/src/aws_encryption_sdk/internal/formatting/serialize.py index 718d4ad7d..344c94703 100644 --- a/src/aws_encryption_sdk/internal/formatting/serialize.py +++ b/src/aws_encryption_sdk/internal/formatting/serialize.py @@ -219,12 +219,12 @@ def _serialize_header_auth_v1(algorithm, header, data_encryption_key, signer=Non def _serialize_header_auth_v2( - algorithm, - header, - data_encryption_key, - signer=None, - required_encryption_context_bytes=None - ): + algorithm, + header, + data_encryption_key, + signer=None, + required_encryption_context_bytes=None +): """Creates serialized header authentication data for messages in serialization version V2. :param algorithm: Algorithm to use for encryption @@ -233,6 +233,11 @@ def _serialize_header_auth_v2( :param bytes data_encryption_key: Data key with which to encrypt message :param signer: Cryptographic signer object (optional) :type signer: aws_encryption_sdk.Signer + :param required_encryption_context_bytes: Serialized encryption context items + for all items whose keys are in the required_encryption_context list. + This is ONLY processed if using the aws-cryptographic-materialproviders library + AND its required encryption context CMM. (optional) + :type required_encryption_context_bytes: bytes :returns: Serialized header authentication data :rtype: bytes """ @@ -249,6 +254,11 @@ def _serialize_header_auth_v2( algorithm=algorithm, key=data_encryption_key, plaintext=b"", + # The AAD MUST be the concatenation of the serialized message header body and the serialization + # of encryption context to only authenticate. The encryption context to only authenticate MUST + # be the encryption context in the encryption materials filtered to only contain key value + # pairs listed in the encryption material's required encryption context keys serialized + # according to the encryption context serialization specification. associated_data=header + required_encryption_context_bytes, iv=header_auth_iv(algorithm), ) @@ -261,7 +271,14 @@ def _serialize_header_auth_v2( return output -def serialize_header_auth(version, algorithm, header, data_encryption_key, signer=None, required_encryption_context_bytes=None): +def serialize_header_auth( + version, + algorithm, + header, + data_encryption_key, + signer=None, + required_encryption_context_bytes=None +): """Creates serialized header authentication data. :param version: The serialization version of the message @@ -272,6 +289,12 @@ def serialize_header_auth(version, algorithm, header, data_encryption_key, signe :param bytes data_encryption_key: Data key with which to encrypt message :param signer: Cryptographic signer object (optional) :type signer: aws_encryption_sdk.Signer + :param required_encryption_context_bytes: Serialized encryption context items + for all items whose keys are in the required_encryption_context list. + This is ONLY processed if using the aws-cryptographic-materialproviders library + AND its required encryption context CMM + AND if using the v2 message format. (optional) + :type required_encryption_context_bytes: bytes :returns: Serialized header authentication data :rtype: bytes """ diff --git a/src/aws_encryption_sdk/materials_managers/__init__.py b/src/aws_encryption_sdk/materials_managers/__init__.py index f1eb30023..cc8cdcf6f 100644 --- a/src/aws_encryption_sdk/materials_managers/__init__.py +++ b/src/aws_encryption_sdk/materials_managers/__init__.py @@ -89,7 +89,9 @@ class DecryptionMaterialsRequest(object): :param encrypted_data_keys: Set of encrypted data keys :type encrypted_data_keys: set of `aws_encryption_sdk.structures.EncryptedDataKey` :param dict encryption_context: Encryption context to provide to master keys for underlying decrypt requests - :param dict reproduced_encryption_context: TODO + :param dict reproduced_encryption_context: Encryption context to provide on decrypt. + This is ONLY processed if using the required encryption context CMM from the + aws-cryptographic-materialproviders library. """ algorithm = attr.ib(validator=attr.validators.instance_of(Algorithm)) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index f678c4b77..568543863 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -198,11 +198,11 @@ def _has_mpl_attrs_post_init(self): # Wrap MPL error into the ESDK error type # so customers only have to catch ESDK error types. raise AWSEncryptionSDKClientError(mpl_exception) - # TODO-MPL: MUST wrap MPL with native + + # If the provided materials_manager is directly from the MPL, wrap it in a native interface + # for internal use. elif (self.materials_manager is not None and isinstance(self.materials_manager, ICryptographicMaterialsManager)): - # If the provided materials manager implements an MPL interface, - # wrap it in a native interface. self.materials_manager = CryptoMaterialsManagerFromMPL(self.materials_manager) def _no_mpl_attrs_post_init(self): @@ -625,15 +625,18 @@ def generate_header(self, message_id): if self._encryption_materials.algorithm.message_format_version == 0x02: version = SerializationVersion.V2 + # If the underlying materials_provider provided required_encryption_context_keys + # (ex. if the materials_provider is a required encryption context CMM), + # then partition the encryption context based on those keys. if hasattr(self._encryption_materials, "required_encryption_context_keys"): self._required_encryption_context = {} self._stored_encryption_context = {} - print(f"{self._encryption_materials.encryption_context=}") for (k, v) in self._encryption_materials.encryption_context.items(): if k in self._encryption_materials.required_encryption_context_keys: self._required_encryption_context[k] = v else: self._stored_encryption_context[k] = v + # Otherwise, store all encryption context with the message. else: self._stored_encryption_context = self._encryption_materials.encryption_context, self._required_encryption_context = None @@ -667,6 +670,8 @@ def _write_header(self): """Builds the message header and writes it to the output stream.""" self.output_buffer += serialize_header(header=self._header, signer=self.signer) + # If there is _required_encryption_context, + # serialize it, then authenticate it if self._required_encryption_context is not None: required_ec_serialized = aws_encryption_sdk.internal.formatting.encryption_context.serialize_encryption_context( self._required_encryption_context @@ -679,6 +684,7 @@ def _write_header(self): signer=self.signer, required_encryption_context_bytes=required_ec_serialized, ) + # Otherwise, do not pass in any required encryption context else: self.output_buffer += serialize_header_auth( version=self._header.version, @@ -884,6 +890,9 @@ class DecryptorConfig(_ClientConfig): :param int max_body_length: Maximum frame size (or content length for non-framed messages) in bytes to read from ciphertext message. + :param dict encryption_context: Dictionary defining encryption context to validate + on decrypt. This is ONLY validated on decrypt if using the required encryption + context CMM from the aws-cryptographic-materialproviders library. """ max_body_length = attr.ib( @@ -971,9 +980,9 @@ def _read_header(self): found=header.frame_length, custom=self.config.max_body_length ) ) - - print(f"{self.config.encryption_context=}") - + + # If encryption_context is provided on decrypt, + # pass it to the DecryptionMaterialsRequest if hasattr(self.config, "encryption_context"): decrypt_materials_request = DecryptionMaterialsRequest( encrypted_data_keys=header.encrypted_data_keys, @@ -989,9 +998,12 @@ def _read_header(self): encryption_context=header.encryption_context, commitment_policy=self.config.commitment_policy, ) - print(f"{decrypt_materials_request=}") + decryption_materials = self.config.materials_manager.decrypt_materials(request=decrypt_materials_request) + # If the materials_manager passed required_encryption_context_keys, + # get the items out of the encryption_context with the keys. + # The items are used in header validation. if hasattr(decryption_materials, "required_encryption_context_keys"): self._required_encryption_context = {} for (k, v) in decryption_materials.encryption_context.items(): @@ -1038,7 +1050,12 @@ def _read_header(self): "message. Halting processing of this message." ) + # If _required_encryption_context is present, + # serialize it and pass it to validate_header. if self._required_encryption_context is not None: + # The authenticated only encryption context is all encryption context key-value pairs where the + # key exists in Required Encryption Context Keys. It is then serialized according to the + # message header Key Value Pairs. required_ec_serialized = aws_encryption_sdk.internal.formatting.encryption_context.serialize_encryption_context( self._required_encryption_context ) @@ -1046,6 +1063,9 @@ def _read_header(self): validate_header( header=header, header_auth=header_auth, + # When verifying the header, the AAD input to the authenticated encryption algorithm + # specified by the algorithm suite is the message header body and the serialized + # authenticated only encryption context. raw_header=raw_header + required_ec_serialized, data_key=self._derived_data_key ) From 21a8c938eb44f352a76fd358e1c865f453e3f75a Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 28 Feb 2024 16:08:02 -0800 Subject: [PATCH 143/184] protect --- examples/src/basic_encryption.py | 2 +- examples/test/test_i_basic_encryption.py | 2 +- src/aws_encryption_sdk/streaming_client.py | 15 ++++++++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/examples/src/basic_encryption.py b/examples/src/basic_encryption.py index cfe8ac791..68be5c594 100644 --- a/examples/src/basic_encryption.py +++ b/examples/src/basic_encryption.py @@ -38,7 +38,7 @@ def cycle_string(key_arn, source_plaintext, botocore_session=None): ciphertext, encryptor_header = client.encrypt(source=source_plaintext, key_provider=master_key_provider) # Decrypt the ciphertext - cycled_plaintext, decrypted_header = client.decrypt(source=ciphertext, key_provider=master_key_provider) + cycled_plaintext, decrypted_header = client.decrypt(source=ciphertext, key_provider=master_key_provider, encryption_context={"a": "v"}) # Verify that the "cycled" (encrypted, then decrypted) plaintext is identical to the source plaintext assert cycled_plaintext == source_plaintext diff --git a/examples/test/test_i_basic_encryption.py b/examples/test/test_i_basic_encryption.py index f2a4fab51..5f509800e 100644 --- a/examples/test/test_i_basic_encryption.py +++ b/examples/test/test_i_basic_encryption.py @@ -23,5 +23,5 @@ def test_cycle_string(): plaintext = static_plaintext - cmk_arn = get_cmk_arn() + cmk_arn = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f" cycle_string(key_arn=cmk_arn, source_plaintext=plaintext, botocore_session=botocore.session.Session()) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 568543863..e3513de97 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -638,7 +638,7 @@ def generate_header(self, message_id): self._stored_encryption_context[k] = v # Otherwise, store all encryption context with the message. else: - self._stored_encryption_context = self._encryption_materials.encryption_context, + self._stored_encryption_context = self._encryption_materials.encryption_context self._required_encryption_context = None kwargs = dict( @@ -1001,6 +1001,19 @@ def _read_header(self): decryption_materials = self.config.materials_manager.decrypt_materials(request=decrypt_materials_request) + # Guard against possible misunderstanding of "encryption context on decrypt". + # The `encryption_context` parameter on the client's `decrypt` method + # is ONLY meant to be used in conjunction with a `materials_manager` + # that validates the encryption context provided to the decrypt method + if hasattr(self.config, "encryption_context"): + try: + assert hasattr(decryption_materials, "required_encryption_context_keys") + except AssertionError as e: + raise ValueError("encryption_context on decrypt is not supported with the configured CMM: " + f"{self.config.materials_manager}. " + "You MUST pass a CMM that supports required encryption context keys to " + "validate encryption context on decrypt.") + # If the materials_manager passed required_encryption_context_keys, # get the items out of the encryption_context with the keys. # The items are used in header validation. From de870b8495dcb34bb795edfa2bd72e24bf8f3c61 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 28 Feb 2024 16:08:23 -0800 Subject: [PATCH 144/184] ex --- .../required_encryption_context_cmm.py | 130 ++++++++++++++++++ .../test_i_required_encryption_context_cmm.py | 13 ++ src/aws_encryption_sdk/streaming_client.py | 6 +- 3 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 examples/src/keyrings/required_encryption_context_cmm.py create mode 100644 examples/test/keyrings/test_i_required_encryption_context_cmm.py diff --git a/examples/src/keyrings/required_encryption_context_cmm.py b/examples/src/keyrings/required_encryption_context_cmm.py new file mode 100644 index 000000000..c36a4b2bd --- /dev/null +++ b/examples/src/keyrings/required_encryption_context_cmm.py @@ -0,0 +1,130 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Example showing basic encryption and decryption of a value already in memory.""" +import sys + +import boto3 +# Ignore missing MPL for pylint, but the MPL is required for this example +# noqa pylint: disable=import-error +from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders +from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig +from aws_cryptographic_materialproviders.mpl.models import ( + CacheTypeDefault, + CreateAwsKmsKeyringInput, + CreateDefaultCryptographicMaterialsManagerInput, + CreateRequiredEncryptionContextCMMInput, + DefaultCache, +) +from aws_cryptographic_materialproviders.mpl.references import ( + IKeyring, + ICryptographicMaterialsManager, +) +from aws_encryption_sdk.materials_managers.mpl.cmm import CryptoMaterialsManagerFromMPL +from typing import Dict + +import aws_encryption_sdk +from aws_encryption_sdk import CommitmentPolicy +from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError + +from .example_branch_key_id_supplier import ExampleBranchKeyIdSupplier + +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 +): + """Creates a hierarchical keyring using the provided resources, then encrypts and decrypts a string with it.""" + # 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 + ) + + # 7. Create an encryption context. + #// Most encrypted data should have an associated encryption context + #// to protect integrity. This sample uses placeholder values. + #// For more information see: + #// blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management + encryption_context: Dict[str, str] = { + "key1": "value1", + "key2": "value2", + "requiredKey1": "requiredValue1", + "requiredKey2": "requiredValue2", + } + + #// 3. Create list of required encryption context keys. + #// This is a list of keys that must be present in the encryption context. + required_encryption_context_keys: List[str] = ["requiredKey1", "requiredKey2"] + + #// 4. Create the AWS KMS keyring. + mpl: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( + config=MaterialProvidersConfig() + ) + keyring_input: CreateAwsKmsKeyringInput = CreateAwsKmsKeyringInput( + kms_key_id=kms_key_id, + kms_client=boto3.client('kms', region_name="us-west-2") + ) + kms_keyring: IKeyring = mpl.create_aws_kms_keyring(keyring_input) + + #// 5. Create the required encryption context CMM. + underlying_cmm: ICryptographicMaterialsManager = \ + mpl.create_default_cryptographic_materials_manager( + CreateDefaultCryptographicMaterialsManagerInput( + keyring=kms_keyring + ) + ) + + required_ec_cmm: ICryptographicMaterialsManager = \ + mpl.create_required_encryption_context_cmm( + CreateRequiredEncryptionContextCMMInput( + required_encryption_context_keys=required_encryption_context_keys, + underlying_cmm=underlying_cmm, + ) + ) + + # 6. Encrypt the data + ciphertext, _ = client.encrypt( + source=EXAMPLE_DATA, + materials_manager=required_ec_cmm, + encryption_context=encryption_context + ) + + # // 7. Reproduce the encryption context. + # // The reproduced encryption context MUST contain a value for + # // every key in the configured required encryption context keys during encryption with + # // Required Encryption Context CMM. + reproduced_encryption_context: Dict[str, str] = { + "requiredKey1": "requiredValue1", + "requiredKey2": "requiredValue2", + } + + # 8. Decrypt the data + plaintext_bytes_A, _ = client.decrypt( + source=ciphertext, + materials_manager=required_ec_cmm, + encryption_context=reproduced_encryption_context + ) + assert plaintext_bytes_A == EXAMPLE_DATA + + + # 9. If we don't provide the required encryption context, this should fail + try: + plaintext_bytes_A, _ = client.decrypt( + source=ciphertext, + materials_manager=required_ec_cmm, + # no encryption context while using required encryption context CMM makes decryption fail + ) + assert plaintext_bytes_A == EXAMPLE_DATA + except AWSEncryptionSDKClientError: + pass \ No newline at end of file diff --git a/examples/test/keyrings/test_i_required_encryption_context_cmm.py b/examples/test/keyrings/test_i_required_encryption_context_cmm.py new file mode 100644 index 000000000..9512a06ee --- /dev/null +++ b/examples/test/keyrings/test_i_required_encryption_context_cmm.py @@ -0,0 +1,13 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Unit test suite for the hierarchical keyring example.""" +import pytest + +from ...src.keyrings.required_encryption_context_cmm import encrypt_and_decrypt_with_keyring + +pytestmark = [pytest.mark.examples] + + +def test_encrypt_and_decrypt_with_keyring(): + key_arn = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f" + encrypt_and_decrypt_with_keyring(key_arn) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index e3513de97..58cd52051 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -1003,8 +1003,10 @@ def _read_header(self): # Guard against possible misunderstanding of "encryption context on decrypt". # The `encryption_context` parameter on the client's `decrypt` method - # is ONLY meant to be used in conjunction with a `materials_manager` - # that validates the encryption context provided to the decrypt method + # is ONLY meant to be used in conjunction with a `materials_manager` + # that validates the encryption context provided to the decrypt method. + # This guards against accidentially passing encryption context on decrypt + # and not realizing nothing is being validated. if hasattr(self.config, "encryption_context"): try: assert hasattr(decryption_materials, "required_encryption_context_keys") From eedf1a3c269473117dc284c52b0d34f63943915b Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 28 Feb 2024 16:24:03 -0800 Subject: [PATCH 145/184] changes --- examples/src/basic_encryption.py | 2 +- .../keyrings/required_encryption_context_cmm.py | 10 +++++----- src/aws_encryption_sdk/streaming_client.py | 17 +---------------- 3 files changed, 7 insertions(+), 22 deletions(-) diff --git a/examples/src/basic_encryption.py b/examples/src/basic_encryption.py index 68be5c594..cfe8ac791 100644 --- a/examples/src/basic_encryption.py +++ b/examples/src/basic_encryption.py @@ -38,7 +38,7 @@ def cycle_string(key_arn, source_plaintext, botocore_session=None): ciphertext, encryptor_header = client.encrypt(source=source_plaintext, key_provider=master_key_provider) # Decrypt the ciphertext - cycled_plaintext, decrypted_header = client.decrypt(source=ciphertext, key_provider=master_key_provider, encryption_context={"a": "v"}) + cycled_plaintext, decrypted_header = client.decrypt(source=ciphertext, key_provider=master_key_provider) # Verify that the "cycled" (encrypted, then decrypted) plaintext is identical to the source plaintext assert cycled_plaintext == source_plaintext diff --git a/examples/src/keyrings/required_encryption_context_cmm.py b/examples/src/keyrings/required_encryption_context_cmm.py index c36a4b2bd..51455fd4f 100644 --- a/examples/src/keyrings/required_encryption_context_cmm.py +++ b/examples/src/keyrings/required_encryption_context_cmm.py @@ -109,7 +109,7 @@ def encrypt_and_decrypt_with_keyring( "requiredKey2": "requiredValue2", } - # 8. Decrypt the data + # # 8. Decrypt the data plaintext_bytes_A, _ = client.decrypt( source=ciphertext, materials_manager=required_ec_cmm, @@ -117,14 +117,14 @@ def encrypt_and_decrypt_with_keyring( ) assert plaintext_bytes_A == EXAMPLE_DATA - - # 9. If we don't provide the required encryption context, this should fail + # 9. If we don't provide the required encryption context, + # decryption will fail. try: plaintext_bytes_A, _ = client.decrypt( source=ciphertext, materials_manager=required_ec_cmm, # no encryption context while using required encryption context CMM makes decryption fail ) - assert plaintext_bytes_A == EXAMPLE_DATA + raise Exception("If this exception is raised, decryption somehow succeeded!") except AWSEncryptionSDKClientError: - pass \ No newline at end of file + pass diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 58cd52051..7e6ede8cd 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -1000,22 +1000,7 @@ def _read_header(self): ) decryption_materials = self.config.materials_manager.decrypt_materials(request=decrypt_materials_request) - - # Guard against possible misunderstanding of "encryption context on decrypt". - # The `encryption_context` parameter on the client's `decrypt` method - # is ONLY meant to be used in conjunction with a `materials_manager` - # that validates the encryption context provided to the decrypt method. - # This guards against accidentially passing encryption context on decrypt - # and not realizing nothing is being validated. - if hasattr(self.config, "encryption_context"): - try: - assert hasattr(decryption_materials, "required_encryption_context_keys") - except AssertionError as e: - raise ValueError("encryption_context on decrypt is not supported with the configured CMM: " - f"{self.config.materials_manager}. " - "You MUST pass a CMM that supports required encryption context keys to " - "validate encryption context on decrypt.") - + # If the materials_manager passed required_encryption_context_keys, # get the items out of the encryption_context with the keys. # The items are used in header validation. From 1db73ebee0d6df0e2ca76b4f16998f018a042d0d Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 28 Feb 2024 16:58:26 -0800 Subject: [PATCH 146/184] changes --- examples/src/keyrings/hierarchical_keyring.py | 33 ++++++++++++++- .../required_encryption_context_cmm.py | 40 +++++++++++-------- .../keyrings/test_i_hierarchical_keyring.py | 2 +- .../test_i_required_encryption_context_cmm.py | 2 +- examples/test/test_i_basic_encryption.py | 2 +- src/aws_encryption_sdk/streaming_client.py | 28 ++++++------- 6 files changed, 72 insertions(+), 35 deletions(-) diff --git a/examples/src/keyrings/hierarchical_keyring.py b/examples/src/keyrings/hierarchical_keyring.py index aa87485f9..b75421359 100644 --- a/examples/src/keyrings/hierarchical_keyring.py +++ b/examples/src/keyrings/hierarchical_keyring.py @@ -1,6 +1,36 @@ # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -"""Example showing basic encryption and decryption of a value already in memory.""" +""" +This example sets up the Hierarchical Keyring, which establishes a key hierarchy where "branch" +keys are persisted in DynamoDb. These branch keys are used to protect your data keys, and these +branch keys are themselves protected by a KMS Key. + +Establishing a key hierarchy like this has two benefits: +First, by caching the branch key material, and only calling KMS to re-establish authentication +regularly according to your configured TTL, you limit how often you need to call KMS to protect +your data. This is a performance security tradeoff, where your authentication, audit, and logging +from KMS is no longer one-to-one with every encrypt or decrypt call. Additionally, KMS Cloudtrail +cannot be used to distinguish Encrypt and Decrypt calls, and you cannot restrict who has +Encryption rights from who has Decryption rights since they both ONLY need KMS:Decrypt. However, +the benefit is that you no longer have to make a network call to KMS for every encrypt or +decrypt. + +Second, this key hierarchy facilitates cryptographic isolation of a tenant's data in a +multi-tenant data store. Each tenant can have a unique Branch Key, that is only used to protect +the tenant's data. You can either statically configure a single branch key to ensure you are +restricting access to a single tenant, or you can implement an interface that selects the Branch +Key based on the Encryption Context. + +This example demonstrates configuring a Hierarchical Keyring with a Branch Key ID Supplier to +encrypt and decrypt data for two separate tenants. + +This example requires access to the DDB Table where you are storing the Branch Keys. This +table must be configured with the following primary key configuration: - Partition key is named +"partition_key" with type (S) - Sort key is named "sort_key" with type (S) + +This example also requires using a KMS Key. You need the following access on this key: - +GenerateDataKeyWithoutPlaintext - Decrypt +""" import sys import boto3 @@ -25,6 +55,7 @@ from .example_branch_key_id_supplier import ExampleBranchKeyIdSupplier +# TODO-MPL: Remove this as part of removing PYTHONPATH hacks module_root_dir = '/'.join(__file__.split("/")[:-1]) sys.path.append(module_root_dir) diff --git a/examples/src/keyrings/required_encryption_context_cmm.py b/examples/src/keyrings/required_encryption_context_cmm.py index 51455fd4f..f3d58c922 100644 --- a/examples/src/keyrings/required_encryption_context_cmm.py +++ b/examples/src/keyrings/required_encryption_context_cmm.py @@ -1,6 +1,11 @@ # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -"""Example showing basic encryption and decryption of a value already in memory.""" +""" +Demonstrate an encrypt/decrypt cycle using a Required Encryption Context CMM. +A required encryption context CMM asks for required keys in the encryption context field +on encrypt such that they will not be stored on the message, but WILL be included in the header signature. +On decrypt, the client MUST supply the key/value pair(s) that were not stored to successfully decrypt the message. +""" import sys import boto3 @@ -28,6 +33,7 @@ from .example_branch_key_id_supplier import ExampleBranchKeyIdSupplier +# TODO-MPL: Remove this as part of removing PYTHONPATH hacks module_root_dir = '/'.join(__file__.split("/")[:-1]) sys.path.append(module_root_dir) @@ -51,11 +57,11 @@ def encrypt_and_decrypt_with_keyring( commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT ) - # 7. Create an encryption context. - #// Most encrypted data should have an associated encryption context - #// to protect integrity. This sample uses placeholder values. - #// For more information see: - #// blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management + # 2. Create an encryption context. + # Most encrypted data should have an associated encryption context + # to protect integrity. This sample uses placeholder values. + # For more information see: + # blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management # noqa: E501 encryption_context: Dict[str, str] = { "key1": "value1", "key2": "value2", @@ -63,11 +69,11 @@ def encrypt_and_decrypt_with_keyring( "requiredKey2": "requiredValue2", } - #// 3. Create list of required encryption context keys. - #// This is a list of keys that must be present in the encryption context. + # 3. Create list of required encryption context keys. + # This is a list of keys that must be present in the encryption context. required_encryption_context_keys: List[str] = ["requiredKey1", "requiredKey2"] - #// 4. Create the AWS KMS keyring. + # 4. Create the AWS KMS keyring. mpl: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( config=MaterialProvidersConfig() ) @@ -77,7 +83,7 @@ def encrypt_and_decrypt_with_keyring( ) kms_keyring: IKeyring = mpl.create_aws_kms_keyring(keyring_input) - #// 5. Create the required encryption context CMM. + # 5. Create the required encryption context CMM. underlying_cmm: ICryptographicMaterialsManager = \ mpl.create_default_cryptographic_materials_manager( CreateDefaultCryptographicMaterialsManagerInput( @@ -100,16 +106,16 @@ def encrypt_and_decrypt_with_keyring( encryption_context=encryption_context ) - # // 7. Reproduce the encryption context. - # // The reproduced encryption context MUST contain a value for - # // every key in the configured required encryption context keys during encryption with - # // Required Encryption Context CMM. + # 7. Reproduce the encryption context. + # The reproduced encryption context MUST contain a value for + # every key in the configured required encryption context keys during encryption with + # Required Encryption Context CMM. reproduced_encryption_context: Dict[str, str] = { "requiredKey1": "requiredValue1", "requiredKey2": "requiredValue2", } - # # 8. Decrypt the data + # 8. Decrypt the data plaintext_bytes_A, _ = client.decrypt( source=ciphertext, materials_manager=required_ec_cmm, @@ -117,13 +123,13 @@ def encrypt_and_decrypt_with_keyring( ) assert plaintext_bytes_A == EXAMPLE_DATA - # 9. If we don't provide the required encryption context, + # 9. Extra: Demonstrate that if we don't provide the required encryption context, # decryption will fail. try: plaintext_bytes_A, _ = client.decrypt( source=ciphertext, materials_manager=required_ec_cmm, - # no encryption context while using required encryption context CMM makes decryption fail + # No encryption context while using required encryption context CMM makes decryption fail. ) raise Exception("If this exception is raised, decryption somehow succeeded!") except AWSEncryptionSDKClientError: diff --git a/examples/test/keyrings/test_i_hierarchical_keyring.py b/examples/test/keyrings/test_i_hierarchical_keyring.py index 4cae478d7..c4583534a 100644 --- a/examples/test/keyrings/test_i_hierarchical_keyring.py +++ b/examples/test/keyrings/test_i_hierarchical_keyring.py @@ -1,6 +1,6 @@ # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -"""Unit test suite for the hierarchical keyring example.""" +"""Test suite for the hierarchical keyring example.""" import pytest from ...src.keyrings.hierarchical_keyring import encrypt_and_decrypt_with_keyring diff --git a/examples/test/keyrings/test_i_required_encryption_context_cmm.py b/examples/test/keyrings/test_i_required_encryption_context_cmm.py index 9512a06ee..724705faa 100644 --- a/examples/test/keyrings/test_i_required_encryption_context_cmm.py +++ b/examples/test/keyrings/test_i_required_encryption_context_cmm.py @@ -1,6 +1,6 @@ # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -"""Unit test suite for the hierarchical keyring example.""" +"""Test suite for the required encryption context CMM example.""" import pytest from ...src.keyrings.required_encryption_context_cmm import encrypt_and_decrypt_with_keyring diff --git a/examples/test/test_i_basic_encryption.py b/examples/test/test_i_basic_encryption.py index 5f509800e..f2a4fab51 100644 --- a/examples/test/test_i_basic_encryption.py +++ b/examples/test/test_i_basic_encryption.py @@ -23,5 +23,5 @@ def test_cycle_string(): plaintext = static_plaintext - cmk_arn = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f" + cmk_arn = get_cmk_arn() cycle_string(key_arn=cmk_arn, source_plaintext=plaintext, botocore_session=botocore.session.Session()) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 7e6ede8cd..34ba01c59 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -80,8 +80,8 @@ CreateDefaultCryptographicMaterialsManagerInput, ) from aws_cryptographic_materialproviders.mpl.references import ( - ICryptographicMaterialsManager, - IKeyring, + ICryptographicMaterialsManager as MPL_ICryptographicMaterialsManager, + IKeyring as MPL_IKeyring, ) _HAS_MPL = True @@ -141,7 +141,7 @@ class _ClientConfig(object): # pylint: disable=too-many-instance-attributes default=None, validator=attr.validators.optional( attr.validators.instance_of( - (CryptoMaterialsManager, ICryptographicMaterialsManager) + (CryptoMaterialsManager, MPL_ICryptographicMaterialsManager) ) ) ) @@ -161,7 +161,7 @@ class _ClientConfig(object): # pylint: disable=too-many-instance-attributes if _HAS_MPL: # Keyrings are only available if the MPL is installed in the runtime keyring = attr.ib( - hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(IKeyring)) + hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(MPL_IKeyring)) ) source_length = attr.ib( hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(six.integer_types)) @@ -202,7 +202,7 @@ def _has_mpl_attrs_post_init(self): # If the provided materials_manager is directly from the MPL, wrap it in a native interface # for internal use. elif (self.materials_manager is not None - and isinstance(self.materials_manager, ICryptographicMaterialsManager)): + and isinstance(self.materials_manager, MPL_ICryptographicMaterialsManager)): self.materials_manager = CryptoMaterialsManagerFromMPL(self.materials_manager) def _no_mpl_attrs_post_init(self): @@ -437,10 +437,10 @@ class EncryptorConfig(_ClientConfig): :param key_provider: `MasterKeyProvider` from which to obtain data keys for encryption (either `materials_manager` or `key_provider` required) :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider - :param keyring: `IKeyring` from the aws_cryptographic_materialproviders library + :param keyring: `MPL_IKeyring` from the aws_cryptographic_materialproviders library which handles encryption and decryption :type keyring: - aws_cryptographic_materialproviders.mpl.references.IKeyring + aws_cryptographic_materialproviders.mpl.references.MPL_IKeyring :param int source_length: Length of source data (optional) .. note:: @@ -492,10 +492,10 @@ class StreamEncryptor(_EncryptionStream): # pylint: disable=too-many-instance-a :param key_provider: `MasterKeyProvider` from which to obtain data keys for encryption (either `materials_manager` or `key_provider` required) :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider - :param keyring: `IKeyring` from the aws_cryptographic_materialproviders library + :param keyring: `MPL_IKeyring` from the aws_cryptographic_materialproviders library which handles encryption and decryption :type keyring: - aws_cryptographic_materialproviders.mpl.references.IKeyring + aws_cryptographic_materialproviders.mpl.references.MPL_IKeyring :param int source_length: Length of source data (optional) .. note:: @@ -878,10 +878,10 @@ class DecryptorConfig(_ClientConfig): :param key_provider: `MasterKeyProvider` from which to obtain data keys for decryption (either `keyring`, `materials_manager` or `key_provider` required) :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider - :param keyring: `IKeyring` from the aws_cryptographic_materialproviders library + :param keyring: `MPL_IKeyring` from the aws_cryptographic_materialproviders library which handles encryption and decryption :type keyring: - aws_cryptographic_materialproviders.mpl.references.IKeyring + aws_cryptographic_materialproviders.mpl.references.MPL_IKeyring :param int source_length: Length of source data (optional) .. note:: @@ -926,10 +926,10 @@ class StreamDecryptor(_EncryptionStream): # pylint: disable=too-many-instance-a :param key_provider: `MasterKeyProvider` from which to obtain data keys for decryption (either `materials_manager` or `key_provider` required) :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider - :param keyring: `IKeyring` from the aws_cryptographic_materialproviders library + :param keyring: `MPL_IKeyring` from the aws_cryptographic_materialproviders library which handles encryption and decryption :type keyring: - aws_cryptographic_materialproviders.mpl.references.IKeyring + aws_cryptographic_materialproviders.mpl.references.MPL_IKeyring :param int source_length: Length of source data (optional) .. note:: @@ -1000,7 +1000,7 @@ def _read_header(self): ) decryption_materials = self.config.materials_manager.decrypt_materials(request=decrypt_materials_request) - + # If the materials_manager passed required_encryption_context_keys, # get the items out of the encryption_context with the keys. # The items are used in header validation. From 8415c2cbb2eb138e330816374dccc0f15cce6a38 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 28 Feb 2024 17:00:17 -0800 Subject: [PATCH 147/184] cleanup --- src/aws_encryption_sdk/streaming_client.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 34ba01c59..953a82a66 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -437,10 +437,10 @@ class EncryptorConfig(_ClientConfig): :param key_provider: `MasterKeyProvider` from which to obtain data keys for encryption (either `materials_manager` or `key_provider` required) :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider - :param keyring: `MPL_IKeyring` from the aws_cryptographic_materialproviders library + :param keyring: `IKeyring` from the aws_cryptographic_materialproviders library which handles encryption and decryption :type keyring: - aws_cryptographic_materialproviders.mpl.references.MPL_IKeyring + aws_cryptographic_materialproviders.mpl.references.IKeyring :param int source_length: Length of source data (optional) .. note:: @@ -492,10 +492,10 @@ class StreamEncryptor(_EncryptionStream): # pylint: disable=too-many-instance-a :param key_provider: `MasterKeyProvider` from which to obtain data keys for encryption (either `materials_manager` or `key_provider` required) :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider - :param keyring: `MPL_IKeyring` from the aws_cryptographic_materialproviders library + :param keyring: `IKeyring` from the aws_cryptographic_materialproviders library which handles encryption and decryption :type keyring: - aws_cryptographic_materialproviders.mpl.references.MPL_IKeyring + aws_cryptographic_materialproviders.mpl.references.IKeyring :param int source_length: Length of source data (optional) .. note:: @@ -878,10 +878,10 @@ class DecryptorConfig(_ClientConfig): :param key_provider: `MasterKeyProvider` from which to obtain data keys for decryption (either `keyring`, `materials_manager` or `key_provider` required) :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider - :param keyring: `MPL_IKeyring` from the aws_cryptographic_materialproviders library + :param keyring: `IKeyring` from the aws_cryptographic_materialproviders library which handles encryption and decryption :type keyring: - aws_cryptographic_materialproviders.mpl.references.MPL_IKeyring + aws_cryptographic_materialproviders.mpl.references.IKeyring :param int source_length: Length of source data (optional) .. note:: @@ -926,10 +926,10 @@ class StreamDecryptor(_EncryptionStream): # pylint: disable=too-many-instance-a :param key_provider: `MasterKeyProvider` from which to obtain data keys for decryption (either `materials_manager` or `key_provider` required) :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider - :param keyring: `MPL_IKeyring` from the aws_cryptographic_materialproviders library + :param keyring: `IKeyring` from the aws_cryptographic_materialproviders library which handles encryption and decryption :type keyring: - aws_cryptographic_materialproviders.mpl.references.MPL_IKeyring + aws_cryptographic_materialproviders.mpl.references.IKeyring :param int source_length: Length of source data (optional) .. note:: From 20bdaffca7b83444348fdfb0e6abab7c3af5c8c9 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 28 Feb 2024 18:21:42 -0800 Subject: [PATCH 148/184] cleanup --- src/aws_encryption_sdk/streaming_client.py | 2 +- .../test_streaming_client_stream_decryptor.py | 42 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 953a82a66..4a742b91f 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -672,7 +672,7 @@ def _write_header(self): # If there is _required_encryption_context, # serialize it, then authenticate it - if self._required_encryption_context is not None: + if hasattr(self, "_required_encryption_context"): required_ec_serialized = aws_encryption_sdk.internal.formatting.encryption_context.serialize_encryption_context( self._required_encryption_context ) diff --git a/test/unit/test_streaming_client_stream_decryptor.py b/test/unit/test_streaming_client_stream_decryptor.py index e06cad308..4929646b5 100644 --- a/test/unit/test_streaming_client_stream_decryptor.py +++ b/test/unit/test_streaming_client_stream_decryptor.py @@ -193,6 +193,9 @@ def test_read_header(self, mock_derive_datakey, mock_decrypt_materials_request, test_decryptor.source_stream = ct_stream test_decryptor._stream_length = len(VALUES["data_128"]) + # Mock: hasattr(self.config, "encryption_context") returns False + del test_decryptor.config.encryption_context + test_header, test_header_auth = test_decryptor._read_header() self.mock_deserialize_header.assert_called_once_with(ct_stream, None) @@ -230,6 +233,45 @@ def test_read_header(self, mock_derive_datakey, mock_decrypt_materials_request, assert test_header is self.mock_header assert test_header_auth is sentinel.header_auth + @patch("aws_encryption_sdk.streaming_client.derive_data_encryption_key") + @patch("aws_encryption_sdk.streaming_client.DecryptionMaterialsRequest") + @patch("aws_encryption_sdk.streaming_client.Verifier") + # Given: no MPL + @pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") + def test_GIVEN_verification_key_AND_no_mpl_WHEN_read_header_THEN_calls_from_key_bytes( + self, + mock_verifier, + mock_decrypt_materials_request, + *_, + ): + + mock_verifier_instance = MagicMock() + mock_verifier.from_key_bytes.return_value = mock_verifier_instance + ct_stream = io.BytesIO(VALUES["data_128"]) + mock_commitment_policy = MagicMock(__class__=CommitmentPolicy) + test_decryptor = StreamDecryptor( + materials_manager=self.mock_materials_manager, + source=ct_stream, + commitment_policy=mock_commitment_policy, + ) + test_decryptor.source_stream = ct_stream + test_decryptor._stream_length = len(VALUES["data_128"]) + # Given: self.config has "encryption_context" + any_reproduced_ec = {"some": "ec"} + test_decryptor.config.encryption_context = any_reproduced_ec + + # When: read header + test_decryptor._read_header() + + # Then: calls decrypt_materials with reproduced_encryption_context + mock_decrypt_materials_request.assert_called_once_with( + encrypted_data_keys=self.mock_header.encrypted_data_keys, + algorithm=self.mock_header.algorithm, + encryption_context=sentinel.encryption_context, + commitment_policy=mock_commitment_policy, + reproduced_encryption_context=any_reproduced_ec, + ) + @patch("aws_encryption_sdk.streaming_client.DecryptionMaterialsRequest") @patch("aws_encryption_sdk.streaming_client.derive_data_encryption_key") @patch("aws_encryption_sdk.streaming_client.Verifier") From 6bf6094eff239f36c7dffb010e2b59cb7e948ab4 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 28 Feb 2024 18:29:50 -0800 Subject: [PATCH 149/184] cleanup --- .../internal/formatting/serialize.py | 8 +++++--- .../materials_managers/mpl/materials.py | 1 - src/aws_encryption_sdk/streaming_client.py | 18 ++++++++++-------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/aws_encryption_sdk/internal/formatting/serialize.py b/src/aws_encryption_sdk/internal/formatting/serialize.py index 344c94703..5a054989c 100644 --- a/src/aws_encryption_sdk/internal/formatting/serialize.py +++ b/src/aws_encryption_sdk/internal/formatting/serialize.py @@ -237,7 +237,7 @@ def _serialize_header_auth_v2( for all items whose keys are in the required_encryption_context list. This is ONLY processed if using the aws-cryptographic-materialproviders library AND its required encryption context CMM. (optional) - :type required_encryption_context_bytes: bytes + :type required_encryption_context_bytes: bytes :returns: Serialized header authentication data :rtype: bytes """ @@ -294,14 +294,16 @@ def serialize_header_auth( This is ONLY processed if using the aws-cryptographic-materialproviders library AND its required encryption context CMM AND if using the v2 message format. (optional) - :type required_encryption_context_bytes: bytes + :type required_encryption_context_bytes: bytes :returns: Serialized header authentication data :rtype: bytes """ if version == SerializationVersion.V1: return _serialize_header_auth_v1(algorithm, header, data_encryption_key, signer) elif version == SerializationVersion.V2: - return _serialize_header_auth_v2(algorithm, header, data_encryption_key, signer, required_encryption_context_bytes) + return _serialize_header_auth_v2( + algorithm, header, data_encryption_key, signer, required_encryption_context_bytes + ) else: raise SerializationError("Unrecognized message format version: {}".format(version)) diff --git a/src/aws_encryption_sdk/materials_managers/mpl/materials.py b/src/aws_encryption_sdk/materials_managers/mpl/materials.py index 5b066c7c7..5e4a66318 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/materials.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/materials.py @@ -95,7 +95,6 @@ def data_encryption_key(self) -> DataKey: def signing_key(self) -> bytes: """Materials' signing key.""" return self.mpl_materials.signing_key - @property def required_encryption_context_keys(self) -> bytes: diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 4a742b91f..048a6caa3 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -198,7 +198,7 @@ def _has_mpl_attrs_post_init(self): # Wrap MPL error into the ESDK error type # so customers only have to catch ESDK error types. raise AWSEncryptionSDKClientError(mpl_exception) - + # If the provided materials_manager is directly from the MPL, wrap it in a native interface # for internal use. elif (self.materials_manager is not None @@ -673,9 +673,10 @@ def _write_header(self): # If there is _required_encryption_context, # serialize it, then authenticate it if hasattr(self, "_required_encryption_context"): - required_ec_serialized = aws_encryption_sdk.internal.formatting.encryption_context.serialize_encryption_context( - self._required_encryption_context - ) + required_ec_serialized = \ + aws_encryption_sdk.internal.formatting.encryption_context.serialize_encryption_context( + self._required_encryption_context + ) self.output_buffer += serialize_header_auth( version=self._header.version, algorithm=self._encryption_materials.algorithm, @@ -955,7 +956,7 @@ def _prep_message(self): self._prep_non_framed() self._message_prepped = True - def _read_header(self): + def _read_header(self): # noqa: C901 """Reads the message header from the input stream. :returns: tuple containing deserialized header and header_auth objects @@ -1056,9 +1057,10 @@ def _read_header(self): # The authenticated only encryption context is all encryption context key-value pairs where the # key exists in Required Encryption Context Keys. It is then serialized according to the # message header Key Value Pairs. - required_ec_serialized = aws_encryption_sdk.internal.formatting.encryption_context.serialize_encryption_context( - self._required_encryption_context - ) + required_ec_serialized = \ + aws_encryption_sdk.internal.formatting.encryption_context.serialize_encryption_context( + self._required_encryption_context + ) validate_header( header=header, From febe6dba05160087e6ac905f2f1582c79c56a1e8 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 28 Feb 2024 18:38:46 -0800 Subject: [PATCH 150/184] cleanup --- .../keyrings/required_encryption_context_cmm.py | 14 +++----------- .../internal/formatting/serialize.py | 10 +++++----- .../materials_managers/mpl/materials.py | 4 ++++ src/aws_encryption_sdk/streaming_client.py | 17 +++++++++-------- .../test_streaming_client_stream_decryptor.py | 2 +- 5 files changed, 22 insertions(+), 25 deletions(-) diff --git a/examples/src/keyrings/required_encryption_context_cmm.py b/examples/src/keyrings/required_encryption_context_cmm.py index f3d58c922..9f8de9976 100644 --- a/examples/src/keyrings/required_encryption_context_cmm.py +++ b/examples/src/keyrings/required_encryption_context_cmm.py @@ -14,25 +14,17 @@ from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig from aws_cryptographic_materialproviders.mpl.models import ( - CacheTypeDefault, CreateAwsKmsKeyringInput, CreateDefaultCryptographicMaterialsManagerInput, CreateRequiredEncryptionContextCMMInput, - DefaultCache, ) -from aws_cryptographic_materialproviders.mpl.references import ( - IKeyring, - ICryptographicMaterialsManager, -) -from aws_encryption_sdk.materials_managers.mpl.cmm import CryptoMaterialsManagerFromMPL -from typing import Dict +from aws_cryptographic_materialproviders.mpl.references import ICryptographicMaterialsManager, IKeyring +from typing import Dict, List import aws_encryption_sdk from aws_encryption_sdk import CommitmentPolicy from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError -from .example_branch_key_id_supplier import ExampleBranchKeyIdSupplier - # TODO-MPL: Remove this as part of removing PYTHONPATH hacks module_root_dir = '/'.join(__file__.split("/")[:-1]) @@ -98,7 +90,7 @@ def encrypt_and_decrypt_with_keyring( underlying_cmm=underlying_cmm, ) ) - + # 6. Encrypt the data ciphertext, _ = client.encrypt( source=EXAMPLE_DATA, diff --git a/src/aws_encryption_sdk/internal/formatting/serialize.py b/src/aws_encryption_sdk/internal/formatting/serialize.py index 5a054989c..66f4800de 100644 --- a/src/aws_encryption_sdk/internal/formatting/serialize.py +++ b/src/aws_encryption_sdk/internal/formatting/serialize.py @@ -223,7 +223,7 @@ def _serialize_header_auth_v2( header, data_encryption_key, signer=None, - required_encryption_context_bytes=None + required_ec_bytes=None ): """Creates serialized header authentication data for messages in serialization version V2. @@ -241,7 +241,7 @@ def _serialize_header_auth_v2( :returns: Serialized header authentication data :rtype: bytes """ - if required_encryption_context_bytes is None: + if required_ec_bytes is None: header_auth = encrypt( algorithm=algorithm, key=data_encryption_key, @@ -259,7 +259,7 @@ def _serialize_header_auth_v2( # be the encryption context in the encryption materials filtered to only contain key value # pairs listed in the encryption material's required encryption context keys serialized # according to the encryption context serialization specification. - associated_data=header + required_encryption_context_bytes, + associated_data=header + required_ec_bytes, iv=header_auth_iv(algorithm), ) output = struct.pack( @@ -277,7 +277,7 @@ def serialize_header_auth( header, data_encryption_key, signer=None, - required_encryption_context_bytes=None + required_ec_bytes=None ): """Creates serialized header authentication data. @@ -302,7 +302,7 @@ def serialize_header_auth( return _serialize_header_auth_v1(algorithm, header, data_encryption_key, signer) elif version == SerializationVersion.V2: return _serialize_header_auth_v2( - algorithm, header, data_encryption_key, signer, required_encryption_context_bytes + algorithm, header, data_encryption_key, signer, required_ec_bytes ) else: raise SerializationError("Unrecognized message format version: {}".format(version)) diff --git a/src/aws_encryption_sdk/materials_managers/mpl/materials.py b/src/aws_encryption_sdk/materials_managers/mpl/materials.py index 5e4a66318..54ea21b39 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/materials.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/materials.py @@ -97,6 +97,8 @@ def signing_key(self) -> bytes: return self.mpl_materials.signing_key @property + # Pylint thinks this name is too long, but it's the best descriptor for this... + # pylint: disable=invalid-name def required_encryption_context_keys(self) -> bytes: """Materials' required encryption context keys.""" return self.mpl_materials.required_encryption_context_keys @@ -148,6 +150,8 @@ def encryption_context(self) -> Dict[str, str]: return self.mpl_materials.encryption_context @property + # Pylint thinks this name is too long, but it's the best descriptor for this... + # pylint: disable=invalid-name def required_encryption_context_keys(self) -> bytes: """Materials' required encryption context keys.""" return self.mpl_materials.required_encryption_context_keys diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 048a6caa3..bbae73bef 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -76,9 +76,7 @@ from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException - from aws_cryptographic_materialproviders.mpl.models import ( - CreateDefaultCryptographicMaterialsManagerInput, - ) + from aws_cryptographic_materialproviders.mpl.models import CreateDefaultCryptographicMaterialsManagerInput from aws_cryptographic_materialproviders.mpl.references import ( ICryptographicMaterialsManager as MPL_ICryptographicMaterialsManager, IKeyring as MPL_IKeyring, @@ -631,11 +629,11 @@ def generate_header(self, message_id): if hasattr(self._encryption_materials, "required_encryption_context_keys"): self._required_encryption_context = {} self._stored_encryption_context = {} - for (k, v) in self._encryption_materials.encryption_context.items(): - if k in self._encryption_materials.required_encryption_context_keys: - self._required_encryption_context[k] = v + for (key, value) in self._encryption_materials.encryption_context.items(): + if key in self._encryption_materials.required_encryption_context_keys: + self._required_encryption_context[key] = value else: - self._stored_encryption_context[k] = v + self._stored_encryption_context[key] = value # Otherwise, store all encryption context with the message. else: self._stored_encryption_context = self._encryption_materials.encryption_context @@ -956,7 +954,10 @@ def _prep_message(self): self._prep_non_framed() self._message_prepped = True - def _read_header(self): # noqa: C901 + # TODO-MPL: Refactor this function, remove these linter disablers + # noqa: C901 + # pylint: disable=too-many-branches + def _read_header(self): """Reads the message header from the input stream. :returns: tuple containing deserialized header and header_auth objects diff --git a/test/unit/test_streaming_client_stream_decryptor.py b/test/unit/test_streaming_client_stream_decryptor.py index 4929646b5..2066dcbdb 100644 --- a/test/unit/test_streaming_client_stream_decryptor.py +++ b/test/unit/test_streaming_client_stream_decryptor.py @@ -238,7 +238,7 @@ def test_read_header(self, mock_derive_datakey, mock_decrypt_materials_request, @patch("aws_encryption_sdk.streaming_client.Verifier") # Given: no MPL @pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") - def test_GIVEN_verification_key_AND_no_mpl_WHEN_read_header_THEN_calls_from_key_bytes( + def test_GIVEN_decrypt_config_has_ec_WHEN_read_header_THEN_calls_decrypt_materials_with_reproduced_ec( self, mock_verifier, mock_decrypt_materials_request, From dc8abca6925ce2cd139ef53c7f3b2c8f9b3e09ce Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 28 Feb 2024 18:40:26 -0800 Subject: [PATCH 151/184] cleanup --- src/aws_encryption_sdk/streaming_client.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index bbae73bef..9488c9f08 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -954,10 +954,8 @@ def _prep_message(self): self._prep_non_framed() self._message_prepped = True - # TODO-MPL: Refactor this function, remove these linter disablers - # noqa: C901 - # pylint: disable=too-many-branches - def _read_header(self): + # TODO-MPL: Refactor this function, remove linter disablers + def _read_header(self): # noqa pylint: disable=too-many-branches """Reads the message header from the input stream. :returns: tuple containing deserialized header and header_auth objects From 8ff46f4520e510767d89365634c0e2da6a139f58 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 28 Feb 2024 18:41:15 -0800 Subject: [PATCH 152/184] cleanup --- src/aws_encryption_sdk/streaming_client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 9488c9f08..ffef9cd3a 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -1006,9 +1006,9 @@ def _read_header(self): # noqa pylint: disable=too-many-branches # The items are used in header validation. if hasattr(decryption_materials, "required_encryption_context_keys"): self._required_encryption_context = {} - for (k, v) in decryption_materials.encryption_context.items(): - if k in decryption_materials.required_encryption_context_keys: - self._required_encryption_context[k] = v + for (key, value) in decryption_materials.encryption_context.items(): + if key in decryption_materials.required_encryption_context_keys: + self._required_encryption_context[key] = value else: self._required_encryption_context = None From aba7cccad05ba9ba60b49fb14a9ee3153354b66b Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Wed, 28 Feb 2024 18:42:53 -0800 Subject: [PATCH 153/184] cleanup --- src/aws_encryption_sdk/streaming_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index ffef9cd3a..fb0935ff2 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -681,7 +681,7 @@ def _write_header(self): header=self.output_buffer, data_encryption_key=self._derived_data_key, signer=self.signer, - required_encryption_context_bytes=required_ec_serialized, + required_ec_bytes=required_ec_serialized, ) # Otherwise, do not pass in any required encryption context else: From 40fecc05de1e42eca0d860ac2b04bee5670d504c Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Thu, 29 Feb 2024 10:54:22 -0800 Subject: [PATCH 154/184] all message format versions --- .../internal/formatting/serialize.py | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/src/aws_encryption_sdk/internal/formatting/serialize.py b/src/aws_encryption_sdk/internal/formatting/serialize.py index 66f4800de..310cf1436 100644 --- a/src/aws_encryption_sdk/internal/formatting/serialize.py +++ b/src/aws_encryption_sdk/internal/formatting/serialize.py @@ -189,7 +189,13 @@ def serialize_header(header, signer=None): raise SerializationError("Unrecognized message format version: {}".format(header.version)) -def _serialize_header_auth_v1(algorithm, header, data_encryption_key, signer=None): +def _serialize_header_auth_v1( + algorithm, + header, + data_encryption_key, + signer=None, + required_ec_bytes=None +): """Creates serialized header authentication data for messages in serialization version V1. :param algorithm: Algorithm to use for encryption @@ -198,16 +204,35 @@ def _serialize_header_auth_v1(algorithm, header, data_encryption_key, signer=Non :param bytes data_encryption_key: Data key with which to encrypt message :param signer: Cryptographic signer object (optional) :type signer: aws_encryption_sdk.Signer + :param required_encryption_context_bytes: Serialized encryption context items + for all items whose keys are in the required_encryption_context list. + This is ONLY processed if using the aws-cryptographic-materialproviders library + AND its required encryption context CMM. (optional) + :type required_encryption_context_bytes: bytes :returns: Serialized header authentication data :rtype: bytes """ - header_auth = encrypt( - algorithm=algorithm, - key=data_encryption_key, - plaintext=b"", - associated_data=header, - iv=header_auth_iv(algorithm), - ) + if required_ec_bytes is None: + header_auth = encrypt( + algorithm=algorithm, + key=data_encryption_key, + plaintext=b"", + associated_data=header, + iv=header_auth_iv(algorithm), + ) + else: + header_auth = encrypt( + algorithm=algorithm, + key=data_encryption_key, + plaintext=b"", + # The AAD MUST be the concatenation of the serialized message header body and the serialization + # of encryption context to only authenticate. The encryption context to only authenticate MUST + # be the encryption context in the encryption materials filtered to only contain key value + # pairs listed in the encryption material's required encryption context keys serialized + # according to the encryption context serialization specification. + associated_data=header + required_ec_bytes, + iv=header_auth_iv(algorithm), + ) output = struct.pack( ">{iv_len}s{tag_len}s".format(iv_len=algorithm.iv_len, tag_len=algorithm.tag_len), header_auth.iv, @@ -292,8 +317,7 @@ def serialize_header_auth( :param required_encryption_context_bytes: Serialized encryption context items for all items whose keys are in the required_encryption_context list. This is ONLY processed if using the aws-cryptographic-materialproviders library - AND its required encryption context CMM - AND if using the v2 message format. (optional) + AND its required encryption context CMM. (optional) :type required_encryption_context_bytes: bytes :returns: Serialized header authentication data :rtype: bytes From 52043b9fd3c5cb3ce40b222f7cfe2e3e0427a771 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Thu, 29 Feb 2024 10:55:07 -0800 Subject: [PATCH 155/184] sync upstream --- src/aws_encryption_sdk/internal/formatting/serialize.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/aws_encryption_sdk/internal/formatting/serialize.py b/src/aws_encryption_sdk/internal/formatting/serialize.py index 310cf1436..9f1325f98 100644 --- a/src/aws_encryption_sdk/internal/formatting/serialize.py +++ b/src/aws_encryption_sdk/internal/formatting/serialize.py @@ -323,7 +323,9 @@ def serialize_header_auth( :rtype: bytes """ if version == SerializationVersion.V1: - return _serialize_header_auth_v1(algorithm, header, data_encryption_key, signer) + return _serialize_header_auth_v1( + algorithm, header, data_encryption_key, signer, required_ec_bytes + ) elif version == SerializationVersion.V2: return _serialize_header_auth_v2( algorithm, header, data_encryption_key, signer, required_ec_bytes From 7374fcb51d1bd1c9d0efd6a4fe9db364f84610ef Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 26 Mar 2024 16:11:55 -0700 Subject: [PATCH 156/184] unit tests --- src/aws_encryption_sdk/streaming_client.py | 123 +++++----- .../unit/test_material_managers_mpl_cmm.py | 3 + .../test_material_managers_mpl_materials.py | 39 ++++ test/unit/test_serialize.py | 50 +++++ test/unit/test_streaming_client_configs.py | 58 +++-- .../test_streaming_client_stream_decryptor.py | 210 ++++++++++++++++++ .../test_streaming_client_stream_encryptor.py | 154 +++++++++++++ 7 files changed, 565 insertions(+), 72 deletions(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index fb0935ff2..4d45918e3 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -954,8 +954,72 @@ def _prep_message(self): self._prep_non_framed() self._message_prepped = True - # TODO-MPL: Refactor this function, remove linter disablers - def _read_header(self): # noqa pylint: disable=too-many-branches + def _create_decrypt_materials_request(self, header): + """ + Create a DecryptionMaterialsRequest based on whether + the StreamDecryptor was provided encryption_context on decrypt + (i.e. expects to use required encryption context CMM from the MPL). + """ + # If encryption_context is provided on decrypt, + # pass it to the DecryptionMaterialsRequest as reproduced_encryption_context + if hasattr(self.config, "encryption_context"): + return DecryptionMaterialsRequest( + encrypted_data_keys=header.encrypted_data_keys, + algorithm=header.algorithm, + encryption_context=header.encryption_context, + commitment_policy=self.config.commitment_policy, + reproduced_encryption_context=self.config.encryption_context + ) + return DecryptionMaterialsRequest( + encrypted_data_keys=header.encrypted_data_keys, + algorithm=header.algorithm, + encryption_context=header.encryption_context, + commitment_policy=self.config.commitment_policy, + ) + + def _validate_parsed_header( + self, + header, + header_auth, + raw_header, + ): + """ + Pass arguments from this StreamDecryptor to validate_header based on whether + the StreamDecryptor has the _required_encryption_context attribute + (i.e. is using the required encryption context CMM from the MPL). + """ + # If _required_encryption_context is present, + # serialize it and pass it to validate_header. + if hasattr(self, "_required_encryption_context") \ + and self._required_encryption_context is not None: + # The authenticated only encryption context is all encryption context key-value pairs where the + # key exists in Required Encryption Context Keys. It is then serialized according to the + # message header Key Value Pairs. + required_ec_serialized = \ + aws_encryption_sdk.internal.formatting.encryption_context.serialize_encryption_context( + self._required_encryption_context + ) + + validate_header( + header=header, + header_auth=header_auth, + # When verifying the header, the AAD input to the authenticated encryption algorithm + # specified by the algorithm suite is the message header body and the serialized + # authenticated only encryption context. + raw_header=raw_header + required_ec_serialized, + data_key=self._derived_data_key + ) + else: + validate_header( + header=header, + header_auth=header_auth, + raw_header=raw_header, + data_key=self._derived_data_key + ) + + return header, header_auth + + def _read_header(self): """Reads the message header from the input stream. :returns: tuple containing deserialized header and header_auth objects @@ -981,24 +1045,7 @@ def _read_header(self): # noqa pylint: disable=too-many-branches ) ) - # If encryption_context is provided on decrypt, - # pass it to the DecryptionMaterialsRequest - if hasattr(self.config, "encryption_context"): - decrypt_materials_request = DecryptionMaterialsRequest( - encrypted_data_keys=header.encrypted_data_keys, - algorithm=header.algorithm, - encryption_context=header.encryption_context, - commitment_policy=self.config.commitment_policy, - reproduced_encryption_context=self.config.encryption_context - ) - else: - decrypt_materials_request = DecryptionMaterialsRequest( - encrypted_data_keys=header.encrypted_data_keys, - algorithm=header.algorithm, - encryption_context=header.encryption_context, - commitment_policy=self.config.commitment_policy, - ) - + decrypt_materials_request = self._create_decrypt_materials_request(header) decryption_materials = self.config.materials_manager.decrypt_materials(request=decrypt_materials_request) # If the materials_manager passed required_encryption_context_keys, @@ -1049,36 +1096,12 @@ def _read_header(self): # noqa pylint: disable=too-many-branches "Key commitment validation failed. Key identity does not match the identity asserted in the " "message. Halting processing of this message." ) - - # If _required_encryption_context is present, - # serialize it and pass it to validate_header. - if self._required_encryption_context is not None: - # The authenticated only encryption context is all encryption context key-value pairs where the - # key exists in Required Encryption Context Keys. It is then serialized according to the - # message header Key Value Pairs. - required_ec_serialized = \ - aws_encryption_sdk.internal.formatting.encryption_context.serialize_encryption_context( - self._required_encryption_context - ) - - validate_header( - header=header, - header_auth=header_auth, - # When verifying the header, the AAD input to the authenticated encryption algorithm - # specified by the algorithm suite is the message header body and the serialized - # authenticated only encryption context. - raw_header=raw_header + required_ec_serialized, - data_key=self._derived_data_key - ) - else: - validate_header( - header=header, - header_auth=header_auth, - raw_header=raw_header, - data_key=self._derived_data_key - ) - - return header, header_auth + + return self._validate_parsed_header( + header=header, + header_auth=header_auth, + raw_header=raw_header, + ) def _prep_non_framed(self): """Prepare the opening data for a non-framed message.""" diff --git a/test/mpl/unit/test_material_managers_mpl_cmm.py b/test/mpl/unit/test_material_managers_mpl_cmm.py index 80d6f00ee..0551e8f30 100644 --- a/test/mpl/unit/test_material_managers_mpl_cmm.py +++ b/test/mpl/unit/test_material_managers_mpl_cmm.py @@ -38,6 +38,7 @@ mock_mpl_cmm = MagicMock(__class__=MPL_ICryptographicMaterialsManager) mock_mpl_encryption_materials = MagicMock(__class__=MPL_EncryptionMaterials) mock_mpl_decrypt_materials = MagicMock(__class__=MPL_DecryptionMaterials) +mock_reproduced_encryption_context = MagicMock(__class_=dict) mock_edk = MagicMock(__class__=Native_EncryptedDataKey) @@ -259,6 +260,7 @@ def test_GIVEN_valid_request_WHEN_create_mpl_decrypt_materials_input_from_reques for mock_edks in [no_mock_edks, one_mock_edk, two_mock_edks]: mock_decryption_materials_request.encrypted_data_keys = mock_edks + mock_decryption_materials_request.reproduced_encryption_context = mock_reproduced_encryption_context # When: _create_mpl_decrypt_materials_input_from_request output = CryptoMaterialsManagerFromMPL._create_mpl_decrypt_materials_input_from_request( @@ -271,6 +273,7 @@ def test_GIVEN_valid_request_WHEN_create_mpl_decrypt_materials_input_from_reques assert output.algorithm_suite_id == mock_algorithm_id assert output.commitment_policy == mock_commitment_policy assert output.encryption_context == mock_decryption_materials_request.encryption_context + assert output.reproduced_encryption_context == mock_reproduced_encryption_context assert len(output.encrypted_data_keys) == len(mock_edks) for i in range(len(output.encrypted_data_keys)): diff --git a/test/mpl/unit/test_material_managers_mpl_materials.py b/test/mpl/unit/test_material_managers_mpl_materials.py index 9e76556a2..94136f806 100644 --- a/test/mpl/unit/test_material_managers_mpl_materials.py +++ b/test/mpl/unit/test_material_managers_mpl_materials.py @@ -160,6 +160,19 @@ def test_GIVEN_valid_signing_key_WHEN_EncryptionMaterials_get_signing_key_THEN_r assert output == mock_signing_key +def test_GIVEN_valid_required_encryption_context_keys_WHEN_EncryptionMaterials_get_required_encryption_context_keys_THEN_returns_required_encryption_context_keys(): + # Given: valid required encryption context keys + mock_required_encryption_context_keys = MagicMock(__class__=bytes) + mock_mpl_encryption_materials.required_encryption_context_keys = mock_required_encryption_context_keys + + # When: get required encryption context keys + mpl_encryption_materials = EncryptionMaterialsFromMPL(mpl_materials=mock_mpl_encryption_materials) + output = mpl_encryption_materials.required_encryption_context_keys + + # Then: returns required encryption context keys + assert output == mock_required_encryption_context_keys + + def test_GIVEN_valid_data_key_WHEN_DecryptionMaterials_get_data_key_THEN_returns_data_key(): # Given: valid MPL data key mock_data_key = MagicMock(__class__=bytes) @@ -187,3 +200,29 @@ def test_GIVEN_valid_verification_key_WHEN_DecryptionMaterials_get_verification_ # Then: returns verification key assert output == mock_verification_key + + +def test_GIVEN_valid_encryption_context_WHEN_DecryptionMaterials_get_encryption_context_THEN_returns_encryption_context(): + # Given: valid encryption context + mock_encryption_context = MagicMock(__class__=Dict[str, str]) + mock_mpl_decrypt_materials.encryption_context = mock_encryption_context + + # When: get encryption context + mpl_decryption_materials = DecryptionMaterialsFromMPL(mpl_materials=mock_mpl_decrypt_materials) + output = mpl_decryption_materials.encryption_context + + # Then: returns valid encryption context + assert output == mock_encryption_context + + +def test_GIVEN_valid_required_encryption_context_keys_WHEN_DecryptionMaterials_get_required_encryption_context_keys_THEN_returns_required_encryption_context_keys(): + # Given: valid required encryption context keys + mock_required_encryption_context_keys = MagicMock(__class__=bytes) + mock_mpl_decrypt_materials.required_encryption_context_keys = mock_required_encryption_context_keys + + # When: get required encryption context keys + mpl_decryption_materials = DecryptionMaterialsFromMPL(mpl_materials=mock_mpl_decrypt_materials) + output = mpl_decryption_materials.required_encryption_context_keys + + # Then: returns required encryption context keys + assert output == mock_required_encryption_context_keys diff --git a/test/unit/test_serialize.py b/test/unit/test_serialize.py index 56da114b4..5e294bf51 100644 --- a/test/unit/test_serialize.py +++ b/test/unit/test_serialize.py @@ -79,6 +79,7 @@ def apply_fixtures(self): "aws_encryption_sdk.internal.formatting.serialize.aws_encryption_sdk.internal.utils.validate_frame_length" ) self.mock_valid_frame_length = self.mock_valid_frame_length_patcher.start() + self.mock_required_ec_bytes = MagicMock() # Set up mock signer self.mock_signer = MagicMock() self.mock_signer.update.return_value = None @@ -167,6 +168,31 @@ def test_serialize_header_auth_v1_no_signer(self): data_encryption_key=VALUES["data_key_obj"], ) + @patch("aws_encryption_sdk.internal.formatting.serialize.header_auth_iv") + def test_GIVEN_required_ec_bytes_WHEN_serialize_header_auth_v1_THEN_aad_has_required_ec_bytes(self, mock_header_auth_iv): + """Validate that the _create_header_auth function + behaves as expected for SerializationVersion.V1 + when required_ec_bytes are provided. + """ + self.mock_encrypt.return_value = VALUES["header_auth_base"] + test = aws_encryption_sdk.internal.formatting.serialize.serialize_header_auth( + version=SerializationVersion.V1, + algorithm=self.mock_algorithm, + header=VALUES["serialized_header"], + data_encryption_key=sentinel.encryption_key, + signer=self.mock_signer, + required_ec_bytes=self.mock_required_ec_bytes, + ) + self.mock_encrypt.assert_called_once_with( + algorithm=self.mock_algorithm, + key=sentinel.encryption_key, + plaintext=b"", + associated_data=VALUES["serialized_header"] + self.mock_required_ec_bytes, + iv=mock_header_auth_iv.return_value, + ) + self.mock_signer.update.assert_called_once_with(VALUES["serialized_header_auth"]) + assert test == VALUES["serialized_header_auth"] + @patch("aws_encryption_sdk.internal.formatting.serialize.header_auth_iv") def test_serialize_header_auth_v2(self, mock_header_auth_iv): """Validate that the _create_header_auth function @@ -203,6 +229,30 @@ def test_serialize_header_auth_v2_no_signer(self): data_encryption_key=VALUES["data_key_obj"], ) + @patch("aws_encryption_sdk.internal.formatting.serialize.header_auth_iv") + def test_GIVEN_required_ec_bytes_WHEN_serialize_header_auth_v2_THEN_aad_has_required_ec_bytes(self, mock_header_auth_iv): + """Validate that the _create_header_auth function + behaves as expected for SerializationVersion.V2. + """ + self.mock_encrypt.return_value = VALUES["header_auth_base"] + test = aws_encryption_sdk.internal.formatting.serialize.serialize_header_auth( + version=SerializationVersion.V2, + algorithm=self.mock_algorithm, + header=VALUES["serialized_header_v2_committing"], + data_encryption_key=sentinel.encryption_key, + signer=self.mock_signer, + required_ec_bytes=self.mock_required_ec_bytes, + ) + self.mock_encrypt.assert_called_once_with( + algorithm=self.mock_algorithm, + key=sentinel.encryption_key, + plaintext=b"", + associated_data=VALUES["serialized_header_v2_committing"] + self.mock_required_ec_bytes, + iv=mock_header_auth_iv.return_value, + ) + self.mock_signer.update.assert_called_once_with(VALUES["serialized_header_auth_v2"]) + assert test == VALUES["serialized_header_auth_v2"] + def test_serialize_non_framed_open(self): """Validate that the serialize_non_framed_open function behaves as expected. diff --git a/test/unit/test_streaming_client_configs.py b/test/unit/test_streaming_client_configs.py index 18886f65b..6ddba3f60 100644 --- a/test/unit/test_streaming_client_configs.py +++ b/test/unit/test_streaming_client_configs.py @@ -15,7 +15,7 @@ import pytest import six -from mock import patch +from mock import MagicMock, patch from aws_encryption_sdk import CommitmentPolicy from aws_encryption_sdk.internal.defaults import ALGORITHM, FRAME_LENGTH, LINE_LENGTH @@ -33,7 +33,10 @@ # Ideally, this logic would be based on mocking imports and testing logic, # but doing that introduces errors that cause other tests to fail. try: - from aws_cryptographic_materialproviders.mpl.references import IKeyring + from aws_cryptographic_materialproviders.mpl.references import ( + ICryptographicMaterialsManager, + IKeyring, + ) HAS_MPL = True from aws_encryption_sdk.materials_managers.mpl.cmm import CryptoMaterialsManagerFromMPL @@ -236,24 +239,21 @@ def test_client_configs_with_mpl( assert test.materials_manager is not None # If materials manager was provided, it should be directly used - if hasattr(kwargs, "materials_manager"): + if "materials_manager" in kwargs: assert kwargs["materials_manager"] == test.materials_manager - # If MPL keyring was provided, it should be wrapped in MPL materials manager - if hasattr(kwargs, "keyring"): - assert test.keyring is not None - assert test.keyring == kwargs["keyring"] - assert isinstance(test.keyring, IKeyring) - assert isinstance(test.materials_manager, CryptoMaterialsManagerFromMPL) - # If native key_provider was provided, it should be wrapped in native materials manager - if hasattr(kwargs, "key_provider"): + elif "key_provider" in kwargs: assert test.key_provider is not None assert test.key_provider == kwargs["key_provider"] assert isinstance(test.materials_manager, DefaultCryptoMaterialsManager) + else: + raise ValueError(f"Test did not find materials_manager or key_provider. {kwargs}") + -# This needs its own test; pytest parametrize cannot use a conditionally-loaded type +# This is an addition to test_client_configs_with_mpl; +# This needs its own test; pytest's parametrize cannot use a conditionally-loaded type (IKeyring) @pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") def test_keyring_client_config_with_mpl( ): @@ -265,16 +265,30 @@ def test_keyring_client_config_with_mpl( test = _ClientConfig(**kwargs) - # In all cases, config should have a materials manager assert test.materials_manager is not None - # If materials manager was provided, it should be directly used - if hasattr(kwargs, "materials_manager"): - assert kwargs["materials_manager"] == test.materials_manager + assert test.keyring is not None + assert test.keyring == kwargs["keyring"] + assert isinstance(test.keyring, IKeyring) + assert isinstance(test.materials_manager, CryptoMaterialsManagerFromMPL) + + +# This is an addition to test_client_configs_with_mpl; +# This needs its own test; pytest's parametrize cannot use a conditionally-loaded type (MPL CMM) +@pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") +def test_mpl_cmm_client_config_with_mpl( +): + mock_mpl_cmm = MagicMock(__class__=ICryptographicMaterialsManager) + kwargs = { + "source": b"", + "materials_manager": mock_mpl_cmm, + "commitment_policy": CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT + } + + test = _ClientConfig(**kwargs) - # If MPL keyring was provided, it should be wrapped in MPL materials manager - if hasattr(kwargs, "keyring"): - assert test.keyring is not None - assert test.keyring == kwargs["keyring"] - assert isinstance(test.keyring, IKeyring) - assert isinstance(test.materials_manager, CryptoMaterialsManagerFromMPL) + assert test.materials_manager is not None + # Assert that the MPL CMM is wrapped in the native interface + assert isinstance(test.materials_manager, CryptoMaterialsManagerFromMPL) + # Assert the MPL CMM is used by the native interface + assert test.materials_manager.mpl_cmm == mock_mpl_cmm diff --git a/test/unit/test_streaming_client_stream_decryptor.py b/test/unit/test_streaming_client_stream_decryptor.py index 2066dcbdb..74d01e5bd 100644 --- a/test/unit/test_streaming_client_stream_decryptor.py +++ b/test/unit/test_streaming_client_stream_decryptor.py @@ -924,3 +924,213 @@ def test_close_no_footer(self, mock_close): with pytest.raises(SerializationError) as excinfo: test_decryptor.close() excinfo.match("Footer not read") + + @patch("aws_encryption_sdk.streaming_client.validate_header") + def test_GIVEN_does_not_have_required_EC_WHEN_validate_parsed_header_THEN_validate_header( + self, + mock_validate_header + ): + self.mock_header.content_type = ContentType.FRAMED_DATA + test_decryptor = StreamDecryptor( + materials_manager=self.mock_materials_manager, + source=self.mock_input_stream, + commitment_policy=self.mock_commitment_policy, + ) + test_decryptor._derived_data_key = sentinel.derived_data_key + # Given: test_decryptor does not have _required_encryption_context attribute + # When: _validate_parsed_header + test_decryptor._validate_parsed_header( + header=self.mock_header, + header_auth=sentinel.header_auth, + raw_header=self.mock_raw_header + ) + # Then: validate_header + mock_validate_header.assert_called_once_with( + header=self.mock_header, + header_auth=sentinel.header_auth, + raw_header=self.mock_raw_header, + data_key=sentinel.derived_data_key, + ) + + @patch("aws_encryption_sdk.internal.formatting.encryption_context.serialize_encryption_context") + @patch("aws_encryption_sdk.streaming_client.validate_header") + def test_GIVEN_has_required_EC_WHEN_validate_parsed_header_THEN_validate_header_with_serialized_required_EC( + self, + mock_validate_header, + mock_serialize_encryption_context, + ): + self.mock_header.content_type = ContentType.FRAMED_DATA + test_decryptor = StreamDecryptor( + materials_manager=self.mock_materials_manager, + source=self.mock_input_stream, + commitment_policy=self.mock_commitment_policy, + ) + test_decryptor._derived_data_key = sentinel.derived_data_key + # Given: test_decryptor has _required_encryption_context attribute + mock_required_ec = MagicMock(__class__=dict) + test_decryptor._required_encryption_context = mock_required_ec + mock_serialized_required_ec = MagicMock(__class__=bytes) + mock_serialize_encryption_context.return_value = mock_serialized_required_ec + # When: _validate_parsed_header + test_decryptor._validate_parsed_header( + header=self.mock_header, + header_auth=sentinel.header_auth, + raw_header=self.mock_raw_header + ) + # Then: call validate_header with serialized required EC + mock_validate_header.assert_called_once_with( + header=self.mock_header, + header_auth=sentinel.header_auth, + raw_header=self.mock_raw_header + mock_serialized_required_ec, + data_key=sentinel.derived_data_key, + ) + + def test_GIVEN_config_has_EC_WHEN_create_decrypt_materials_request_THEN_provide_reproduced_EC( + self, + ): + self.mock_header.content_type = ContentType.FRAMED_DATA + test_decryptor = StreamDecryptor( + materials_manager=self.mock_materials_manager, + source=self.mock_input_stream, + commitment_policy=self.mock_commitment_policy, + ) + + # Given: StreamDecryptor.config has encryption_context attribute + mock_reproduced_encryption_context = MagicMock(__class__=dict) + test_decryptor.config.encryption_context = mock_reproduced_encryption_context + # Type checking on header encryption context seems to require concrete instance, + # neither MagicMock nor sentinel value work + self.mock_header.encryption_context = {"some_key_to_pass_type_validation": "some_value"} + + # When: _create_decrypt_materials_request + output = test_decryptor._create_decrypt_materials_request( + header=self.mock_header, + ) + + # Then: decrypt_materials_request has reproduced_encryption_context attribute + assert hasattr(output, "reproduced_encryption_context") + assert output.reproduced_encryption_context == mock_reproduced_encryption_context + + def test_GIVEN_config_does_not_have_EC_WHEN_create_decrypt_materials_request_THEN_request_does_not_have_reproduced_EC( + self, + ): + self.mock_header.content_type = ContentType.FRAMED_DATA + test_decryptor = StreamDecryptor( + materials_manager=self.mock_materials_manager, + source=self.mock_input_stream, + commitment_policy=self.mock_commitment_policy, + ) + + # Given: StreamDecryptor.config does not have an encryption_context attribute + del test_decryptor.config.encryption_context + # Type checking on header encryption context seems to require concrete instance, + # neither MagicMock nor sentinel value work + self.mock_header.encryption_context = {"some_key_to_pass_type_validation": "some_value"} + + # When: _create_decrypt_materials_request + output = test_decryptor._create_decrypt_materials_request( + header=self.mock_header, + ) + + # Then: decrypt_materials_request.reproduced_encryption_context is None + assert output.reproduced_encryption_context is None + + @patch("aws_encryption_sdk.streaming_client.derive_data_encryption_key") + @patch("aws_encryption_sdk.streaming_client.DecryptionMaterialsRequest") + @patch("aws_encryption_sdk.streaming_client.Verifier") + def test_GIVEN_materials_has_no_required_encryption_context_keys_attr_WHEN_read_header_THEN_required_EC_is_None( + self, + mock_verifier, + *_ + ): + + mock_verifier_instance = MagicMock() + mock_verifier.from_key_bytes.return_value = mock_verifier_instance + + self.mock_header.content_type = ContentType.FRAMED_DATA + test_decryptor = StreamDecryptor( + materials_manager=self.mock_materials_manager, + source=self.mock_input_stream, + commitment_policy=self.mock_commitment_policy, + ) + + # Given: decryption_materials does not have a required_encryption_context_keys attribute + del self.mock_decrypt_materials.required_encryption_context_keys + + # When: _read_header + test_decryptor._read_header() + + # Then: StreamDecryptor._required_encryption_context is None + assert test_decryptor._required_encryption_context is None + + @patch("aws_encryption_sdk.streaming_client.derive_data_encryption_key") + @patch("aws_encryption_sdk.streaming_client.DecryptionMaterialsRequest") + @patch("aws_encryption_sdk.streaming_client.Verifier") + def test_GIVEN_materials_has_required_encryption_context_keys_attr_WHEN_read_header_THEN_creates_correct_required_EC( + self, + mock_verifier, + *_ + ): + required_encryption_context_keys_values = [ + # Case of empty encryption context list is not allowed; + # if a list is provided, it must be non-empty. + # The MPL enforces this behavior on construction. + ["one_key"], + ["one_key", "two_key"], + ["one_key", "two_key", "red_key"], + ["one_key", "two_key", "red_key", "blue_key"], + ] + + encryption_context_values = [ + {}, + {"one_key": "some_value"}, + { + "one_key": "some_value", + "two_key": "some_other_value", + }, + { + "one_key": "some_value", + "two_key": "some_other_value", + "red_key": "some_red_value", + }, + { + "one_key": "some_value", + "two_key": "some_other_value", + "red_key": "some_red_value", + "blue_key": "some_blue_value", + } + ] + + for required_encryption_context_keys in required_encryption_context_keys_values: + + # Given: decryption_materials has required_encryption_context_keys + self.mock_decrypt_materials.required_encryption_context_keys = \ + required_encryption_context_keys + + for encryption_context in encryption_context_values: + + self.mock_decrypt_materials.encryption_context = encryption_context + + mock_verifier_instance = MagicMock() + mock_verifier.from_key_bytes.return_value = mock_verifier_instance + + self.mock_header.content_type = ContentType.FRAMED_DATA + test_decryptor = StreamDecryptor( + materials_manager=self.mock_materials_manager, + source=self.mock_input_stream, + commitment_policy=self.mock_commitment_policy, + ) + + # When: _read_header + test_decryptor._read_header() + + # Then: Assert correctness of partitioned EC + for k in encryption_context: + # If a key is in required_encryption_context_keys, then ... + if k in required_encryption_context_keys: + # ... its EC is in the StreamEncryptor._required_encryption_context + assert k in test_decryptor._required_encryption_context + # If a key is NOT in required_encryption_context_keys, then ... + else: + # ... its EC is NOT in the StreamEncryptor._required_encryption_context + assert k not in test_decryptor._required_encryption_context diff --git a/test/unit/test_streaming_client_stream_encryptor.py b/test/unit/test_streaming_client_stream_encryptor.py index e43752689..2d18f67ce 100644 --- a/test/unit/test_streaming_client_stream_encryptor.py +++ b/test/unit/test_streaming_client_stream_encryptor.py @@ -451,6 +451,113 @@ def test_GIVEN_has_mpl_AND_has_MPLCMM_AND_uses_signer_WHEN_prep_message_THEN_sig encoding=serialization.Encoding.PEM ) + # Given: has MPL + @pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") + def test_GIVEN_has_mpl_AND_encryption_materials_has_required_EC_keys_WHEN_prep_message_THEN_paritions_stored_and_required_EC(self): + # Create explicit values to explicitly test logic in smaller cases + required_encryption_context_keys_values = [ + # Case of empty encryption context list is not allowed; + # if a list is provided, it must be non-empty. + # The MPL enforces this behavior on construction. + ["one_key"], + ["one_key", "two_key"], + ["one_key", "two_key", "red_key"], + ["one_key", "two_key", "red_key", "blue_key"], + ] + + encryption_context_values = [ + {}, + {"one_key": "some_value"}, + { + "one_key": "some_value", + "two_key": "some_other_value", + }, + { + "one_key": "some_value", + "two_key": "some_other_value", + "red_key": "some_red_value", + }, + { + "one_key": "some_value", + "two_key": "some_other_value", + "red_key": "some_red_value", + "blue_key": "some_blue_value", + } + ] + + self.mock_encryption_materials.algorithm = Algorithm.AES_128_GCM_IV12_TAG16 + + for required_encryption_context_keys in required_encryption_context_keys_values: + + # Given: encryption context has required_encryption_context_keys + self.mock_encryption_materials.required_encryption_context_keys = \ + required_encryption_context_keys + + for encryption_context in encryption_context_values: + self.mock_encryption_materials.encryption_context = encryption_context + + test_encryptor = StreamEncryptor( + source=VALUES["data_128"], + materials_manager=self.mock_mpl_materials_manager, + frame_length=self.mock_frame_length, + algorithm=Algorithm.AES_128_GCM_IV12_TAG16, + commitment_policy=self.mock_commitment_policy, + signature_policy=self.mock_signature_policy, + ) + test_encryptor.content_type = ContentType.FRAMED_DATA + # When: prep_message + test_encryptor._prep_message() + + # Then: Assert correctness of partitioned EC + for k in encryption_context: + # If a key is in required_encryption_context_keys, then + if k in required_encryption_context_keys: + # 1) Its EC is in the StreamEncryptor._required_encryption_context + assert k in test_encryptor._required_encryption_context + # 2) Its EC is NOT in the StreamEncryptor._stored_encryption_context + assert k not in test_encryptor._stored_encryption_context + # If a key is NOT in required_encryption_context_keys, then + else: + # 1) Its EC is NOT in the StreamEncryptor._required_encryption_context + assert k not in test_encryptor._required_encryption_context + # 2) Its EC is in the StreamEncryptor._stored_encryption_context + assert k in test_encryptor._stored_encryption_context + + # Assert size(stored_EC) + size(required_EC) == size(EC) + # (i.e. every EC was sorted into one or the other) + assert len(test_encryptor._required_encryption_context) \ + + len(test_encryptor._stored_encryption_context) \ + == len(encryption_context) + + # Given: has MPL + @pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") + def test_GIVEN_has_mpl_AND_encryption_materials_does_not_have_required_EC_keys_WHEN_prep_message_THEN_stored_EC_is_EC(self): + + self.mock_encryption_materials.algorithm = Algorithm.AES_128_GCM_IV12_TAG16 + + mock_encryption_context = MagicMock(__class__=dict) + self.mock_encryption_materials.encryption_context = mock_encryption_context + # Given: encryption materials does not have required encryption context keys + # (MagicMock default is to "make up" "Some" value here; this deletes that value) + del self.mock_encryption_materials.required_encryption_context_keys + + test_encryptor = StreamEncryptor( + source=VALUES["data_128"], + materials_manager=self.mock_mpl_materials_manager, + frame_length=self.mock_frame_length, + algorithm=Algorithm.AES_128_GCM_IV12_TAG16, + commitment_policy=self.mock_commitment_policy, + signature_policy=self.mock_signature_policy, + ) + test_encryptor.content_type = ContentType.FRAMED_DATA + # When: prep_message + test_encryptor._prep_message() + + # Then: _stored_encryption_context is the provided encryption_context + assert test_encryptor._stored_encryption_context == mock_encryption_context + # Then: _required_encryption_context is None + assert test_encryptor._required_encryption_context is None + def test_prep_message_no_signer(self): self.mock_encryption_materials.algorithm = Algorithm.AES_128_GCM_IV12_TAG16 test_encryptor = StreamEncryptor( @@ -575,6 +682,53 @@ def test_write_header(self): ) assert test_encryptor.output_buffer == b"1234567890" + @patch("aws_encryption_sdk.internal.formatting.encryption_context.serialize_encryption_context") + # Given: has MPL + @pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") + def test_GIVEN_has_mpl_AND_has_required_EC_WHEN_write_header_THEN_adds_serialized_required_ec_to_header_auth( + self, + serialize_encryption_context + ): + self.mock_serialize_header.return_value = b"12345" + self.mock_serialize_header_auth.return_value = b"67890" + pt_stream = io.BytesIO(self.plaintext) + test_encryptor = StreamEncryptor( + source=pt_stream, + materials_manager=self.mock_materials_manager, + algorithm=aws_encryption_sdk.internal.defaults.ALGORITHM, + frame_length=self.mock_frame_length, + commitment_policy=self.mock_commitment_policy, + signature_policy=self.mock_signature_policy, + ) + test_encryptor.signer = sentinel.signer + test_encryptor.content_type = sentinel.content_type + test_encryptor._header = sentinel.header + sentinel.header.version = SerializationVersion.V1 + test_encryptor.output_buffer = b"" + test_encryptor._encryption_materials = self.mock_encryption_materials + test_encryptor._derived_data_key = sentinel.derived_data_key + + # Given: StreamEncryptor has _required_encryption_context + mock_required_ec = MagicMock(__class__=dict) + test_encryptor._required_encryption_context = mock_required_ec + mock_serialized_required_ec = MagicMock(__class__=bytes) + serialize_encryption_context.return_value = mock_serialized_required_ec + + # When: _write_header() + test_encryptor._write_header() + + self.mock_serialize_header.assert_called_once_with(header=test_encryptor._header, signer=sentinel.signer) + self.mock_serialize_header_auth.assert_called_once_with( + version=sentinel.header.version, + algorithm=self.mock_encryption_materials.algorithm, + header=b"12345", + data_encryption_key=sentinel.derived_data_key, + signer=sentinel.signer, + # Then: Pass serialized required EC to serialize_header_auth + required_ec_bytes=mock_serialized_required_ec, + ) + assert test_encryptor.output_buffer == b"1234567890" + @patch("aws_encryption_sdk.streaming_client.non_framed_body_iv") def test_prep_non_framed(self, mock_non_framed_iv): self.mock_serialize_non_framed_open.return_value = b"1234567890" From f9d60a8eeb54a916ca09fb660defc90e77606f03 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 26 Mar 2024 16:19:46 -0700 Subject: [PATCH 157/184] lint --- src/aws_encryption_sdk/streaming_client.py | 4 ++-- .../test_material_managers_mpl_materials.py | 6 +++--- test/unit/test_serialize.py | 10 ++++++++-- .../test_streaming_client_stream_decryptor.py | 10 ++++++---- .../test_streaming_client_stream_encryptor.py | 19 +++++++++++++------ 5 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 4d45918e3..98b54b3df 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -976,7 +976,7 @@ def _create_decrypt_materials_request(self, header): encryption_context=header.encryption_context, commitment_policy=self.config.commitment_policy, ) - + def _validate_parsed_header( self, header, @@ -1096,7 +1096,7 @@ def _read_header(self): "Key commitment validation failed. Key identity does not match the identity asserted in the " "message. Halting processing of this message." ) - + return self._validate_parsed_header( header=header, header_auth=header_auth, diff --git a/test/mpl/unit/test_material_managers_mpl_materials.py b/test/mpl/unit/test_material_managers_mpl_materials.py index 94136f806..8d9052c0a 100644 --- a/test/mpl/unit/test_material_managers_mpl_materials.py +++ b/test/mpl/unit/test_material_managers_mpl_materials.py @@ -160,7 +160,7 @@ def test_GIVEN_valid_signing_key_WHEN_EncryptionMaterials_get_signing_key_THEN_r assert output == mock_signing_key -def test_GIVEN_valid_required_encryption_context_keys_WHEN_EncryptionMaterials_get_required_encryption_context_keys_THEN_returns_required_encryption_context_keys(): +def test_GIVEN_valid_required_encryption_context_keys_WHEN_EncryptionMaterials_get_required_encryption_context_keys_THEN_returns_required_encryption_context_keys(): # noqa pylint: disable=line-too-long # Given: valid required encryption context keys mock_required_encryption_context_keys = MagicMock(__class__=bytes) mock_mpl_encryption_materials.required_encryption_context_keys = mock_required_encryption_context_keys @@ -202,7 +202,7 @@ def test_GIVEN_valid_verification_key_WHEN_DecryptionMaterials_get_verification_ assert output == mock_verification_key -def test_GIVEN_valid_encryption_context_WHEN_DecryptionMaterials_get_encryption_context_THEN_returns_encryption_context(): +def test_GIVEN_valid_encryption_context_WHEN_DecryptionMaterials_get_encryption_context_THEN_returns_encryption_context(): # noqa pylint: disable=line-too-long # Given: valid encryption context mock_encryption_context = MagicMock(__class__=Dict[str, str]) mock_mpl_decrypt_materials.encryption_context = mock_encryption_context @@ -215,7 +215,7 @@ def test_GIVEN_valid_encryption_context_WHEN_DecryptionMaterials_get_encryption_ assert output == mock_encryption_context -def test_GIVEN_valid_required_encryption_context_keys_WHEN_DecryptionMaterials_get_required_encryption_context_keys_THEN_returns_required_encryption_context_keys(): +def test_GIVEN_valid_required_encryption_context_keys_WHEN_DecryptionMaterials_get_required_encryption_context_keys_THEN_returns_required_encryption_context_keys(): # noqa pylint: disable=line-too-long # Given: valid required encryption context keys mock_required_encryption_context_keys = MagicMock(__class__=bytes) mock_mpl_decrypt_materials.required_encryption_context_keys = mock_required_encryption_context_keys diff --git a/test/unit/test_serialize.py b/test/unit/test_serialize.py index 5e294bf51..06ac6126b 100644 --- a/test/unit/test_serialize.py +++ b/test/unit/test_serialize.py @@ -169,7 +169,10 @@ def test_serialize_header_auth_v1_no_signer(self): ) @patch("aws_encryption_sdk.internal.formatting.serialize.header_auth_iv") - def test_GIVEN_required_ec_bytes_WHEN_serialize_header_auth_v1_THEN_aad_has_required_ec_bytes(self, mock_header_auth_iv): + def test_GIVEN_required_ec_bytes_WHEN_serialize_header_auth_v1_THEN_aad_has_required_ec_bytes( + self, + mock_header_auth_iv, + ): """Validate that the _create_header_auth function behaves as expected for SerializationVersion.V1 when required_ec_bytes are provided. @@ -230,7 +233,10 @@ def test_serialize_header_auth_v2_no_signer(self): ) @patch("aws_encryption_sdk.internal.formatting.serialize.header_auth_iv") - def test_GIVEN_required_ec_bytes_WHEN_serialize_header_auth_v2_THEN_aad_has_required_ec_bytes(self, mock_header_auth_iv): + def test_GIVEN_required_ec_bytes_WHEN_serialize_header_auth_v2_THEN_aad_has_required_ec_bytes( + self, + mock_header_auth_iv, + ): """Validate that the _create_header_auth function behaves as expected for SerializationVersion.V2. """ diff --git a/test/unit/test_streaming_client_stream_decryptor.py b/test/unit/test_streaming_client_stream_decryptor.py index 74d01e5bd..e29f8d16d 100644 --- a/test/unit/test_streaming_client_stream_decryptor.py +++ b/test/unit/test_streaming_client_stream_decryptor.py @@ -11,6 +11,7 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Unit test suite for aws_encryption_sdk.streaming_client.StreamDecryptor""" +# noqa pylint: disable=too-many-lines import io import pytest @@ -1011,7 +1012,7 @@ def test_GIVEN_config_has_EC_WHEN_create_decrypt_materials_request_THEN_provide_ assert hasattr(output, "reproduced_encryption_context") assert output.reproduced_encryption_context == mock_reproduced_encryption_context - def test_GIVEN_config_does_not_have_EC_WHEN_create_decrypt_materials_request_THEN_request_does_not_have_reproduced_EC( + def test_GIVEN_config_does_not_have_EC_WHEN_create_decrypt_materials_request_THEN_request_does_not_have_reproduced_EC( # noqa pylint: disable=line-too-long self, ): self.mock_header.content_type = ContentType.FRAMED_DATA @@ -1066,7 +1067,7 @@ def test_GIVEN_materials_has_no_required_encryption_context_keys_attr_WHEN_read_ @patch("aws_encryption_sdk.streaming_client.derive_data_encryption_key") @patch("aws_encryption_sdk.streaming_client.DecryptionMaterialsRequest") @patch("aws_encryption_sdk.streaming_client.Verifier") - def test_GIVEN_materials_has_required_encryption_context_keys_attr_WHEN_read_header_THEN_creates_correct_required_EC( + def test_GIVEN_materials_has_required_encryption_context_keys_attr_WHEN_read_header_THEN_creates_correct_required_EC( # noqa pylint: disable=line-too-long self, mock_verifier, *_ @@ -1106,9 +1107,9 @@ def test_GIVEN_materials_has_required_encryption_context_keys_attr_WHEN_read_hea # Given: decryption_materials has required_encryption_context_keys self.mock_decrypt_materials.required_encryption_context_keys = \ required_encryption_context_keys - + for encryption_context in encryption_context_values: - + self.mock_decrypt_materials.encryption_context = encryption_context mock_verifier_instance = MagicMock() @@ -1130,6 +1131,7 @@ def test_GIVEN_materials_has_required_encryption_context_keys_attr_WHEN_read_hea if k in required_encryption_context_keys: # ... its EC is in the StreamEncryptor._required_encryption_context assert k in test_decryptor._required_encryption_context + assert test_decryptor._required_encryption_context[k] == encryption_context[k] # If a key is NOT in required_encryption_context_keys, then ... else: # ... its EC is NOT in the StreamEncryptor._required_encryption_context diff --git a/test/unit/test_streaming_client_stream_encryptor.py b/test/unit/test_streaming_client_stream_encryptor.py index 2d18f67ce..7d97d1b25 100644 --- a/test/unit/test_streaming_client_stream_encryptor.py +++ b/test/unit/test_streaming_client_stream_encryptor.py @@ -11,6 +11,7 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Unit test suite for aws_encryption_sdk.streaming_client.StreamEncryptor""" +# noqa pylint: disable=too-many-lines import io import pytest @@ -453,7 +454,9 @@ def test_GIVEN_has_mpl_AND_has_MPLCMM_AND_uses_signer_WHEN_prep_message_THEN_sig # Given: has MPL @pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") - def test_GIVEN_has_mpl_AND_encryption_materials_has_required_EC_keys_WHEN_prep_message_THEN_paritions_stored_and_required_EC(self): + def test_GIVEN_has_mpl_AND_encryption_materials_has_required_EC_keys_WHEN_prep_message_THEN_paritions_stored_and_required_EC( # noqa pylint: disable=line-too-long + self + ): # Create explicit values to explicitly test logic in smaller cases required_encryption_context_keys_values = [ # Case of empty encryption context list is not allowed; @@ -492,7 +495,7 @@ def test_GIVEN_has_mpl_AND_encryption_materials_has_required_EC_keys_WHEN_prep_m # Given: encryption context has required_encryption_context_keys self.mock_encryption_materials.required_encryption_context_keys = \ required_encryption_context_keys - + for encryption_context in encryption_context_values: self.mock_encryption_materials.encryption_context = encryption_context @@ -514,6 +517,7 @@ def test_GIVEN_has_mpl_AND_encryption_materials_has_required_EC_keys_WHEN_prep_m if k in required_encryption_context_keys: # 1) Its EC is in the StreamEncryptor._required_encryption_context assert k in test_encryptor._required_encryption_context + assert test_encryptor._required_encryption_context[k] == encryption_context[k] # 2) Its EC is NOT in the StreamEncryptor._stored_encryption_context assert k not in test_encryptor._stored_encryption_context # If a key is NOT in required_encryption_context_keys, then @@ -522,16 +526,19 @@ def test_GIVEN_has_mpl_AND_encryption_materials_has_required_EC_keys_WHEN_prep_m assert k not in test_encryptor._required_encryption_context # 2) Its EC is in the StreamEncryptor._stored_encryption_context assert k in test_encryptor._stored_encryption_context - + assert test_encryptor._stored_encryption_context[k] == encryption_context[k] + # Assert size(stored_EC) + size(required_EC) == size(EC) # (i.e. every EC was sorted into one or the other) assert len(test_encryptor._required_encryption_context) \ + len(test_encryptor._stored_encryption_context) \ == len(encryption_context) - + # Given: has MPL @pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") - def test_GIVEN_has_mpl_AND_encryption_materials_does_not_have_required_EC_keys_WHEN_prep_message_THEN_stored_EC_is_EC(self): + def test_GIVEN_has_mpl_AND_encryption_materials_does_not_have_required_EC_keys_WHEN_prep_message_THEN_stored_EC_is_EC( # noqa pylint: disable=line-too-long + self + ): self.mock_encryption_materials.algorithm = Algorithm.AES_128_GCM_IV12_TAG16 @@ -557,7 +564,7 @@ def test_GIVEN_has_mpl_AND_encryption_materials_does_not_have_required_EC_keys_W assert test_encryptor._stored_encryption_context == mock_encryption_context # Then: _required_encryption_context is None assert test_encryptor._required_encryption_context is None - + def test_prep_message_no_signer(self): self.mock_encryption_materials.algorithm = Algorithm.AES_128_GCM_IV12_TAG16 test_encryptor = StreamEncryptor( From 16725f86b103258123b231c5c09d9e30b4e2725c Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 26 Mar 2024 16:24:43 -0700 Subject: [PATCH 158/184] lint --- test/unit/test_streaming_client_configs.py | 5 +---- test/unit/test_streaming_client_stream_decryptor.py | 4 ++-- test/unit/test_streaming_client_stream_encryptor.py | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/test/unit/test_streaming_client_configs.py b/test/unit/test_streaming_client_configs.py index 6ddba3f60..ba69da704 100644 --- a/test/unit/test_streaming_client_configs.py +++ b/test/unit/test_streaming_client_configs.py @@ -33,10 +33,7 @@ # Ideally, this logic would be based on mocking imports and testing logic, # but doing that introduces errors that cause other tests to fail. try: - from aws_cryptographic_materialproviders.mpl.references import ( - ICryptographicMaterialsManager, - IKeyring, - ) + from aws_cryptographic_materialproviders.mpl.references import ICryptographicMaterialsManager, IKeyring HAS_MPL = True from aws_encryption_sdk.materials_managers.mpl.cmm import CryptoMaterialsManagerFromMPL diff --git a/test/unit/test_streaming_client_stream_decryptor.py b/test/unit/test_streaming_client_stream_decryptor.py index e29f8d16d..cb003d868 100644 --- a/test/unit/test_streaming_client_stream_decryptor.py +++ b/test/unit/test_streaming_client_stream_decryptor.py @@ -1071,7 +1071,7 @@ def test_GIVEN_materials_has_required_encryption_context_keys_attr_WHEN_read_hea self, mock_verifier, *_ - ): + ): required_encryption_context_keys_values = [ # Case of empty encryption context list is not allowed; # if a list is provided, it must be non-empty. @@ -1126,7 +1126,7 @@ def test_GIVEN_materials_has_required_encryption_context_keys_attr_WHEN_read_hea test_decryptor._read_header() # Then: Assert correctness of partitioned EC - for k in encryption_context: + for k in encryption_context.keys(): # If a key is in required_encryption_context_keys, then ... if k in required_encryption_context_keys: # ... its EC is in the StreamEncryptor._required_encryption_context diff --git a/test/unit/test_streaming_client_stream_encryptor.py b/test/unit/test_streaming_client_stream_encryptor.py index 7d97d1b25..e456dfd21 100644 --- a/test/unit/test_streaming_client_stream_encryptor.py +++ b/test/unit/test_streaming_client_stream_encryptor.py @@ -512,7 +512,7 @@ def test_GIVEN_has_mpl_AND_encryption_materials_has_required_EC_keys_WHEN_prep_m test_encryptor._prep_message() # Then: Assert correctness of partitioned EC - for k in encryption_context: + for k in encryption_context.keys(): # If a key is in required_encryption_context_keys, then if k in required_encryption_context_keys: # 1) Its EC is in the StreamEncryptor._required_encryption_context From 1706db21586f81cb319a8096c7fc7f9a6dbd5164 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 26 Mar 2024 16:27:12 -0700 Subject: [PATCH 159/184] lint --- test/unit/test_streaming_client_stream_decryptor.py | 2 +- test/unit/test_streaming_client_stream_encryptor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/test_streaming_client_stream_decryptor.py b/test/unit/test_streaming_client_stream_decryptor.py index cb003d868..7b4e36995 100644 --- a/test/unit/test_streaming_client_stream_decryptor.py +++ b/test/unit/test_streaming_client_stream_decryptor.py @@ -1126,7 +1126,7 @@ def test_GIVEN_materials_has_required_encryption_context_keys_attr_WHEN_read_hea test_decryptor._read_header() # Then: Assert correctness of partitioned EC - for k in encryption_context.keys(): + for k, _ in encryption_context.items(): # If a key is in required_encryption_context_keys, then ... if k in required_encryption_context_keys: # ... its EC is in the StreamEncryptor._required_encryption_context diff --git a/test/unit/test_streaming_client_stream_encryptor.py b/test/unit/test_streaming_client_stream_encryptor.py index e456dfd21..5030acf16 100644 --- a/test/unit/test_streaming_client_stream_encryptor.py +++ b/test/unit/test_streaming_client_stream_encryptor.py @@ -512,7 +512,7 @@ def test_GIVEN_has_mpl_AND_encryption_materials_has_required_EC_keys_WHEN_prep_m test_encryptor._prep_message() # Then: Assert correctness of partitioned EC - for k in encryption_context.keys(): + for k, _ in encryption_context.items(): # If a key is in required_encryption_context_keys, then if k in required_encryption_context_keys: # 1) Its EC is in the StreamEncryptor._required_encryption_context From 06e08429c6204200442faa4165514ded0d3e0201 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 26 Mar 2024 16:33:39 -0700 Subject: [PATCH 160/184] lint --- test/unit/test_streaming_client_stream_decryptor.py | 4 ++-- test/unit/test_streaming_client_stream_encryptor.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/unit/test_streaming_client_stream_decryptor.py b/test/unit/test_streaming_client_stream_decryptor.py index 7b4e36995..f38f413f4 100644 --- a/test/unit/test_streaming_client_stream_decryptor.py +++ b/test/unit/test_streaming_client_stream_decryptor.py @@ -1126,12 +1126,12 @@ def test_GIVEN_materials_has_required_encryption_context_keys_attr_WHEN_read_hea test_decryptor._read_header() # Then: Assert correctness of partitioned EC - for k, _ in encryption_context.items(): + for k, v in encryption_context.items(): # If a key is in required_encryption_context_keys, then ... if k in required_encryption_context_keys: # ... its EC is in the StreamEncryptor._required_encryption_context assert k in test_decryptor._required_encryption_context - assert test_decryptor._required_encryption_context[k] == encryption_context[k] + assert test_decryptor._required_encryption_context[k] == v # If a key is NOT in required_encryption_context_keys, then ... else: # ... its EC is NOT in the StreamEncryptor._required_encryption_context diff --git a/test/unit/test_streaming_client_stream_encryptor.py b/test/unit/test_streaming_client_stream_encryptor.py index 5030acf16..4df79e146 100644 --- a/test/unit/test_streaming_client_stream_encryptor.py +++ b/test/unit/test_streaming_client_stream_encryptor.py @@ -512,12 +512,12 @@ def test_GIVEN_has_mpl_AND_encryption_materials_has_required_EC_keys_WHEN_prep_m test_encryptor._prep_message() # Then: Assert correctness of partitioned EC - for k, _ in encryption_context.items(): + for k, v in encryption_context.items(): # If a key is in required_encryption_context_keys, then if k in required_encryption_context_keys: # 1) Its EC is in the StreamEncryptor._required_encryption_context assert k in test_encryptor._required_encryption_context - assert test_encryptor._required_encryption_context[k] == encryption_context[k] + assert test_encryptor._required_encryption_context[k] == v # 2) Its EC is NOT in the StreamEncryptor._stored_encryption_context assert k not in test_encryptor._stored_encryption_context # If a key is NOT in required_encryption_context_keys, then @@ -526,7 +526,7 @@ def test_GIVEN_has_mpl_AND_encryption_materials_has_required_EC_keys_WHEN_prep_m assert k not in test_encryptor._required_encryption_context # 2) Its EC is in the StreamEncryptor._stored_encryption_context assert k in test_encryptor._stored_encryption_context - assert test_encryptor._stored_encryption_context[k] == encryption_context[k] + assert test_encryptor._stored_encryption_context[k] == v # Assert size(stored_EC) + size(required_EC) == size(EC) # (i.e. every EC was sorted into one or the other) From 5ad8e3a9318bab554ce75df73ee6c29552e49874 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Mon, 15 Apr 2024 14:09:40 -0700 Subject: [PATCH 161/184] Update examples/src/keyrings/hierarchical_keyring.py --- examples/src/keyrings/hierarchical_keyring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/src/keyrings/hierarchical_keyring.py b/examples/src/keyrings/hierarchical_keyring.py index b75421359..9c77379cf 100644 --- a/examples/src/keyrings/hierarchical_keyring.py +++ b/examples/src/keyrings/hierarchical_keyring.py @@ -55,7 +55,7 @@ from .example_branch_key_id_supplier import ExampleBranchKeyIdSupplier -# TODO-MPL: Remove this as part of removing PYTHONPATH hacks +# TODO-MPL: Remove this as part of removing PYTHONPATH hacks. module_root_dir = '/'.join(__file__.split("/")[:-1]) sys.path.append(module_root_dir) From 280e038d0423528e824b1080c48383da5b5aecf1 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Tue, 16 Apr 2024 10:06:10 -0700 Subject: [PATCH 162/184] Update examples/src/keyrings/hierarchical_keyring.py --- examples/src/keyrings/hierarchical_keyring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/src/keyrings/hierarchical_keyring.py b/examples/src/keyrings/hierarchical_keyring.py index 9c77379cf..32a6cbf8b 100644 --- a/examples/src/keyrings/hierarchical_keyring.py +++ b/examples/src/keyrings/hierarchical_keyring.py @@ -26,7 +26,7 @@ This example requires access to the DDB Table where you are storing the Branch Keys. This table must be configured with the following primary key configuration: - Partition key is named -"partition_key" with type (S) - Sort key is named "sort_key" with type (S) +"partition_key" with type (S) - Sort key is named "sort_key" with type (S). This example also requires using a KMS Key. You need the following access on this key: - GenerateDataKeyWithoutPlaintext - Decrypt From 0dfeb5d87b96a50883d3bd0fe61eda9a858ab255 Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Thu, 18 Apr 2024 15:29:58 -0700 Subject: [PATCH 163/184] Adding test for KMS keyring --- .../src/keyrings/aws_kms_keyring_example.py | 115 ++++++++++++++++++ .../test_i_aws_kms_keyring_example.py | 13 ++ 2 files changed, 128 insertions(+) create mode 100644 examples/src/keyrings/aws_kms_keyring_example.py create mode 100644 examples/test/keyrings/test_i_aws_kms_keyring_example.py diff --git a/examples/src/keyrings/aws_kms_keyring_example.py b/examples/src/keyrings/aws_kms_keyring_example.py new file mode 100644 index 000000000..20d656b75 --- /dev/null +++ b/examples/src/keyrings/aws_kms_keyring_example.py @@ -0,0 +1,115 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +This example sets up the KMS Keyring +""" +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 +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 an AWS KMS keyring.""" + + """ + 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 boto3 clients for KMS. + """ + kms_client = boto3.client('kms', region_name="us-west-2") + + """ + 3. Instantiate the Material Providers + """ + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( + config=MaterialProvidersConfig() + ) + + """ + 4. Create encryption context + Remember that your encryption context is NOT SECRET. + 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", + } + + """ + 5. Create the KMS keyring + """ + keyring_input: CreateAwsKmsKeyringInput = CreateAwsKmsKeyringInput( + kms_key_id=kms_key_id, + kms_client=kms_client + ) + + kms_keyring: IKeyring = mat_prov.create_aws_kms_keyring( + input=keyring_input + ) + + """ + 6. Encrypt the data for the encryptionContext + """ + ciphertext, _ = client.encrypt( + source=EXAMPLE_DATA, + keyring=kms_keyring, + encryption_context=encryption_context + ) + + """ + 7. Demonstrate that the ciphertext and plaintext are different. + """ + assert ciphertext != EXAMPLE_DATA, "Ciphertext and plaintext data are the same. Invalid encryption" + + """ + 8. Decrypt your encrypted data using the same keyring you used on encrypt. + You do not need to specify the encryption context on decrypt + because the header of the encrypted message includes the encryption context. + """ + plaintext_bytes, dec_header = client.decrypt( + source=ciphertext, + keyring=kms_keyring + ) + + """ + 9. Demonstrate that the encryption context is correct in the decrypted message header + """ + 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. + """ + assert plaintext_bytes == EXAMPLE_DATA \ No newline at end of file diff --git a/examples/test/keyrings/test_i_aws_kms_keyring_example.py b/examples/test/keyrings/test_i_aws_kms_keyring_example.py new file mode 100644 index 000000000..ca1378db0 --- /dev/null +++ b/examples/test/keyrings/test_i_aws_kms_keyring_example.py @@ -0,0 +1,13 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Test suite for the AWS KMS keyring example.""" +import pytest + +from ...src.keyrings.aws_kms_keyring_example import encrypt_and_decrypt_with_keyring + +pytestmark = [pytest.mark.examples] + + +def test_encrypt_and_decrypt_with_keyring(): + key_arn = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f" + encrypt_and_decrypt_with_keyring(key_arn) From 18328ad8de7177c2eb6be698985331464afb9946 Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Fri, 19 Apr 2024 11:25:47 -0700 Subject: [PATCH 164/184] chore: KMS keyring example --- .../src/keyrings/aws_kms_keyring_example.py | 108 +++++++++--------- .../test_i_aws_kms_keyring_example.py | 6 +- .../keyrings/test_i_hierarchical_keyring.py | 5 +- 3 files changed, 63 insertions(+), 56 deletions(-) diff --git a/examples/src/keyrings/aws_kms_keyring_example.py b/examples/src/keyrings/aws_kms_keyring_example.py index 20d656b75..416521d75 100644 --- a/examples/src/keyrings/aws_kms_keyring_example.py +++ b/examples/src/keyrings/aws_kms_keyring_example.py @@ -2,63 +2,73 @@ # SPDX-License-Identifier: Apache-2.0 """ This example sets up the KMS Keyring + +The AWS KMS keyring uses symmetric encryption KMS keys to generate, encrypt and +decrypt data keys. This example creates a KMS Keyring and then encrypts a custom input EXAMPLE_DATA +with an encryption context. This example also demonstrates 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 + +AWS KMS keyrings can be used independently or in a multi-keyring with other keyrings +of the same or a different type. + +For more info on how to use KMS keyring, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html """ import sys - +from typing import Dict 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 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]) +MODULE_ROOT_DIR = '/'.join(__file__.split("/")[:-1]) -sys.path.append(module_root_dir) +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 an AWS KMS keyring.""" + """Demonstrate an encrypt/decrypt cycle using an AWS KMS keyring. + 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. + :type kms_key_id: string + + For more info 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()`. - """ + + # 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 boto3 clients for KMS. - """ + # 2. Create a boto3 client for KMS. kms_client = boto3.client('kms', region_name="us-west-2") - """ - 3. Instantiate the Material Providers - """ - mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( - config=MaterialProvidersConfig() - ) - - """ - 4. Create encryption context - Remember that your encryption context is NOT SECRET. - https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context - """ + # 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", @@ -67,9 +77,11 @@ def encrypt_and_decrypt_with_keyring( "the data you are handling": "is what you think it is", } - """ - 5. Create the KMS keyring - """ + # 4. Create a KMS keyring + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( + config=MaterialProvidersConfig() + ) + keyring_input: CreateAwsKmsKeyringInput = CreateAwsKmsKeyringInput( kms_key_id=kms_key_id, kms_client=kms_client @@ -79,37 +91,29 @@ def encrypt_and_decrypt_with_keyring( input=keyring_input ) - """ - 6. Encrypt the data for the encryptionContext - """ + # 5. Encrypt the data for the encryptionContext ciphertext, _ = client.encrypt( source=EXAMPLE_DATA, keyring=kms_keyring, encryption_context=encryption_context ) - """ - 7. Demonstrate that the ciphertext and plaintext are different. - """ - assert ciphertext != EXAMPLE_DATA, "Ciphertext and plaintext data are the same. Invalid encryption" - - """ - 8. Decrypt your encrypted data using the same keyring you used on encrypt. - You do not need to specify the encryption context on decrypt - because the header of the encrypted message includes the 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. Decrypt your encrypted data using the same keyring you used on encrypt. plaintext_bytes, dec_header = client.decrypt( source=ciphertext, keyring=kms_keyring ) - """ - 9. Demonstrate that the encryption context is correct in the decrypted message header - """ + # 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" + 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. - """ - assert plaintext_bytes == EXAMPLE_DATA \ No newline at end of file + # 9. Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert plaintext_bytes == EXAMPLE_DATA diff --git a/examples/test/keyrings/test_i_aws_kms_keyring_example.py b/examples/test/keyrings/test_i_aws_kms_keyring_example.py index ca1378db0..cd4b7086f 100644 --- a/examples/test/keyrings/test_i_aws_kms_keyring_example.py +++ b/examples/test/keyrings/test_i_aws_kms_keyring_example.py @@ -9,5 +9,7 @@ def test_encrypt_and_decrypt_with_keyring(): - key_arn = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f" - encrypt_and_decrypt_with_keyring(key_arn) + """Test function for encryption and decryption using the AWS KMS 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/examples/test/keyrings/test_i_hierarchical_keyring.py b/examples/test/keyrings/test_i_hierarchical_keyring.py index c4583534a..768cf994a 100644 --- a/examples/test/keyrings/test_i_hierarchical_keyring.py +++ b/examples/test/keyrings/test_i_hierarchical_keyring.py @@ -9,6 +9,7 @@ def test_encrypt_and_decrypt_with_keyring(): + """Test function for encryption and decryption using the AWS KMS Hierarchical Keyring example""" key_store_table_name = "KeyStoreDdbTable" - key_arn = "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126" - encrypt_and_decrypt_with_keyring(key_store_table_name, key_store_table_name, key_arn) + kms_key_id = "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126" + encrypt_and_decrypt_with_keyring(key_store_table_name, key_store_table_name, kms_key_id) From a7feaaf3fa238b3c34346724c448f628015ea3a4 Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Fri, 19 Apr 2024 12:02:33 -0700 Subject: [PATCH 165/184] chore: verified flake8 and isort checks for aws_kms_keyring_example.py --- examples/src/keyrings/aws_kms_keyring_example.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/examples/src/keyrings/aws_kms_keyring_example.py b/examples/src/keyrings/aws_kms_keyring_example.py index 416521d75..c3d76b6cb 100644 --- a/examples/src/keyrings/aws_kms_keyring_example.py +++ b/examples/src/keyrings/aws_kms_keyring_example.py @@ -10,25 +10,24 @@ 2. Encryption context is correct in the decrypted message header 3. Decrypted plaintext value matches EXAMPLE_DATA -AWS KMS keyrings can be used independently or in a multi-keyring with other keyrings +AWS KMS keyrings can be used independently or in a multi-keyring with other keyrings of the same or a different type. For more info on how to use KMS keyring, see https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html """ import sys -from typing import Dict -import boto3 +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 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]) @@ -50,7 +49,6 @@ def encrypt_and_decrypt_with_keyring( For more info 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 From cad5a8872060a1522f5efcceeaa0798af47fded0 Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Fri, 19 Apr 2024 12:11:45 -0700 Subject: [PATCH 166/184] chore: valid isort and flake8 for all files --- examples/test/keyrings/test_i_aws_kms_keyring_example.py | 3 +-- examples/test/keyrings/test_i_hierarchical_keyring.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/test/keyrings/test_i_aws_kms_keyring_example.py b/examples/test/keyrings/test_i_aws_kms_keyring_example.py index cd4b7086f..83a784a9e 100644 --- a/examples/test/keyrings/test_i_aws_kms_keyring_example.py +++ b/examples/test/keyrings/test_i_aws_kms_keyring_example.py @@ -9,7 +9,6 @@ def test_encrypt_and_decrypt_with_keyring(): - """Test function for encryption and decryption using the AWS KMS Keyring example""" - + """Test function for encrypt and decrypt using the AWS KMS 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/examples/test/keyrings/test_i_hierarchical_keyring.py b/examples/test/keyrings/test_i_hierarchical_keyring.py index 768cf994a..5a3adaa1c 100644 --- a/examples/test/keyrings/test_i_hierarchical_keyring.py +++ b/examples/test/keyrings/test_i_hierarchical_keyring.py @@ -9,7 +9,7 @@ def test_encrypt_and_decrypt_with_keyring(): - """Test function for encryption and decryption using the AWS KMS Hierarchical Keyring example""" + """Test function for encrypt and decrypt using the AWS KMS Hierarchical Keyring example.""" key_store_table_name = "KeyStoreDdbTable" kms_key_id = "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126" encrypt_and_decrypt_with_keyring(key_store_table_name, key_store_table_name, kms_key_id) From 1c43dd9b83963b4a6287bf0586499f05662e23a9 Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Fri, 19 Apr 2024 15:00:15 -0700 Subject: [PATCH 167/184] chore: minor comment fixes --- examples/src/keyrings/aws_kms_keyring_example.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/src/keyrings/aws_kms_keyring_example.py b/examples/src/keyrings/aws_kms_keyring_example.py index c3d76b6cb..dddfa5adc 100644 --- a/examples/src/keyrings/aws_kms_keyring_example.py +++ b/examples/src/keyrings/aws_kms_keyring_example.py @@ -5,15 +5,16 @@ The AWS KMS keyring uses symmetric encryption KMS keys to generate, encrypt and decrypt data keys. This example creates a KMS Keyring and then encrypts a custom input EXAMPLE_DATA -with an encryption context. This example also demonstrates some sanity checks for demonstration. +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. AWS KMS keyrings can be used independently or in a multi-keyring with other keyrings of the same or a different type. -For more info on how to use KMS keyring, see +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 @@ -46,7 +47,7 @@ def encrypt_and_decrypt_with_keyring( decryption of your data keys. :type kms_key_id: string - For more info on KMS Key identifiers, see + 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. @@ -114,4 +115,5 @@ def encrypt_and_decrypt_with_keyring( "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 From 6f1623367dd4f4cb42eba4da5a4e9f65bdb1bd38 Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Tue, 23 Apr 2024 16:42:26 -0700 Subject: [PATCH 168/184] chore: added examples for raw rsa and raw aes keyrings --- .../src/keyrings/raw_aes_keyring_example.py | 127 ++++++++++++++ .../src/keyrings/raw_rsa_keyring_example.py | 166 ++++++++++++++++++ .../test_i_raw_aes_keyring_example.py | 13 ++ .../test_i_raw_rsa_keyring_example.py | 13 ++ 4 files changed, 319 insertions(+) create mode 100644 examples/src/keyrings/raw_aes_keyring_example.py create mode 100644 examples/src/keyrings/raw_rsa_keyring_example.py create mode 100644 examples/test/keyrings/test_i_raw_aes_keyring_example.py create mode 100644 examples/test/keyrings/test_i_raw_rsa_keyring_example.py diff --git a/examples/src/keyrings/raw_aes_keyring_example.py b/examples/src/keyrings/raw_aes_keyring_example.py new file mode 100644 index 000000000..3e68fb056 --- /dev/null +++ b/examples/src/keyrings/raw_aes_keyring_example.py @@ -0,0 +1,127 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +This example sets up the Raw AES Keyring + +The Raw AES keyring lets you use an AES symmetric key that you provide as a wrapping key that +protects your data key. You need to generate, store, and protect the key material, +preferably in a hardware security module (HSM) or key management system. Use a Raw AES keyring +when you need to provide the wrapping key and encrypt the data keys locally or offline. + +This example creates a Raw AES 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. + +The Raw AES keyring encrypts data by using the AES-GCM algorithm and a wrapping key that +you specify as a byte array. You can specify only one wrapping key in each Raw AES keyring, +but you can include multiple Raw AES keyrings, alone or with other keyrings, in a multi-keyring. + +For more information on how to use Raw AES keyrings, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-aes-keyring.html +""" +import secrets +import sys + +from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders +from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig +from aws_cryptographic_materialproviders.mpl.models import 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(): + """Demonstrate an encrypt/decrypt cycle using a Raw AES keyring. + + Usage: encrypt_and_decrypt_with_keyring() + """ + # 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. 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" + + # 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. Generate a 256-bit AES key to use with your keyring. + # Here, the input to secrets.token_bytes() = 32 bytes = 256 bits + static_key = secrets.token_bytes(32) + + # 5. Create a Raw AES keyring + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( + config=MaterialProvidersConfig() + ) + + keyring_input: CreateRawAesKeyringInput = CreateRawAesKeyringInput( + key_namespace=key_name_space, + key_name=key_name, + wrapping_key=static_key, + wrapping_alg="ALG_AES256_GCM_IV12_TAG16" + ) + + raw_aes_keyring: IKeyring = mat_prov.create_raw_aes_keyring( + input=keyring_input + ) + + # 6. Encrypt the data for the encryptionContext + ciphertext, _ = client.encrypt( + source=EXAMPLE_DATA, + keyring=raw_aes_keyring, + encryption_context=encryption_context + ) + + # 7. 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" + + # 8. Decrypt your encrypted data using the same keyring you used on encrypt. + plaintext_bytes, dec_header = client.decrypt( + source=ciphertext, + keyring=raw_aes_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/raw_rsa_keyring_example.py b/examples/src/keyrings/raw_rsa_keyring_example.py new file mode 100644 index 000000000..80b21fd93 --- /dev/null +++ b/examples/src/keyrings/raw_rsa_keyring_example.py @@ -0,0 +1,166 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +This example sets up the Raw RSA Keyring + +The Raw RSA keyring performs asymmetric encryption and decryption of data keys in local memory +with RSA public and private keys that you provide. In this example, we define the RSA keys to +encrypt and decrypt the data keys. + +You need to generate, store, and protect the private key, preferably in a +hardware security module (HSM) or key management system. +The encryption function encrypts the data key under the RSA public key. The decryption function +decrypts the data key using the private key. + +This example creates a Raw 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. + +A Raw RSA keyring that encrypts and decrypts must include an asymmetric public key and private +key pair. However, you can encrypt data with a Raw RSA keyring that has only a public key, +and you can decrypt data with a Raw RSA keyring that has only a private key. You can include +any Raw RSA keyring in a multi-keyring. If you configure a Raw RSA keyring with a public and +private key, be sure that they are part of the same key pair. Some language implementations +of the AWS Encryption SDK will not construct a Raw RSA keyring with keys from different pairs. +Others rely on you to verify that your keys are from the same key pair. + +For more information on how to use Raw RSA keyrings, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-rsa-keyring.html +""" +import sys + +from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders +from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig +from aws_cryptographic_materialproviders.mpl.models import CreateRawRsaKeyringInput +from aws_cryptographic_materialproviders.mpl.references import IKeyring +from cryptography.hazmat.backends import default_backend as crypto_default_backend +from cryptography.hazmat.primitives import serialization as crypto_serialization +from cryptography.hazmat.primitives.asymmetric import rsa +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 generate_rsa_keyring(): + """Creates a new RSA keyring along with generating new keys + + Usage: generate_rsa_keyring() + """ + # 1. The key namespace and key name are defined by you. + # and are used by the Raw RSA keyring + # For more information, see + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-rsa-keyring.html + key_name_space = "Some managed raw keys" + key_name = "My 4096-bit RSA wrapping key" + + # 2. Generate a 4096-bit RSA key to use with your keyring. + ssh_rsa_exponent = 65537 + bit_strength = 4096 + key = rsa.generate_private_key( + backend=crypto_default_backend(), + public_exponent=ssh_rsa_exponent, + key_size=bit_strength + ) + + # This example choses a particular type of encoding, format and encryption_algorithm + # Users can choose the PrivateFormat, PublicFormat and encryption_algorithm that align most + # with their use-cases + private_key = key.private_bytes( + encoding=crypto_serialization.Encoding.PEM, + format=crypto_serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=crypto_serialization.NoEncryption() + ) + public_key = key.public_key().public_bytes( + encoding=crypto_serialization.Encoding.PEM, + format=crypto_serialization.PublicFormat.SubjectPublicKeyInfo + ) + + # 3. Create a Raw RSA keyring + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( + config=MaterialProvidersConfig() + ) + + keyring_input: CreateRawRsaKeyringInput = CreateRawRsaKeyringInput( + key_namespace=key_name_space, + key_name=key_name, + padding_scheme="OAEP_SHA256_MGF1", + public_key=public_key, + private_key=private_key + ) + + raw_rsa_keyring: IKeyring = mat_prov.create_raw_rsa_keyring( + input=keyring_input + ) + + return raw_rsa_keyring + + +def encrypt_and_decrypt_with_keyring(): + """Demonstrate an encrypt/decrypt cycle using a Raw RSA keyring. + + Usage: encrypt_and_decrypt_with_keyring() + """ + # 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 a Raw RSA keyring + raw_rsa_keyring = generate_rsa_keyring() + + # 4. Encrypt the data for the encryptionContext + ciphertext, _ = client.encrypt( + source=EXAMPLE_DATA, + keyring=raw_rsa_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" + + # 6. Decrypt your encrypted data using the same keyring you used on encrypt. + plaintext_bytes, dec_header = client.decrypt( + source=ciphertext, + keyring=raw_rsa_keyring + ) + + # 7. 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" + + # 8. 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/test/keyrings/test_i_raw_aes_keyring_example.py b/examples/test/keyrings/test_i_raw_aes_keyring_example.py new file mode 100644 index 000000000..e5bd6d4c2 --- /dev/null +++ b/examples/test/keyrings/test_i_raw_aes_keyring_example.py @@ -0,0 +1,13 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Test suite for the Raw AES keyring example.""" +import pytest + +from ...src.keyrings.raw_aes_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 Raw AES Keyring example.""" + encrypt_and_decrypt_with_keyring() diff --git a/examples/test/keyrings/test_i_raw_rsa_keyring_example.py b/examples/test/keyrings/test_i_raw_rsa_keyring_example.py new file mode 100644 index 000000000..0a9813458 --- /dev/null +++ b/examples/test/keyrings/test_i_raw_rsa_keyring_example.py @@ -0,0 +1,13 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Test suite for the Raw AES keyring example.""" +import pytest + +from ...src.keyrings.raw_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 Raw AES Keyring example.""" + encrypt_and_decrypt_with_keyring() From 8222cea584d03d92dd2c59198aee6f1df7bc5da2 Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Tue, 23 Apr 2024 16:46:35 -0700 Subject: [PATCH 169/184] removed kms keyring from this PR --- .../src/keyrings/aws_kms_keyring_example.py | 119 ------------------ .../test_i_aws_kms_keyring_example.py | 14 --- 2 files changed, 133 deletions(-) delete mode 100644 examples/src/keyrings/aws_kms_keyring_example.py delete mode 100644 examples/test/keyrings/test_i_aws_kms_keyring_example.py diff --git a/examples/src/keyrings/aws_kms_keyring_example.py b/examples/src/keyrings/aws_kms_keyring_example.py deleted file mode 100644 index dddfa5adc..000000000 --- a/examples/src/keyrings/aws_kms_keyring_example.py +++ /dev/null @@ -1,119 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -This example sets up the KMS Keyring - -The AWS KMS keyring uses symmetric encryption KMS keys to generate, encrypt and -decrypt data keys. This example creates a KMS 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. - -AWS KMS keyrings can be used independently or in a multi-keyring with other keyrings -of the same or a different type. - -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 CreateAwsKmsKeyringInput -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 an AWS KMS keyring. - - 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. - :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. Create a KMS keyring - mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( - config=MaterialProvidersConfig() - ) - - keyring_input: CreateAwsKmsKeyringInput = CreateAwsKmsKeyringInput( - kms_key_id=kms_key_id, - kms_client=kms_client - ) - - kms_keyring: IKeyring = mat_prov.create_aws_kms_keyring( - input=keyring_input - ) - - # 5. Encrypt the data for the encryptionContext - ciphertext, _ = client.encrypt( - source=EXAMPLE_DATA, - keyring=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. Decrypt your encrypted data using the same keyring you used on encrypt. - plaintext_bytes, dec_header = client.decrypt( - source=ciphertext, - keyring=kms_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/test/keyrings/test_i_aws_kms_keyring_example.py b/examples/test/keyrings/test_i_aws_kms_keyring_example.py deleted file mode 100644 index 83a784a9e..000000000 --- a/examples/test/keyrings/test_i_aws_kms_keyring_example.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Test suite for the AWS KMS keyring example.""" -import pytest - -from ...src.keyrings.aws_kms_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 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) From 6b88b54844949ba4f2fc034047cec2a174f6d07c Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Tue, 23 Apr 2024 16:47:58 -0700 Subject: [PATCH 170/184] updated test_i_raw_rsa_keyring_example.py --- examples/test/keyrings/test_i_raw_rsa_keyring_example.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/test/keyrings/test_i_raw_rsa_keyring_example.py b/examples/test/keyrings/test_i_raw_rsa_keyring_example.py index 0a9813458..810bfea60 100644 --- a/examples/test/keyrings/test_i_raw_rsa_keyring_example.py +++ b/examples/test/keyrings/test_i_raw_rsa_keyring_example.py @@ -1,6 +1,6 @@ # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -"""Test suite for the Raw AES keyring example.""" +"""Test suite for the Raw RSA keyring example.""" import pytest from ...src.keyrings.raw_rsa_keyring_example import encrypt_and_decrypt_with_keyring @@ -9,5 +9,5 @@ def test_encrypt_and_decrypt_with_keyring(): - """Test function for encrypt and decrypt using the Raw AES Keyring example.""" + """Test function for encrypt and decrypt using the Raw RSA Keyring example.""" encrypt_and_decrypt_with_keyring() From 37ce8032b5dc9939b1028c4cd84b737931c50de9 Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Tue, 23 Apr 2024 16:57:23 -0700 Subject: [PATCH 171/184] updated ../../src/keyrings/raw_rsa_keyring_example.py --- .../src/keyrings/raw_rsa_keyring_example.py | 79 ++++++++----------- 1 file changed, 34 insertions(+), 45 deletions(-) diff --git a/examples/src/keyrings/raw_rsa_keyring_example.py b/examples/src/keyrings/raw_rsa_keyring_example.py index 80b21fd93..d3140f634 100644 --- a/examples/src/keyrings/raw_rsa_keyring_example.py +++ b/examples/src/keyrings/raw_rsa_keyring_example.py @@ -52,19 +52,42 @@ EXAMPLE_DATA: bytes = b"Hello World" -def generate_rsa_keyring(): - """Creates a new RSA keyring along with generating new keys +def encrypt_and_decrypt_with_keyring(): + """Demonstrate an encrypt/decrypt cycle using a Raw RSA keyring. - Usage: generate_rsa_keyring() + Usage: encrypt_and_decrypt_with_keyring() """ - # 1. The key namespace and key name are defined by you. + # 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. The key namespace and key name are defined by you. # and are used by the Raw RSA keyring # For more information, see # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-rsa-keyring.html key_name_space = "Some managed raw keys" key_name = "My 4096-bit RSA wrapping key" - # 2. Generate a 4096-bit RSA key to use with your keyring. + # 4. Generate a 4096-bit RSA key to use with your keyring. ssh_rsa_exponent = 65537 bit_strength = 4096 key = rsa.generate_private_key( @@ -86,7 +109,7 @@ def generate_rsa_keyring(): format=crypto_serialization.PublicFormat.SubjectPublicKeyInfo ) - # 3. Create a Raw RSA keyring + # 5. Create a Raw RSA keyring mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( config=MaterialProvidersConfig() ) @@ -103,64 +126,30 @@ def generate_rsa_keyring(): input=keyring_input ) - return raw_rsa_keyring - - -def encrypt_and_decrypt_with_keyring(): - """Demonstrate an encrypt/decrypt cycle using a Raw RSA keyring. - - Usage: encrypt_and_decrypt_with_keyring() - """ - # 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 a Raw RSA keyring - raw_rsa_keyring = generate_rsa_keyring() - - # 4. Encrypt the data for the encryptionContext + # 6. Encrypt the data for the encryptionContext ciphertext, _ = client.encrypt( source=EXAMPLE_DATA, keyring=raw_rsa_keyring, encryption_context=encryption_context ) - # 5. Demonstrate that the ciphertext and plaintext are different. + # 7. 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" - # 6. Decrypt your encrypted data using the same keyring you used on encrypt. + # 8. Decrypt your encrypted data using the same keyring you used on encrypt. plaintext_bytes, dec_header = client.decrypt( source=ciphertext, keyring=raw_rsa_keyring ) - # 7. Demonstrate that the encryption context is correct in the decrypted message header + # 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" - # 8. Demonstrate that the decrypted plaintext is identical to the original plaintext. + # 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 From 2e19e1a3bf6a5f3a4072c0fc5a412add13bba1b1 Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Tue, 23 Apr 2024 16:59:39 -0700 Subject: [PATCH 172/184] reverted test_i_hierarchical_keyring to previous version --- examples/test/keyrings/test_i_hierarchical_keyring.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/test/keyrings/test_i_hierarchical_keyring.py b/examples/test/keyrings/test_i_hierarchical_keyring.py index 5a3adaa1c..8ab447120 100644 --- a/examples/test/keyrings/test_i_hierarchical_keyring.py +++ b/examples/test/keyrings/test_i_hierarchical_keyring.py @@ -11,5 +11,5 @@ def test_encrypt_and_decrypt_with_keyring(): """Test function for encrypt and decrypt using the AWS KMS Hierarchical Keyring example.""" key_store_table_name = "KeyStoreDdbTable" - kms_key_id = "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126" - encrypt_and_decrypt_with_keyring(key_store_table_name, key_store_table_name, kms_key_id) + key_arn = "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126" + encrypt_and_decrypt_with_keyring(key_store_table_name, key_store_table_name, key_arn) From 8039f62bc5bed05b4919fc47a68ce3d615950004 Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Tue, 23 Apr 2024 17:00:13 -0700 Subject: [PATCH 173/184] reverted test_i_hierarchical_keyring to previous version --- examples/test/keyrings/test_i_hierarchical_keyring.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/test/keyrings/test_i_hierarchical_keyring.py b/examples/test/keyrings/test_i_hierarchical_keyring.py index 8ab447120..c4583534a 100644 --- a/examples/test/keyrings/test_i_hierarchical_keyring.py +++ b/examples/test/keyrings/test_i_hierarchical_keyring.py @@ -9,7 +9,6 @@ def test_encrypt_and_decrypt_with_keyring(): - """Test function for encrypt and decrypt using the AWS KMS Hierarchical Keyring example.""" key_store_table_name = "KeyStoreDdbTable" key_arn = "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126" encrypt_and_decrypt_with_keyring(key_store_table_name, key_store_table_name, key_arn) From 7799a57a66d1d8821ab26623033b9a664add9da2 Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Wed, 24 Apr 2024 17:14:30 -0700 Subject: [PATCH 174/184] chore: updated raw rsa and aes keyrings --- .../src/keyrings/raw_aes_keyring_example.py | 4 +- .../src/keyrings/raw_rsa_keyring_example.py | 108 ++++++++++++------ 2 files changed, 74 insertions(+), 38 deletions(-) diff --git a/examples/src/keyrings/raw_aes_keyring_example.py b/examples/src/keyrings/raw_aes_keyring_example.py index 3e68fb056..c68461112 100644 --- a/examples/src/keyrings/raw_aes_keyring_example.py +++ b/examples/src/keyrings/raw_aes_keyring_example.py @@ -27,7 +27,7 @@ from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig -from aws_cryptographic_materialproviders.mpl.models import CreateRawAesKeyringInput +from aws_cryptographic_materialproviders.mpl.models import AesWrappingAlg, CreateRawAesKeyringInput from aws_cryptographic_materialproviders.mpl.references import IKeyring from typing import Dict @@ -91,7 +91,7 @@ def encrypt_and_decrypt_with_keyring(): key_namespace=key_name_space, key_name=key_name, wrapping_key=static_key, - wrapping_alg="ALG_AES256_GCM_IV12_TAG16" + wrapping_alg=AesWrappingAlg.ALG_AES256_GCM_IV12_TAG16 ) raw_aes_keyring: IKeyring = mat_prov.create_raw_aes_keyring( diff --git a/examples/src/keyrings/raw_rsa_keyring_example.py b/examples/src/keyrings/raw_rsa_keyring_example.py index d3140f634..8fac41451 100644 --- a/examples/src/keyrings/raw_rsa_keyring_example.py +++ b/examples/src/keyrings/raw_rsa_keyring_example.py @@ -17,6 +17,9 @@ 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. After verifying that the encrypt and decrypt works, this example also demonstrates +that the original ciphertext should not be decrypted using a new Raw RSA keyring generated by +another user, let's say Bob (Points 9 and 10). These sanity checks are for demonstration in the example only. You do not need these in your code. A Raw RSA keyring that encrypts and decrypts must include an asymmetric public key and private @@ -34,8 +37,11 @@ from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig -from aws_cryptographic_materialproviders.mpl.models import CreateRawRsaKeyringInput +from aws_cryptographic_materialproviders.mpl.models import CreateRawRsaKeyringInput, PaddingScheme from aws_cryptographic_materialproviders.mpl.references import IKeyring +from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.errors import ( + CollectionOfErrors, +) from cryptography.hazmat.backends import default_backend as crypto_default_backend from cryptography.hazmat.primitives import serialization as crypto_serialization from cryptography.hazmat.primitives.asymmetric import rsa @@ -52,42 +58,20 @@ EXAMPLE_DATA: bytes = b"Hello World" -def encrypt_and_decrypt_with_keyring(): - """Demonstrate an encrypt/decrypt cycle using a Raw RSA keyring. +def generate_rsa_keyring(): + """Generates new public and private keys to create a Raw RSA keyring and + then generates the keyring - Usage: encrypt_and_decrypt_with_keyring() + Usage: generate_rsa_keyring() """ - # 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. The key namespace and key name are defined by you. + # 1. The key namespace and key name are defined by you. # and are used by the Raw RSA keyring # For more information, see # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-rsa-keyring.html key_name_space = "Some managed raw keys" key_name = "My 4096-bit RSA wrapping key" - # 4. Generate a 4096-bit RSA key to use with your keyring. + # 2. Generate a 4096-bit RSA key to use with your keyring. ssh_rsa_exponent = 65537 bit_strength = 4096 key = rsa.generate_private_key( @@ -109,7 +93,7 @@ def encrypt_and_decrypt_with_keyring(): format=crypto_serialization.PublicFormat.SubjectPublicKeyInfo ) - # 5. Create a Raw RSA keyring + # 3. Create a Raw RSA keyring mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( config=MaterialProvidersConfig() ) @@ -117,7 +101,7 @@ def encrypt_and_decrypt_with_keyring(): keyring_input: CreateRawRsaKeyringInput = CreateRawRsaKeyringInput( key_namespace=key_name_space, key_name=key_name, - padding_scheme="OAEP_SHA256_MGF1", + padding_scheme=PaddingScheme.OAEP_SHA256_MGF1, public_key=public_key, private_key=private_key ) @@ -126,30 +110,82 @@ def encrypt_and_decrypt_with_keyring(): input=keyring_input ) - # 6. Encrypt the data for the encryptionContext + return raw_rsa_keyring + + +def encrypt_and_decrypt_with_keyring(): + """Demonstrate an encrypt/decrypt cycle using a Raw RSA keyring. + + Usage: encrypt_and_decrypt_with_keyring() + """ + # 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 a Raw RSA keyring + raw_rsa_keyring = generate_rsa_keyring() + + # 4. Encrypt the data for the encryptionContext ciphertext, _ = client.encrypt( source=EXAMPLE_DATA, keyring=raw_rsa_keyring, encryption_context=encryption_context ) - # 7. Demonstrate that the ciphertext and plaintext are different. + # 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" - # 8. Decrypt your encrypted data using the same keyring you used on encrypt. + # 6. Decrypt your encrypted data using the same keyring you used on encrypt. plaintext_bytes, dec_header = client.decrypt( source=ciphertext, keyring=raw_rsa_keyring ) - # 9. Demonstrate that the encryption context is correct in the decrypted message header + # 7. 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. + # 8. 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 + + # The next part of the example creates a new RSA keyring (for Bob) to demonstrate that + # decryption of the original ciphertext is not possible with a different keyring (Bob's) + # (This is an example for demonstration; you do not need to do this in your own code.) + + # 9. Generate a new Raw RSA keyring for Bob + raw_rsa_keyring_bob = generate_rsa_keyring() + + # 10. Test decrypt for the original ciphertext using raw_rsa_keyring_bob + try: + plaintext_bytes_bob, dec_header_bob = client.decrypt( + source=ciphertext, + keyring=raw_rsa_keyring_bob + ) + + assert False, "client.decrypt should throw a error of type CollectionOfErrors!" + except CollectionOfErrors: + pass From 3c5c5a055b178ad7aa93ae867dba38b22334f6af Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Wed, 24 Apr 2024 17:16:10 -0700 Subject: [PATCH 175/184] chore: minor fix --- examples/src/keyrings/raw_rsa_keyring_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/src/keyrings/raw_rsa_keyring_example.py b/examples/src/keyrings/raw_rsa_keyring_example.py index 8fac41451..29712cc1c 100644 --- a/examples/src/keyrings/raw_rsa_keyring_example.py +++ b/examples/src/keyrings/raw_rsa_keyring_example.py @@ -186,6 +186,6 @@ def encrypt_and_decrypt_with_keyring(): keyring=raw_rsa_keyring_bob ) - assert False, "client.decrypt should throw a error of type CollectionOfErrors!" + assert False, "client.decrypt should throw an error of type CollectionOfErrors!" except CollectionOfErrors: pass From 59faa8c2df9cbefb80a0a1b297ea698521ac37eb Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Wed, 24 Apr 2024 23:32:34 -0700 Subject: [PATCH 176/184] fix: raise assertion error instead of assert False --- examples/src/keyrings/raw_rsa_keyring_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/src/keyrings/raw_rsa_keyring_example.py b/examples/src/keyrings/raw_rsa_keyring_example.py index 29712cc1c..c95215608 100644 --- a/examples/src/keyrings/raw_rsa_keyring_example.py +++ b/examples/src/keyrings/raw_rsa_keyring_example.py @@ -186,6 +186,6 @@ def encrypt_and_decrypt_with_keyring(): keyring=raw_rsa_keyring_bob ) - assert False, "client.decrypt should throw an error of type CollectionOfErrors!" + raise AssertionError("client.decrypt should throw an error of type CollectionOfErrors!") except CollectionOfErrors: pass From 5d698db80378d3a89627fdcacfe0e58a142422be Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Thu, 25 Apr 2024 14:01:19 -0700 Subject: [PATCH 177/184] chore: updated and refactored raw rsa keyring example --- .../src/keyrings/raw_rsa_keyring_example.py | 107 ++++++++++++------ .../test_i_raw_rsa_keyring_example.py | 43 ++++++- 2 files changed, 115 insertions(+), 35 deletions(-) diff --git a/examples/src/keyrings/raw_rsa_keyring_example.py b/examples/src/keyrings/raw_rsa_keyring_example.py index c95215608..2e6c08fd1 100644 --- a/examples/src/keyrings/raw_rsa_keyring_example.py +++ b/examples/src/keyrings/raw_rsa_keyring_example.py @@ -17,18 +17,18 @@ 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. After verifying that the encrypt and decrypt works, this example also demonstrates -that the original ciphertext should not be decrypted using a new Raw RSA keyring generated by -another user, let's say Bob (Points 9 and 10). +4. The original ciphertext is not decryptable using a keyring with a different RSA key pair These sanity checks are for demonstration in the example only. You do not need these in your code. A Raw RSA keyring that encrypts and decrypts must include an asymmetric public key and private key pair. However, you can encrypt data with a Raw RSA keyring that has only a public key, -and you can decrypt data with a Raw RSA keyring that has only a private key. You can include -any Raw RSA keyring in a multi-keyring. If you configure a Raw RSA keyring with a public and -private key, be sure that they are part of the same key pair. Some language implementations -of the AWS Encryption SDK will not construct a Raw RSA keyring with keys from different pairs. -Others rely on you to verify that your keys are from the same key pair. +and you can decrypt data with a Raw RSA keyring that has only a private key. This example requires +the user to either provide both private and public keys, or not provide any keys and the example +generates both to test encryption and decryption. If you configure a Raw RSA keyring with a +public and private key, be sure that they are part of the same key pair. Some language +implementations of the AWS Encryption SDK will not construct a Raw RSA keyring with keys +from different pairs. Others rely on you to verify that your keys are from the same key pair. +You can include any Raw RSA keyring in a multi-keyring. For more information on how to use Raw RSA keyrings, see https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-rsa-keyring.html @@ -58,20 +58,31 @@ EXAMPLE_DATA: bytes = b"Hello World" -def generate_rsa_keyring(): - """Generates new public and private keys to create a Raw RSA keyring and - then generates the keyring +def should_generate_new_rsa_key_pair(public_key, private_key): + """Returns True if user doesn't provide keys, and we need to generate them and + returns False if the user has already provided both public and private keys + Raises an AssertionError if the user only provides one of private_key and public_key - Usage: generate_rsa_keyring() + Usage: should_generate_new_rsa_key_pair(public_key, private_key) """ - # 1. The key namespace and key name are defined by you. - # and are used by the Raw RSA keyring - # For more information, see - # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-rsa-keyring.html - key_name_space = "Some managed raw keys" - key_name = "My 4096-bit RSA wrapping key" + # If only one of public_key and private_key is provided, raise an Assertion Error + if (public_key and not private_key) or (not public_key and private_key): + raise AssertionError("Either both public and private keys should be provided! Or no keys \ + should be provided and the example can create the keys for you!") + + # If no keys are provided, we should generate a new rsa key pair, so return True + if not public_key and not private_key: + return True + + # If both keys are already provided, return False + return False + - # 2. Generate a 4096-bit RSA key to use with your keyring. +def generate_rsa_keys(): + """Generates a 4096-bit RSA public and private key pair + + Usage: generate_rsa_keys() + """ ssh_rsa_exponent = 65537 bit_strength = 4096 key = rsa.generate_private_key( @@ -81,19 +92,34 @@ def generate_rsa_keyring(): ) # This example choses a particular type of encoding, format and encryption_algorithm - # Users can choose the PrivateFormat, PublicFormat and encryption_algorithm that align most + # Users can choose the PublicFormat, PrivateFormat and encryption_algorithm that align most # with their use-cases + public_key = key.public_key().public_bytes( + encoding=crypto_serialization.Encoding.PEM, + format=crypto_serialization.PublicFormat.SubjectPublicKeyInfo + ) private_key = key.private_bytes( encoding=crypto_serialization.Encoding.PEM, format=crypto_serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=crypto_serialization.NoEncryption() ) - public_key = key.public_key().public_bytes( - encoding=crypto_serialization.Encoding.PEM, - format=crypto_serialization.PublicFormat.SubjectPublicKeyInfo - ) - # 3. Create a Raw RSA keyring + return public_key, private_key + + +def create_rsa_keyring(public_key, private_key): + """Create a Raw RSA keyring using the key pair + + Usage: create_rsa_keyring(public_key, private_key) + """ + # 1. The key namespace and key name are defined by you. + # and are used by the Raw RSA keyring + # For more information, see + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-rsa-keyring.html + key_name_space = "Some managed raw keys" + key_name = "My 4096-bit RSA wrapping key" + + # 2. Create a Raw RSA keyring mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( config=MaterialProvidersConfig() ) @@ -113,10 +139,12 @@ def generate_rsa_keyring(): return raw_rsa_keyring -def encrypt_and_decrypt_with_keyring(): - """Demonstrate an encrypt/decrypt cycle using a Raw RSA keyring. +def encrypt_and_decrypt_with_keyring(public_key=None, private_key=None): + """Demonstrate an encrypt/decrypt cycle using a Raw RSA keyring + with user defined keys. If no keys are present, generate new RSA + public and private keys and use them to create a Raw RSA keyring - Usage: encrypt_and_decrypt_with_keyring() + Usage: encrypt_and_decrypt_with_keyring(public_key, private_key) """ # 1. Instantiate the encryption SDK client. # This builds the client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy, @@ -142,7 +170,18 @@ def encrypt_and_decrypt_with_keyring(): } # 3. Create a Raw RSA keyring - raw_rsa_keyring = generate_rsa_keyring() + + # Check if we need to generate an RSA key pair + should_generate_new_rsa_key_pair_bool = \ + should_generate_new_rsa_key_pair(public_key=public_key, private_key=private_key) + + # If user doesn't provide the keys, that is, if should_generate_new_rsa_key_pair_bool is True + # generate a new RSA public and private key pair + if should_generate_new_rsa_key_pair_bool: + public_key, private_key = generate_rsa_keys() + + # Create the keyring + raw_rsa_keyring = create_rsa_keyring(public_key=public_key, private_key=private_key) # 4. Encrypt the data for the encryptionContext ciphertext, _ = client.encrypt( @@ -176,12 +215,16 @@ def encrypt_and_decrypt_with_keyring(): # decryption of the original ciphertext is not possible with a different keyring (Bob's) # (This is an example for demonstration; you do not need to do this in your own code.) - # 9. Generate a new Raw RSA keyring for Bob - raw_rsa_keyring_bob = generate_rsa_keyring() + # 9. Create a new Raw RSA keyring for Bob + # Generate new keys + public_key_bob, private_key_bob = generate_rsa_keys() + + # Create the keyring + raw_rsa_keyring_bob = create_rsa_keyring(public_key=public_key_bob, private_key=private_key_bob) # 10. Test decrypt for the original ciphertext using raw_rsa_keyring_bob try: - plaintext_bytes_bob, dec_header_bob = client.decrypt( + plaintext_bytes_bob, _ = client.decrypt( source=ciphertext, keyring=raw_rsa_keyring_bob ) diff --git a/examples/test/keyrings/test_i_raw_rsa_keyring_example.py b/examples/test/keyrings/test_i_raw_rsa_keyring_example.py index 810bfea60..e60ae7bbb 100644 --- a/examples/test/keyrings/test_i_raw_rsa_keyring_example.py +++ b/examples/test/keyrings/test_i_raw_rsa_keyring_example.py @@ -3,11 +3,48 @@ """Test suite for the Raw RSA keyring example.""" import pytest -from ...src.keyrings.raw_rsa_keyring_example import encrypt_and_decrypt_with_keyring +from ...src.keyrings.raw_rsa_keyring_example import encrypt_and_decrypt_with_keyring, generate_rsa_keys pytestmark = [pytest.mark.examples] -def test_encrypt_and_decrypt_with_keyring(): - """Test function for encrypt and decrypt using the Raw RSA Keyring example.""" +def test_encrypt_and_decrypt_with_keyring_without_user_defined_keys(): + """Test function for encrypt and decrypt using the Raw RSA Keyring example + where user doesn't provide the public and private keys""" encrypt_and_decrypt_with_keyring() + + +def test_encrypt_and_decrypt_with_keyring_with_user_defined_keys(): + """Test function for encrypt and decrypt using the Raw RSA Keyring example + where user provides the public and private keys. To test this, we create the + keys using the generate_rsa_keys function""" + user_public_key, user_private_key = generate_rsa_keys() + encrypt_and_decrypt_with_keyring(public_key=user_public_key, private_key=user_private_key) + + +def test_encrypt_and_decrypt_fails_if_user_provides_only_public_key(): + """Test function for encrypt and decrypt using the Raw RSA Keyring example + where user provides only the public key. The program should throw an Assertion error + as this example requires the user to either provide both private and public keys to + test both encryption and decryption, or not provide any keys and the example generates both""" + user_public_key, user_private_key = generate_rsa_keys() + try: + encrypt_and_decrypt_with_keyring(public_key=user_public_key) + + raise AssertionError("encrypt_and_decrypt_with_keyring should raise an error") + except AssertionError: + pass + + +def test_encrypt_and_decrypt_fails_if_user_provides_only_private_key(): + """Test function for encrypt and decrypt using the Raw RSA Keyring example + where user provides only the private key. The program should throw an Assertion error + as this example requires the user to either provide both private and public keys to + test both encryption and decryption, or not provide any keys and the example generates both""" + user_public_key, user_private_key = generate_rsa_keys() + try: + encrypt_and_decrypt_with_keyring(private_key=user_private_key) + + raise AssertionError("encrypt_and_decrypt_with_keyring should raise an error") + except AssertionError: + pass From f6e53e2ac610175fd17f7b1e4a8530b8b6ee07ef Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Thu, 25 Apr 2024 14:50:00 -0700 Subject: [PATCH 178/184] minor fix --- .../test/keyrings/test_i_raw_rsa_keyring_example.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/examples/test/keyrings/test_i_raw_rsa_keyring_example.py b/examples/test/keyrings/test_i_raw_rsa_keyring_example.py index e60ae7bbb..812c62726 100644 --- a/examples/test/keyrings/test_i_raw_rsa_keyring_example.py +++ b/examples/test/keyrings/test_i_raw_rsa_keyring_example.py @@ -10,14 +10,16 @@ def test_encrypt_and_decrypt_with_keyring_without_user_defined_keys(): """Test function for encrypt and decrypt using the Raw RSA Keyring example - where user doesn't provide the public and private keys""" + where user doesn't provide the public and private keys + """ encrypt_and_decrypt_with_keyring() def test_encrypt_and_decrypt_with_keyring_with_user_defined_keys(): """Test function for encrypt and decrypt using the Raw RSA Keyring example where user provides the public and private keys. To test this, we create the - keys using the generate_rsa_keys function""" + keys using the generate_rsa_keys function + """ user_public_key, user_private_key = generate_rsa_keys() encrypt_and_decrypt_with_keyring(public_key=user_public_key, private_key=user_private_key) @@ -26,7 +28,8 @@ def test_encrypt_and_decrypt_fails_if_user_provides_only_public_key(): """Test function for encrypt and decrypt using the Raw RSA Keyring example where user provides only the public key. The program should throw an Assertion error as this example requires the user to either provide both private and public keys to - test both encryption and decryption, or not provide any keys and the example generates both""" + test both encryption and decryption, or not provide any keys and the example generates both + """ user_public_key, user_private_key = generate_rsa_keys() try: encrypt_and_decrypt_with_keyring(public_key=user_public_key) @@ -40,7 +43,8 @@ def test_encrypt_and_decrypt_fails_if_user_provides_only_private_key(): """Test function for encrypt and decrypt using the Raw RSA Keyring example where user provides only the private key. The program should throw an Assertion error as this example requires the user to either provide both private and public keys to - test both encryption and decryption, or not provide any keys and the example generates both""" + test both encryption and decryption, or not provide any keys and the example generates both + """ user_public_key, user_private_key = generate_rsa_keys() try: encrypt_and_decrypt_with_keyring(private_key=user_private_key) From ac96226dffe2bf8189c400e3a643d9cf06294757 Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Thu, 25 Apr 2024 14:54:14 -0700 Subject: [PATCH 179/184] minor fix --- .../test_i_raw_rsa_keyring_example.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/examples/test/keyrings/test_i_raw_rsa_keyring_example.py b/examples/test/keyrings/test_i_raw_rsa_keyring_example.py index 812c62726..be374e338 100644 --- a/examples/test/keyrings/test_i_raw_rsa_keyring_example.py +++ b/examples/test/keyrings/test_i_raw_rsa_keyring_example.py @@ -9,15 +9,17 @@ def test_encrypt_and_decrypt_with_keyring_without_user_defined_keys(): - """Test function for encrypt and decrypt using the Raw RSA Keyring example - where user doesn't provide the public and private keys + """Test function for encrypt and decrypt using the Raw RSA Keyring example. + + Here user doesn't provide the public and private keys """ encrypt_and_decrypt_with_keyring() def test_encrypt_and_decrypt_with_keyring_with_user_defined_keys(): - """Test function for encrypt and decrypt using the Raw RSA Keyring example - where user provides the public and private keys. To test this, we create the + """Test function for encrypt and decrypt using the Raw RSA Keyring example. + + Here user provides the public and private keys. To test this, we create the keys using the generate_rsa_keys function """ user_public_key, user_private_key = generate_rsa_keys() @@ -25,8 +27,9 @@ def test_encrypt_and_decrypt_with_keyring_with_user_defined_keys(): def test_encrypt_and_decrypt_fails_if_user_provides_only_public_key(): - """Test function for encrypt and decrypt using the Raw RSA Keyring example - where user provides only the public key. The program should throw an Assertion error + """Test function for encrypt and decrypt using the Raw RSA Keyring example. + + Here user provides only the public key. The program should throw an Assertion error as this example requires the user to either provide both private and public keys to test both encryption and decryption, or not provide any keys and the example generates both """ @@ -40,8 +43,9 @@ def test_encrypt_and_decrypt_fails_if_user_provides_only_public_key(): def test_encrypt_and_decrypt_fails_if_user_provides_only_private_key(): - """Test function for encrypt and decrypt using the Raw RSA Keyring example - where user provides only the private key. The program should throw an Assertion error + """Test function for encrypt and decrypt using the Raw RSA Keyring example. + + Here user provides only the private key. The program should throw an Assertion error as this example requires the user to either provide both private and public keys to test both encryption and decryption, or not provide any keys and the example generates both """ From 877a7b9cdbadd1a6b6d240245f14f59ee8b7efe5 Mon Sep 17 00:00:00 2001 From: Ritvik Kapila <61410899+RitvikKapila@users.noreply.github.com> Date: Fri, 26 Apr 2024 13:05:05 -0700 Subject: [PATCH 180/184] Update test_streaming_client_stream_decryptor.py --- .../test_streaming_client_stream_decryptor.py | 136 ------------------ 1 file changed, 136 deletions(-) diff --git a/test/unit/test_streaming_client_stream_decryptor.py b/test/unit/test_streaming_client_stream_decryptor.py index be9304006..ce3d6ee3c 100644 --- a/test/unit/test_streaming_client_stream_decryptor.py +++ b/test/unit/test_streaming_client_stream_decryptor.py @@ -365,142 +365,6 @@ def test_GIVEN_verification_key_AND_has_mpl_AND_has_MPLCMM_WHEN_read_header_THEN algorithm=self.mock_header.algorithm, encoded_point=mock_b64encoding() ) - @patch("aws_encryption_sdk.streaming_client.derive_data_encryption_key") - @patch("aws_encryption_sdk.streaming_client.DecryptionMaterialsRequest") - @patch("aws_encryption_sdk.streaming_client.Verifier") - # Given: no MPL - @pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") - def test_GIVEN_decrypt_config_has_ec_WHEN_read_header_THEN_calls_decrypt_materials_with_reproduced_ec( - self, - mock_verifier, - mock_decrypt_materials_request, - *_, - ): - - mock_verifier_instance = MagicMock() - mock_verifier.from_key_bytes.return_value = mock_verifier_instance - ct_stream = io.BytesIO(VALUES["data_128"]) - mock_commitment_policy = MagicMock(__class__=CommitmentPolicy) - test_decryptor = StreamDecryptor( - materials_manager=self.mock_materials_manager, - source=ct_stream, - commitment_policy=mock_commitment_policy, - ) - test_decryptor.source_stream = ct_stream - test_decryptor._stream_length = len(VALUES["data_128"]) - # Given: self.config has "encryption_context" - any_reproduced_ec = {"some": "ec"} - test_decryptor.config.encryption_context = any_reproduced_ec - - # When: read header - test_decryptor._read_header() - - # Then: calls decrypt_materials with reproduced_encryption_context - mock_decrypt_materials_request.assert_called_once_with( - encrypted_data_keys=self.mock_header.encrypted_data_keys, - algorithm=self.mock_header.algorithm, - encryption_context=sentinel.encryption_context, - commitment_policy=mock_commitment_policy, - reproduced_encryption_context=any_reproduced_ec, - ) - - @patch("aws_encryption_sdk.streaming_client.DecryptionMaterialsRequest") - @patch("aws_encryption_sdk.streaming_client.derive_data_encryption_key") - @patch("aws_encryption_sdk.streaming_client.Verifier") - # Given: no MPL - @pytest.mark.skipif(HAS_MPL, reason="Test should only be executed without MPL in installation") - def test_GIVEN_verification_key_AND_no_mpl_WHEN_read_header_THEN_calls_from_key_bytes( - self, - mock_verifier, - *_, - ): - # Given: verification key - mock_verifier_instance = MagicMock() - mock_verifier.from_key_bytes.return_value = mock_verifier_instance - ct_stream = io.BytesIO(VALUES["data_128"]) - mock_commitment_policy = MagicMock(__class__=CommitmentPolicy) - test_decryptor = StreamDecryptor( - materials_manager=self.mock_materials_manager, - source=ct_stream, - commitment_policy=mock_commitment_policy, - ) - test_decryptor.source_stream = ct_stream - test_decryptor._stream_length = len(VALUES["data_128"]) - - # When: read header - test_decryptor._read_header() - - # Then: calls from_key_bytes - mock_verifier.from_key_bytes.assert_called_once_with( - algorithm=self.mock_header.algorithm, key_bytes=sentinel.verification_key - ) - - @patch("aws_encryption_sdk.streaming_client.DecryptionMaterialsRequest") - @patch("aws_encryption_sdk.streaming_client.derive_data_encryption_key") - @patch("aws_encryption_sdk.streaming_client.Verifier") - # Given: has MPL - @pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") - def test_GIVEN_verification_key_AND_has_mpl_AND_not_MPLCMM_WHEN_read_header_THEN_calls_from_key_bytes( - self, - mock_verifier, - *_, - ): - # Given: verification key - mock_verifier_instance = MagicMock() - mock_verifier.from_key_bytes.return_value = mock_verifier_instance - ct_stream = io.BytesIO(VALUES["data_128"]) - mock_commitment_policy = MagicMock(__class__=CommitmentPolicy) - test_decryptor = StreamDecryptor( - # Given: native CMM - materials_manager=self.mock_materials_manager, - source=ct_stream, - commitment_policy=mock_commitment_policy, - ) - test_decryptor.source_stream = ct_stream - test_decryptor._stream_length = len(VALUES["data_128"]) - - # When: read_header - test_decryptor._read_header() - - # Then: calls from_key_bytess - mock_verifier.from_key_bytes.assert_called_once_with( - algorithm=self.mock_header.algorithm, key_bytes=sentinel.verification_key - ) - - @patch("aws_encryption_sdk.streaming_client.DecryptionMaterialsRequest") - @patch("aws_encryption_sdk.streaming_client.derive_data_encryption_key") - @patch("aws_encryption_sdk.streaming_client.Verifier") - @patch("base64.b64encode") - # Given: has MPL - @pytest.mark.skipif(not HAS_MPL, reason="Test should only be executed with MPL in installation") - def test_GIVEN_verification_key_AND_has_mpl_AND_has_MPLCMM_WHEN_read_header_THEN_calls_from_encoded_point( - self, - mock_b64encoding, - mock_verifier, - *_, - ): - # Given: Verification key - mock_verifier_instance = MagicMock() - mock_verifier.from_key_bytes.return_value = mock_verifier_instance - ct_stream = io.BytesIO(VALUES["data_128"]) - mock_commitment_policy = MagicMock(__class__=CommitmentPolicy) - test_decryptor = StreamDecryptor( - # Given: MPL CMM - materials_manager=self.mock_mpl_materials_manager, - source=ct_stream, - commitment_policy=mock_commitment_policy, - ) - test_decryptor.source_stream = ct_stream - test_decryptor._stream_length = len(VALUES["data_128"]) - - # When: read header - test_decryptor._read_header() - - # Then: calls from_encoded_point - mock_verifier.from_encoded_point.assert_called_once_with( - algorithm=self.mock_header.algorithm, encoded_point=mock_b64encoding() - ) - @patch("aws_encryption_sdk.streaming_client.derive_data_encryption_key") def test_read_header_frame_too_large(self, mock_derive_datakey): self.mock_header.content_type = ContentType.FRAMED_DATA From 69abc8b730e547da9222bc9c9ec5d386bf13b6ea Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Thu, 2 May 2024 12:16:20 -0700 Subject: [PATCH 181/184] updated raw rsa keyring to get keys from user files --- .gitignore | 1 + .../src/keyrings/raw_aes_keyring_example.py | 4 +- .../src/keyrings/raw_rsa_keyring_example.py | 43 +++++++---- .../test_i_raw_rsa_keyring_example.py | 74 +++++++++++++++++-- 4 files changed, 99 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index fc224adc4..a58ed6568 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ __pycache__ # PyTest .pytest_cache +test_keys/ # PyCharm .idea/ diff --git a/examples/src/keyrings/raw_aes_keyring_example.py b/examples/src/keyrings/raw_aes_keyring_example.py index c68461112..ee0ab7618 100644 --- a/examples/src/keyrings/raw_aes_keyring_example.py +++ b/examples/src/keyrings/raw_aes_keyring_example.py @@ -79,6 +79,8 @@ def encrypt_and_decrypt_with_keyring(): } # 4. Generate a 256-bit AES key to use with your keyring. + # In practice, you should get this key from a secure key management system such as an HSM. + # Here, the input to secrets.token_bytes() = 32 bytes = 256 bits static_key = secrets.token_bytes(32) @@ -98,7 +100,7 @@ def encrypt_and_decrypt_with_keyring(): input=keyring_input ) - # 6. Encrypt the data for the encryptionContext + # 6. Encrypt the data with the encryptionContext ciphertext, _ = client.encrypt( source=EXAMPLE_DATA, keyring=raw_aes_keyring, diff --git a/examples/src/keyrings/raw_rsa_keyring_example.py b/examples/src/keyrings/raw_rsa_keyring_example.py index 2e6c08fd1..5c0c25527 100644 --- a/examples/src/keyrings/raw_rsa_keyring_example.py +++ b/examples/src/keyrings/raw_rsa_keyring_example.py @@ -58,20 +58,21 @@ EXAMPLE_DATA: bytes = b"Hello World" -def should_generate_new_rsa_key_pair(public_key, private_key): - """Returns True if user doesn't provide keys, and we need to generate them and - returns False if the user has already provided both public and private keys - Raises an AssertionError if the user only provides one of private_key and public_key +def should_generate_new_rsa_key_pair(public_key_file_name, private_key_file_name): + """Returns True if user doesn't provide keys, and we need to generate them; + Returns False if the user has already provided both public and private keys + Raises a ValueError if the user only provides one of private_key and public_key - Usage: should_generate_new_rsa_key_pair(public_key, private_key) + Usage: should_generate_new_rsa_key_pair(public_key_file_name, private_key_file_name) """ - # If only one of public_key and private_key is provided, raise an Assertion Error - if (public_key and not private_key) or (not public_key and private_key): - raise AssertionError("Either both public and private keys should be provided! Or no keys \ + # If only one of public_key and private_key files is provided, raise a ValueError + if (public_key_file_name and not private_key_file_name)\ + or (not public_key_file_name and private_key_file_name): + raise ValueError("Either both public and private keys should be provided! Or no keys \ should be provided and the example can create the keys for you!") # If no keys are provided, we should generate a new rsa key pair, so return True - if not public_key and not private_key: + if not public_key_file_name and not private_key_file_name: return True # If both keys are already provided, return False @@ -139,12 +140,12 @@ def create_rsa_keyring(public_key, private_key): return raw_rsa_keyring -def encrypt_and_decrypt_with_keyring(public_key=None, private_key=None): +def encrypt_and_decrypt_with_keyring(public_key_file_name=None, private_key_file_name=None): """Demonstrate an encrypt/decrypt cycle using a Raw RSA keyring with user defined keys. If no keys are present, generate new RSA public and private keys and use them to create a Raw RSA keyring - Usage: encrypt_and_decrypt_with_keyring(public_key, private_key) + Usage: encrypt_and_decrypt_with_keyring(public_key_file_name, private_key_file_name) """ # 1. Instantiate the encryption SDK client. # This builds the client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy, @@ -173,17 +174,31 @@ def encrypt_and_decrypt_with_keyring(public_key=None, private_key=None): # Check if we need to generate an RSA key pair should_generate_new_rsa_key_pair_bool = \ - should_generate_new_rsa_key_pair(public_key=public_key, private_key=private_key) + should_generate_new_rsa_key_pair(public_key_file_name=public_key_file_name, + private_key_file_name=private_key_file_name) # If user doesn't provide the keys, that is, if should_generate_new_rsa_key_pair_bool is True # generate a new RSA public and private key pair if should_generate_new_rsa_key_pair_bool: public_key, private_key = generate_rsa_keys() + else: + # If user provides the keys, read the keys from the files + with open(public_key_file_name, "r", encoding='utf-8') as f: + public_key = f.read() + + # Convert the public key from a string to bytes + public_key = bytes(public_key, 'utf-8') + + with open(private_key_file_name, "r", encoding='utf-8') as f: + private_key = f.read() + + # Convert the private key from a string to bytes + private_key = bytes(private_key, 'utf-8') # Create the keyring raw_rsa_keyring = create_rsa_keyring(public_key=public_key, private_key=private_key) - # 4. Encrypt the data for the encryptionContext + # 4. Encrypt the data with the encryptionContext ciphertext, _ = client.encrypt( source=EXAMPLE_DATA, keyring=raw_rsa_keyring, @@ -212,7 +227,7 @@ def encrypt_and_decrypt_with_keyring(public_key=None, private_key=None): assert plaintext_bytes == EXAMPLE_DATA # The next part of the example creates a new RSA keyring (for Bob) to demonstrate that - # decryption of the original ciphertext is not possible with a different keyring (Bob's) + # decryption of the original ciphertext is not possible with a different keyring (Bob's). # (This is an example for demonstration; you do not need to do this in your own code.) # 9. Create a new Raw RSA keyring for Bob diff --git a/examples/test/keyrings/test_i_raw_rsa_keyring_example.py b/examples/test/keyrings/test_i_raw_rsa_keyring_example.py index be374e338..e036eb0b0 100644 --- a/examples/test/keyrings/test_i_raw_rsa_keyring_example.py +++ b/examples/test/keyrings/test_i_raw_rsa_keyring_example.py @@ -1,6 +1,8 @@ # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 """Test suite for the Raw RSA keyring example.""" +import os + import pytest from ...src.keyrings.raw_rsa_keyring_example import encrypt_and_decrypt_with_keyring, generate_rsa_keys @@ -20,39 +22,95 @@ def test_encrypt_and_decrypt_with_keyring_with_user_defined_keys(): """Test function for encrypt and decrypt using the Raw RSA Keyring example. Here user provides the public and private keys. To test this, we create the - keys using the generate_rsa_keys function + keys using the generate_rsa_keys function and write them to the file. + Then we call the encrypt_and_decrypt_with_keyring function and pass them """ + # Generate the user keys for testing user_public_key, user_private_key = generate_rsa_keys() - encrypt_and_decrypt_with_keyring(public_key=user_public_key, private_key=user_private_key) + + # Convert the keys to strings + user_public_key = user_public_key.decode('utf-8') + user_private_key = user_private_key.decode('utf-8') + + test_keys_directory = 'test_keys' + if not os.path.exists(test_keys_directory): + os.makedirs(test_keys_directory) + + # Define the file names for the keys + user_public_key_file_name = test_keys_directory + '/user_public_key_file_name.pem' + user_private_key_file_name = test_keys_directory + '/user_private_key_file_name.pem' + + # Write the public key to the file + with open(user_public_key_file_name, "w", encoding="utf-8") as f: + f.write(user_public_key) + + # Write the private key to the file + with open(user_private_key_file_name, "w", encoding="utf-8") as f: + f.write(user_private_key) + + encrypt_and_decrypt_with_keyring(public_key_file_name=user_public_key_file_name, + private_key_file_name=user_private_key_file_name) def test_encrypt_and_decrypt_fails_if_user_provides_only_public_key(): """Test function for encrypt and decrypt using the Raw RSA Keyring example. - Here user provides only the public key. The program should throw an Assertion error + Here user provides only the public key. The program should throw an Value error as this example requires the user to either provide both private and public keys to test both encryption and decryption, or not provide any keys and the example generates both """ + # Generate the user keys for testing user_public_key, user_private_key = generate_rsa_keys() + + # Convert the public key to string + user_public_key = user_public_key.decode('utf-8') + + test_keys_directory = 'test_keys' + if not os.path.exists(test_keys_directory): + os.makedirs(test_keys_directory) + + # Define the file name for the public key + user_public_key_file_name = test_keys_directory + '/user_public_key_file_name.pem' + + # Write the public key to the file + with open(user_public_key_file_name, "w", encoding="utf-8") as f: + f.write(user_public_key) + try: - encrypt_and_decrypt_with_keyring(public_key=user_public_key) + encrypt_and_decrypt_with_keyring(public_key_file_name=user_public_key_file_name) raise AssertionError("encrypt_and_decrypt_with_keyring should raise an error") - except AssertionError: + except ValueError: pass def test_encrypt_and_decrypt_fails_if_user_provides_only_private_key(): """Test function for encrypt and decrypt using the Raw RSA Keyring example. - Here user provides only the private key. The program should throw an Assertion error + Here user provides only the private key. The program should throw an Value error as this example requires the user to either provide both private and public keys to test both encryption and decryption, or not provide any keys and the example generates both """ + # Generate the user keys for testing user_public_key, user_private_key = generate_rsa_keys() + + # Convert the private key to string + user_private_key = user_private_key.decode('utf-8') + + test_keys_directory = 'test_keys' + if not os.path.exists(test_keys_directory): + os.makedirs(test_keys_directory) + + # Define the file name for the private key + user_private_key_file_name = test_keys_directory + '/user_private_key_file_name.pem' + + # Write the private key to the file + with open(user_private_key_file_name, "w", encoding="utf-8") as f: + f.write(user_private_key) + try: - encrypt_and_decrypt_with_keyring(private_key=user_private_key) + encrypt_and_decrypt_with_keyring(private_key_file_name=user_private_key_file_name) raise AssertionError("encrypt_and_decrypt_with_keyring should raise an error") - except AssertionError: + except ValueError: pass From 72c1c3118d588f524692a2df96b0294b5271b5b2 Mon Sep 17 00:00:00 2001 From: Lucas McDonald Date: Thu, 2 May 2024 12:54:34 -0700 Subject: [PATCH 182/184] reset --- examples/src/keyrings/raw_rsa_keyring_example.py | 8 +++----- src/aws_encryption_sdk/materials_managers/mpl/cmm.py | 7 +++++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/examples/src/keyrings/raw_rsa_keyring_example.py b/examples/src/keyrings/raw_rsa_keyring_example.py index 5c0c25527..7c9db3d2b 100644 --- a/examples/src/keyrings/raw_rsa_keyring_example.py +++ b/examples/src/keyrings/raw_rsa_keyring_example.py @@ -39,9 +39,6 @@ from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig from aws_cryptographic_materialproviders.mpl.models import CreateRawRsaKeyringInput, PaddingScheme from aws_cryptographic_materialproviders.mpl.references import IKeyring -from aws_cryptographic_materialproviders.smithygenerated.aws_cryptography_materialproviders.errors import ( - CollectionOfErrors, -) from cryptography.hazmat.backends import default_backend as crypto_default_backend from cryptography.hazmat.primitives import serialization as crypto_serialization from cryptography.hazmat.primitives.asymmetric import rsa @@ -49,6 +46,7 @@ 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]) @@ -244,6 +242,6 @@ def encrypt_and_decrypt_with_keyring(public_key_file_name=None, private_key_file keyring=raw_rsa_keyring_bob ) - raise AssertionError("client.decrypt should throw an error of type CollectionOfErrors!") - except CollectionOfErrors: + raise AssertionError("client.decrypt should throw an error of type AWSEncryptionSDKClientError!") + except AWSEncryptionSDKClientError: pass diff --git a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py index 1f2102757..d0e58661d 100644 --- a/src/aws_encryption_sdk/materials_managers/mpl/cmm.py +++ b/src/aws_encryption_sdk/materials_managers/mpl/cmm.py @@ -7,7 +7,10 @@ # pylint should pass even if the MPL isn't installed # Also thinks these imports aren't used if it can't import them # noqa pylint: disable=import-error,unused-import -from aws_cryptographic_materialproviders.mpl.errors import AwsCryptographicMaterialProvidersException +from aws_cryptographic_materialproviders.mpl.errors import ( + AwsCryptographicMaterialProvidersException, + CollectionOfErrors, +) from aws_cryptographic_materialproviders.mpl.models import ( AlgorithmSuiteIdESDK as MPL_AlgorithmSuiteIdESDK, CommitmentPolicyESDK as MPL_CommitmentPolicyESDK, @@ -114,7 +117,7 @@ def decrypt_materials( CryptoMaterialsManagerFromMPL._create_mpl_decrypt_materials_input_from_request(request) mpl_output: 'MPL_DecryptMaterialsOutput' = self.mpl_cmm.decrypt_materials(mpl_input) return DecryptionMaterialsFromMPL(mpl_output.decryption_materials) - except AwsCryptographicMaterialProvidersException as mpl_exception: + except (AwsCryptographicMaterialProvidersException, CollectionOfErrors) as mpl_exception: # Wrap MPL error into the ESDK error type # so customers only have to catch ESDK error types. raise AWSEncryptionSDKClientError(mpl_exception) From e6bc447370e154a8e43f490144fc012b320fe6dc Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Thu, 2 May 2024 14:13:35 -0700 Subject: [PATCH 183/184] fix --- .gitignore | 1 + examples/src/keyrings/raw_rsa_keyring_example.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a58ed6568..78bc3d5b8 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ __pycache__ # PyTest .pytest_cache +# Ignore key materials generated by examples or tests test_keys/ # PyCharm diff --git a/examples/src/keyrings/raw_rsa_keyring_example.py b/examples/src/keyrings/raw_rsa_keyring_example.py index 7c9db3d2b..741f32683 100644 --- a/examples/src/keyrings/raw_rsa_keyring_example.py +++ b/examples/src/keyrings/raw_rsa_keyring_example.py @@ -168,7 +168,7 @@ def encrypt_and_decrypt_with_keyring(public_key_file_name=None, private_key_file "the data you are handling": "is what you think it is", } - # 3. Create a Raw RSA keyring + # 3. Create a Raw RSA keyring. # Check if we need to generate an RSA key pair should_generate_new_rsa_key_pair_bool = \ From 2d7c5a9b5403d691362318fa4baf70d29df2e40c Mon Sep 17 00:00:00 2001 From: Ritvik Kapila Date: Thu, 2 May 2024 14:14:21 -0700 Subject: [PATCH 184/184] fix --- examples/src/keyrings/raw_rsa_keyring_example.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/src/keyrings/raw_rsa_keyring_example.py b/examples/src/keyrings/raw_rsa_keyring_example.py index 741f32683..49d868a86 100644 --- a/examples/src/keyrings/raw_rsa_keyring_example.py +++ b/examples/src/keyrings/raw_rsa_keyring_example.py @@ -169,6 +169,8 @@ def encrypt_and_decrypt_with_keyring(public_key_file_name=None, private_key_file } # 3. Create a Raw RSA keyring. + # If you have provided keys in a PEM file, they will be loaded into the keyring. + # Otherwise, a key pair will be randomly generated for you. # Check if we need to generate an RSA key pair should_generate_new_rsa_key_pair_bool = \