From ef514ae584ff30953b7269e444b51f1da55200fd Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Fri, 22 May 2020 11:45:51 -0700 Subject: [PATCH 1/7] Revert "docs: add example to replicate AWS KMS MKP behavior with AWS KMS keyring (#255)" This reverts commit 027b127f8fdef020d187799123cefb2c74feed58. --- examples/README.md | 6 - .../act_like_aws_kms_master_key_provider.py | 117 ------------------ .../src/keyring/aws_kms/discovery_decrypt.py | 9 +- .../discovery_decrypt_in_region_only.py | 9 +- ...iscovery_decrypt_with_preferred_regions.py | 9 +- 5 files changed, 6 insertions(+), 144 deletions(-) delete mode 100644 examples/src/keyring/aws_kms/act_like_aws_kms_master_key_provider.py diff --git a/examples/README.md b/examples/README.md index a5c9a93de..d592de09c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -44,12 +44,6 @@ We start with AWS KMS examples, then show how to use other wrapping keys. * [with keyrings](./src/keyring/aws_kms/discovery_decrypt_in_region_only.py) * How to decrypt with a preferred region but failover to others * [with keyrings](./src/keyring/aws_kms/discovery_decrypt_with_preferred_regions.py) - * How to reproduce the behavior of an AWS KMS master key provider - * [with keyrings](./src/keyring/aws_kms/act_like_aws_kms_master_key_provider.py) - * How to use AWS KMS clients with custom configuration - * [with keyrings](./src/keyring/aws_kms/custom_kms_client_config.py) - * How to use different AWS KMS client for different regions - * [with keyrings](./src/keyring/aws_kms/custom_client_supplier.py) * Using raw wrapping keys * How to use a raw AES wrapping key * [with keyrings](./src/keyring/raw_aes/raw_aes.py) diff --git a/examples/src/keyring/aws_kms/act_like_aws_kms_master_key_provider.py b/examples/src/keyring/aws_kms/act_like_aws_kms_master_key_provider.py deleted file mode 100644 index a0d620179..000000000 --- a/examples/src/keyring/aws_kms/act_like_aws_kms_master_key_provider.py +++ /dev/null @@ -1,117 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -You might have used master key providers to protect your data keys -in an earlier version of the AWS Encryption SDK. -This example shows how to configure a keyring that behaves like an AWS KMS master key provider. - -The AWS Encryption SDK provided an AWS KMS master key provider for -interacting with AWS Key Management Service (AWS KMS). -On encrypt, the AWS KMS master key provider behaves like the AWS KMS keyring -and encrypts with all CMKs that you identify. -However, on decrypt, -the AWS KMS master key provider reviews each encrypted data key (EDK). -If the EDK was encrypted under an AWS KMS CMK, -the AWS KMS master key provider attempts to decrypt it. -Whether decryption succeeds depends on permissions on the CMK. -This continues until the AWS KMS master key provider either runs out of EDKs -or succeeds in decrypting an EDK. -We have found that separating these two behaviors -makes the expected behavior clearer, -so that is what we did with the AWS KMS keyring and the AWS KMS discovery keyring. -However, as you migrate from master key providers to keyrings, -you might want a keyring that behaves like the AWS KMS master key provider. - -For more examples of how to use the AWS KMS keyring, -see the ``keyring/aws_kms`` directory. -""" -import aws_encryption_sdk -from aws_encryption_sdk.key_providers.kms import KMSMasterKeyProvider -from aws_encryption_sdk.keyrings.aws_kms import AwsKmsKeyring -from aws_encryption_sdk.keyrings.multi import MultiKeyring - -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Sequence # noqa pylint: disable=unused-import -except ImportError: # pragma: no cover - # We only actually need these imports when running the mypy checks - pass - - -def run(aws_kms_cmk, aws_kms_additional_cmks, source_plaintext): - # type: (str, Sequence[str], bytes) -> None - """Demonstrate how to create a keyring that behaves like an AWS KMS master key provider. - - :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys - :param List[str] aws_kms_additional_cmks: Additional ARNs of secondary AWS KMS CMKs - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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", - } - - # This is the master key provider whose behavior we want to reproduce. - # - # When encrypting, this master key provider generates the data key using the first CMK in the list - # and encrypts the data key using all specified CMKs. - # However, when decrypting, this master key provider attempts to decrypt - # any data keys that were encrypted under an AWS KMS CMK. - master_key_provider_cmks = [aws_kms_cmk] + aws_kms_additional_cmks - _master_key_provider_to_replicate = KMSMasterKeyProvider( # noqa: intentionally never used - key_ids=master_key_provider_cmks, - ) - - # Create a CMK keyring that encrypts and decrypts using the specified AWS KMS CMKs. - # - # This keyring reproduces the encryption behavior of the AWS KMS master key provider. - # - # The AWS KMS keyring requires that you explicitly identify the CMK - # that you want the keyring to use to generate the data key. - cmk_keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk, key_ids=aws_kms_additional_cmks) - - # Create an AWS KMS discovery keyring that will attempt to decrypt - # any data keys that were encrypted under an AWS KMS CMK. - discovery_keyring = AwsKmsKeyring(is_discovery=True) - - # Combine the CMK and discovery keyrings - # to create a keyring that behaves like an AWS KMS master key provider. - # - # The CMK keyring reproduces the encryption behavior - # and the discovery keyring reproduces the decryption behavior. - # This also means that it does not matter if the CMK keyring fails to decrypt. - # For example, if you configured the CMK keyring with aliases, - # it works on encrypt but fails to match any encrypted data keys on decrypt - # because the serialized key name is the resulting CMK ARN rather than the alias name. - # However, because the discovery keyring attempts to decrypt any AWS KMS-encrypted - # data keys that it finds, the message still decrypts successfully. - keyring = MultiKeyring(generator=cmk_keyring, children=[discovery_keyring]) - - # Encrypt your plaintext data. - ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, keyring=keyring - ) - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # 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. - decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, keyring=keyring) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/src/keyring/aws_kms/discovery_decrypt.py b/examples/src/keyring/aws_kms/discovery_decrypt.py index 5ebf57b00..e43283f52 100644 --- a/examples/src/keyring/aws_kms/discovery_decrypt.py +++ b/examples/src/keyring/aws_kms/discovery_decrypt.py @@ -6,13 +6,8 @@ However, sometimes you need more flexibility on decrypt, especially when you don't know which CMKs were used to encrypt a message. To address this need, you can use an AWS KMS discovery keyring. -The AWS KMS discovery keyring does nothing on encrypt. -On decrypt it reviews each encrypted data key (EDK). -If an EDK was encrypted under an AWS KMS CMK, -the AWS KMS discovery keyring attempts to decrypt it. -Whether decryption succeeds depends on permissions on the CMK. -This continues until the AWS KMS discovery keyring either runs out of EDKs -or succeeds in decrypting an EDK. +The AWS KMS discovery keyring does nothing on encrypt, +but attempts to decrypt *any* data keys that were encrypted under an AWS KMS CMK. This example shows how to configure and use an AWS KMS discovery keyring. diff --git a/examples/src/keyring/aws_kms/discovery_decrypt_in_region_only.py b/examples/src/keyring/aws_kms/discovery_decrypt_in_region_only.py index e3e32a354..54caa0c7b 100644 --- a/examples/src/keyring/aws_kms/discovery_decrypt_in_region_only.py +++ b/examples/src/keyring/aws_kms/discovery_decrypt_in_region_only.py @@ -6,13 +6,8 @@ However, sometimes you need more flexibility on decrypt, especially when you don't know which CMKs were used to encrypt a message. To address this need, you can use an AWS KMS discovery keyring. -The AWS KMS discovery keyring does nothing on encrypt. -On decrypt it reviews each encrypted data key (EDK). -If an EDK was encrypted under an AWS KMS CMK, -the AWS KMS discovery keyring attempts to decrypt it. -Whether decryption succeeds depends on permissions on the CMK. -This continues until the AWS KMS discovery keyring either runs out of EDKs -or succeeds in decrypting an EDK. +The AWS KMS discovery keyring does nothing on encrypt, +but attempts to decrypt *any* data keys that were encrypted under an AWS KMS CMK. However, sometimes you need to be a *bit* more restrictive than that. To address this need, you can use a client supplier that restricts the regions an AWS KMS keyring can talk to. diff --git a/examples/src/keyring/aws_kms/discovery_decrypt_with_preferred_regions.py b/examples/src/keyring/aws_kms/discovery_decrypt_with_preferred_regions.py index 50eafb7d2..a3b22f9a1 100644 --- a/examples/src/keyring/aws_kms/discovery_decrypt_with_preferred_regions.py +++ b/examples/src/keyring/aws_kms/discovery_decrypt_with_preferred_regions.py @@ -6,13 +6,8 @@ However, sometimes you need more flexibility on decrypt, especially when you don't know which CMKs were used to encrypt a message. To address this need, you can use an AWS KMS discovery keyring. -The AWS KMS discovery keyring does nothing on encrypt. -On decrypt it reviews each encrypted data key (EDK). -If an EDK was encrypted under an AWS KMS CMK, -the AWS KMS discovery keyring attempts to decrypt it. -Whether decryption succeeds depends on permissions on the CMK. -This continues until the AWS KMS discovery keyring either runs out of EDKs -or succeeds in decrypting an EDK. +The AWS KMS discovery keyring does nothing on encrypt, +but attempts to decrypt *any* data keys that were encrypted under an AWS KMS CMK. However, sometimes you need to be a *bit* more restrictive than that. To address this need, you can use a client supplier to restrict what regions an AWS KMS keyring can talk to. From 255b36535b7adc9fecb1c82907ff1c657e22b0c1 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Fri, 22 May 2020 11:47:57 -0700 Subject: [PATCH 2/7] Revert "feat: refactor raw RSA keyrings configuration per #257 (#260)" This reverts commit 6ffc135dbecd5c63277e13d1702a7c44ca2bb3c0. --- examples/README.md | 8 +- .../{keypair.py => private_key_only.py} | 3 +- ...om_pem.py => private_key_only_from_pem.py} | 8 +- src/aws_encryption_sdk/keyrings/raw.py | 15 +- test/functional/keyrings/raw/test_raw_rsa.py | 180 +++++------------- test/functional/keyrings/test_multi.py | 20 +- test/unit/keyrings/raw/test_raw_rsa.py | 34 ++-- test/unit/unit_test_utils.py | 30 +-- 8 files changed, 105 insertions(+), 193 deletions(-) rename examples/src/keyring/raw_rsa/{keypair.py => private_key_only.py} (97%) rename examples/src/keyring/raw_rsa/{keypair_from_pem.py => private_key_only_from_pem.py} (94%) diff --git a/examples/README.md b/examples/README.md index d592de09c..081e62fab 100644 --- a/examples/README.md +++ b/examples/README.md @@ -49,9 +49,9 @@ We start with AWS KMS examples, then show how to use other wrapping keys. * [with keyrings](./src/keyring/raw_aes/raw_aes.py) * [with master key providers](./src/master_key_provider/raw_aes/raw_aes.py) * How to use a raw RSA wrapping key - * [with keyrings](./src/keyring/raw_rsa/keypair.py) + * [with keyrings](./src/keyring/raw_rsa/private_key_only.py) * How to use a raw RSA wrapping key when the key is PEM or DER encoded - * [with keyrings](./src/keyring/raw_rsa/keypair_from_pem.py) + * [with keyrings](./src/keyring/raw_rsa/private_key_only_from_pem.py) * [with master key providers](./src/master_key_provider/raw_rsa/private_key_only_from_pem.py) * How to encrypt with a raw RSA public key wrapping key without access to the private key * [with keyrings](./src/keyring/raw_rsa/public_private_key_separate.py) @@ -62,9 +62,9 @@ We start with AWS KMS examples, then show how to use other wrapping keys. * How to reuse data keys across multiple messages * [with the caching cryptographic materials manager](./src/crypto_materials_manager/caching/simple_cache.py) * How to restrict algorithm suites - * [with a custom cryptographic materials manager](./src/crypto_materials_manager/custom/algorithm_suite_enforcement.py) + * [with a custom cryptographic materials manager](src/crypto_materials_manager/custom/algorithm_suite_enforcement.py) * How to require encryption context fields - * [with a custom cryptographic materials manager](./src/crypto_materials_manager/custom/requiring_encryption_context_fields.py) + * [with a custom cryptographic materials manager](src/crypto_materials_manager/custom/requiring_encryption_context_fields.py) ### Keyrings diff --git a/examples/src/keyring/raw_rsa/keypair.py b/examples/src/keyring/raw_rsa/private_key_only.py similarity index 97% rename from examples/src/keyring/raw_rsa/keypair.py rename to examples/src/keyring/raw_rsa/private_key_only.py index f053b75a3..b29c7718a 100644 --- a/examples/src/keyring/raw_rsa/keypair.py +++ b/examples/src/keyring/raw_rsa/private_key_only.py @@ -1,7 +1,7 @@ # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 """ -This examples shows how to configure and use a raw RSA keyring using a pre-loaded RSA keypair. +This examples shows how to configure and use a raw RSA keyring using a pre-loaded RSA private key. If your RSA key is in PEM or DER format, see the ``keyring/raw_rsa/private_key_only_from_pem`` example. @@ -56,7 +56,6 @@ def run(source_plaintext): key_namespace="some managed raw keys", key_name=b"my RSA wrapping key", private_wrapping_key=private_key, - public_wrapping_key=private_key.public_key(), # The wrapping algorithm tells the raw RSA keyring # how to use your wrapping key to encrypt data keys. # diff --git a/examples/src/keyring/raw_rsa/keypair_from_pem.py b/examples/src/keyring/raw_rsa/private_key_only_from_pem.py similarity index 94% rename from examples/src/keyring/raw_rsa/keypair_from_pem.py rename to examples/src/keyring/raw_rsa/private_key_only_from_pem.py index 2ec7a6ba1..d72f82ae0 100644 --- a/examples/src/keyring/raw_rsa/keypair_from_pem.py +++ b/examples/src/keyring/raw_rsa/private_key_only_from_pem.py @@ -3,7 +3,7 @@ """ When you store RSA keys, you have to serialize them somehow. -This example shows how to configure and use a raw RSA keyring using a PEM-encoded RSA keypair. +This example shows how to configure and use a raw RSA keyring using a PEM-encoded RSA private key. The most commonly used encodings for RSA keys tend to be PEM and DER. The raw RSA keyring supports loading both public and private keys from these encodings. @@ -48,16 +48,13 @@ def run(source_plaintext): # https://crypto.stanford.edu/~dabo/pubs/papers/RSA-survey.pdf private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096, backend=default_backend()) - # Serialize the RSA keypair to PEM encoding. + # Serialize the RSA private key to PEM encoding. # This or DER encoding is likely to be what you get from your key management system in practice. private_key_pem = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption(), ) - public_key_pem = private_key.public_key().public_bytes( - encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, - ) # Create the keyring that determines how your data keys are protected. # @@ -72,7 +69,6 @@ def run(source_plaintext): key_namespace="some managed raw keys", key_name=b"my RSA wrapping key", private_encoded_key=private_key_pem, - public_encoded_key=public_key_pem, # The wrapping algorithm tells the raw RSA keyring # how to use your wrapping key to encrypt data keys. # diff --git a/src/aws_encryption_sdk/keyrings/raw.py b/src/aws_encryption_sdk/keyrings/raw.py index ddb07fb03..450111ad7 100644 --- a/src/aws_encryption_sdk/keyrings/raw.py +++ b/src/aws_encryption_sdk/keyrings/raw.py @@ -283,12 +283,8 @@ def __attrs_post_init__(self): if self._public_wrapping_key is None and self._private_wrapping_key is None: raise TypeError("At least one of public key or private key must be provided.") - if self._public_wrapping_key is not None and self._private_wrapping_key is not None: - derived_public_key = self._private_wrapping_key.public_key() - # We cannot compare the public key objects directly. - # Instead, extract their numbers and compare those. - if derived_public_key.public_numbers() != self._public_wrapping_key.public_numbers(): - raise ValueError("Private and public wrapping keys MUST be from the same keypair.") + if self._private_wrapping_key is not None and self._public_wrapping_key is None: + self._public_wrapping_key = self._private_wrapping_key.public_key() @classmethod def from_pem_encoding( @@ -379,12 +375,13 @@ def on_encrypt(self, encryption_materials): """ new_materials = encryption_materials - if self._public_wrapping_key is None: - raise EncryptKeyError("A public key is required to encrypt") - if new_materials.data_encryption_key is None: new_materials = _generate_data_key(encryption_materials=new_materials, key_provider=self._key_provider) + if self._public_wrapping_key is None: + # This should be impossible, but just in case, give a useful error message. + raise EncryptKeyError("Raw RSA keyring unable to encrypt data key: no public key available") + try: # Encrypt data key encrypted_wrapped_key = EncryptedData( diff --git a/test/functional/keyrings/raw/test_raw_rsa.py b/test/functional/keyrings/raw/test_raw_rsa.py index f72ffee51..b7a278d2b 100644 --- a/test/functional/keyrings/raw/test_raw_rsa.py +++ b/test/functional/keyrings/raw/test_raw_rsa.py @@ -17,7 +17,6 @@ from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa -from aws_encryption_sdk.exceptions import EncryptKeyError from aws_encryption_sdk.identifiers import ( Algorithm, EncryptionKeyType, @@ -43,41 +42,79 @@ _BACKEND = default_backend() _PRIVATE_WRAPPING_KEY = rsa.generate_private_key(public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND) -_PUBLIC_WRAPPING_KEY = _PRIVATE_WRAPPING_KEY.public_key() -_PRIVATE_WRAPPING_KEY_PEM = _PRIVATE_WRAPPING_KEY.private_bytes( +_PRIVATE_WRAPPING_KEY_PEM = ( + b"-----BEGIN RSA PRIVATE KEY-----\n" + b"MIIEowIBAAKCAQEAo8uCyhiO4JUGZV+rtNq5DBA9Lm4xkw5kTA3v6EPybs8bVXL2\n" + b"ZE6jkbo+xT4Jg/bKzUpnp1fE+T1ruGPtsPdoEmhY/P64LDNIs3sRq5U4QV9IETU1\n" + b"vIcbNNkgGhRjV8J87YNY0tV0H7tuWuZRpqnS+gjV6V9lUMkbvjMCc5IBqQc3heut\n" + b"/+fH4JwpGlGxOVXI8QAapnSy1XpCr3+PT29kydVJnIMuAoFrurojRpOQbOuVvhtA\n" + b"gARhst1Ji4nfROGYkj6eZhvkz2Bkud4/+3lGvVU5LO1vD8oY7WoGtpin3h50VcWe\n" + b"aBT4kejx4s9/G9C4R24lTH09J9HO2UUsuCqZYQIDAQABAoIBAQCfC90bCk+qaWqF\n" + b"gymC+qOWwCn4bM28gswHQb1D5r6AtKBRD8mKywVvWs7azguFVV3Fi8sspkBA2FBC\n" + b"At5p6ULoJOTL/TauzLl6djVJTCMM701WUDm2r+ZOIctXJ5bzP4n5Q4I7b0NMEL7u\n" + b"ixib4elYGr5D1vrVQAKtZHCr8gmkqyx8Mz7wkJepzBP9EeVzETCHsmiQDd5WYlO1\n" + b"C2IQYgw6MJzgM4entJ0V/GPytkodblGY95ORVK7ZhyNtda+r5BZ6/jeMW+hA3VoK\n" + b"tHSWjHt06ueVCCieZIATmYzBNt+zEz5UA2l7ksg3eWfVORJQS7a6Ef4VvbJLM9Ca\n" + b"m1kdsjelAoGBANKgvRf39i3bSuvm5VoyJuqinSb/23IH3Zo7XOZ5G164vh49E9Cq\n" + b"dOXXVxox74ppj/kbGUoOk+AvaB48zzfzNvac0a7lRHExykPH2kVrI/NwH/1OcT/x\n" + b"2e2DnFYocXcb4gbdZQ+m6X3zkxOYcONRzPVW1uMrFTWHcJveMUm4PGx7AoGBAMcU\n" + b"IRvrT6ye5se0s27gHnPweV+3xjsNtXZcK82N7duXyHmNjxrwOAv0SOhUmTkRXArM\n" + b"6aN5D8vyZBSWma2TgUKwpQYFTI+4Sp7sdkkyojGAEixJ+c5TZJNxZFrUe0FwAoic\n" + b"c2kb7ntaiEj5G+qHvykJJro5hy6uLnjiMVbAiJDTAoGAKb67241EmHAXGEwp9sdr\n" + b"2SMjnIAnQSF39UKAthkYqJxa6elXDQtLoeYdGE7/V+J2K3wIdhoPiuY6b4vD0iX9\n" + b"JcGM+WntN7YTjX2FsC588JmvbWfnoDHR7HYiPR1E58N597xXdFOzgUgORVr4PMWQ\n" + b"pqtwaZO3X2WZlvrhr+e46hMCgYBfdIdrm6jYXFjL6RkgUNZJQUTxYGzsY+ZemlNm\n" + b"fGdQo7a8kePMRuKY2MkcnXPaqTg49YgRmjq4z8CtHokRcWjJUWnPOTs8rmEZUshk\n" + b"0KJ0mbQdCFt/Uv0mtXgpFTkEZ3DPkDTGcV4oR4CRfOCl0/EU/A5VvL/U4i/mRo7h\n" + b"ye+xgQKBgD58b+9z+PR5LAJm1tZHIwb4tnyczP28PzwknxFd2qylR4ZNgvAUqGtU\n" + b"xvpUDpzMioz6zUH9YV43YNtt+5Xnzkqj+u9Mr27/H2v9XPwORGfwQ5XPwRJz/2oC\n" + b"EnPmP1SZoY9lXKUpQXHXSpDZ2rE2Klt3RHMUMHt8Zpy36E8Vwx8o\n" + b"-----END RSA PRIVATE KEY-----\n" +) + +_RAW_RSA_PRIVATE_KEY_PEM_ENCODED_WITHOUT_PASSWORD = rsa.generate_private_key( + public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND +).private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), ) -_PUBLIC_WRAPPING_KEY_PEM = _PUBLIC_WRAPPING_KEY.public_bytes( - encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo -) -_RAW_RSA_PRIVATE_KEY_PEM_ENCODED_WITHOUT_PASSWORD = _PRIVATE_WRAPPING_KEY_PEM - -_RAW_RSA_PRIVATE_KEY_PEM_ENCODED_WITH_PASSWORD = _PRIVATE_WRAPPING_KEY.private_bytes( +_RAW_RSA_PRIVATE_KEY_PEM_ENCODED_WITH_PASSWORD = rsa.generate_private_key( + public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND +).private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.BestAvailableEncryption(b"mypassword"), ) -_RAW_RSA_PUBLIC_KEY_PEM_ENCODED = _PUBLIC_WRAPPING_KEY_PEM +_RAW_RSA_PUBLIC_KEY_PEM_ENCODED = ( + rsa.generate_private_key(public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND) + .public_key() + .public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo) +) -_RAW_RSA_PRIVATE_KEY_DER_ENCODED_WITHOUT_PASSWORD = _PRIVATE_WRAPPING_KEY.private_bytes( +_RAW_RSA_PRIVATE_KEY_DER_ENCODED_WITHOUT_PASSWORD = rsa.generate_private_key( + public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND +).private_bytes( encoding=serialization.Encoding.DER, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), ) -_RAW_RSA_PRIVATE_KEY_DER_ENCODED_WITH_PASSWORD = _PRIVATE_WRAPPING_KEY.private_bytes( +_RAW_RSA_PRIVATE_KEY_DER_ENCODED_WITH_PASSWORD = rsa.generate_private_key( + public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND +).private_bytes( encoding=serialization.Encoding.DER, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.BestAvailableEncryption(b"mypassword"), ) -_RAW_RSA_PUBLIC_KEY_DER_ENCODED = _PUBLIC_WRAPPING_KEY.public_bytes( - encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo +_RAW_RSA_PUBLIC_KEY_DER_ENCODED = ( + rsa.generate_private_key(public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND) + .public_key() + .public_bytes(encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo) ) @@ -111,21 +148,18 @@ def sample_raw_rsa_keyring_using_different_wrapping_algorithm(): key_name=_KEY_ID, wrapping_algorithm=alg, private_wrapping_key=_PRIVATE_WRAPPING_KEY, - public_wrapping_key=_PUBLIC_WRAPPING_KEY, ) pem_and_der_encoded_raw_rsa_keyring = [ RawRSAKeyring.from_pem_encoding( key_namespace=_PROVIDER_ID, key_name=_KEY_ID, private_encoded_key=_RAW_RSA_PRIVATE_KEY_PEM_ENCODED_WITHOUT_PASSWORD, - public_encoded_key=_RAW_RSA_PUBLIC_KEY_PEM_ENCODED, wrapping_algorithm=_WRAPPING_ALGORITHM, ), RawRSAKeyring.from_pem_encoding( key_namespace=_PROVIDER_ID, key_name=_KEY_ID, private_encoded_key=_RAW_RSA_PRIVATE_KEY_PEM_ENCODED_WITH_PASSWORD, - public_encoded_key=_RAW_RSA_PUBLIC_KEY_PEM_ENCODED, password=b"mypassword", wrapping_algorithm=_WRAPPING_ALGORITHM, ), @@ -139,14 +173,12 @@ def sample_raw_rsa_keyring_using_different_wrapping_algorithm(): key_namespace=_PROVIDER_ID, key_name=_KEY_ID, private_encoded_key=_RAW_RSA_PRIVATE_KEY_DER_ENCODED_WITHOUT_PASSWORD, - public_encoded_key=_RAW_RSA_PUBLIC_KEY_DER_ENCODED, wrapping_algorithm=_WRAPPING_ALGORITHM, ), RawRSAKeyring.from_der_encoding( key_namespace=_PROVIDER_ID, key_name=_KEY_ID, private_encoded_key=_RAW_RSA_PRIVATE_KEY_DER_ENCODED_WITH_PASSWORD, - public_encoded_key=_RAW_RSA_PUBLIC_KEY_DER_ENCODED, password=b"mypassword", wrapping_algorithm=_WRAPPING_ALGORITHM, ), @@ -154,6 +186,7 @@ def sample_raw_rsa_keyring_using_different_wrapping_algorithm(): key_namespace=_PROVIDER_ID, key_name=_KEY_ID, public_encoded_key=_RAW_RSA_PUBLIC_KEY_DER_ENCODED, + password=b"mypassword", wrapping_algorithm=_WRAPPING_ALGORITHM, ), ] @@ -194,7 +227,6 @@ def test_raw_master_key_decrypts_what_raw_keyring_encrypts(encryption_materials_ key_name=_KEY_ID, wrapping_algorithm=_WRAPPING_ALGORITHM, private_encoded_key=_PRIVATE_WRAPPING_KEY_PEM, - public_encoded_key=_PUBLIC_WRAPPING_KEY_PEM, ) # Creating an instance of a raw master key @@ -240,7 +272,6 @@ def test_raw_keyring_decrypts_what_raw_master_key_encrypts(encryption_materials_ key_name=_KEY_ID, wrapping_algorithm=_WRAPPING_ALGORITHM, private_encoded_key=_PRIVATE_WRAPPING_KEY_PEM, - public_encoded_key=_PUBLIC_WRAPPING_KEY_PEM, ) raw_mkp_generated_data_key = test_raw_master_key.generate_data_key( @@ -264,110 +295,3 @@ def test_raw_keyring_decrypts_what_raw_master_key_encrypts(encryption_materials_ ) assert raw_mkp_generated_data_key.data_key == decryption_materials.data_encryption_key.data_key - - -def test_public_key_only_can_encrypt(): - test_keyring = RawRSAKeyring( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - wrapping_algorithm=_WRAPPING_ALGORITHM, - public_wrapping_key=_PUBLIC_WRAPPING_KEY, - ) - initial_materials = EncryptionMaterials( - algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, encryption_context=_ENCRYPTION_CONTEXT - ) - - test_materials = test_keyring.on_encrypt(initial_materials) - - assert test_materials is not initial_materials - assert test_materials.data_encryption_key is not None - assert test_materials.encrypted_data_keys - - -def test_public_key_only_cannot_decrypt(): - test_keyring = RawRSAKeyring( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - wrapping_algorithm=_WRAPPING_ALGORITHM, - public_wrapping_key=_PUBLIC_WRAPPING_KEY, - ) - initial_materials = EncryptionMaterials( - algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, encryption_context=_ENCRYPTION_CONTEXT - ) - - encryption_materials = test_keyring.on_encrypt(initial_materials) - - initial_decryption_materials = DecryptionMaterials( - algorithm=Algorithm.AES_192_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, encryption_context=_ENCRYPTION_CONTEXT - ) - - test_materials = test_keyring.on_decrypt( - decryption_materials=initial_decryption_materials, encrypted_data_keys=encryption_materials.encrypted_data_keys - ) - - assert test_materials is initial_decryption_materials - - -def test_private_key_can_decrypt(): - complete_keyring = RawRSAKeyring( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - wrapping_algorithm=_WRAPPING_ALGORITHM, - private_wrapping_key=_PRIVATE_WRAPPING_KEY, - public_wrapping_key=_PUBLIC_WRAPPING_KEY, - ) - test_keyring = RawRSAKeyring( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - wrapping_algorithm=_WRAPPING_ALGORITHM, - private_wrapping_key=_PRIVATE_WRAPPING_KEY, - ) - initial_materials = EncryptionMaterials( - algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, encryption_context=_ENCRYPTION_CONTEXT - ) - - encryption_materials = complete_keyring.on_encrypt(initial_materials) - - initial_decryption_materials = DecryptionMaterials( - algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, encryption_context=_ENCRYPTION_CONTEXT - ) - - test_materials = test_keyring.on_decrypt( - decryption_materials=initial_decryption_materials, encrypted_data_keys=encryption_materials.encrypted_data_keys - ) - - assert test_materials is not initial_decryption_materials - assert test_materials.data_encryption_key is not None - - -def test_private_key_cannot_encrypt(): - test_keyring = RawRSAKeyring( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - wrapping_algorithm=_WRAPPING_ALGORITHM, - private_wrapping_key=_PRIVATE_WRAPPING_KEY, - ) - initial_materials = EncryptionMaterials( - algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, encryption_context=_ENCRYPTION_CONTEXT - ) - - with pytest.raises(EncryptKeyError) as excinfo: - test_keyring.on_encrypt(initial_materials) - - excinfo.match("A public key is required to encrypt") - - -def test_keypair_must_match(): - wrapping_key_a = rsa.generate_private_key(public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND) - wrapping_key_b = rsa.generate_private_key(public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND) - - with pytest.raises(ValueError) as excinfo: - RawRSAKeyring( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - wrapping_algorithm=_WRAPPING_ALGORITHM, - private_wrapping_key=wrapping_key_a, - public_wrapping_key=wrapping_key_b.public_key(), - ) - - excinfo.match("Private and public wrapping keys MUST be from the same keypair.") diff --git a/test/functional/keyrings/test_multi.py b/test/functional/keyrings/test_multi.py index 43833a41a..ff9eb2440 100644 --- a/test/functional/keyrings/test_multi.py +++ b/test/functional/keyrings/test_multi.py @@ -49,8 +49,6 @@ ], ) -_rsa_private_key_a = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()) -_rsa_private_key_b = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()) _MULTI_KEYRING_WITH_GENERATOR_AND_CHILDREN = MultiKeyring( generator=RawAESKeyring(key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_key=_WRAPPING_KEY_AES,), children=[ @@ -58,15 +56,17 @@ key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=_rsa_private_key_a, - public_wrapping_key=_rsa_private_key_a.public_key(), + private_wrapping_key=rsa.generate_private_key( + public_exponent=65537, key_size=2048, backend=default_backend() + ), ), RawRSAKeyring( key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=_rsa_private_key_b, - public_wrapping_key=_rsa_private_key_b.public_key(), + private_wrapping_key=rsa.generate_private_key( + public_exponent=65537, key_size=2048, backend=default_backend() + ), ), ], ) @@ -76,8 +76,7 @@ key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=_rsa_private_key_a, - public_wrapping_key=_rsa_private_key_a.public_key(), + private_wrapping_key=rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()), ) ) @@ -87,8 +86,9 @@ key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=_rsa_private_key_a, - public_wrapping_key=_rsa_private_key_a.public_key(), + private_wrapping_key=rsa.generate_private_key( + public_exponent=65537, key_size=2048, backend=default_backend() + ), ), RawAESKeyring(key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_key=_WRAPPING_KEY_AES,), ] diff --git a/test/unit/keyrings/raw/test_raw_rsa.py b/test/unit/keyrings/raw/test_raw_rsa.py index 55b91de92..5416ae24d 100644 --- a/test/unit/keyrings/raw/test_raw_rsa.py +++ b/test/unit/keyrings/raw/test_raw_rsa.py @@ -13,8 +13,6 @@ """Unit tests for Raw AES keyring.""" import pytest -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa import aws_encryption_sdk.key_providers.raw @@ -47,15 +45,11 @@ @pytest.fixture def raw_rsa_keyring(): - private_key = serialization.load_pem_private_key( - data=VALUES["private_rsa_key_bytes"][1], password=None, backend=default_backend() - ) - return RawRSAKeyring( + return RawRSAKeyring.from_pem_encoding( key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=private_key, - public_wrapping_key=private_key.public_key(), + private_encoded_key=VALUES["private_rsa_key_bytes"][1], ) @@ -149,20 +143,12 @@ def test_on_encrypt_when_data_encryption_key_given(raw_rsa_keyring, patch_genera def test_on_encrypt_no_public_key(raw_rsa_keyring): - private_key = raw_rsa_private_key() - test_keyring = RawRSAKeyring( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=private_key, - ) - - initial_materials = get_encryption_materials_without_data_encryption_key() + raw_rsa_keyring._public_wrapping_key = None with pytest.raises(EncryptKeyError) as excinfo: - test_keyring.on_encrypt(encryption_materials=initial_materials) + raw_rsa_keyring.on_encrypt(encryption_materials=get_encryption_materials_without_data_encryption_key()) - excinfo.match("A public key is required to encrypt") + excinfo.match("Raw RSA keyring unable to encrypt data key: no public key available") def test_on_encrypt_keyring_trace_when_data_encryption_key_given(raw_rsa_keyring): @@ -203,6 +189,16 @@ def test_on_encrypt_when_data_encryption_key_not_given(raw_rsa_keyring): assert len(test.encrypted_data_keys) == original_number_of_encrypted_data_keys + 1 +def test_on_encrypt_cannot_encrypt(raw_rsa_keyring, mocker): + encrypt_patch = mocker.patch.object(raw_rsa_keyring._public_wrapping_key, "encrypt") + encrypt_patch.side_effect = Exception("ENCRYPT FAIL") + + with pytest.raises(EncryptKeyError) as excinfo: + raw_rsa_keyring.on_encrypt(encryption_materials=get_encryption_materials_without_data_encryption_key()) + + excinfo.match("Raw RSA keyring unable to encrypt data key") + + def test_on_decrypt_when_data_key_given(raw_rsa_keyring, patch_decrypt_on_wrapping_key): test_raw_rsa_keyring = raw_rsa_keyring test_raw_rsa_keyring.on_decrypt( diff --git a/test/unit/unit_test_utils.py b/test/unit/unit_test_utils.py index bd6a9a82f..d175f0b3e 100644 --- a/test/unit/unit_test_utils.py +++ b/test/unit/unit_test_utils.py @@ -255,7 +255,6 @@ def get_decryption_materials_without_data_key(): def get_multi_keyring_with_generator_and_children(): - private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()) return MultiKeyring( generator=RawAESKeyring(key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_key=_WRAPPING_KEY_AES,), children=[ @@ -263,43 +262,45 @@ def get_multi_keyring_with_generator_and_children(): key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=private_key, - public_wrapping_key=private_key.public_key(), + private_wrapping_key=rsa.generate_private_key( + public_exponent=65537, key_size=2048, backend=default_backend() + ), ), RawRSAKeyring( key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=private_key, - public_wrapping_key=private_key.public_key(), + private_wrapping_key=rsa.generate_private_key( + public_exponent=65537, key_size=2048, backend=default_backend() + ), ), ], ) def get_multi_keyring_with_no_children(): - private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()) return MultiKeyring( generator=RawRSAKeyring( key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=private_key, - public_wrapping_key=private_key.public_key(), + private_wrapping_key=rsa.generate_private_key( + public_exponent=65537, key_size=2048, backend=default_backend() + ), ) ) def get_multi_keyring_with_no_generator(): - private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()) return MultiKeyring( children=[ RawRSAKeyring( key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=private_key, - public_wrapping_key=private_key.public_key(), + private_wrapping_key=rsa.generate_private_key( + public_exponent=65537, key_size=2048, backend=default_backend() + ), ), RawAESKeyring(key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_key=_WRAPPING_KEY_AES,), ] @@ -409,13 +410,12 @@ def ephemeral_raw_rsa_master_key(size=4096): def ephemeral_raw_rsa_keyring(size=4096, wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1): # type: (int, WrappingAlgorithm) -> RawRSAKeyring - private_key = rsa.generate_private_key(public_exponent=65537, key_size=size, backend=default_backend()) - return RawRSAKeyring( + key_bytes = _generate_rsa_key_bytes(size) + return RawRSAKeyring.from_pem_encoding( key_namespace="fake", key_name="rsa-{}".format(size).encode("utf-8"), wrapping_algorithm=wrapping_algorithm, - private_wrapping_key=private_key, - public_wrapping_key=private_key.public_key(), + private_encoded_key=key_bytes, ) From 51613af63dde7d1effa9d5994abc22a304dc4008 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Fri, 22 May 2020 12:01:31 -0700 Subject: [PATCH 3/7] Revert "docs: clarify KMSMasterKeyProvider credentials configuration (#251)" This reverts commit 613662070c63e270e21f68a0908ddf1289ee7c7a. --- README.rst | 3 ++ src/aws_encryption_sdk/key_providers/kms.py | 58 ++++----------------- 2 files changed, 14 insertions(+), 47 deletions(-) diff --git a/README.rst b/README.rst index 5c39e3593..3407dc177 100644 --- a/README.rst +++ b/README.rst @@ -112,6 +112,9 @@ to your use-case in order to obtain peak performance. .. _GitHub: https://github.com/aws/aws-encryption-sdk-python/ .. _AWS KMS: https://docs.aws.amazon.com/kms/latest/developerguide/overview.html .. _KMS customer master key (CMK): https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#master_keys +.. _boto3 SDK: https://boto3.readthedocs.io/en/latest/ +.. _standard means by which boto3 locates credentials: https://boto3.readthedocs.io/en/latest/guide/configuration.html +.. _final message: https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/message-format.html .. _encryption context: https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#encrypt_context .. _examples: https://github.com/aws/aws-encryption-sdk-python/tree/master/examples .. _Security issue notifications: https://github.com/aws/aws-encryption-sdk-python/tree/master/CONTRIBUTING.md#security-issue-notifications diff --git a/src/aws_encryption_sdk/key_providers/kms.py b/src/aws_encryption_sdk/key_providers/kms.py index 917819fdb..83443e40d 100644 --- a/src/aws_encryption_sdk/key_providers/kms.py +++ b/src/aws_encryption_sdk/key_providers/kms.py @@ -82,58 +82,22 @@ class KMSMasterKeyProvider(MasterKeyProvider): Master key providers are deprecated. Use :class:`aws_encryption_sdk.keyrings.aws_kms.AwsKmsKeyring` instead. - To encrypt data, you must configure :class:`KMSMasterKeyProvider` with at least one CMK. - If you configure :class:`KMSMasterKeyProvider` with multiple CMKs, - it generates the data key using the first CMK and encrypts that data key using the rest, - so that the `encrypted message`_ includes a copy of the data key encrypted under each configured CMK. - - .. _encrypted message: https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/message-format.html - - >>> from aws_encryption_sdk.key_providers.kms import KMSMasterKeyProvider - >>> kms_key_provider = KMSMasterKeyProvider(key_ids=[ - ... "arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222", - ... "arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333", - ... ]) - - You can also configure :class:`KMSMasterKeyProvider` with CMKs in multiple regions: - - >>> from aws_encryption_sdk.key_providers.kms import KMSMasterKeyProvider - >>> kms_key_provider = KMSMasterKeyProvider(key_ids=[ - ... "arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222", - ... "arn:aws:kms:us-west-2:3333333333333:key/33333333-3333-3333-3333-333333333333", - ... "arn:aws:kms:ap-northeast-1:4444444444444:key/44444444-4444-4444-4444-444444444444", + >>> import aws_encryption_sdk + >>> kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ + ... 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', + ... 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' ... ]) + >>> kms_key_provider.add_master_key('arn:aws:kms:ap-northeast-1:4444444444444:alias/another-key') - :class:`KMSMasterKeyProvider` needs AWS credentials in order to interact with `AWS KMS`_. - There are two ways that you can provide these credentials: - - .. _AWS KMS: https://docs.aws.amazon.com/kms/latest/developerguide/overview.html - - 1. Provide your AWS credentials in one of the standard `AWS credential discovery locations`_ - and the :class:`KMSMasterKeyProvider` instance automatically discovers those credentials. - - .. _AWS credential discovery locations: - https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#configuring-credentials - - >>> from aws_encryption_sdk.key_providers.kms import KMSMasterKeyProvider - >>> import botocore.session - >>> kms_key_provider = KMSMasterKeyProvider() + .. note:: + If no botocore_session is provided, the default botocore session will be used. - 2. Provide an existing botocore session to :class:`KMSMasterKeyProvider`. - This option can be useful if you want to use specific credentials - or if you want to reuse an existing botocore session instance to decrease startup costs. + .. note:: + If multiple AWS Identities are needed, one of two options are available: - >>> from aws_encryption_sdk.key_providers.kms import KMSMasterKeyProvider - >>> import botocore.session - >>> existing_botocore_session = botocore.session.Session(profile="custom") - >>> kms_key_provider = KMSMasterKeyProvider(botocore_session=existing_botocore_session) + * Additional KMSMasterKeyProvider instances may be added to the primary MasterKeyProvider. - If you need different credentials to use different CMKs, - you can combine multiple :class:`KMSMasterKeyProvider` or :class:`KMSMasterKey` instances, - each with their own credentials. - However, we recommend that you use - :class:`aws_encryption_sdk.keyrings.aws_kms.AwsKmsKeyring` and client suppliers - for a simpler user experience. + * KMSMasterKey instances may be manually created and added to this KMSMasterKeyProvider. :param config: Configuration object (optional) :type config: aws_encryption_sdk.key_providers.kms.KMSMasterKeyProviderConfig From fde17a6ccb74b8ae5b48024a6fe8766275b7e0af Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Fri, 22 May 2020 12:14:00 -0700 Subject: [PATCH 4/7] Revert "fix: hanging object references in helper functions avoided (#264)" This reverts commit b10c415d0daea753693c61f1769b5d7c9250b9bc. --- src/aws_encryption_sdk/__init__.py | 12 ++---------- test/unit/test_client.py | 6 ++---- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/aws_encryption_sdk/__init__.py b/src/aws_encryption_sdk/__init__.py index 88da93d25..aba067601 100644 --- a/src/aws_encryption_sdk/__init__.py +++ b/src/aws_encryption_sdk/__init__.py @@ -1,8 +1,6 @@ # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 """High level AWS Encryption SDK client functions.""" -import copy - # Below are imported for ease of use by implementors from aws_encryption_sdk.caches.local import LocalCryptoMaterialsCache # noqa from aws_encryption_sdk.caches.null import NullCryptoMaterialsCache # noqa @@ -86,10 +84,7 @@ def encrypt(**kwargs): with StreamEncryptor(**kwargs) as encryptor: ciphertext = encryptor.read() - header_copy = copy.deepcopy(encryptor.header) - keyring_trace_copy = copy.deepcopy(encryptor.keyring_trace) - - return CryptoResult(result=ciphertext, header=header_copy, keyring_trace=keyring_trace_copy) + return CryptoResult(result=ciphertext, header=encryptor.header, keyring_trace=encryptor.keyring_trace) def decrypt(**kwargs): @@ -148,10 +143,7 @@ def decrypt(**kwargs): with StreamDecryptor(**kwargs) as decryptor: plaintext = decryptor.read() - header_copy = copy.deepcopy(decryptor.header) - keyring_trace_copy = copy.deepcopy(decryptor.keyring_trace) - - return CryptoResult(result=plaintext, header=header_copy, keyring_trace=keyring_trace_copy) + return CryptoResult(result=plaintext, header=decryptor.header, keyring_trace=decryptor.keyring_trace) def stream(**kwargs): diff --git a/test/unit/test_client.py b/test/unit/test_client.py index bd40eb03a..d6b763b49 100644 --- a/test/unit/test_client.py +++ b/test/unit/test_client.py @@ -53,15 +53,13 @@ def test_encrypt(self): test_ciphertext, test_header = aws_encryption_sdk.encrypt(a=sentinel.a, b=sentinel.b, c=sentinel.b) self.mock_stream_encryptor.called_once_with(a=sentinel.a, b=sentinel.b, c=sentinel.b) assert test_ciphertext is _CIPHERTEXT - assert test_header == _HEADER - assert test_header is not _HEADER + assert test_header is _HEADER def test_decrypt(self): test_plaintext, test_header = aws_encryption_sdk.decrypt(a=sentinel.a, b=sentinel.b, c=sentinel.b) self.mock_stream_encryptor.called_once_with(a=sentinel.a, b=sentinel.b, c=sentinel.b) assert test_plaintext is _PLAINTEXT - assert test_header == _HEADER - assert test_header is not _HEADER + assert test_header is _HEADER def test_stream_encryptor_e(self): test = aws_encryption_sdk.stream(mode="e", a=sentinel.a, b=sentinel.b, c=sentinel.b) From 582feabba04844d599acbf2de395da158999da13 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Fri, 22 May 2020 12:33:26 -0700 Subject: [PATCH 5/7] Revert "Merge pull request #249 from aws/keyring" This reverts commit 00c7e1dbfd0aade7ccb61e5e5735e26a8a239c38, reversing changes made to 42dfdfe54d58195ccd8e55d3151a1a449d2ba380. --- .github/workflows/ci_decrypt-oracle.yaml | 53 -- .github/workflows/ci_static-analysis.yaml | 40 -- .github/workflows/ci_test-vector-handler.yaml | 88 --- .github/workflows/ci_tests.yaml | 100 ---- .travis.yml | 213 ++++++- CHANGELOG.rst | 43 -- README.rst | 196 +++++- appveyor.yml | 64 +- ci-requirements.txt | 1 - doc/conf.py | 5 +- doc/index.rst | 7 - examples/README.md | 138 ----- examples/src/{legacy => }/basic_encryption.py | 26 +- ...file_encryption_with_multiple_providers.py | 24 +- ...c_file_encryption_with_raw_key_provider.py | 16 +- .../src/crypto_materials_manager/__init__.py | 7 - .../caching/__init__.py | 7 - .../caching/simple_cache.py | 95 --- .../custom/__init__.py | 10 - .../custom/algorithm_suite_enforcement.py | 139 ----- .../requiring_encryption_context_fields.py | 154 ----- .../{legacy => }/data_key_caching_basic.py | 22 +- examples/src/file_streaming_defaults.py | 83 --- examples/src/in_memory_streaming_defaults.py | 80 --- examples/src/keyring/__init__.py | 7 - examples/src/keyring/aws_kms/__init__.py | 7 - .../keyring/aws_kms/custom_client_supplier.py | 116 ---- .../aws_kms/custom_kms_client_config.py | 89 --- .../src/keyring/aws_kms/discovery_decrypt.py | 77 --- .../discovery_decrypt_in_region_only.py | 90 --- ...iscovery_decrypt_with_preferred_regions.py | 111 ---- .../src/keyring/aws_kms/multiple_regions.py | 94 --- examples/src/keyring/aws_kms/single_cmk.py | 67 --- examples/src/keyring/multi/__init__.py | 7 - .../src/keyring/multi/aws_kms_with_escrow.py | 129 ---- examples/src/keyring/raw_aes/__init__.py | 7 - examples/src/keyring/raw_aes/raw_aes.py | 73 --- examples/src/keyring/raw_rsa/__init__.py | 7 - .../src/keyring/raw_rsa/private_key_only.py | 89 --- .../raw_rsa/private_key_only_from_pem.py | 102 ---- .../raw_rsa/public_private_key_separate.py | 126 ---- examples/src/legacy/__init__.py | 9 - examples/src/master_key_provider/__init__.py | 7 - .../master_key_provider/aws_kms/__init__.py | 7 - .../aws_kms/discovery_decrypt.py | 72 --- .../aws_kms/multiple_regions.py | 92 --- .../master_key_provider/aws_kms/single_cmk.py | 65 -- .../src/master_key_provider/multi/__init__.py | 7 - .../multi/aws_kms_with_escrow.py | 151 ----- .../master_key_provider/raw_aes/__init__.py | 7 - .../master_key_provider/raw_aes/raw_aes.py | 82 --- .../master_key_provider/raw_rsa/__init__.py | 7 - .../raw_rsa/private_key_only_from_pem.py | 105 ---- examples/src/one_kms_cmk.py | 48 ++ .../one_kms_cmk_streaming_data.py | 25 +- .../src/{legacy => }/one_kms_cmk_unsigned.py | 22 +- examples/src/onestep_defaults.py | 58 -- examples/src/onestep_unsigned.py | 78 --- examples/test/examples_test_utils.py | 96 +-- examples/test/test_i_basic_encryption.py | 27 + ...file_encryption_with_multiple_providers.py | 41 ++ ...c_file_encryption_with_raw_key_provider.py | 36 ++ .../test/test_i_data_key_caching_basic.py | 16 +- .../test/test_i_one_kms_cmk.py | 18 +- .../test/test_i_one_kms_cmk_streaming_data.py | 40 ++ .../test/test_i_one_kms_cmk_unsigned.py | 18 +- examples/test/test_run_examples.py | 26 - requirements.txt | 3 +- setup.cfg | 2 +- setup.py | 2 +- src/aws_encryption_sdk/__init__.py | 121 ++-- src/aws_encryption_sdk/exceptions.py | 35 +- src/aws_encryption_sdk/identifiers.py | 25 - .../internal/formatting/serialize.py | 2 +- src/aws_encryption_sdk/internal/validators.py | 24 - src/aws_encryption_sdk/key_providers/base.py | 114 ++-- src/aws_encryption_sdk/key_providers/kms.py | 8 - src/aws_encryption_sdk/key_providers/raw.py | 10 - .../keyrings/aws_kms/__init__.py | 392 ------------ .../keyrings/aws_kms/_client_cache.py | 119 ---- .../keyrings/aws_kms/client_suppliers.py | 159 ----- src/aws_encryption_sdk/keyrings/base.py | 46 -- src/aws_encryption_sdk/keyrings/multi.py | 105 ---- src/aws_encryption_sdk/keyrings/raw.py | 461 -------------- .../materials_managers/__init__.py | 489 ++------------- .../materials_managers/caching.py | 94 +-- .../materials_managers/default.py | 168 +----- src/aws_encryption_sdk/streaming_client.py | 134 ++--- src/aws_encryption_sdk/structures.py | 200 ++----- src/pylintrc | 3 - test/__init__.py | 0 test/functional/__init__.py | 3 +- test/functional/functional_test_utils.py | 29 - test/functional/internal/crypto/__init__.py | 13 - test/functional/keyrings/aws_kms/__init__.py | 3 - .../keyrings/aws_kms/test_aws_kms.py | 494 --------------- .../keyrings/aws_kms/test_client_cache.py | 33 - .../keyrings/aws_kms/test_client_suppliers.py | 116 ---- test/functional/keyrings/raw/__init__.py | 13 - test/functional/keyrings/raw/test_raw_aes.py | 181 ------ test/functional/keyrings/raw/test_raw_rsa.py | 297 --------- test/functional/keyrings/test_multi.py | 122 ---- ...py => test_f_aws_encryption_sdk_client.py} | 276 +++------ .../test_crypto.py => test_f_crypto.py} | 0 .../crypto/test_iv.py => test_f_crypto_iv.py} | 0 test/integration/README.rst | 5 +- test/integration/__init__.py | 3 +- test/integration/integration_test_utils.py | 59 +- test/integration/key_providers/__init__.py | 3 - test/integration/key_providers/test_kms.py | 31 - test/integration/keyrings/__init__.py | 3 - test/integration/keyrings/aws_kms/__init__.py | 3 - .../keyrings/aws_kms/test_client_cache.py | 49 -- test/integration/test_client.py | 128 ---- .../test_i_aws_encrytion_sdk_client.py | 452 ++++++++++++++ ...read_safety.py => test_i_thread_safety.py} | 0 test/requirements.txt | 1 - test/unit/__init__.py | 3 +- test/unit/caches/__init__.py | 13 - test/unit/internal/__init__.py | 13 - test/unit/internal/crypto/__init__.py | 13 - .../crypto/authentication/__init__.py | 13 - .../internal/crypto/encryption/__init__.py | 13 - test/unit/internal/formatting/__init__.py | 13 - test/unit/internal/utils/__init__.py | 13 - test/unit/key_providers/__init__.py | 13 - test/unit/key_providers/base/__init__.py | 13 - test/unit/key_providers/kms/__init__.py | 13 - test/unit/key_providers/raw/__init__.py | 13 - test/unit/keyrings/__init__.py | 13 - test/unit/keyrings/raw/__init__.py | 13 - test/unit/keyrings/raw/test_raw_aes.py | 318 ---------- test/unit/keyrings/raw/test_raw_rsa.py | 321 ---------- test/unit/keyrings/test_aws_kms.py | 198 ------ test/unit/keyrings/test_base.py | 45 -- test/unit/keyrings/test_multi.py | 244 -------- test/unit/materials_managers/__init__.py | 13 - .../test_material_managers.py | 484 --------------- test/unit/streaming_client/__init__.py | 13 - ...t_client.py => test_aws_encryption_sdk.py} | 21 +- test/unit/{caches => }/test_caches.py | 8 +- .../test_base.py => test_caches_base.py} | 0 ...y.py => test_caches_crypto_cache_entry.py} | 0 .../test_local.py => test_caches_local.py} | 0 .../test_null.py => test_caches_null.py} | 0 .../crypto/vectors.py => test_crypto.py} | 0 ...y => test_crypto_authentication_signer.py} | 2 +- ...=> test_crypto_authentication_verifier.py} | 2 +- ..._data_keys.py => test_crypto_data_keys.py} | 0 ...curve.py => test_crypto_elliptic_curve.py} | 3 +- ...py => test_crypto_encryption_decryptor.py} | 0 ...py => test_crypto_encryption_encryptor.py} | 0 ...> test_crypto_prehashing_authenticator.py} | 0 ...g_keys.py => test_crypto_wrapping_keys.py} | 2 +- test/unit/{internal => }/test_defaults.py | 0 .../formatting => }/test_deserialize.py | 2 +- .../test_encryption_context.py | 2 +- ...uctures.py => test_internal_structures.py} | 2 +- test/unit/test_material_managers.py | 98 +++ ...base.py => test_material_managers_base.py} | 0 ...g.py => test_material_managers_caching.py} | 50 +- ...t.py => test_material_managers_default.py} | 161 +---- ...y.py => test_providers_base_master_key.py} | 2 +- ... test_providers_base_master_key_config.py} | 2 +- ...est_providers_base_master_key_provider.py} | 8 +- ...viders_base_master_key_provider_config.py} | 4 - ...ey.py => test_providers_kms_master_key.py} | 2 +- ...> test_providers_kms_master_key_config.py} | 2 +- ...test_providers_kms_master_key_provider.py} | 4 +- ...oviders_kms_master_key_provider_config.py} | 2 +- ...ey.py => test_providers_raw_master_key.py} | 2 +- ...> test_providers_raw_master_key_config.py} | 2 +- ...test_providers_raw_master_key_provider.py} | 2 +- .../formatting => }/test_serialize.py | 6 +- ...gs.py => test_streaming_client_configs.py} | 2 +- ...est_streaming_client_encryption_stream.py} | 4 +- ...test_streaming_client_stream_decryptor.py} | 2 +- ...test_streaming_client_stream_encryptor.py} | 4 +- test/unit/test_structures.py | 97 +-- .../test_str_ops.py => test_util_str_ops.py} | 0 .../test_streams.py => test_util_streams.py} | 2 +- test/unit/{internal/utils => }/test_utils.py | 7 +- test/unit/{vectors.py => test_values.py} | 49 -- test/unit/unit_test_utils.py | 562 +----------------- test/upstream-requirements-py27.txt | 94 +-- test/upstream-requirements-py37.txt | 80 +-- test_vector_handlers/README.rst | 2 +- test_vector_handlers/setup.py | 2 +- test_vector_handlers/tox.ini | 2 +- tox.ini | 15 +- 190 files changed, 1877 insertions(+), 10248 deletions(-) delete mode 100644 .github/workflows/ci_decrypt-oracle.yaml delete mode 100644 .github/workflows/ci_static-analysis.yaml delete mode 100644 .github/workflows/ci_test-vector-handler.yaml delete mode 100644 .github/workflows/ci_tests.yaml delete mode 100644 ci-requirements.txt delete mode 100644 examples/README.md rename examples/src/{legacy => }/basic_encryption.py (58%) rename examples/src/{legacy => }/basic_file_encryption_with_multiple_providers.py (86%) rename examples/src/{legacy => }/basic_file_encryption_with_raw_key_provider.py (83%) delete mode 100644 examples/src/crypto_materials_manager/__init__.py delete mode 100644 examples/src/crypto_materials_manager/caching/__init__.py delete mode 100644 examples/src/crypto_materials_manager/caching/simple_cache.py delete mode 100644 examples/src/crypto_materials_manager/custom/__init__.py delete mode 100644 examples/src/crypto_materials_manager/custom/algorithm_suite_enforcement.py delete mode 100644 examples/src/crypto_materials_manager/custom/requiring_encryption_context_fields.py rename examples/src/{legacy => }/data_key_caching_basic.py (62%) delete mode 100644 examples/src/file_streaming_defaults.py delete mode 100644 examples/src/in_memory_streaming_defaults.py delete mode 100644 examples/src/keyring/__init__.py delete mode 100644 examples/src/keyring/aws_kms/__init__.py delete mode 100644 examples/src/keyring/aws_kms/custom_client_supplier.py delete mode 100644 examples/src/keyring/aws_kms/custom_kms_client_config.py delete mode 100644 examples/src/keyring/aws_kms/discovery_decrypt.py delete mode 100644 examples/src/keyring/aws_kms/discovery_decrypt_in_region_only.py delete mode 100644 examples/src/keyring/aws_kms/discovery_decrypt_with_preferred_regions.py delete mode 100644 examples/src/keyring/aws_kms/multiple_regions.py delete mode 100644 examples/src/keyring/aws_kms/single_cmk.py delete mode 100644 examples/src/keyring/multi/__init__.py delete mode 100644 examples/src/keyring/multi/aws_kms_with_escrow.py delete mode 100644 examples/src/keyring/raw_aes/__init__.py delete mode 100644 examples/src/keyring/raw_aes/raw_aes.py delete mode 100644 examples/src/keyring/raw_rsa/__init__.py delete mode 100644 examples/src/keyring/raw_rsa/private_key_only.py delete mode 100644 examples/src/keyring/raw_rsa/private_key_only_from_pem.py delete mode 100644 examples/src/keyring/raw_rsa/public_private_key_separate.py delete mode 100644 examples/src/legacy/__init__.py delete mode 100644 examples/src/master_key_provider/__init__.py delete mode 100644 examples/src/master_key_provider/aws_kms/__init__.py delete mode 100644 examples/src/master_key_provider/aws_kms/discovery_decrypt.py delete mode 100644 examples/src/master_key_provider/aws_kms/multiple_regions.py delete mode 100644 examples/src/master_key_provider/aws_kms/single_cmk.py delete mode 100644 examples/src/master_key_provider/multi/__init__.py delete mode 100644 examples/src/master_key_provider/multi/aws_kms_with_escrow.py delete mode 100644 examples/src/master_key_provider/raw_aes/__init__.py delete mode 100644 examples/src/master_key_provider/raw_aes/raw_aes.py delete mode 100644 examples/src/master_key_provider/raw_rsa/__init__.py delete mode 100644 examples/src/master_key_provider/raw_rsa/private_key_only_from_pem.py create mode 100644 examples/src/one_kms_cmk.py rename examples/src/{legacy => }/one_kms_cmk_streaming_data.py (68%) rename examples/src/{legacy => }/one_kms_cmk_unsigned.py (64%) delete mode 100644 examples/src/onestep_defaults.py delete mode 100644 examples/src/onestep_unsigned.py create mode 100644 examples/test/test_i_basic_encryption.py create mode 100644 examples/test/test_i_basic_file_encryption_with_multiple_providers.py create mode 100644 examples/test/test_i_basic_file_encryption_with_raw_key_provider.py rename src/aws_encryption_sdk/keyrings/__init__.py => examples/test/test_i_data_key_caching_basic.py (50%) rename test/functional/internal/__init__.py => examples/test/test_i_one_kms_cmk.py (52%) create mode 100644 examples/test/test_i_one_kms_cmk_streaming_data.py rename test/functional/keyrings/__init__.py => examples/test/test_i_one_kms_cmk_unsigned.py (50%) delete mode 100644 examples/test/test_run_examples.py delete mode 100644 src/aws_encryption_sdk/internal/validators.py delete mode 100644 src/aws_encryption_sdk/keyrings/aws_kms/__init__.py delete mode 100644 src/aws_encryption_sdk/keyrings/aws_kms/_client_cache.py delete mode 100644 src/aws_encryption_sdk/keyrings/aws_kms/client_suppliers.py delete mode 100644 src/aws_encryption_sdk/keyrings/base.py delete mode 100644 src/aws_encryption_sdk/keyrings/multi.py delete mode 100644 src/aws_encryption_sdk/keyrings/raw.py delete mode 100644 test/__init__.py delete mode 100644 test/functional/functional_test_utils.py delete mode 100644 test/functional/internal/crypto/__init__.py delete mode 100644 test/functional/keyrings/aws_kms/__init__.py delete mode 100644 test/functional/keyrings/aws_kms/test_aws_kms.py delete mode 100644 test/functional/keyrings/aws_kms/test_client_cache.py delete mode 100644 test/functional/keyrings/aws_kms/test_client_suppliers.py delete mode 100644 test/functional/keyrings/raw/__init__.py delete mode 100644 test/functional/keyrings/raw/test_raw_aes.py delete mode 100644 test/functional/keyrings/raw/test_raw_rsa.py delete mode 100644 test/functional/keyrings/test_multi.py rename test/functional/{test_client.py => test_f_aws_encryption_sdk_client.py} (80%) rename test/functional/{internal/crypto/test_crypto.py => test_f_crypto.py} (100%) rename test/functional/{internal/crypto/test_iv.py => test_f_crypto_iv.py} (100%) delete mode 100644 test/integration/key_providers/__init__.py delete mode 100644 test/integration/key_providers/test_kms.py delete mode 100644 test/integration/keyrings/__init__.py delete mode 100644 test/integration/keyrings/aws_kms/__init__.py delete mode 100644 test/integration/keyrings/aws_kms/test_client_cache.py delete mode 100644 test/integration/test_client.py create mode 100644 test/integration/test_i_aws_encrytion_sdk_client.py rename test/integration/{test_thread_safety.py => test_i_thread_safety.py} (100%) delete mode 100644 test/unit/caches/__init__.py delete mode 100644 test/unit/internal/__init__.py delete mode 100644 test/unit/internal/crypto/__init__.py delete mode 100644 test/unit/internal/crypto/authentication/__init__.py delete mode 100644 test/unit/internal/crypto/encryption/__init__.py delete mode 100644 test/unit/internal/formatting/__init__.py delete mode 100644 test/unit/internal/utils/__init__.py delete mode 100644 test/unit/key_providers/__init__.py delete mode 100644 test/unit/key_providers/base/__init__.py delete mode 100644 test/unit/key_providers/kms/__init__.py delete mode 100644 test/unit/key_providers/raw/__init__.py delete mode 100644 test/unit/keyrings/__init__.py delete mode 100644 test/unit/keyrings/raw/__init__.py delete mode 100644 test/unit/keyrings/raw/test_raw_aes.py delete mode 100644 test/unit/keyrings/raw/test_raw_rsa.py delete mode 100644 test/unit/keyrings/test_aws_kms.py delete mode 100644 test/unit/keyrings/test_base.py delete mode 100644 test/unit/keyrings/test_multi.py delete mode 100644 test/unit/materials_managers/__init__.py delete mode 100644 test/unit/materials_managers/test_material_managers.py delete mode 100644 test/unit/streaming_client/__init__.py rename test/unit/{test_client.py => test_aws_encryption_sdk.py} (89%) rename test/unit/{caches => }/test_caches.py (97%) rename test/unit/{caches/test_base.py => test_caches_base.py} (100%) rename test/unit/{caches/test_crypto_cache_entry.py => test_caches_crypto_cache_entry.py} (100%) rename test/unit/{caches/test_local.py => test_caches_local.py} (100%) rename test/unit/{caches/test_null.py => test_caches_null.py} (100%) rename test/unit/{internal/crypto/vectors.py => test_crypto.py} (100%) rename test/unit/{internal/crypto/authentication/test_signer.py => test_crypto_authentication_signer.py} (99%) rename test/unit/{internal/crypto/authentication/test_verifier.py => test_crypto_authentication_verifier.py} (99%) rename test/unit/{internal/crypto/test_data_keys.py => test_crypto_data_keys.py} (100%) rename test/unit/{internal/crypto/test_elliptic_curve.py => test_crypto_elliptic_curve.py} (99%) rename test/unit/{internal/crypto/encryption/test_decryptor.py => test_crypto_encryption_decryptor.py} (100%) rename test/unit/{internal/crypto/encryption/test_encryptor.py => test_crypto_encryption_encryptor.py} (100%) rename test/unit/{internal/crypto/authentication/test_prehashing_authenticator.py => test_crypto_prehashing_authenticator.py} (100%) rename test/unit/{internal/crypto/test_wrapping_keys.py => test_crypto_wrapping_keys.py} (99%) rename test/unit/{internal => }/test_defaults.py (100%) rename test/unit/{internal/formatting => }/test_deserialize.py (99%) rename test/unit/{internal/formatting => }/test_encryption_context.py (99%) rename test/unit/{internal/test_structures.py => test_internal_structures.py} (97%) create mode 100644 test/unit/test_material_managers.py rename test/unit/{materials_managers/test_base.py => test_material_managers_base.py} (100%) rename test/unit/{materials_managers/test_caching.py => test_material_managers_caching.py} (92%) rename test/unit/{materials_managers/test_default.py => test_material_managers_default.py} (59%) rename test/unit/{key_providers/base/test_base_master_key.py => test_providers_base_master_key.py} (99%) rename test/unit/{key_providers/base/test_base_master_key_config.py => test_providers_base_master_key_config.py} (96%) rename test/unit/{key_providers/base/test_base_master_key_provider.py => test_providers_base_master_key_provider.py} (99%) rename test/unit/{key_providers/base/test_base_master_key_provider_config.py => test_providers_base_master_key_provider_config.py} (93%) rename test/unit/{key_providers/kms/test_kms_master_key.py => test_providers_kms_master_key.py} (99%) rename test/unit/{key_providers/kms/test_kms_master_key_config.py => test_providers_kms_master_key_config.py} (97%) rename test/unit/{key_providers/kms/test_kms_master_key_provider.py => test_providers_kms_master_key_provider.py} (98%) rename test/unit/{key_providers/kms/test_kms_master_key_provider_config.py => test_providers_kms_master_key_provider_config.py} (97%) rename test/unit/{key_providers/raw/test_raw_master_key.py => test_providers_raw_master_key.py} (99%) rename test/unit/{key_providers/raw/test_raw_master_key_config.py => test_providers_raw_master_key_config.py} (96%) rename test/unit/{key_providers/raw/test_raw_master_key_provider.py => test_providers_raw_master_key_provider.py} (98%) rename test/unit/{internal/formatting => }/test_serialize.py (98%) rename test/unit/{streaming_client/test_configs.py => test_streaming_client_configs.py} (98%) rename test/unit/{streaming_client/test_encryption_stream.py => test_streaming_client_encryption_stream.py} (99%) rename test/unit/{streaming_client/test_stream_decryptor.py => test_streaming_client_stream_decryptor.py} (99%) rename test/unit/{streaming_client/test_stream_encryptor.py => test_streaming_client_stream_encryptor.py} (99%) rename test/unit/{internal/utils/test_str_ops.py => test_util_str_ops.py} (100%) rename test/unit/{internal/utils/test_streams.py => test_util_streams.py} (96%) rename test/unit/{internal/utils => }/test_utils.py (98%) rename test/unit/{vectors.py => test_values.py} (88%) diff --git a/.github/workflows/ci_decrypt-oracle.yaml b/.github/workflows/ci_decrypt-oracle.yaml deleted file mode 100644 index d8ecff117..000000000 --- a/.github/workflows/ci_decrypt-oracle.yaml +++ /dev/null @@ -1,53 +0,0 @@ -name: Continuous Integration tests for the decrypt oracle - -on: - pull_request: - push: - # Run once a day - schedule: - - cron: '0 0 * * *' - -jobs: - tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v1 - with: - # The oracle runs in a Python 3.6 Lamba - python-version: 3.6 - - run: | - python -m pip install --upgrade pip - pip install --upgrade -r ci-requirements.txt - - name: run test - env: - TOXENV: local - run: | - cd decrypt_oracle - tox -- -vv - static-analysis: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - category: - - bandit - - readme - - flake8 - - pylint - - flake8-tests - - pylint-tests - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v1 - with: - python-version: 3.x - - run: | - python -m pip install --upgrade pip - pip install --upgrade -r ci-requirements.txt - - name: run test - env: - TOXENV: ${{ matrix.category }} - run: | - cd decrypt_oracle - tox -- -vv diff --git a/.github/workflows/ci_static-analysis.yaml b/.github/workflows/ci_static-analysis.yaml deleted file mode 100644 index f80c429fe..000000000 --- a/.github/workflows/ci_static-analysis.yaml +++ /dev/null @@ -1,40 +0,0 @@ -name: Static analysis checks - -on: - pull_request: - push: - # Run once a day - schedule: - - cron: '0 0 * * *' - -jobs: - analysis: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - category: - - bandit - - doc8 - - docs - - readme - - flake8 - - pylint - - flake8-tests - - pylint-tests - - flake8-examples - - pylint-examples - - black-check - - isort-check - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v1 - with: - python-version: 3.x - - run: | - python -m pip install --upgrade pip - pip install --upgrade -r ci-requirements.txt - - name: run test - env: - TOXENV: ${{ matrix.category }} - run: tox -- -vv diff --git a/.github/workflows/ci_test-vector-handler.yaml b/.github/workflows/ci_test-vector-handler.yaml deleted file mode 100644 index 570133231..000000000 --- a/.github/workflows/ci_test-vector-handler.yaml +++ /dev/null @@ -1,88 +0,0 @@ -name: Continuous Integration tests for the test vector handler - -on: - pull_request: - push: - # Run once a day - schedule: - - cron: '0 0 * * *' - -jobs: - tests: - # Leaving this defined but disabled - # until we address the credentials problem. - if: 1 == 0 - runs-on: ${{ matrix.os }} - strategy: - fail-fast: true - matrix: - os: - - ubuntu-latest - - windows-latest - - macos-latest - python: - - 2.7 - - 3.5 - - 3.6 - - 3.7 - - 3.8 - - 3.x - architecture: - - x64 - - x86 - category: - - awses_1.3.3 - - awses_1.3.max - - awses_latest - exclude: - # x86 builds are only meaningful for Windows - - os: ubuntu-latest - architecture: x86 - - os: macos-latest - architecture: x86 - steps: - - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-access-key-id: ${{ secrets.INTEG_AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.INTEG_AWS_SECRET_ACCESS_KEY }} - aws-region: us-west-2 - - uses: actions/checkout@v2 - - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python }} - architecture: ${{ matrix.architecture }} - - run: | - python -m pip install --upgrade pip - pip install --upgrade -r ci-requirements.txt - - name: run test - env: - TOXENV: ${{ matrix.category }} - run: | - cd test_vector_handlers - tox -- -vv - static-analysis: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - category: - - bandit - - readme - - flake8 - - pylint - - flake8-tests - - pylint-tests - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v1 - with: - python-version: 3.x - - run: | - python -m pip install --upgrade pip - pip install --upgrade -r ci-requirements.txt - - name: run test - env: - TOXENV: ${{ matrix.category }} - run: | - cd test_vector_handlers - tox -- -vv diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml deleted file mode 100644 index c2f297ea2..000000000 --- a/.github/workflows/ci_tests.yaml +++ /dev/null @@ -1,100 +0,0 @@ -name: Continuous Integration tests - -on: - pull_request: - push: - # Run once a day - schedule: - - cron: '0 0 * * *' - -env: - 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 - -jobs: - tests: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: true - matrix: - os: - - ubuntu-latest - - windows-latest - - macos-latest - python: - - 2.7 - - 3.5 - - 3.6 - - 3.7 - - 3.8 - - 3.x - architecture: - - x64 - - x86 - category: - - local - - accept -# These require credentials. -# Enable them once we sort how to provide them. -# - integ -# - examples - exclude: - # x86 builds are only meaningful for Windows - - os: ubuntu-latest - architecture: x86 - - os: macos-latest - architecture: x86 - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python }} - architecture: ${{ matrix.architecture }} - - run: | - python -m pip install --upgrade pip - pip install --upgrade -r ci-requirements.txt - - name: run test - env: - TOXENV: ${{ matrix.category }} - run: tox -- -vv - upstream-py3: - runs-on: ubuntu-latest - strategy: - fail-fast: true - matrix: - category: - - nocmk - - test-upstream-requirements-py37 - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v1 - with: - python-version: 3.7 - - run: | - python -m pip install --upgrade pip - pip install --upgrade -r ci-requirements.txt - - name: run test - env: - TOXENV: ${{ matrix.category }} - run: tox -- -vv - upstream-py2: - runs-on: ubuntu-latest - strategy: - fail-fast: true - matrix: - category: - - test-upstream-requirements-py27 - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v1 - with: - python-version: 2.7 - - run: | - python -m pip install --upgrade pip - pip install --upgrade -r ci-requirements.txt - - name: run test - env: - TOXENV: ${{ matrix.category }} - run: tox -- -vv diff --git a/.travis.yml b/.travis.yml index a8ca00f68..86702c536 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,23 +3,54 @@ language: python matrix: include: # CPython 2.7 + - python: 2.7 + env: TOXENV=py27-local + stage: Client Tests - python: 2.7 env: TOXENV=py27-integ stage: Client Tests + - python: 2.7 + env: TOXENV=py27-accept + stage: Client Tests - python: 2.7 env: TOXENV=py27-examples stage: Client Tests + # CPython 3.4 + - python: 3.4 + env: TOXENV=py34-local + stage: Client Tests + - python: 3.4 + env: TOXENV=py34-integ + stage: Client Tests + - python: 3.4 + env: TOXENV=py34-accept + stage: Client Tests + - python: 3.4 + env: TOXENV=py34-examples + stage: Client Tests # CPython 3.5 + - python: 3.5 + env: TOXENV=py35-local + stage: Client Tests - python: 3.5 env: TOXENV=py35-integ stage: Client Tests + - python: 3.5 + env: TOXENV=py35-accept + stage: Client Tests - python: 3.5 env: TOXENV=py35-examples stage: Client Tests # CPython 3.6 + - python: 3.6 + env: TOXENV=py36-local + stage: Client Tests - python: 3.6 env: TOXENV=py36-integ stage: Client Tests + - python: 3.6 + env: TOXENV=py36-accept + stage: Client Tests - python: 3.6 env: TOXENV=py36-examples stage: Client Tests @@ -27,28 +58,77 @@ matrix: # xenial + sudo are currently needed to get 3.7 # https://github.com/travis-ci/travis-ci/issues/9815 - python: 3.7 - env: TOXENV=py37-integ + env: TOXENV=py37-local dist: xenial sudo: true stage: Client Tests - python: 3.7 - env: TOXENV=py37-examples + env: TOXENV=py37-integ dist: xenial sudo: true stage: Client Tests - # CPython 3.8 - # xenial + sudo are currently needed to get 3.8 - # https://github.com/travis-ci/travis-ci/issues/9815 - - python: 3.8 - env: TOXENV=py38-integ + - python: 3.7 + env: TOXENV=py37-accept dist: xenial sudo: true stage: Client Tests - - python: 3.8 - env: TOXENV=py38-examples + - python: 3.7 + env: TOXENV=py37-examples dist: xenial sudo: true stage: Client Tests + # Upstream tests + - python: 3.6 + env: TOXENV=nocmk + stage: Upstream Tests + - python: 2.7 + env: TOXENV=test-upstream-requirements-py27 + stage: Upstream Tests + # xenial + sudo are currently needed to get 3.7 + # https://github.com/travis-ci/travis-ci/issues/9815 + - python: 3.7 + env: TOXENV=test-upstream-requirements-py37 + dist: xenial + sudo: true + stage: Upstream Tests + # Security + - python: 3.6 + env: TOXENV=bandit + stage: Security Checks + # Linting and autoformatting + - python: 3.6 + env: TOXENV=doc8 + stage: Formatting Checks + - python: 3.6 + env: TOXENV=docs + stage: Formatting Checks + - python: 3.6 + env: TOXENV=readme + stage: Formatting Checks + - python: 3.6 + env: TOXENV=flake8 + stage: Formatting Checks + - python: 3.6 + env: TOXENV=pylint + stage: Formatting Checks + - python: 3.6 + env: TOXENV=flake8-tests + stage: Formatting Checks + - python: 3.6 + env: TOXENV=pylint-tests + stage: Formatting Checks + - python: 3.6 + env: TOXENV=flake8-examples + stage: Formatting Checks + - python: 3.6 + env: TOXENV=pylint-examples + stage: Formatting Checks + - python: 3.6 + env: TOXENV=black-check + stage: Formatting Checks + - python: 3.6 + env: TOXENV=isort-check + stage: Formatting Checks ######################## # Test Vector Handlers # ######################## @@ -68,6 +148,22 @@ matrix: TEST_VECTOR_HANDLERS=1 TOXENV=py27-awses_latest stage: Test Vector Handler Tests + # CPython 3.4 + - python: 3.4 + env: + TEST_VECTOR_HANDLERS=1 + TOXENV=py34-awses_1.3.3 + stage: Test Vector Handler Tests + - python: 3.4 + env: + TEST_VECTOR_HANDLERS=1 + TOXENV=py34-awses_1.3.max + stage: Test Vector Handler Tests + - python: 3.4 + env: + TEST_VECTOR_HANDLERS=1 + TOXENV=py34-awses_latest + stage: Test Vector Handler Tests # CPython 3.5 - python: 3.5 env: @@ -122,28 +218,93 @@ matrix: dist: xenial sudo: true stage: Test Vector Handler Tests - # CPython 3.8 - - python: 3.8 + # Linters + - python: 3.6 env: TEST_VECTOR_HANDLERS=1 - TOXENV=py38-awses_1.3.3 - dist: xenial - sudo: true - stage: Test Vector Handler Tests - - python: 3.8 + TOXENV=bandit + stage: Test Vector Handler Formatting Checks + - python: 3.6 env: TEST_VECTOR_HANDLERS=1 - TOXENV=py38-awses_1.3.max - dist: xenial - sudo: true - stage: Test Vector Handler Tests - - python: 3.8 + TOXENV=readme + stage: Test Vector Handler Formatting Checks + # Pending buildout of docs + #- python: 3.6 + # env: + # TEST_VECTOR_HANDLERS=1 + # TOXENV=docs + #- python: 3.6 + # env: + # TEST_VECTOR_HANDLERS=1 + # TOXENV=doc8 + # Pending linting cleanup + #- python: 3.6 + # env: + # TEST_VECTOR_HANDLERS=1 + # TOXENV=flake8 + #- python: 3.6 + # env: + # TEST_VECTOR_HANDLERS=1 + # TOXENV=pylint + #- python: 3.6 + # env: + # TEST_VECTOR_HANDLERS=1 + # TOXENV=flake8-tests + #- python: 3.6 + # env: + # TEST_VECTOR_HANDLERS=1 + # TOXENV=pylint-tests + ################## + # Decrypt Oracle # + ################## + # CPython 3.6 + # Because this build as Python 3.6 Lambda, this is the only runtime we are targetting. + - python: 3.6 env: - TEST_VECTOR_HANDLERS=1 - TOXENV=py38-awses_latest - dist: xenial - sudo: true - stage: Test Vector Handler Tests + DECRYPT_ORACLE=1 + TOXENV=py36-local + stage: Decrypt Oracle Tests + # Linters + - python: 3.6 + env: + DECRYPT_ORACLE=1 + TOXENV=bandit + stage: Decrypt Oracle Formatting Checks + - python: 3.6 + env: + DECRYPT_ORACLE=1 + TOXENV=readme + stage: Decrypt Oracle Formatting Checks + # Pending buildout of docs + #- python: 3.6 + # env: + # DECRYPT_ORACLE=1 + # TOXENV=docs + #- python: 3.6 + # env: + # DECRYPT_ORACLE=1 + # TOXENV=doc8 + - python: 3.6 + env: + DECRYPT_ORACLE=1 + TOXENV=flake8 + stage: Decrypt Oracle Formatting Checks + - python: 3.6 + env: + DECRYPT_ORACLE=1 + TOXENV=pylint + stage: Decrypt Oracle Formatting Checks + - python: 3.6 + env: + DECRYPT_ORACLE=1 + TOXENV=flake8-tests + stage: Decrypt Oracle Formatting Checks + - python: 3.6 + env: + DECRYPT_ORACLE=1 + TOXENV=pylint-tests + stage: Decrypt Oracle Formatting Checks install: pip install tox script: - | diff --git a/CHANGELOG.rst b/CHANGELOG.rst index aaaed8044..d9bca1f73 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,48 +2,6 @@ Changelog ********* -1.5.0 -- 2020-xx-xx -=================== - -Major Features --------------- - -* Add `keyrings`_. -* Change one-step APIs to return a :class:`CryptoResult` rather than a tuple. - - * Modified APIs: ``aws_encryption_sdk.encrypt`` and ``aws_encryption_sdk.decrypt``. - -.. note:: - - For backwards compatibility, - :class:`CryptoResult` also unpacks like a 2-member tuple. - This allows for backwards compatibility with the previous outputs - so this change should not break any existing consumers - unless you are specifically relying on the output being an instance of :class:`tuple`. - -Deprecations ------------- - -* Deprecate master key providers in favor of keyrings. - - * We still support using master key providers and are not removing them yet. - When we decide to remove them, - we will communicate that as defined in our versioning policy. - -* Deprecate support for Python 3.4. - - * This does not mean that this library will no longer work or install with 3.4, - but we are no longer testing against or advertising support for 3.4. - -Documentation -------------- - -* Added new examples demonstrating how to use - APIs, keyrings, cryptographic materials managers, and master key providers. - `#221 `_ - `#236 `_ - `#239 `_ - 1.4.1 -- 2019-09-20 =================== @@ -235,4 +193,3 @@ Minor .. _pylint: https://www.pylint.org/ .. _flake8: http://flake8.pycqa.org/en/latest/ .. _doc8: https://launchpad.net/doc8 -.. _keyrings: https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html diff --git a/README.rst b/README.rst index 3407dc177..7bc8038ea 100644 --- a/README.rst +++ b/README.rst @@ -38,7 +38,7 @@ Getting Started Required Prerequisites ====================== -* Python 2.7 or 3.5+ +* Python 2.7+ or 3.4+ * cryptography >= 1.8.1 * boto3 * attrs @@ -57,42 +57,189 @@ Installation Concepts ======== -There are three main concepts that are helpful to understand when using the AWS Encryption SDK. - -For further information, see the `AWS Encryption SDK developer guide concepts`_. +There are four main concepts that you need to understand to use this library: Cryptographic Materials Managers -------------------------------- -The cryptographic materials manager (CMM) assembles the cryptographic materials -that are used to encrypt and decrypt data. +Cryptographic materials managers (CMMs) are resources that collect cryptographic materials and prepare them for +use by the Encryption SDK core logic. -`For more details, -see the AWS Encryption SDK developer guide cryptographic materials manager concept. -`_ +An example of a CMM is the default CMM, which is automatically generated anywhere a caller provides a master +key provider. The default CMM collects encrypted data keys from all master keys referenced by the master key +provider. -Keyrings --------- +An example of a more advanced CMM is the caching CMM, which caches cryptographic materials provided by another CMM. -A keyring generates, encrypts, and decrypts data keys. +Master Key Providers +-------------------- +Master key providers are resources that provide master keys. +An example of a master key provider is `AWS KMS`_. -`For more details, -see the AWS Encryption SDK developer guide keyring concept. -`_ +To encrypt data in this client, a ``MasterKeyProvider`` object must contain at least one ``MasterKey`` object. -Data Keys ---------- +``MasterKeyProvider`` objects can also contain other ``MasterKeyProvider`` objects. -A data key is an encryption key that the AWS Encryption SDK uses to encrypt your data. +Master Keys +----------- +Master keys generate, encrypt, and decrypt data keys. +An example of a master key is a `KMS customer master key (CMK)`_. -`For more details, -see the AWS Encryption SDK developer guide data key concept. -`_ +Data Keys +--------- +Data keys are the encryption keys that are used to encrypt your data. If your algorithm suite +uses a key derivation function, the data key is used to generate the key that directly encrypts the data. ***** Usage ***** +To use this client, you (the caller) must provide an instance of either a master key provider +or a CMM. The examples in this readme use the ``KMSMasterKeyProvider`` class. + +KMSMasterKeyProvider +==================== +Because the ``KMSMasterKeyProvider`` uses the `boto3 SDK`_ to interact with `AWS KMS`_, it requires AWS Credentials. +To provide these credentials, use the `standard means by which boto3 locates credentials`_ or provide a +pre-existing instance of a ``botocore session`` to the ``KMSMasterKeyProvider``. +This latter option can be useful if you have an alternate way to store your AWS credentials or +you want to reuse an existing instance of a botocore session in order to decrease startup costs. + +.. code:: python + + import aws_encryption_sdk + import botocore.session + + kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider() + + existing_botocore_session = botocore.session.Session() + kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(botocore_session=existing_botocore_session) + + +You can pre-load the ``KMSMasterKeyProvider`` with one or more CMKs. +To encrypt data, you must configure the ``KMSMasterKeyProvider`` with as least one CMK. +If you configure the the ``KMSMasterKeyProvider`` with multiple CMKs, the `final message`_ +will include a copy of the data key encrypted by each configured CMK. + +.. code:: python + + import aws_encryption_sdk + + kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ + 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', + 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' + ]) + +You can add CMKs from multiple regions to the ``KMSMasterKeyProvider``. + +.. code:: python + + import aws_encryption_sdk + + kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ + 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', + 'arn:aws:kms:us-west-2:3333333333333:key/33333333-3333-3333-3333-333333333333', + 'arn:aws:kms:ap-northeast-1:4444444444444:key/44444444-4444-4444-4444-444444444444' + ]) + + +Encryption and Decryption +========================= +After you create an instance of a ``MasterKeyProvider``, you can use either of the two +high-level ``encrypt``/``decrypt`` functions to encrypt and decrypt your data. + +.. code:: python + + import aws_encryption_sdk + + kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ + 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', + 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' + ]) + my_plaintext = b'This is some super secret data! Yup, sure is!' + + my_ciphertext, encryptor_header = aws_encryption_sdk.encrypt( + source=my_plaintext, + key_provider=kms_key_provider + ) + + decrypted_plaintext, decryptor_header = aws_encryption_sdk.decrypt( + source=my_ciphertext, + key_provider=kms_key_provider + ) + + assert my_plaintext == decrypted_plaintext + assert encryptor_header.encryption_context == decryptor_header.encryption_context + +You can provide an `encryption context`_: a form of additional authenticating information. + +.. code:: python + + import aws_encryption_sdk + + kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ + 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', + 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' + ]) + my_plaintext = b'This is some super secret data! Yup, sure is!' + + my_ciphertext, encryptor_header = aws_encryption_sdk.encrypt( + source=my_plaintext, + key_provider=kms_key_provider, + encryption_context={ + 'not really': 'a secret', + 'but adds': 'some authentication' + } + ) + + decrypted_plaintext, decryptor_header = aws_encryption_sdk.decrypt( + source=my_ciphertext, + key_provider=kms_key_provider + ) + + assert my_plaintext == decrypted_plaintext + assert encryptor_header.encryption_context == decryptor_header.encryption_context + + +Streaming +========= +If you are handling large files or simply do not want to put the entire plaintext or ciphertext in +memory at once, you can use this library's streaming clients directly. The streaming clients are +file-like objects, and behave exactly as you would expect a Python file object to behave, +offering context manager and iteration support. + +.. code:: python + + import aws_encryption_sdk + import filecmp + + kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ + 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', + 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' + ]) + plaintext_filename = 'my-secret-data.dat' + ciphertext_filename = 'my-encrypted-data.ct' + + with open(plaintext_filename, 'rb') as pt_file, open(ciphertext_filename, 'wb') as ct_file: + with aws_encryption_sdk.stream( + mode='e', + source=pt_file, + key_provider=kms_key_provider + ) as encryptor: + for chunk in encryptor: + ct_file.write(chunk) + + new_plaintext_filename = 'my-decrypted-data.dat' -For examples of how to use these concepts to accomplish different tasks, see our `examples`_. + with open(ciphertext_filename, 'rb') as ct_file, open(new_plaintext_filename, 'wb') as pt_file: + with aws_encryption_sdk.stream( + mode='d', + source=ct_file, + key_provider=kms_key_provider + ) as decryptor: + for chunk in decryptor: + pt_file.write(chunk) + + assert filecmp.cmp(plaintext_filename, new_plaintext_filename) + assert encryptor.header.encryption_context == decryptor.header.encryption_context Performance Considerations ========================== @@ -104,8 +251,6 @@ to your use-case in order to obtain peak performance. .. _AWS Encryption SDK: https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/introduction.html -.. _AWS Encryption SDK developer guide concepts: - https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html .. _cryptography: https://cryptography.io/en/latest/ .. _cryptography installation guide: https://cryptography.io/en/latest/installation/ .. _Read the Docs: http://aws-encryption-sdk-python.readthedocs.io/en/latest/ @@ -116,5 +261,4 @@ to your use-case in order to obtain peak performance. .. _standard means by which boto3 locates credentials: https://boto3.readthedocs.io/en/latest/guide/configuration.html .. _final message: https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/message-format.html .. _encryption context: https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#encrypt_context -.. _examples: https://github.com/aws/aws-encryption-sdk-python/tree/master/examples -.. _Security issue notifications: https://github.com/aws/aws-encryption-sdk-python/tree/master/CONTRIBUTING.md#security-issue-notifications +.. _Security issue notifications: ./CONTRIBUTING.md#security-issue-notifications diff --git a/appveyor.yml b/appveyor.yml index d7791f6ae..cfb4bdcdb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,55 +7,99 @@ environment: # analysis, etc are only run on Linux (via Travis CI). # Python 2.7 + - PYTHON: "C:\\Python27" + TOXENV: "py27-local" - PYTHON: "C:\\Python27" TOXENV: "py27-integ" + - PYTHON: "C:\\Python27" + TOXENV: "py27-accept" - PYTHON: "C:\\Python27" TOXENV: "py27-examples" + - PYTHON: "C:\\Python27-x64" + TOXENV: "py27-local" - PYTHON: "C:\\Python27-x64" TOXENV: "py27-integ" + - PYTHON: "C:\\Python27-x64" + TOXENV: "py27-accept" - PYTHON: "C:\\Python27-x64" TOXENV: "py27-examples" + # Python 3.4 + - PYTHON: "C:\\Python34" + TOXENV: "py34-local" + - PYTHON: "C:\\Python34" + TOXENV: "py34-integ" + - PYTHON: "C:\\Python34" + TOXENV: "py34-accept" + - PYTHON: "C:\\Python34" + TOXENV: "py34-examples" + - PYTHON: "C:\\Python34-x64" + DISTUTILS_USE_SDK: "1" + TOXENV: "py34-local" + - PYTHON: "C:\\Python34-x64" + DISTUTILS_USE_SDK: "1" + TOXENV: "py34-integ" + - PYTHON: "C:\\Python34-x64" + DISTUTILS_USE_SDK: "1" + TOXENV: "py34-accept" + - PYTHON: "C:\\Python34-x64" + DISTUTILS_USE_SDK: "1" + TOXENV: "py34-examples" + # Python 3.5 + - PYTHON: "C:\\Python35" + TOXENV: "py35-local" - PYTHON: "C:\\Python35" TOXENV: "py35-integ" + - PYTHON: "C:\\Python35" + TOXENV: "py35-accept" - PYTHON: "C:\\Python35" TOXENV: "py35-examples" + - PYTHON: "C:\\Python35-x64" + TOXENV: "py35-local" - PYTHON: "C:\\Python35-x64" TOXENV: "py35-integ" + - PYTHON: "C:\\Python35-x64" + TOXENV: "py35-accept" - PYTHON: "C:\\Python35-x64" TOXENV: "py35-examples" # Python 3.6 + - PYTHON: "C:\\Python36" + TOXENV: "py36-local" - PYTHON: "C:\\Python36" TOXENV: "py36-integ" + - PYTHON: "C:\\Python36" + TOXENV: "py36-accept" - PYTHON: "C:\\Python36" TOXENV: "py36-examples" + - PYTHON: "C:\\Python36-x64" + TOXENV: "py36-local" - PYTHON: "C:\\Python36-x64" TOXENV: "py36-integ" + - PYTHON: "C:\\Python36-x64" + TOXENV: "py36-accept" - PYTHON: "C:\\Python36-x64" TOXENV: "py36-examples" # Python 3.7 + - PYTHON: "C:\\Python37" + TOXENV: "py37-local" - PYTHON: "C:\\Python37" TOXENV: "py37-integ" + - PYTHON: "C:\\Python37" + TOXENV: "py37-accept" - PYTHON: "C:\\Python37" TOXENV: "py37-examples" + - PYTHON: "C:\\Python37-x64" + TOXENV: "py37-local" - PYTHON: "C:\\Python37-x64" TOXENV: "py37-integ" + - PYTHON: "C:\\Python37-x64" + TOXENV: "py37-accept" - PYTHON: "C:\\Python37-x64" TOXENV: "py37-examples" - # Python 3.8 - - PYTHON: "C:\\Python38" - TOXENV: "py38-integ" - - PYTHON: "C:\\Python38" - TOXENV: "py38-examples" - - PYTHON: "C:\\Python38-x64" - TOXENV: "py38-integ" - - PYTHON: "C:\\Python38-x64" - TOXENV: "py38-examples" - install: # Prepend newly installed Python to the PATH of this build - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" diff --git a/ci-requirements.txt b/ci-requirements.txt deleted file mode 100644 index 053148f84..000000000 --- a/ci-requirements.txt +++ /dev/null @@ -1 +0,0 @@ -tox diff --git a/doc/conf.py b/doc/conf.py index 42a7771b4..2164a52a6 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -67,10 +67,7 @@ def get_version(): htmlhelp_basename = "%sdoc" % project # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = { - "python": ("http://docs.python.org/3/", None), - "cryptography": ("https://cryptography.io/en/latest/", None), -} +intersphinx_mapping = {"http://docs.python.org/": None} # autosummary autosummary_generate = True diff --git a/doc/index.rst b/doc/index.rst index 2e21bccbd..10957074e 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -14,11 +14,6 @@ Modules aws_encryption_sdk.caches.base aws_encryption_sdk.caches.local aws_encryption_sdk.caches.null - aws_encryption_sdk.keyrings.base - aws_encryption_sdk.keyrings.aws_kms - aws_encryption_sdk.keyrings.aws_kms.client_suppliers - aws_encryption_sdk.keyrings.multi - aws_encryption_sdk.keyrings.raw aws_encryption_sdk.key_providers.base aws_encryption_sdk.key_providers.kms aws_encryption_sdk.key_providers.raw @@ -42,8 +37,6 @@ Modules aws_encryption_sdk.internal.formatting.serialize aws_encryption_sdk.internal.str_ops aws_encryption_sdk.internal.structures - aws_encryption_sdk.internal.validators aws_encryption_sdk.internal.utils - aws_encryption_sdk.keyrings.aws_kms._client_cache .. include:: ../CHANGELOG.rst diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index 081e62fab..000000000 --- a/examples/README.md +++ /dev/null @@ -1,138 +0,0 @@ -# AWS Encryption SDK Examples - -This section features examples that show you -how to use the AWS Encryption SDK. -We demonstrate how to use the encryption and decryption APIs -and how to set up some common configuration patterns. - -## APIs - -The AWS Encryption SDK provides two high-level APIs: -one-step APIs that process the entire operation in memory -and streaming APIs. - -You can find examples that demonstrate these APIs -in the [`examples/src/`](./src) directory. - -* [How to encrypt and decrypt](./src/onestep_defaults.py) -* [How to change the algorithm suite](./src/onestep_unsigned.py) -* [How to encrypt and decrypt data streams in memory](./src/in_memory_streaming_defaults.py) -* [How to encrypt and decrypt data streamed between files](./src/file_streaming_defaults.py) - -## Configuration - -To use the encryption and decryption APIs, -you need to describe how you want the library to protect your data keys. -You can do this by configuring -[keyrings](#keyrings) or [cryptographic materials managers](#cryptographic-materials-managers), -or by configuring [master key providers](#master-key-providers). -These examples will show you how to use the configuration tools that we include for you -and how to create some of your own. -We start with AWS KMS examples, then show how to use other wrapping keys. - -* Using AWS Key Management Service (AWS KMS) - * How to use one AWS KMS CMK - * [with keyrings](./src/keyring/aws_kms/single_cmk.py) - * [with master key providers](./src/master_key_provider/aws_kms/single_cmk.py) - * How to use multiple AWS KMS CMKs in different regions - * [with keyrings](./src/keyring/aws_kms/multiple_regions.py) - * [with master key providers](./src/master_key_provider/aws_kms/multiple_regions.py) - * How to decrypt when you don't know the CMK - * [with keyrings](./src/keyring/aws_kms/discovery_decrypt.py) - * [with master key providers](./src/master_key_provider/aws_kms/discovery_decrypt.py) - * How to decrypt within a region - * [with keyrings](./src/keyring/aws_kms/discovery_decrypt_in_region_only.py) - * How to decrypt with a preferred region but failover to others - * [with keyrings](./src/keyring/aws_kms/discovery_decrypt_with_preferred_regions.py) -* Using raw wrapping keys - * How to use a raw AES wrapping key - * [with keyrings](./src/keyring/raw_aes/raw_aes.py) - * [with master key providers](./src/master_key_provider/raw_aes/raw_aes.py) - * How to use a raw RSA wrapping key - * [with keyrings](./src/keyring/raw_rsa/private_key_only.py) - * How to use a raw RSA wrapping key when the key is PEM or DER encoded - * [with keyrings](./src/keyring/raw_rsa/private_key_only_from_pem.py) - * [with master key providers](./src/master_key_provider/raw_rsa/private_key_only_from_pem.py) - * How to encrypt with a raw RSA public key wrapping key without access to the private key - * [with keyrings](./src/keyring/raw_rsa/public_private_key_separate.py) -* Combining wrapping keys - * How to combine AWS KMS with an offline escrow key - * [with keyrings](./src/keyring/multi/aws_kms_with_escrow.py) - * [with master key providers](./src/master_key_provider/multi/aws_kms_with_escrow.py) -* How to reuse data keys across multiple messages - * [with the caching cryptographic materials manager](./src/crypto_materials_manager/caching/simple_cache.py) -* How to restrict algorithm suites - * [with a custom cryptographic materials manager](src/crypto_materials_manager/custom/algorithm_suite_enforcement.py) -* How to require encryption context fields - * [with a custom cryptographic materials manager](src/crypto_materials_manager/custom/requiring_encryption_context_fields.py) - -### Keyrings - -Keyrings are the most common way for you to configure the AWS Encryption SDK. -They determine how the AWS Encryption SDK protects your data. -You can find these examples in [`examples/src/keyring`](./src/keyring). - -### Cryptographic Materials Managers - -Keyrings define how your data keys are protected, -but there is more going on here than just protecting data keys. - -Cryptographic materials managers give you higher-level controls -over how the AWS Encryption SDK protects your data. -This can include things like -enforcing the use of certain algorithm suites or encryption context settings, -reusing data keys across messages, -or changing how you interact with keyrings. -You can find these examples in -[`examples/src/crypto_materials_manager`](./src/crypto_materials_manager). - -### Master Key Providers - -Before there were keyrings, there were master key providers. -Master key providers were the original configuration structure -that we provided for defining how you want to protect your data keys. -Keyrings provide a simpler experience and often more powerful configuration options, -but if you need to use master key providers, -need help migrating from master key providers to keyrings, -or simply want to see the difference between these configuration experiences, -you can find these examples in [`examples/src/master_key_provider`](./src/master_key_provider). - -## Legacy - -This section includes older examples, -including examples of using master keys and master key providers. -You can use them as a reference, -but we recommend looking at the newer examples, which explain the preferred ways of using this library. -You can find these examples in [`examples/src/legacy`](./src/legacy). - -# Writing Examples - -If you want to contribute a new example, that's awesome! -To make sure that your example is tested in our CI, -please make sure that it meets the following requirements: - -1. The example MUST be a distinct module in the [`examples/src/`](./src) directory. -1. The example MAY be nested arbitrarily deeply, - but every intermediate directory MUST contain a `__init__.py` file - so that CPython 2.7 will recognize it as a module. -1. Every example MUST be CPython 2.7 compatible. -1. Each example file MUST contain exactly one example. -1. Each example file MUST contain a function called `run` that runs the example. -1. If your `run` function needs any of the following inputs, - the parameters MUST have the following names: - * `aws_kms_cmk` (`str`) : A single AWS KMS CMK ARN. - * NOTE: You can assume that automatically discovered credentials have - `kms:GenerateDataKey`, `kms:Encrypt`, and `kms:Decrypt` permissions on this CMK. - * `aws_kms_generator_cmk` (`str`) : A single AWS KMS CMK ARN to use as a generator key. - * NOTE: You can assume that automatically discovered credentials have - `kms:GenerateDataKey`, `kms:Encrypt`, and `kms:Decrypt` permissions on this CMK. - * `aws_kms_additional_cmks` (`List[str]`) : - A list of AWS KMS CMK ARNs to use for encrypting and decrypting data keys. - * NOTE: You can assume that automatically discovered credentials have - `kms:Encrypt` and `kms:Decrypt` permissions on these CMKs. - * `source_plaintext` (`bytes`) : Plaintext data to encrypt. - * `source_plaintext_filename` (`str`) : A path to a file containing plaintext to encrypt. - * NOTE: You can assume that you have write access to the parent directory - and that anything you do in that directory will be cleaned up - by our test runners. -1. Any additional parameters MUST be optional. diff --git a/examples/src/legacy/basic_encryption.py b/examples/src/basic_encryption.py similarity index 58% rename from examples/src/legacy/basic_encryption.py rename to examples/src/basic_encryption.py index d650d59ca..6c194e45d 100644 --- a/examples/src/legacy/basic_encryption.py +++ b/examples/src/basic_encryption.py @@ -1,19 +1,29 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Example showing how to encrypt and decrypt a value in memory.""" +# 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. +"""Example showing basic encryption and decryption of a value already in memory.""" import aws_encryption_sdk -def run(aws_kms_cmk, source_plaintext, botocore_session=None): - """Encrypts and then decrypts a string under an AWS KMS customer master key (CMK). +def cycle_string(key_arn, source_plaintext, botocore_session=None): + """Encrypts and then decrypts a string under a KMS customer master key (CMK). - :param str aws_kms_cmk: Amazon Resource Name (ARN) of the AWS KMS CMK + :param str key_arn: Amazon Resource Name (ARN) of the KMS CMK :param bytes source_plaintext: Data to encrypt :param botocore_session: existing botocore session instance :type botocore_session: botocore.session.Session """ - # Create an AWS KMS master key provider - kms_kwargs = dict(key_ids=[aws_kms_cmk]) + # Create a KMS master key provider + 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.KMSMasterKeyProvider(**kms_kwargs) diff --git a/examples/src/legacy/basic_file_encryption_with_multiple_providers.py b/examples/src/basic_file_encryption_with_multiple_providers.py similarity index 86% rename from examples/src/legacy/basic_file_encryption_with_multiple_providers.py rename to examples/src/basic_file_encryption_with_multiple_providers.py index 89f3491ed..e60b4f6c6 100644 --- a/examples/src/legacy/basic_file_encryption_with_multiple_providers.py +++ b/examples/src/basic_file_encryption_with_multiple_providers.py @@ -1,5 +1,15 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.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. """Example showing creation of a RawMasterKeyProvider, how to use multiple master key providers to encrypt, and demonstrating that each master key provider can then be used independently to decrypt the same encrypted message. @@ -50,12 +60,12 @@ def _get_raw_key(self, key_id): ) -def run(aws_kms_cmk, source_plaintext_filename, botocore_session=None): - """Encrypts and then decrypts a file using an AWS KMS master key provider and a custom static master +def cycle_file(key_arn, source_plaintext_filename, botocore_session=None): + """Encrypts and then decrypts a file using a KMS master key provider and a custom static master key provider. Both master key providers are used to encrypt the plaintext file, so either one alone can decrypt it. - :param str aws_kms_cmk: Amazon Resource Name (ARN) of the AWS KMS Customer Master Key (CMK) + :param str key_arn: Amazon Resource Name (ARN) of the KMS Customer Master Key (CMK) (http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html) :param str source_plaintext_filename: Filename of file to encrypt :param botocore_session: existing botocore session instance @@ -66,8 +76,8 @@ def run(aws_kms_cmk, source_plaintext_filename, botocore_session=None): cycled_kms_plaintext_filename = source_plaintext_filename + ".kms.decrypted" cycled_static_plaintext_filename = source_plaintext_filename + ".static.decrypted" - # Create an AWS KMS master key provider - kms_kwargs = dict(key_ids=[aws_kms_cmk]) + # Create a KMS master key provider + kms_kwargs = dict(key_ids=[key_arn]) if botocore_session is not None: kms_kwargs["botocore_session"] = botocore_session kms_master_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(**kms_kwargs) diff --git a/examples/src/legacy/basic_file_encryption_with_raw_key_provider.py b/examples/src/basic_file_encryption_with_raw_key_provider.py similarity index 83% rename from examples/src/legacy/basic_file_encryption_with_raw_key_provider.py rename to examples/src/basic_file_encryption_with_raw_key_provider.py index 1e3eff8e0..91e2a7e9a 100644 --- a/examples/src/legacy/basic_file_encryption_with_raw_key_provider.py +++ b/examples/src/basic_file_encryption_with_raw_key_provider.py @@ -1,5 +1,15 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.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. """Example showing creation and use of a RawMasterKeyProvider.""" import filecmp import os @@ -38,7 +48,7 @@ def _get_raw_key(self, key_id): ) -def run(source_plaintext_filename): +def cycle_file(source_plaintext_filename): """Encrypts and then decrypts a file under a custom static master key provider. :param str source_plaintext_filename: Filename of file to encrypt diff --git a/examples/src/crypto_materials_manager/__init__.py b/examples/src/crypto_materials_manager/__init__.py deleted file mode 100644 index f413e63bd..000000000 --- a/examples/src/crypto_materials_manager/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -Cryptographic materials manager examples. - -These examples show how to create and use cryptographic materials managers. -""" diff --git a/examples/src/crypto_materials_manager/caching/__init__.py b/examples/src/crypto_materials_manager/caching/__init__.py deleted file mode 100644 index 2c55faad8..000000000 --- a/examples/src/crypto_materials_manager/caching/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -Caching cryptographic materials manager examples. - -These examples show how to configure and use the caching cryptographic materials manager. -""" diff --git a/examples/src/crypto_materials_manager/caching/simple_cache.py b/examples/src/crypto_materials_manager/caching/simple_cache.py deleted file mode 100644 index c8213e485..000000000 --- a/examples/src/crypto_materials_manager/caching/simple_cache.py +++ /dev/null @@ -1,95 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -The default cryptographic materials manager (CMM) -creates new encryption and decryption materials -on every call. -This means every encrypted message is protected by a unique data key, -but it also means that you need to interact with your key management system -in order to process any message. -If this causes performance, operations, or cost issues for you, -you might benefit from data key caching. - -https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/data-key-caching.html - -This example shows how to configure the caching CMM -to reuse data keys across multiple encrypted messages. - -In this example, we use an AWS KMS customer master key (CMK), -but you can use other key management options with the AWS Encryption SDK. -For examples that demonstrate how to use other key management configurations, -see the ``keyring`` and ``master_key_provider`` directories. - -In this example, we use the one-step encrypt and decrypt APIs. -""" -import aws_encryption_sdk -from aws_encryption_sdk.caches.local import LocalCryptoMaterialsCache -from aws_encryption_sdk.keyrings.aws_kms import AwsKmsKeyring -from aws_encryption_sdk.materials_managers.caching import CachingCryptoMaterialsManager - - -def run(aws_kms_cmk, source_plaintext): - # type: (str, bytes) -> None - """Demonstrate an encrypt/decrypt cycle using the caching cryptographic materials manager. - - :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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 the keyring that determines how your data keys are protected. - keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk) - - # Create the caching cryptographic materials manager using your keyring. - cmm = CachingCryptoMaterialsManager( - keyring=keyring, - # The cache is where the caching CMM stores the materials. - # - # LocalCryptoMaterialsCache gives you a local, in-memory, cache. - cache=LocalCryptoMaterialsCache(capacity=100), - # max_age determines how long the caching CMM will reuse materials. - # - # This example uses two minutes. - # In production, always choose as small a value as possible - # that works for your requirements. - max_age=120.0, - # max_messages_encrypted determines how many messages - # the caching CMM will protect with the same materials. - # - # In production, always choose as small a value as possible - # that works for your requirements. - max_messages_encrypted=10, - ) - - # Encrypt your plaintext data. - ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, materials_manager=cmm - ) - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # Decrypt your encrypted data using the same cryptographic materials manager 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. - decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, materials_manager=cmm) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/src/crypto_materials_manager/custom/__init__.py b/examples/src/crypto_materials_manager/custom/__init__.py deleted file mode 100644 index 202647480..000000000 --- a/examples/src/crypto_materials_manager/custom/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -Custom cryptographic materials manager (CMM) examples. - -The AWS Encryption SDK includes CMMs for common use cases, -but you might need to do something else. - -These examples show how you could create your own CMM for some specific requirements. -""" diff --git a/examples/src/crypto_materials_manager/custom/algorithm_suite_enforcement.py b/examples/src/crypto_materials_manager/custom/algorithm_suite_enforcement.py deleted file mode 100644 index 97b4e3907..000000000 --- a/examples/src/crypto_materials_manager/custom/algorithm_suite_enforcement.py +++ /dev/null @@ -1,139 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -The AWS Encryption SDK supports several different algorithm suites -that offer different security properties. - -https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/supported-algorithms.html - -By default, the AWS Encryption SDK will let you use any of these, -but you might want to restrict that further. - -We recommend that you use the default algorithm suite, -which uses AES-GCM with 256-bit keys, HKDF, and ECDSA message signing. -If your readers and writers have the same permissions, -you might want to omit the message signature for faster operation. -For more information about choosing a signed or unsigned algorithm suite, -see the AWS Encryption SDK developer guide: - -https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/supported-algorithms.html#other-algorithms - -This example shows how you can make a custom cryptographic materials manager (CMM) -that only allows encrypt requests that either specify one of these two algorithm suites -or do not specify an algorithm suite, in which case the default CMM uses the default algorithm suite. -""" -import aws_encryption_sdk -from aws_encryption_sdk.identifiers import AlgorithmSuite -from aws_encryption_sdk.keyrings.aws_kms import AwsKmsKeyring -from aws_encryption_sdk.keyrings.base import Keyring -from aws_encryption_sdk.materials_managers import ( - DecryptionMaterials, - DecryptionMaterialsRequest, - EncryptionMaterials, - EncryptionMaterialsRequest, -) -from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager -from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager - - -class UnapprovedAlgorithmSuite(Exception): - """Indicate that an unsupported algorithm suite was requested.""" - - -class RequireApprovedAlgorithmSuitesCryptoMaterialsManager(CryptoMaterialsManager): - """Only allow encryption requests for approved algorithm suites.""" - - def __init__(self, keyring): - # type: (Keyring) -> None - """Set up the inner cryptographic materials manager using the provided keyring. - - :param Keyring keyring: Keyring to use in the inner cryptographic materials manager - """ - self._allowed_algorithm_suites = { - None, # no algorithm suite in the request - AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, # the default algorithm suite - AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA256, # the recommended unsigned algorithm suite - } - # Wrap the provided keyring in the default cryptographic materials manager (CMM). - # - # This is the same thing that the encrypt and decrypt APIs, as well as the caching CMM, - # do if you provide a keyring instead of a CMM. - self._cmm = DefaultCryptoMaterialsManager(keyring=keyring) - - def get_encryption_materials(self, request): - # type: (EncryptionMaterialsRequest) -> EncryptionMaterials - """Block any requests that include an unapproved algorithm suite.""" - if request.algorithm not in self._allowed_algorithm_suites: - raise UnapprovedAlgorithmSuite("Unapproved algorithm suite requested!") - - return self._cmm.get_encryption_materials(request) - - def decrypt_materials(self, request): - # type: (DecryptionMaterialsRequest) -> DecryptionMaterials - """Be more permissive on decrypt and just pass through.""" - return self._cmm.decrypt_materials(request) - - -def run(aws_kms_cmk, source_plaintext): - # type: (str, bytes) -> None - """Demonstrate an encrypt/decrypt cycle using a custom cryptographic materials manager that filters requests. - - :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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 the keyring that determines how your data keys are protected. - keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk) - - # Create the algorithm suite restricting cryptographic materials manager using your keyring. - cmm = RequireApprovedAlgorithmSuitesCryptoMaterialsManager(keyring=keyring) - - # Demonstrate that the algorithm suite restricting CMM will not let you use an unapproved algorithm suite. - try: - aws_encryption_sdk.encrypt( - source=source_plaintext, - encryption_context=encryption_context, - materials_manager=cmm, - algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16, - ) - except UnapprovedAlgorithmSuite: - # You asked for an unapproved algorithm suite. - # Reaching this point means everything is working as expected. - pass - else: - # The algorithm suite restricting CMM keeps this from happening. - raise AssertionError("The algorithm suite restricting CMM does not let this happen!") - - # Encrypt your plaintext data. - ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, materials_manager=cmm - ) - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # Decrypt your encrypted data using the same cryptographic materials manager 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. - decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, materials_manager=cmm) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/src/crypto_materials_manager/custom/requiring_encryption_context_fields.py b/examples/src/crypto_materials_manager/custom/requiring_encryption_context_fields.py deleted file mode 100644 index 95fdd0ff5..000000000 --- a/examples/src/crypto_materials_manager/custom/requiring_encryption_context_fields.py +++ /dev/null @@ -1,154 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -Encryption context is a powerful tool for access and audit controls -because it lets you tie *non-secret* metadata about a plaintext value to the encrypted message. -Within the AWS Encryption SDK, -you can use cryptographic materials managers to analyse the encryption context -to provide logical controls and additional metadata. - -https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context - -If you are using the AWS Encryption SDK with AWS KMS, -you can use AWS KMS to provide additional powerful controls using the encryption context. -For more information on that, see the AWS KMS developer guide: - -https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#encrypt_context - -This example shows how to create a custom cryptographic materials manager (CMM) -that requires a particular field in the encryption context. -""" -import aws_encryption_sdk -from aws_encryption_sdk.keyrings.aws_kms import AwsKmsKeyring -from aws_encryption_sdk.keyrings.base import Keyring -from aws_encryption_sdk.materials_managers import ( - DecryptionMaterials, - DecryptionMaterialsRequest, - EncryptionMaterials, - EncryptionMaterialsRequest, -) -from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager -from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager - - -class MissingClassificationError(Exception): - """Indicates that an encryption context was found that lacked a classification identifier.""" - - -class ClassificationRequiringCryptoMaterialsManager(CryptoMaterialsManager): - """Only allow requests when the encryption context contains a classification identifier.""" - - def __init__(self, keyring): - # type: (Keyring) -> None - """Set up the inner cryptographic materials manager using the provided keyring. - - :param Keyring keyring: Keyring to use in the inner cryptographic materials manager - """ - self._classification_field = "classification" - self._classification_error = MissingClassificationError("Encryption context does not contain classification!") - # Wrap the provided keyring in the default cryptographic materials manager (CMM). - # - # This is the same thing that the encrypt and decrypt APIs, as well as the caching CMM, - # do if you provide a keyring instead of a CMM. - self._cmm = DefaultCryptoMaterialsManager(keyring=keyring) - - def get_encryption_materials(self, request): - # type: (EncryptionMaterialsRequest) -> EncryptionMaterials - """Block any requests that do not contain a classification identifier in the encryption context.""" - if self._classification_field not in request.encryption_context: - raise self._classification_error - - return self._cmm.get_encryption_materials(request) - - def decrypt_materials(self, request): - # type: (DecryptionMaterialsRequest) -> DecryptionMaterials - """Block any requests that do not contain a classification identifier in the encryption context.""" - if self._classification_field not in request.encryption_context: - raise self._classification_error - - return self._cmm.decrypt_materials(request) - - -def run(aws_kms_cmk, source_plaintext): - # type: (str, bytes) -> None - """Demonstrate an encrypt/decrypt cycle using a custom cryptographic materials manager that filters requests. - - :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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 the keyring that determines how your data keys are protected. - keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk) - - # Create the classification requiring cryptographic materials manager using your keyring. - cmm = ClassificationRequiringCryptoMaterialsManager(keyring=keyring) - - # Demonstrate that the classification requiring CMM will not let you encrypt without a classification identifier. - try: - aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, materials_manager=cmm, - ) - except MissingClassificationError: - # Your encryption context did not contain a classification identifier. - # Reaching this point means everything is working as expected. - pass - else: - # The classification requiring CMM keeps this from happening. - raise AssertionError("The classification requiring CMM does not let this happen!") - - # Encrypt your plaintext data. - classified_ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, - encryption_context=dict(classification="secret", **encryption_context), - materials_manager=cmm, - ) - - # Demonstrate that the ciphertext and plaintext are different. - assert classified_ciphertext != source_plaintext - - # Decrypt your encrypted data using the same cryptographic materials manager 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. - decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=classified_ciphertext, materials_manager=cmm) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) - - # Now demonstrate the decrypt path of the classification requiring cryptographic materials manager. - - # Encrypt your plaintext using the keyring and do not include a classification identifier. - unclassified_ciphertext, encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, keyring=keyring - ) - - assert "classification" not in encrypt_header.encryption_context - - # Demonstrate that the classification requiring CMM - # will not let you decrypt messages without classification identifiers. - try: - aws_encryption_sdk.decrypt(source=unclassified_ciphertext, materials_manager=cmm) - except MissingClassificationError: - # Your encryption context did not contain a classification identifier. - # Reaching this point means everything is working as expected. - pass - else: - # The classification requiring CMM keeps this from happening. - raise AssertionError("The classification requiring CMM does not let this happen!") diff --git a/examples/src/legacy/data_key_caching_basic.py b/examples/src/data_key_caching_basic.py similarity index 62% rename from examples/src/legacy/data_key_caching_basic.py rename to examples/src/data_key_caching_basic.py index 490e82b49..1d5445615 100644 --- a/examples/src/legacy/data_key_caching_basic.py +++ b/examples/src/data_key_caching_basic.py @@ -1,13 +1,23 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.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. """Example of encryption with data key caching.""" import aws_encryption_sdk -def run(aws_kms_cmk, max_age_in_cache=10.0, cache_capacity=10): +def encrypt_with_caching(kms_cmk_arn, max_age_in_cache, cache_capacity): """Encrypts a string using an AWS KMS customer master key (CMK) and data key caching. - :param str aws_kms_cmk: Amazon Resource Name (ARN) of the AWS KMS customer master key + :param str kms_cmk_arn: Amazon Resource Name (ARN) of the KMS customer master key :param float max_age_in_cache: Maximum time in seconds that a cached entry can be used :param int cache_capacity: Maximum number of entries to retain in cache at once """ @@ -21,8 +31,8 @@ def run(aws_kms_cmk, max_age_in_cache=10.0, cache_capacity=10): # Create an encryption context encryption_context = {"purpose": "test"} - # Create a master key provider for the AWS KMS customer master key (CMK) - key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[aws_kms_cmk]) + # Create a master key provider for the KMS customer master key (CMK) + key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[kms_cmk_arn]) # Create a local cache cache = aws_encryption_sdk.LocalCryptoMaterialsCache(cache_capacity) diff --git a/examples/src/file_streaming_defaults.py b/examples/src/file_streaming_defaults.py deleted file mode 100644 index 7fced660a..000000000 --- a/examples/src/file_streaming_defaults.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -This example shows how to use the streaming encrypt and decrypt APIs when working with files. - -One benefit of using the streaming API is that -we can check the encryption context in the header before we start decrypting. - -In this example, we use an AWS KMS customer master key (CMK), -but you can use other key management options with the AWS Encryption SDK. -For examples that demonstrate how to use other key management configurations, -see the ``keyring`` and ``master_key_provider`` directories. -""" -import filecmp - -import aws_encryption_sdk -from aws_encryption_sdk.keyrings.aws_kms import AwsKmsKeyring - - -def run(aws_kms_cmk, source_plaintext_filename): - # type: (str, str) -> None - """Demonstrate an encrypt/decrypt cycle using the streaming encrypt/decrypt APIs with files. - - :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys - :param str source_plaintext_filename: Path to plaintext file to encrypt - """ - # We assume that you can also write to the directory containing the plaintext file, - # so that is where we will put all of the results. - ciphertext_filename = source_plaintext_filename + ".encrypted" - decrypted_filename = ciphertext_filename + ".decrypted" - - # Prepare your 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 = { - "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 the keyring that determines how your data keys are protected. - keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk) - - # Open the files you want to work with. - with open(source_plaintext_filename, "rb") as plaintext, open(ciphertext_filename, "wb") as ciphertext: - # The streaming API provides a context manager. - # You can read from it just as you read from a file. - with aws_encryption_sdk.stream( - mode="encrypt", source=plaintext, encryption_context=encryption_context, keyring=keyring - ) as encryptor: - # Iterate through the segments in the context manager - # and write the results to the ciphertext. - for segment in encryptor: - ciphertext.write(segment) - - # Demonstrate that the ciphertext and plaintext are different. - assert not filecmp.cmp(source_plaintext_filename, ciphertext_filename) - - # Open the files you want to work with. - with open(ciphertext_filename, "rb") as ciphertext, open(decrypted_filename, "wb") as decrypted: - # 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. - with aws_encryption_sdk.stream(mode="decrypt", source=ciphertext, keyring=keyring) as decryptor: - # Check the encryption context in the header before we start decrypting. - # - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decryptor.header.encryption_context.items()) - - # Now that we are more confident that we will decrypt the right message, - # we can start decrypting. - for segment in decryptor: - decrypted.write(segment) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert filecmp.cmp(source_plaintext_filename, decrypted_filename) diff --git a/examples/src/in_memory_streaming_defaults.py b/examples/src/in_memory_streaming_defaults.py deleted file mode 100644 index f4a7811b4..000000000 --- a/examples/src/in_memory_streaming_defaults.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -This example shows how to use the streaming encrypt and decrypt APIs on data in memory. - -One benefit of using the streaming API is that -we can check the encryption context in the header before we start decrypting. - -In this example, we use an AWS KMS customer master key (CMK), -but you can use other key management options with the AWS Encryption SDK. -For examples that demonstrate how to use other key management configurations, -see the ``keyring`` and ``master_key_provider`` directories. -""" -import io - -import aws_encryption_sdk -from aws_encryption_sdk.keyrings.aws_kms import AwsKmsKeyring - - -def run(aws_kms_cmk, source_plaintext): - # type: (str, bytes) -> None - """Demonstrate an encrypt/decrypt cycle using the streaming encrypt/decrypt APIs in-memory. - - :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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 the keyring that determines how your data keys are protected. - keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk) - - ciphertext = io.BytesIO() - - # The streaming API provides a context manager. - # You can read from it just as you read from a file. - with aws_encryption_sdk.stream( - mode="encrypt", source=source_plaintext, encryption_context=encryption_context, keyring=keyring - ) as encryptor: - # Iterate through the segments in the context manager - # and write the results to the ciphertext. - for segment in encryptor: - ciphertext.write(segment) - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext.getvalue() != source_plaintext - - # Reset the ciphertext stream position so that we can read from the beginning. - ciphertext.seek(0) - - # 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. - decrypted = io.BytesIO() - with aws_encryption_sdk.stream(mode="decrypt", source=ciphertext, keyring=keyring) as decryptor: - # Check the encryption context in the header before we start decrypting. - # - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decryptor.header.encryption_context.items()) - - # Now that we are more confident that we will decrypt the right message, - # we can start decrypting. - for segment in decryptor: - decrypted.write(segment) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted.getvalue() == source_plaintext diff --git a/examples/src/keyring/__init__.py b/examples/src/keyring/__init__.py deleted file mode 100644 index c718f08e8..000000000 --- a/examples/src/keyring/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -Keyring examples. - -These examples show how to use keyrings. -""" diff --git a/examples/src/keyring/aws_kms/__init__.py b/examples/src/keyring/aws_kms/__init__.py deleted file mode 100644 index 4f67cf9c4..000000000 --- a/examples/src/keyring/aws_kms/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -AWS KMS keyring examples. - -These examples show how to use the AWS KMS keyring. -""" diff --git a/examples/src/keyring/aws_kms/custom_client_supplier.py b/examples/src/keyring/aws_kms/custom_client_supplier.py deleted file mode 100644 index 7037beaf7..000000000 --- a/examples/src/keyring/aws_kms/custom_client_supplier.py +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -By default, the AWS KMS keyring uses a client supplier that -supplies a client with the same configuration for every region. -If you need different behavior, you can write your own client supplier. - -You might use this -if you need different credentials in different AWS regions. -This might be because you are crossing partitions (ex: ``aws`` and ``aws-cn``) -or if you are working with regions that have separate authentication silos -like ``ap-east-1`` and ``me-south-1``. - -This example shows how to create a client supplier -that will supply AWS KMS clients with valid credentials for the target region -even when working with regions that need different credentials. - -https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring - -For an example of how to use the AWS KMS keyring with CMKs in multiple regions, -see the ``keyring/aws_kms/multiple_regions`` example. - -For another example of how to use the AWS KMS keyring with a custom client configuration, -see the ``keyring/aws_kms/custom_kms_client_config`` example. - -For examples of how to use the AWS KMS keyring in discovery mode on decrypt, -see the ``keyring/aws_kms/discovery_decrypt``, -``keyring/aws_kms/discovery_decrypt_in_region_only``, -and ``keyring/aws_kms/discovery_decrypt_with_preferred_region`` examples. -""" -from botocore.client import BaseClient -from botocore.session import Session - -import aws_encryption_sdk -from aws_encryption_sdk.keyrings.aws_kms import AwsKmsKeyring -from aws_encryption_sdk.keyrings.aws_kms.client_suppliers import ClientSupplier, DefaultClientSupplier - -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Union # noqa pylint: disable=unused-import -except ImportError: # pragma: no cover - # We only need these imports when running the mypy checks - pass - - -class MultiPartitionClientSupplier(ClientSupplier): - """Client supplier that supplies clients across AWS partitions and identity silos.""" - - def __init__(self): - """Set up default client suppliers for identity silos.""" - self._china_supplier = DefaultClientSupplier(botocore_session=Session(profile="china")) - self._middle_east_supplier = DefaultClientSupplier(botocore_session=Session(profile="middle-east")) - self._hong_kong_supplier = DefaultClientSupplier(botocore_session=Session(profile="hong-kong")) - self._default_supplier = DefaultClientSupplier() - - def __call__(self, region_name): - # type: (Union[None, str]) -> BaseClient - """Return a client for the requested region. - - :rtype: BaseClient - """ - if region_name.startswith("cn-"): - return self._china_supplier(region_name) - - if region_name.startswith("me-"): - return self._middle_east_supplier(region_name) - - if region_name == "ap-east-1": - return self._hong_kong_supplier(region_name) - - return self._default_supplier(region_name) - - -def run(aws_kms_cmk, source_plaintext): - # type: (str, bytes) -> None - """Demonstrate an encrypt/decrypt cycle using an AWS KMS keyring with a custom client supplier. - - :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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 the keyring that determines how your data keys are protected. - keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk, client_supplier=MultiPartitionClientSupplier()) - - # Encrypt your plaintext data. - ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, keyring=keyring - ) - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # 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. - decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, keyring=keyring) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/src/keyring/aws_kms/custom_kms_client_config.py b/examples/src/keyring/aws_kms/custom_kms_client_config.py deleted file mode 100644 index c1313ee8f..000000000 --- a/examples/src/keyring/aws_kms/custom_kms_client_config.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -By default, the AWS KMS keyring uses the default configurations -for all AWS KMS clients and uses the default discoverable credentials. -If you need to change this configuration, -you can configure the client supplier. - -This example shows how to use custom-configured clients with the AWS KMS keyring. - -https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring - -For an example of how to use the AWS KMS keyring with CMKs in multiple regions, -see the ``keyring/aws_kms/multiple_regions`` example. - -For another example of how to use the AWS KMS keyring with custom client configuration, -see the ``keyring/aws_kms/custom_client_supplier`` example. - -For examples of how to use the AWS KMS keyring in discovery mode on decrypt, -see the ``keyring/aws_kms/discovery_decrypt``, -``keyring/aws_kms/discovery_decrypt_in_region_only``, -and ``keyring/aws_kms/discovery_decrypt_with_preferred_region`` examples. -""" -from botocore.config import Config -from botocore.session import Session - -import aws_encryption_sdk -from aws_encryption_sdk.identifiers import USER_AGENT_SUFFIX -from aws_encryption_sdk.keyrings.aws_kms import AwsKmsKeyring -from aws_encryption_sdk.keyrings.aws_kms.client_suppliers import DefaultClientSupplier - - -def run(aws_kms_cmk, source_plaintext): - # type: (str, bytes) -> None - """Demonstrate an encrypt/decrypt cycle using an AWS KMS keyring with custom AWS KMS client configuration. - - :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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", - } - - # Prepare your custom configuration values. - # - # Set your custom connection timeout value. - # https://botocore.amazonaws.com/v1/documentation/api/latest/reference/config.html - custom_client_config = Config(connect_timeout=10.0, user_agent_extra=USER_AGENT_SUFFIX) - # For this example we will just use the default botocore session configuration - # but if you need to, you can set custom credentials in the botocore session. - custom_session = Session() - - # Use your custom configuration values to configure your client supplier. - client_supplier = DefaultClientSupplier(botocore_session=custom_session, client_config=custom_client_config) - - # Create the keyring that determines how your data keys are protected, - # providing the client supplier that you created. - keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk, client_supplier=client_supplier) - - # Encrypt your plaintext data. - ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, keyring=keyring - ) - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # 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. - decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, keyring=keyring) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/src/keyring/aws_kms/discovery_decrypt.py b/examples/src/keyring/aws_kms/discovery_decrypt.py deleted file mode 100644 index e43283f52..000000000 --- a/examples/src/keyring/aws_kms/discovery_decrypt.py +++ /dev/null @@ -1,77 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -When you give the AWS KMS keyring specific key IDs it will use those CMKs and nothing else. -This is true both on encrypt and on decrypt. -However, sometimes you need more flexibility on decrypt, -especially when you don't know which CMKs were used to encrypt a message. -To address this need, you can use an AWS KMS discovery keyring. -The AWS KMS discovery keyring does nothing on encrypt, -but attempts to decrypt *any* data keys that were encrypted under an AWS KMS CMK. - -This example shows how to configure and use an AWS KMS discovery keyring. - -https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring - -For an example of how to use the AWS KMS keyring with CMKs in multiple regions, -see the ``keyring/aws_kms/multiple_regions`` example. - -For examples of how to use the AWS KMS keyring with custom client configurations, -see the ``keyring/aws_kms/custom_client_supplier`` -and ``keyring/aws_kms/custom_kms_client_config`` examples. - -For examples of how to use the AWS KMS discovery keyring on decrypt, -see the ``keyring/aws_kms/discovery_decrypt_in_region_only`` -and ``keyring/aws_kms/discovery_decrypt_with_preferred_region`` examples. -""" -import aws_encryption_sdk -from aws_encryption_sdk.keyrings.aws_kms import AwsKmsKeyring - - -def run(aws_kms_cmk, source_plaintext): - # type: (str, bytes) -> None - """Demonstrate configuring an AWS KMS discovery keyring for decryption. - - :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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 the keyring that determines how your data keys are protected. - encrypt_keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk) - - # Create an AWS KMS discovery keyring to use on decrypt. - decrypt_keyring = AwsKmsKeyring(is_discovery=True) - - # Encrypt your plaintext data. - ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, keyring=encrypt_keyring - ) - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # Decrypt your encrypted data using the AWS KMS discovery keyring. - # - # You do not need to specify the encryption context on decrypt - # because the header of the encrypted message includes the encryption context. - decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, keyring=decrypt_keyring) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/src/keyring/aws_kms/discovery_decrypt_in_region_only.py b/examples/src/keyring/aws_kms/discovery_decrypt_in_region_only.py deleted file mode 100644 index 54caa0c7b..000000000 --- a/examples/src/keyring/aws_kms/discovery_decrypt_in_region_only.py +++ /dev/null @@ -1,90 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -When you give the AWS KMS keyring specific key IDs it will use those CMKs and nothing else. -This is true both on encrypt and on decrypt. -However, sometimes you need more flexibility on decrypt, -especially when you don't know which CMKs were used to encrypt a message. -To address this need, you can use an AWS KMS discovery keyring. -The AWS KMS discovery keyring does nothing on encrypt, -but attempts to decrypt *any* data keys that were encrypted under an AWS KMS CMK. - -However, sometimes you need to be a *bit* more restrictive than that. -To address this need, you can use a client supplier that restricts the regions an AWS KMS keyring can talk to. - -This example shows how to configure and use an AWS KMS regional discovery keyring that is restricted to one region. - -https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring - -For an example of how to use the AWS KMS keyring with CMKs in multiple regions, -see the ``keyring/aws_kms/multiple_regions`` example. - -For examples of how to use the AWS KMS keyring with custom client configurations, -see the ``keyring/aws_kms/custom_client_supplier`` -and ``keyring/aws_kms/custom_kms_client_config`` examples. - -For examples of how to use the AWS KMS discovery keyring on decrypt, -see the ``keyring/aws_kms/discovery_decrypt`` -and ``keyring/aws_kms/discovery_decrypt_with_preferred_region`` examples. -""" -import aws_encryption_sdk -from aws_encryption_sdk.keyrings.aws_kms import AwsKmsKeyring -from aws_encryption_sdk.keyrings.aws_kms.client_suppliers import AllowRegionsClientSupplier - - -def run(aws_kms_cmk, source_plaintext): - # type: (str, bytes) -> None - """Demonstrate configuring an AWS KMS discovery keyring to only work within a single region. - - :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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 the keyring that determines how your data keys are protected. - encrypt_keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk) - - # Extract the region from the CMK ARN. - decrypt_region = aws_kms_cmk.split(":", 4)[3] - - # Create the AWS KMS discovery keyring that we will use on decrypt. - # - # The client supplier that we specify here will only supply clients for the specified region. - # The keyring only attempts to decrypt data keys if it can get a client for that region, - # so this keyring will now ignore any data keys that were encrypted under a CMK in another region. - decrypt_keyring = AwsKmsKeyring( - is_discovery=True, client_supplier=AllowRegionsClientSupplier(allowed_regions=[decrypt_region]) - ) - - # Encrypt your plaintext data. - ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, keyring=encrypt_keyring - ) - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # Decrypt your encrypted data using the AWS KMS discovery keyring. - # - # You do not need to specify the encryption context on decrypt - # because the header of the encrypted message includes the encryption context. - decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, keyring=decrypt_keyring) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/src/keyring/aws_kms/discovery_decrypt_with_preferred_regions.py b/examples/src/keyring/aws_kms/discovery_decrypt_with_preferred_regions.py deleted file mode 100644 index a3b22f9a1..000000000 --- a/examples/src/keyring/aws_kms/discovery_decrypt_with_preferred_regions.py +++ /dev/null @@ -1,111 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -When you give the AWS KMS keyring specific key IDs it will use those CMKs and nothing else. -This is true both on encrypt and on decrypt. -However, sometimes you need more flexibility on decrypt, -especially when you don't know which CMKs were used to encrypt a message. -To address this need, you can use an AWS KMS discovery keyring. -The AWS KMS discovery keyring does nothing on encrypt, -but attempts to decrypt *any* data keys that were encrypted under an AWS KMS CMK. - -However, sometimes you need to be a *bit* more restrictive than that. -To address this need, you can use a client supplier to restrict what regions an AWS KMS keyring can talk to. - -A more complex but more common use-case is that you would *prefer* to stay within a region, -but you would rather make calls to other regions than fail to decrypt the message. -In this case, you want a keyring that will try to decrypt data keys in this region first, -then try other regions. - -This example shows how to configure and use a multi-keyring with the AWS KMS keyring -to prefer the current AWS region while also failing over to other AWS regions. - -https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring - -For an example of how to use the AWS KMS keyring with CMKs in multiple regions, -see the ``keyring/aws_kms/multiple_regions`` example. - -For examples of how to use the AWS KMS keyring with custom client configurations, -see the ``keyring/aws_kms/custom_client_supplier`` -and ``keyring/aws_kms/custom_kms_client_config`` examples. - -For examples of how to use the AWS KMS discovery keyring on decrypt, -see the ``keyring/aws_kms/discovery_decrypt`` -and ``keyring/aws_kms/discovery_decrypt_in_region_only`` examples. -""" -from boto3.session import Session - -import aws_encryption_sdk -from aws_encryption_sdk.keyrings.aws_kms import AwsKmsKeyring -from aws_encryption_sdk.keyrings.aws_kms.client_suppliers import AllowRegionsClientSupplier, DenyRegionsClientSupplier -from aws_encryption_sdk.keyrings.multi import MultiKeyring - - -def run(aws_kms_cmk, source_plaintext): - # type: (str, bytes) -> None - """Demonstrate configuring an AWS KMS discovery-like keyring a particular AWS region and failover to others. - - :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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 the keyring that determines how your data keys are protected. - encrypt_keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk) - - # To create our decrypt keyring, we need to know our current default AWS region. - # - # Create a throw-away boto3 session to discover the default region. - local_region = Session().region_name - - # Now, use that region name to create two AWS KMS discovery keyrings: - # - # One that only works in the local region - local_region_decrypt_keyring = AwsKmsKeyring( - is_discovery=True, client_supplier=AllowRegionsClientSupplier(allowed_regions=[local_region]) - ) - # and one that will work in any other region but NOT the local region. - other_regions_decrypt_keyring = AwsKmsKeyring( - is_discovery=True, client_supplier=DenyRegionsClientSupplier(denied_regions=[local_region]) - ) - - # Finally, combine those two keyrings into a multi-keyring. - # - # The multi-keyring steps through its member keyrings in the order that you provide them, - # attempting to decrypt every encrypted data key with each keyring before moving on to the next keyring. - # Because of this, other_regions_decrypt_keyring will not be called - # unless local_region_decrypt_keyring fails to decrypt every encrypted data key. - decrypt_keyring = MultiKeyring(children=[local_region_decrypt_keyring, other_regions_decrypt_keyring]) - - # Encrypt your plaintext data. - ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, keyring=encrypt_keyring - ) - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # Decrypt your encrypted data using the multi-keyring. - # - # You do not need to specify the encryption context on decrypt - # because the header of the encrypted message includes the encryption context. - decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, keyring=decrypt_keyring) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/src/keyring/aws_kms/multiple_regions.py b/examples/src/keyring/aws_kms/multiple_regions.py deleted file mode 100644 index d75498a7a..000000000 --- a/examples/src/keyring/aws_kms/multiple_regions.py +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -This example shows how to configure and use an AWS KMS keyring with with CMKs in multiple regions. - -https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring - -For an example of how to use the AWS KMS keyring with a single CMK, -see the ``keyring/aws_kms/single_cmk`` example. - -For examples of how to use the AWS KMS keyring with custom client configurations, -see the ``keyring/aws_kms/custom_client_supplier`` -and ``keyring/aws_kms/custom_kms_client_config`` examples. - -For examples of how to use the AWS KMS keyring in discovery mode on decrypt, -see the ``keyring/aws_kms/discovery_decrypt``, -``keyring/aws_kms/discovery_decrypt_in_region_only``, -and ``keyring/aws_kms/discovery_decrypt_with_preferred_region`` examples. -""" -import aws_encryption_sdk -from aws_encryption_sdk.keyrings.aws_kms import AwsKmsKeyring - -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Sequence # noqa pylint: disable=unused-import -except ImportError: # pragma: no cover - # We only actually need these imports when running the mypy checks - pass - - -def run(aws_kms_generator_cmk, aws_kms_additional_cmks, source_plaintext): - # type: (str, Sequence[str], bytes) -> None - """Demonstrate an encrypt/decrypt cycle using an AWS KMS keyring with CMKs in multiple regions. - - :param str aws_kms_generator_cmk: The ARN of the primary AWS KMS CMK - :param List[str] aws_kms_additional_cmks: Additional ARNs of secondary AWS KMS CMKs - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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 the keyring that will encrypt your data keys under all requested CMKs. - many_cmks_keyring = AwsKmsKeyring(generator_key_id=aws_kms_generator_cmk, key_ids=aws_kms_additional_cmks) - - # Create keyrings that each only use one of the CMKs. - # We will use these later to demonstrate that any of the CMKs can be used to decrypt the message. - # - # We provide these in "key_ids" rather than "generator_key_id" - # so that these keyrings cannot be used to generate a new data key. - # We will only be using them on decrypt. - single_cmk_keyring_that_generated = AwsKmsKeyring(key_ids=[aws_kms_generator_cmk]) - single_cmk_keyring_that_encrypted = AwsKmsKeyring(key_ids=[aws_kms_additional_cmks[0]]) - - # Encrypt your plaintext data using the keyring that uses all requests CMKs. - ciphertext, encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, keyring=many_cmks_keyring - ) - - # Verify that the header contains the expected number of encrypted data keys (EDKs). - # It should contain one EDK for each CMK. - assert len(encrypt_header.encrypted_data_keys) == len(aws_kms_additional_cmks) + 1 - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # Decrypt your encrypted data separately using the single-CMK keyrings. - # - # You do not need to specify the encryption context on decrypt - # because the header of the encrypted message includes the encryption context. - decrypted_1, decrypt_header_1 = aws_encryption_sdk.decrypt( - source=ciphertext, keyring=single_cmk_keyring_that_generated - ) - decrypted_2, decrypt_header_2 = aws_encryption_sdk.decrypt( - source=ciphertext, keyring=single_cmk_keyring_that_encrypted - ) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted_1 == source_plaintext - assert decrypted_2 == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header_1.encryption_context.items()) - assert set(encryption_context.items()) <= set(decrypt_header_2.encryption_context.items()) diff --git a/examples/src/keyring/aws_kms/single_cmk.py b/examples/src/keyring/aws_kms/single_cmk.py deleted file mode 100644 index c4f628614..000000000 --- a/examples/src/keyring/aws_kms/single_cmk.py +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -This example shows how to configure and use an AWS KMS keyring with a single AWS KMS CMK. - -https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring - -For an example of how to use the AWS KMS keyring with CMKs in multiple regions, -see the ``keyring/aws_kms/multiple_regions`` example. - -For examples of how to use the AWS KMS keyring with custom client configurations, -see the ``keyring/aws_kms/custom_client_supplier`` -and ``keyring/aws_kms/custom_kms_client_config`` examples. - -For examples of how to use the AWS KMS keyring in discovery mode on decrypt, -see the ``keyring/aws_kms/discovery_decrypt``, -``keyring/aws_kms/discovery_decrypt_in_region_only``, -and ``keyring/aws_kms/discovery_decrypt_with_preferred_region`` examples. -""" -import aws_encryption_sdk -from aws_encryption_sdk.keyrings.aws_kms import AwsKmsKeyring - - -def run(aws_kms_cmk, source_plaintext): - # type: (str, bytes) -> None - """Demonstrate an encrypt/decrypt cycle using an AWS KMS keyring with a single CMK. - - :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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 the keyring that determines how your data keys are protected. - keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk) - - # Encrypt your plaintext data. - ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, keyring=keyring - ) - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # 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. - decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, keyring=keyring) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/src/keyring/multi/__init__.py b/examples/src/keyring/multi/__init__.py deleted file mode 100644 index e5703355a..000000000 --- a/examples/src/keyring/multi/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -Multi-keyring examples. - -These examples show how to use the multi-keyring. -""" diff --git a/examples/src/keyring/multi/aws_kms_with_escrow.py b/examples/src/keyring/multi/aws_kms_with_escrow.py deleted file mode 100644 index 90b57def4..000000000 --- a/examples/src/keyring/multi/aws_kms_with_escrow.py +++ /dev/null @@ -1,129 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -One use-case that we have seen customers need is -the ability to enjoy the benefits of AWS KMS during normal operation -but retain the ability to decrypt encrypted messages without access to AWS KMS. -This example shows how you can use the multi-keyring to achieve this -by combining an AWS KMS keyring with a raw RSA keyring. - -https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-multi-keyring - -For more examples of how to use the AWS KMS keyring, see the ``keyring/aws_kms`` examples. - -For more examples of how to use the raw RSA keyring, see the ``keyring/raw_rsa`` examples. - -In this example we generate an RSA keypair -but in practice you would want to keep your private key in an HSM -or other key management system. - -In this example, we use the one-step encrypt and decrypt APIs. -""" -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.asymmetric import rsa - -import aws_encryption_sdk -from aws_encryption_sdk.identifiers import WrappingAlgorithm -from aws_encryption_sdk.keyrings.aws_kms import AwsKmsKeyring -from aws_encryption_sdk.keyrings.multi import MultiKeyring -from aws_encryption_sdk.keyrings.raw import RawRSAKeyring - - -def run(aws_kms_cmk, source_plaintext): - # type: (str, bytes) -> None - """Demonstrate configuring a keyring to use an AWS KMS CMK and an RSA wrapping key. - - :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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", - } - - # Generate an RSA private key to use with your keyring. - # In practice, you should get this key from a secure key management system such as an HSM. - # - # The National Institute of Standards and Technology (NIST) recommends a minimum of 2048-bit keys for RSA. - # https://www.nist.gov/publications/transitioning-use-cryptographic-algorithms-and-key-lengths - # - # Why did we use this public exponent? - # https://crypto.stanford.edu/~dabo/pubs/papers/RSA-survey.pdf - private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096, backend=default_backend()) - - # Collect the public key from the private key. - public_key = private_key.public_key() - - # Create the encrypt keyring that only has access to the public key. - escrow_encrypt_keyring = RawRSAKeyring( - # The key namespace and key name are defined by you - # and are used by the raw RSA keyring - # to determine whether it should attempt to decrypt - # an encrypted data key. - # - # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring - key_namespace="some managed raw keys", - key_name=b"my RSA wrapping key", - public_wrapping_key=public_key, - # The wrapping algorithm tells the raw RSA keyring - # how to use your wrapping key to encrypt data keys. - # - # We recommend using RSA_OAEP_SHA256_MGF1. - # You should not use RSA_PKCS1 unless you require it for backwards compatibility. - wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - ) - - # Create the decrypt keyring that has access to the private key. - escrow_decrypt_keyring = RawRSAKeyring( - # The key namespace and key name MUST match the encrypt keyring. - key_namespace="some managed raw keys", - key_name=b"my RSA wrapping key", - private_wrapping_key=private_key, - # The wrapping algorithm MUST match the encrypt keyring. - wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - ) - - # Create the AWS KMS keyring that you will use for decryption during normal operations. - kms_keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk) - - # Combine the AWS KMS keyring and the escrow encrypt keyring using the multi-keyring. - encrypt_keyring = MultiKeyring(generator=kms_keyring, children=[escrow_encrypt_keyring]) - - # Encrypt your plaintext data using the multi-keyring. - ciphertext, encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, keyring=encrypt_keyring - ) - - # Verify that the header contains the expected number of encrypted data keys (EDKs). - # It should contain one EDK for AWS KMS and one for the escrow key. - assert len(encrypt_header.encrypted_data_keys) == 2 - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # Decrypt your encrypted data separately using the AWS KMS keyring and the escrow decrypt keyring. - # - # You do not need to specify the encryption context on decrypt - # because the header of the encrypted message includes the encryption context. - decrypted_kms, decrypt_header_kms = aws_encryption_sdk.decrypt(source=ciphertext, keyring=kms_keyring) - decrypted_escrow, decrypt_header_escrow = aws_encryption_sdk.decrypt( - source=ciphertext, keyring=escrow_decrypt_keyring - ) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted_kms == source_plaintext - assert decrypted_escrow == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header_kms.encryption_context.items()) - assert set(encryption_context.items()) <= set(decrypt_header_escrow.encryption_context.items()) diff --git a/examples/src/keyring/raw_aes/__init__.py b/examples/src/keyring/raw_aes/__init__.py deleted file mode 100644 index 2159bf30d..000000000 --- a/examples/src/keyring/raw_aes/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -Raw AES keyring examples. - -These examples show how to use the raw AES keyring. -""" diff --git a/examples/src/keyring/raw_aes/raw_aes.py b/examples/src/keyring/raw_aes/raw_aes.py deleted file mode 100644 index 57b5c3487..000000000 --- a/examples/src/keyring/raw_aes/raw_aes.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -This examples shows how to configure and use a raw AES keyring. - -https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-aes-keyring - -In this example, we use the one-step encrypt and decrypt APIs. -""" -import os - -import aws_encryption_sdk -from aws_encryption_sdk.keyrings.raw import RawAESKeyring - - -def run(source_plaintext): - # type: (bytes) -> None - """Demonstrate an encrypt/decrypt cycle using a raw AES keyring. - - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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", - } - - # Generate a 256-bit (32 byte) AES key to use with your keyring. - # - # In practice, you should get this key from a secure key management system such as an HSM. - key = os.urandom(32) - - # Create the keyring that determines how your data keys are protected. - keyring = RawAESKeyring( - # 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. - # - # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-aes-keyring - key_namespace="some managed raw keys", - key_name=b"my AES wrapping key", - wrapping_key=key, - ) - - # Encrypt your plaintext data. - ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, keyring=keyring - ) - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # 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. - decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, keyring=keyring) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/src/keyring/raw_rsa/__init__.py b/examples/src/keyring/raw_rsa/__init__.py deleted file mode 100644 index 742761bbe..000000000 --- a/examples/src/keyring/raw_rsa/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -Raw RSA keyring examples. - -These examples show how to use the raw RSA keyring. -""" diff --git a/examples/src/keyring/raw_rsa/private_key_only.py b/examples/src/keyring/raw_rsa/private_key_only.py deleted file mode 100644 index b29c7718a..000000000 --- a/examples/src/keyring/raw_rsa/private_key_only.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -This examples shows how to configure and use a raw RSA keyring using a pre-loaded RSA private key. - -If your RSA key is in PEM or DER format, -see the ``keyring/raw_rsa/private_key_only_from_pem`` example. - -https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring - -In this example, we use the one-step encrypt and decrypt APIs. -""" -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.asymmetric import rsa - -import aws_encryption_sdk -from aws_encryption_sdk.identifiers import WrappingAlgorithm -from aws_encryption_sdk.keyrings.raw import RawRSAKeyring - - -def run(source_plaintext): - # type: (bytes) -> None - """Demonstrate an encrypt/decrypt cycle using a raw RSA keyring. - - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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", - } - - # Generate an RSA private key to use with your keyring. - # In practice, you should get this key from a secure key management system such as an HSM. - # - # The National Institute of Standards and Technology (NIST) recommends a minimum of 2048-bit keys for RSA. - # https://www.nist.gov/publications/transitioning-use-cryptographic-algorithms-and-key-lengths - # - # Why did we use this public exponent? - # https://crypto.stanford.edu/~dabo/pubs/papers/RSA-survey.pdf - private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096, backend=default_backend()) - - # Create the keyring that determines how your data keys are protected. - keyring = RawRSAKeyring( - # The key namespace and key name are defined by you - # and are used by the raw RSA keyring - # to determine whether it should attempt to decrypt - # an encrypted data key. - # - # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring - key_namespace="some managed raw keys", - key_name=b"my RSA wrapping key", - private_wrapping_key=private_key, - # The wrapping algorithm tells the raw RSA keyring - # how to use your wrapping key to encrypt data keys. - # - # We recommend using RSA_OAEP_SHA256_MGF1. - # You should not use RSA_PKCS1 unless you require it for backwards compatibility. - wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - ) - - # Encrypt your plaintext data. - ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, keyring=keyring - ) - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # 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. - decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, keyring=keyring) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/src/keyring/raw_rsa/private_key_only_from_pem.py b/examples/src/keyring/raw_rsa/private_key_only_from_pem.py deleted file mode 100644 index d72f82ae0..000000000 --- a/examples/src/keyring/raw_rsa/private_key_only_from_pem.py +++ /dev/null @@ -1,102 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -When you store RSA keys, you have to serialize them somehow. - -This example shows how to configure and use a raw RSA keyring using a PEM-encoded RSA private key. - -The most commonly used encodings for RSA keys tend to be PEM and DER. -The raw RSA keyring supports loading both public and private keys from these encodings. - -https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring - -In this example, we use the one-step encrypt and decrypt APIs. -""" -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import rsa - -import aws_encryption_sdk -from aws_encryption_sdk.identifiers import WrappingAlgorithm -from aws_encryption_sdk.keyrings.raw import RawRSAKeyring - - -def run(source_plaintext): - # type: (bytes) -> None - """Demonstrate an encrypt/decrypt cycle using a raw RSA keyring loaded from a PEM-encoded key. - - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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", - } - - # Generate an RSA private key to use with your keyring. - # In practice, you should get this key from a secure key management system such as an HSM. - # - # The National Institute of Standards and Technology (NIST) recommends a minimum of 2048-bit keys for RSA. - # https://www.nist.gov/publications/transitioning-use-cryptographic-algorithms-and-key-lengths - # - # Why did we use this public exponent? - # https://crypto.stanford.edu/~dabo/pubs/papers/RSA-survey.pdf - private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096, backend=default_backend()) - - # Serialize the RSA private key to PEM encoding. - # This or DER encoding is likely to be what you get from your key management system in practice. - private_key_pem = private_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.PKCS8, - encryption_algorithm=serialization.NoEncryption(), - ) - - # Create the keyring that determines how your data keys are protected. - # - # If your key is encoded using DER, you can use RawRSAKeyring.from_der_encoding - keyring = RawRSAKeyring.from_pem_encoding( - # The key namespace and key name are defined by you - # and are used by the raw RSA keyring - # to determine whether it should attempt to decrypt - # an encrypted data key. - # - # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring - key_namespace="some managed raw keys", - key_name=b"my RSA wrapping key", - private_encoded_key=private_key_pem, - # The wrapping algorithm tells the raw RSA keyring - # how to use your wrapping key to encrypt data keys. - # - # We recommend using RSA_OAEP_SHA256_MGF1. - # You should not use RSA_PKCS1 unless you require it for backwards compatibility. - wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - ) - - # Encrypt your plaintext data. - ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, keyring=keyring - ) - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # 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. - decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, keyring=keyring) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/src/keyring/raw_rsa/public_private_key_separate.py b/examples/src/keyring/raw_rsa/public_private_key_separate.py deleted file mode 100644 index dcda39cb9..000000000 --- a/examples/src/keyring/raw_rsa/public_private_key_separate.py +++ /dev/null @@ -1,126 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -One of the benefits of asymmetric encryption -is that you can encrypt with just the public key. -This means that you can give someone the ability to encrypt -without giving them the ability to decrypt. - -The raw RSA keyring supports encrypt-only operations -when it only has access to a public key. - -This example shows how to construct and use the raw RSA keyring -to encrypt with only the public key and decrypt with the private key. - -If your RSA key is in PEM or DER format, -see the ``keyring/raw_rsa/private_key_only_from_pem`` example. - -https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring - -In this example, we use the one-step encrypt and decrypt APIs. -""" -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.asymmetric import rsa - -import aws_encryption_sdk -from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError -from aws_encryption_sdk.identifiers import WrappingAlgorithm -from aws_encryption_sdk.keyrings.raw import RawRSAKeyring - - -def run(source_plaintext): - # type: (bytes) -> None - """Demonstrate an encrypt/decrypt cycle using separate public and private raw RSA keyrings. - - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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", - } - - # Generate an RSA private key to use with your keyring. - # In practice, you should get this key from a secure key management system such as an HSM. - # - # The National Institute of Standards and Technology (NIST) recommends a minimum of 2048-bit keys for RSA. - # https://www.nist.gov/publications/transitioning-use-cryptographic-algorithms-and-key-lengths - # - # Why did we use this public exponent? - # https://crypto.stanford.edu/~dabo/pubs/papers/RSA-survey.pdf - private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096, backend=default_backend()) - - # Collect the public key from the private key. - public_key = private_key.public_key() - - # The keyring determines how your data keys are protected. - # - # Create the encrypt keyring that only has access to the public key. - public_key_keyring = RawRSAKeyring( - # The key namespace and key name are defined by you - # and are used by the raw RSA keyring - # to determine whether it should attempt to decrypt - # an encrypted data key. - # - # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring - key_namespace="some managed raw keys", - key_name=b"my RSA wrapping key", - public_wrapping_key=public_key, - # The wrapping algorithm tells the raw RSA keyring - # how to use your wrapping key to encrypt data keys. - # - # We recommend using RSA_OAEP_SHA256_MGF1. - # You should not use RSA_PKCS1 unless you require it for backwards compatibility. - wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - ) - - # Create the decrypt keyring that has access to the private key. - private_key_keyring = RawRSAKeyring( - # The key namespace and key name MUST match the encrypt keyring. - key_namespace="some managed raw keys", - key_name=b"my RSA wrapping key", - private_wrapping_key=private_key, - # The wrapping algorithm MUST match the encrypt keyring. - wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - ) - - # Encrypt your plaintext data using the encrypt keyring. - ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, keyring=public_key_keyring - ) - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # Try to decrypt your encrypted data using the *encrypt* keyring. - # This demonstrates that you cannot decrypt using the public key. - try: - aws_encryption_sdk.decrypt(source=ciphertext, keyring=public_key_keyring) - except AWSEncryptionSDKClientError: - # The public key cannot decrypt. - # Reaching this point means everything is working as expected. - pass - else: - # Show that the public keyring could not decrypt. - raise AssertionError("The public key can never decrypt!") - - # Decrypt your encrypted data using the decrypt keyring. - # - # You do not need to specify the encryption context on decrypt - # because the header of the encrypted message includes the encryption context. - decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, keyring=private_key_keyring) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/src/legacy/__init__.py b/examples/src/legacy/__init__.py deleted file mode 100644 index e4646257c..000000000 --- a/examples/src/legacy/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -Legacy examples. - -We keep these older examples as reference material, -but we recommend that you use the new examples. -The new examples reflect our current guidance for using the library. -""" diff --git a/examples/src/master_key_provider/__init__.py b/examples/src/master_key_provider/__init__.py deleted file mode 100644 index 0edf699c5..000000000 --- a/examples/src/master_key_provider/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -Master key provider examples. - -These examples show how to use master key providers. -""" diff --git a/examples/src/master_key_provider/aws_kms/__init__.py b/examples/src/master_key_provider/aws_kms/__init__.py deleted file mode 100644 index e3cd51b1b..000000000 --- a/examples/src/master_key_provider/aws_kms/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -AWS KMS master key provider examples. - -These examples show how to use the AWS KMS master key provider. -""" diff --git a/examples/src/master_key_provider/aws_kms/discovery_decrypt.py b/examples/src/master_key_provider/aws_kms/discovery_decrypt.py deleted file mode 100644 index 1d0b18be1..000000000 --- a/examples/src/master_key_provider/aws_kms/discovery_decrypt.py +++ /dev/null @@ -1,72 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -This example is intended to serve as reference material for users migrating away from master key providers. -We recommend using keyrings rather than master key providers. -For examples using keyrings, see the ``examples/src/keyrings`` directory. - -The AWS KMS master key provider uses any key IDs that you specify on encrypt, -but attempts to decrypt *any* data keys that were encrypted under an AWS KMS CMK. -This means that you do not need to know which CMKs were used to encrypt a message. - -This example shows how to configure and use an AWS KMS master key provider to decrypt without provider key IDs. - -https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#master-key-provider - -For an example of how to use the AWS KMS master key with a single CMK, -see the ``master_key_provider/aws_kms/single_cmk`` example. - -For an example of how to use the AWS KMS master key provider with CMKs in multiple regions, -see the ``master_key_provider/aws_kms/multiple_regions`` example. -""" -import aws_encryption_sdk -from aws_encryption_sdk.key_providers.kms import KMSMasterKey, KMSMasterKeyProvider - - -def run(aws_kms_cmk, source_plaintext): - # type: (str, bytes) -> None - """Demonstrate configuring an AWS KMS master key provider for decryption. - - :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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 the master key that determines how your data keys are protected. - encrypt_master_key = KMSMasterKey(key_id=aws_kms_cmk) - - # Create an AWS KMS master key provider to use on decrypt. - decrypt_master_key_provider = KMSMasterKeyProvider() - - # Encrypt your plaintext data. - ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, key_provider=encrypt_master_key - ) - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # Decrypt your encrypted data using the AWS KMS master key provider. - # - # You do not need to specify the encryption context on decrypt - # because the header of the encrypted message includes the encryption context. - decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=decrypt_master_key_provider) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/src/master_key_provider/aws_kms/multiple_regions.py b/examples/src/master_key_provider/aws_kms/multiple_regions.py deleted file mode 100644 index f5d34d105..000000000 --- a/examples/src/master_key_provider/aws_kms/multiple_regions.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -This example is intended to serve as reference material for users migrating away from master key providers. -We recommend using keyrings rather than master key providers. -For examples using keyrings, see the ``examples/src/keyrings`` directory. - -This example shows how to configure and use an AWS KMS master key provider with with CMKs in multiple regions. - -https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#master-key-provider - -For an example of how to use the AWS KMS master key with a single CMK, -see the ``master_key_provider/aws_kms/single_cmk`` example. - -For an example of how to use the AWS KMS master key provider in discovery mode on decrypt, -see the ``master_key_provider/aws_kms/discovery_decrypt`` example. -""" -import aws_encryption_sdk -from aws_encryption_sdk.key_providers.kms import KMSMasterKey, KMSMasterKeyProvider - -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Sequence # noqa pylint: disable=unused-import -except ImportError: # pragma: no cover - # We only actually need these imports when running the mypy checks - pass - - -def run(aws_kms_generator_cmk, aws_kms_additional_cmks, source_plaintext): - # type: (str, Sequence[str], bytes) -> None - """Demonstrate an encrypt/decrypt cycle using an AWS KMS master key provider with CMKs in multiple regions. - - :param str aws_kms_generator_cmk: The ARN of the primary AWS KMS CMK - :param List[str] aws_kms_additional_cmks: Additional ARNs of secondary AWS KMS CMKs - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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 the master key provider that will encrypt your data keys under all requested CMKs. - # - # The AWS KMS master key provider generates the data key using the first key ID in the list. - key_ids = [aws_kms_generator_cmk] - key_ids.extend(aws_kms_additional_cmks) - master_key_provider = KMSMasterKeyProvider(key_ids=key_ids) - - # Create master keys that each only use one of the CMKs. - # We will use these later to demonstrate that any of the CMKs can be used to decrypt the message. - single_cmk_master_key_that_generated = KMSMasterKey(key_id=aws_kms_generator_cmk) - single_cmk_master_key_that_encrypted = KMSMasterKey(key_id=aws_kms_additional_cmks[0]) - - # Encrypt your plaintext data using the master key provider that uses all requests CMKs. - ciphertext, encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, key_provider=master_key_provider - ) - - # Verify that the header contains the expected number of encrypted data keys (EDKs). - # It should contain one EDK for each CMK. - assert len(encrypt_header.encrypted_data_keys) == len(aws_kms_additional_cmks) + 1 - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # Decrypt your encrypted data separately using the single-CMK master keys. - # - # You do not need to specify the encryption context on decrypt - # because the header of the encrypted message includes the encryption context. - decrypted_1, decrypt_header_1 = aws_encryption_sdk.decrypt( - source=ciphertext, key_provider=single_cmk_master_key_that_generated - ) - decrypted_2, decrypt_header_2 = aws_encryption_sdk.decrypt( - source=ciphertext, key_provider=single_cmk_master_key_that_encrypted - ) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted_1 == source_plaintext - assert decrypted_2 == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header_1.encryption_context.items()) - assert set(encryption_context.items()) <= set(decrypt_header_2.encryption_context.items()) diff --git a/examples/src/master_key_provider/aws_kms/single_cmk.py b/examples/src/master_key_provider/aws_kms/single_cmk.py deleted file mode 100644 index e75f24653..000000000 --- a/examples/src/master_key_provider/aws_kms/single_cmk.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -This example is intended to serve as reference material for users migrating away from master key providers. -We recommend using keyrings rather than master key providers. -For examples using keyrings, see the ``examples/src/keyrings`` directory. - -This example shows how to configure and use an AWS KMS master key with a single AWS KMS CMK. - -https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#master-key-provider - -For an example of how to use the AWS KMS master key provider with CMKs in multiple regions, -see the ``master_key_provider/aws_kms/multiple_regions`` example. - -For an example of how to use the AWS KMS master key provider in discovery mode on decrypt, -see the ``master_key_provider/aws_kms/discovery_decrypt`` example. -""" -import aws_encryption_sdk -from aws_encryption_sdk.key_providers.kms import KMSMasterKey - - -def run(aws_kms_cmk, source_plaintext): - # type: (str, bytes) -> None - """Demonstrate an encrypt/decrypt cycle using an AWS KMS master key with a single CMK. - - :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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 the master key that determines how your data keys are protected. - master_key = KMSMasterKey(key_id=aws_kms_cmk) - - # Encrypt your plaintext data. - ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, key_provider=master_key - ) - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # Decrypt your encrypted data using the same master key 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. - decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=master_key) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/src/master_key_provider/multi/__init__.py b/examples/src/master_key_provider/multi/__init__.py deleted file mode 100644 index 22f5195fc..000000000 --- a/examples/src/master_key_provider/multi/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -Multi-master key provider examples. - -These examples show how to combine master key providers. -""" diff --git a/examples/src/master_key_provider/multi/aws_kms_with_escrow.py b/examples/src/master_key_provider/multi/aws_kms_with_escrow.py deleted file mode 100644 index 4d84a7edb..000000000 --- a/examples/src/master_key_provider/multi/aws_kms_with_escrow.py +++ /dev/null @@ -1,151 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -This example is intended to serve as reference material for users migrating away from master key providers. -We recommend using keyrings rather than master key providers. -For examples using keyrings, see the ``examples/src/keyrings`` directory. - -One use-case that we have seen customers need is -the ability to enjoy the benefits of AWS KMS during normal operation -but retain the ability to decrypt encrypted messages without access to AWS KMS. -This example shows how you can achieve this -by combining an AWS KMS master key with a raw RSA master key. - -https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#master-key-provider - -For more examples of how to use the AWS KMS master key provider, see the ``master_key_provider/aws_kms`` examples. - -For more examples of how to use the raw RSA master key, see the ``master_key_provider/raw_rsa`` examples. - -In this example we generate an RSA keypair -but in practice you would want to keep your private key in an HSM -or other key management system. - -In this example, we use the one-step encrypt and decrypt APIs. -""" -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import rsa - -import aws_encryption_sdk -from aws_encryption_sdk.identifiers import EncryptionKeyType, WrappingAlgorithm -from aws_encryption_sdk.key_providers.kms import KMSMasterKeyProvider -from aws_encryption_sdk.key_providers.raw import RawMasterKey, WrappingKey - - -def run(aws_kms_cmk, source_plaintext): - # type: (str, bytes) -> None - """Demonstrate configuring a master key provider to use an AWS KMS CMK and an RSA wrapping key. - - :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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", - } - - # Generate an RSA private key to use with your master key. - # In practice, you should get this key from a secure key management system such as an HSM. - # - # The National Institute of Standards and Technology (NIST) recommends a minimum of 2048-bit keys for RSA. - # https://www.nist.gov/publications/transitioning-use-cryptographic-algorithms-and-key-lengths - # - # Why did we use this public exponent? - # https://crypto.stanford.edu/~dabo/pubs/papers/RSA-survey.pdf - private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096, backend=default_backend()) - - # Serialize the RSA private key to PEM encoding. - # This or DER encoding is likely to be what you get from your key management system in practice. - private_key_pem = private_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.PKCS8, - encryption_algorithm=serialization.NoEncryption(), - ) - - # Collect the public key from the private key. - public_key = private_key.public_key() - - # Serialize the RSA public key to PEM encoding. - # This or DER encoding is likely to be what you get from your key management system in practice. - public_key_pem = public_key.public_bytes( - encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, - ) - - # Create the encrypt master key that only has access to the public key. - escrow_encrypt_master_key = RawMasterKey( - # The provider ID and key ID are defined by you - # and are used by the raw RSA master key - # to determine whether it should attempt to decrypt - # an encrypted data key. - provider_id="some managed raw keys", # provider ID corresponds to key namespace for keyrings - key_id=b"my RSA wrapping key", # key ID corresponds to key name for keyrings - wrapping_key=WrappingKey( - wrapping_key=public_key_pem, - wrapping_key_type=EncryptionKeyType.PUBLIC, - # The wrapping algorithm tells the raw RSA master key - # how to use your wrapping key to encrypt data keys. - # - # We recommend using RSA_OAEP_SHA256_MGF1. - # You should not use RSA_PKCS1 unless you require it for backwards compatibility. - wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - ), - ) - - # Create the decrypt master key that has access to the private key. - escrow_decrypt_master_key = RawMasterKey( - # The key namespace and key name MUST match the encrypt master key. - provider_id="some managed raw keys", # provider ID corresponds to key namespace for keyrings - key_id=b"my RSA wrapping key", # key ID corresponds to key name for keyrings - wrapping_key=WrappingKey( - wrapping_key=private_key_pem, - wrapping_key_type=EncryptionKeyType.PRIVATE, - # The wrapping algorithm MUST match the encrypt master key. - wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - ), - ) - - # Create the AWS KMS master key that you will use for decryption during normal operations. - kms_master_key = KMSMasterKeyProvider(key_ids=[aws_kms_cmk]) - - # Add the escrow encrypt master key to the AWS KMS master key. - kms_master_key.add_master_key_provider(escrow_encrypt_master_key) - - # Encrypt your plaintext data using the combined master keys. - ciphertext, encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, key_provider=kms_master_key - ) - - # Verify that the header contains the expected number of encrypted data keys (EDKs). - # It should contain one EDK for AWS KMS and one for the escrow key. - assert len(encrypt_header.encrypted_data_keys) == 2 - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # Decrypt your encrypted data separately using the AWS KMS master key and the escrow decrypt master key. - # - # You do not need to specify the encryption context on decrypt - # because the header of the encrypted message includes the encryption context. - decrypted_kms, decrypt_header_kms = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=kms_master_key) - decrypted_escrow, decrypt_header_escrow = aws_encryption_sdk.decrypt( - source=ciphertext, key_provider=escrow_decrypt_master_key - ) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted_kms == source_plaintext - assert decrypted_escrow == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header_kms.encryption_context.items()) - assert set(encryption_context.items()) <= set(decrypt_header_escrow.encryption_context.items()) diff --git a/examples/src/master_key_provider/raw_aes/__init__.py b/examples/src/master_key_provider/raw_aes/__init__.py deleted file mode 100644 index 5572015a7..000000000 --- a/examples/src/master_key_provider/raw_aes/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -Raw AES master key provider examples. - -These examples show how to use the raw AES master key. -""" diff --git a/examples/src/master_key_provider/raw_aes/raw_aes.py b/examples/src/master_key_provider/raw_aes/raw_aes.py deleted file mode 100644 index e4d06b40e..000000000 --- a/examples/src/master_key_provider/raw_aes/raw_aes.py +++ /dev/null @@ -1,82 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -This example is intended to serve as reference material for users migrating away from master key providers. -We recommend using keyrings rather than master key providers. -For examples using keyrings, see the ``examples/src/keyrings`` directory. - -This examples shows how to configure and use a raw AES master key. - -https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#master-key-provider - -In this example, we use the one-step encrypt and decrypt APIs. -""" -import os - -import aws_encryption_sdk -from aws_encryption_sdk.identifiers import EncryptionKeyType, WrappingAlgorithm -from aws_encryption_sdk.key_providers.raw import RawMasterKey, WrappingKey - - -def run(source_plaintext): - # type: (bytes) -> None - """Demonstrate an encrypt/decrypt cycle using a raw AES master key. - - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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", - } - - # Choose the wrapping algorithm for your master key to use. - wrapping_algorithm = WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING - - # Generate an AES key to use with your master key. - # The key size depends on the wrapping algorithm. - # - # In practice, you should get this key from a secure key management system such as an HSM. - key = os.urandom(wrapping_algorithm.algorithm.kdf_input_len) - - # Create the master key that determines how your data keys are protected. - master_key = RawMasterKey( - # The provider ID and key ID are defined by you - # and are used by the raw AES master key - # to determine whether it should attempt to decrypt - # an encrypted data key. - provider_id="some managed raw keys", # provider ID corresponds to key namespace for keyrings - key_id=b"my AES wrapping key", # key ID corresponds to key name for keyrings - wrapping_key=WrappingKey( - wrapping_algorithm=wrapping_algorithm, wrapping_key_type=EncryptionKeyType.SYMMETRIC, wrapping_key=key, - ), - ) - - # Encrypt your plaintext data. - ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, key_provider=master_key - ) - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # Decrypt your encrypted data using the same master key 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. - decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=master_key) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/src/master_key_provider/raw_rsa/__init__.py b/examples/src/master_key_provider/raw_rsa/__init__.py deleted file mode 100644 index 374a606fb..000000000 --- a/examples/src/master_key_provider/raw_rsa/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -Raw RSA master key provider examples. - -These examples show how to use the raw RSA master key. -""" diff --git a/examples/src/master_key_provider/raw_rsa/private_key_only_from_pem.py b/examples/src/master_key_provider/raw_rsa/private_key_only_from_pem.py deleted file mode 100644 index f5e89507f..000000000 --- a/examples/src/master_key_provider/raw_rsa/private_key_only_from_pem.py +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -This example is intended to serve as reference material for users migrating away from master key providers. -We recommend using keyrings rather than master key providers. -For examples using keyrings, see the ``examples/src/keyrings`` directory. - -This example shows how to configure and use a raw RSA master key using a PEM-encoded RSA private key. - -The most commonly used encodings for RSA keys tend to be PEM and DER. -The raw RSA master key supports loading both public and private keys from PEM encoding. - -https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#master-key-provider - -In this example, we use the one-step encrypt and decrypt APIs. -""" -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import rsa - -import aws_encryption_sdk -from aws_encryption_sdk.identifiers import EncryptionKeyType, WrappingAlgorithm -from aws_encryption_sdk.key_providers.raw import RawMasterKey, WrappingKey - - -def run(source_plaintext): - # type: (bytes) -> None - """Demonstrate an encrypt/decrypt cycle using a raw RSA master key loaded from a PEM-encoded key. - - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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", - } - - # Generate an RSA private key to use with your master key. - # In practice, you should get this key from a secure key management system such as an HSM. - # - # The National Institute of Standards and Technology (NIST) recommends a minimum of 2048-bit keys for RSA. - # https://www.nist.gov/publications/transitioning-use-cryptographic-algorithms-and-key-lengths - # - # Why did we use this public exponent? - # https://crypto.stanford.edu/~dabo/pubs/papers/RSA-survey.pdf - private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096, backend=default_backend()) - - # Serialize the RSA private key to PEM encoding. - # This or DER encoding is likely to be what you get from your key management system in practice. - private_key_pem = private_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.PKCS8, - encryption_algorithm=serialization.NoEncryption(), - ) - - # Create the master key that determines how your data keys are protected. - # - # WrappingKey can only load PEM-encoded keys. - master_key = RawMasterKey( - # The provider ID and key ID are defined by you - # and are used by the raw RSA master key - # to determine whether it should attempt to decrypt - # an encrypted data key. - provider_id="some managed raw keys", # provider ID corresponds to key namespace for keyrings - key_id=b"my RSA wrapping key", # key ID corresponds to key name for keyrings - wrapping_key=WrappingKey( - wrapping_key=private_key_pem, - wrapping_key_type=EncryptionKeyType.PRIVATE, - # The wrapping algorithm tells the raw RSA master key - # how to use your wrapping key to encrypt data keys. - # - # We recommend using RSA_OAEP_SHA256_MGF1. - # You should not use RSA_PKCS1 unless you require it for backwards compatibility. - wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - ), - ) - - # Encrypt your plaintext data. - ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, key_provider=master_key - ) - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # Decrypt your encrypted data using the same master key 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. - decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=master_key) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/src/one_kms_cmk.py b/examples/src/one_kms_cmk.py new file mode 100644 index 000000000..1ba1d869f --- /dev/null +++ b/examples/src/one_kms_cmk.py @@ -0,0 +1,48 @@ +# Copyright 2019 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. +"""Example showing basic encryption and decryption of a value already in memory using one KMS CMK.""" +import aws_encryption_sdk + + +def encrypt_decrypt(key_arn, source_plaintext, botocore_session=None): + """Encrypts and then decrypts a string under one KMS customer master key (CMK). + + :param str key_arn: Amazon Resource Name (ARN) of the KMS CMK + :param bytes source_plaintext: Data to encrypt + :param botocore_session: existing botocore session instance + :type botocore_session: botocore.session.Session + """ + kwargs = dict(key_ids=[key_arn]) + + if botocore_session is not None: + kwargs["botocore_session"] = botocore_session + + # Create master key provider using the ARN of the key and the session (botocore_session) + kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(**kwargs) + + # Encrypt the plaintext using the AWS Encryption SDK. It returns the encrypted message and the header + ciphertext, encrypted_message_header = aws_encryption_sdk.encrypt( + source=source_plaintext, key_provider=kms_key_provider + ) + + # Decrypt the encrypted message using the AWS Encryption SDK. It returns the decrypted message and the header + plaintext, decrypted_message_header = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=kms_key_provider) + + # Check if the original message and the decrypted message are the same + assert source_plaintext == plaintext + + # Check if the headers of the encrypted message and decrypted message match + assert all( + pair in encrypted_message_header.encryption_context.items() + for pair in decrypted_message_header.encryption_context.items() + ) diff --git a/examples/src/legacy/one_kms_cmk_streaming_data.py b/examples/src/one_kms_cmk_streaming_data.py similarity index 68% rename from examples/src/legacy/one_kms_cmk_streaming_data.py rename to examples/src/one_kms_cmk_streaming_data.py index 27d916122..d45e653ff 100644 --- a/examples/src/legacy/one_kms_cmk_streaming_data.py +++ b/examples/src/one_kms_cmk_streaming_data.py @@ -1,22 +1,33 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Example showing basic encryption and decryption of streaming data in memory using one AWS KMS CMK.""" + +# Copyright 2019 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. +"""Example showing basic encryption and decryption of streaming data in memory using one KMS CMK.""" import filecmp import aws_encryption_sdk -def run(aws_kms_cmk, source_plaintext_filename, botocore_session=None): - """Encrypts and then decrypts streaming data under one AWS KMS customer master key (CMK). +def encrypt_decrypt_stream(key_arn, source_plaintext_filename, botocore_session=None): + """Encrypts and then decrypts streaming data under one KMS customer master key (CMK). - :param str aws_kms_cmk: Amazon Resource Name (ARN) of the AWS KMS CMK + :param str key_arn: Amazon Resource Name (ARN) of the KMS CMK :param str source_plaintext_filename: Filename of file to encrypt :param botocore_session: existing botocore session instance :type botocore_session: botocore.session.Session """ kwargs = dict() - kwargs["key_ids"] = [aws_kms_cmk] + kwargs["key_ids"] = [key_arn] if botocore_session is not None: kwargs["botocore_session"] = botocore_session diff --git a/examples/src/legacy/one_kms_cmk_unsigned.py b/examples/src/one_kms_cmk_unsigned.py similarity index 64% rename from examples/src/legacy/one_kms_cmk_unsigned.py rename to examples/src/one_kms_cmk_unsigned.py index b2099deba..783e640b4 100644 --- a/examples/src/legacy/one_kms_cmk_unsigned.py +++ b/examples/src/one_kms_cmk_unsigned.py @@ -1,5 +1,15 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2019 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. """Example showing basic encryption and decryption of a value already in memory using one AWS KMS CMK with an unsigned algorithm. """ @@ -7,15 +17,15 @@ from aws_encryption_sdk.identifiers import Algorithm -def run(aws_kms_cmk, source_plaintext, botocore_session=None): - """Encrypts and then decrypts a string under one AWS KMS customer master key (CMK) with an unsigned algorithm. +def encrypt_decrypt(key_arn, source_plaintext, botocore_session=None): + """Encrypts and then decrypts a string under one KMS customer master key (CMK) with an unsigned algorithm. - :param str aws_kms_cmk: Amazon Resource Name (ARN) of the AWS KMS CMK + :param str key_arn: Amazon Resource Name (ARN) of the KMS CMK :param bytes source_plaintext: Data to encrypt :param botocore_session: existing botocore session instance :type botocore_session: botocore.session.Session """ - kwargs = dict(key_ids=[aws_kms_cmk]) + kwargs = dict(key_ids=[key_arn]) if botocore_session is not None: kwargs["botocore_session"] = botocore_session diff --git a/examples/src/onestep_defaults.py b/examples/src/onestep_defaults.py deleted file mode 100644 index 15725aacc..000000000 --- a/examples/src/onestep_defaults.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -This example shows how to use the one-step encrypt and decrypt APIs. - -In this example, we use an AWS KMS customer master key (CMK), -but you can use other key management options with the AWS Encryption SDK. -For examples that demonstrate how to use other key management configurations, -see the ``keyring`` and ``master_key_provider`` directories. -""" -import aws_encryption_sdk -from aws_encryption_sdk.keyrings.aws_kms import AwsKmsKeyring - - -def run(aws_kms_cmk, source_plaintext): - # type: (str, bytes) -> None - """Demonstrate an encrypt/decrypt cycle using the one-step encrypt/decrypt APIs. - - :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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 the keyring that determines how your data keys are protected. - keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk) - - # Encrypt your plaintext data. - ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, encryption_context=encryption_context, keyring=keyring - ) - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # 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. - decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, keyring=keyring) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/src/onestep_unsigned.py b/examples/src/onestep_unsigned.py deleted file mode 100644 index 25410497e..000000000 --- a/examples/src/onestep_unsigned.py +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -This example shows how to specify an algorithm suite -when using the one-step encrypt and decrypt APIs. - -In this example, we use an AWS KMS customer master key (CMK), -but you can use other key management options with the AWS Encryption SDK. -For examples that demonstrate how to use other key management configurations, -see the ``keyring`` and ``master_key_provider`` directories. - -The default algorithm suite includes a message-level signature -that protects you from an attacker who has *decrypt* but not *encrypt* capability -for a wrapping key that you used when encrypting a message -under multiple wrapping keys. - -However, if all of your readers and writers have the same permissions, -then this additional protection does not always add value. -This example shows you how to select another algorithm suite -that has all of the other properties of the default suite -but does not include a message-level signature. -""" -import aws_encryption_sdk -from aws_encryption_sdk.identifiers import AlgorithmSuite -from aws_encryption_sdk.keyrings.aws_kms import AwsKmsKeyring - - -def run(aws_kms_cmk, source_plaintext): - # type: (str, bytes) -> None - """Demonstrate requesting a specific algorithm suite through the one-step encrypt/decrypt APIs. - - :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys - :param bytes source_plaintext: Plaintext to encrypt - """ - # Prepare your 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 = { - "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 the keyring that determines how your data keys are protected. - keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk) - - # Encrypt your plaintext data. - ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( - source=source_plaintext, - encryption_context=encryption_context, - keyring=keyring, - # Here we can specify the algorithm suite that we want to use. - algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA256, - ) - - # Demonstrate that the ciphertext and plaintext are different. - assert ciphertext != source_plaintext - - # 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. - # - # You do not need to specify the algorithm suite on decrypt - # because the header message includes the algorithm suite identifier. - decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, keyring=keyring) - - # Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert decrypted == source_plaintext - - # Verify that the encryption context used in the decrypt operation includes - # the encryption context that you specified when encrypting. - # The AWS Encryption SDK can add pairs, so don't require an exact match. - # - # In production, always use a meaningful encryption context. - assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/test/examples_test_utils.py b/examples/test/examples_test_utils.py index 49379ff1b..0984ee684 100644 --- a/examples/test/examples_test_utils.py +++ b/examples/test/examples_test_utils.py @@ -1,33 +1,21 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2019 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. """Helper utilities for use while testing examples.""" import os import sys -import inspect - -import pytest -import six - -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Callable, Dict, Iterable, List # noqa pylint: disable=unused-import - - # we only need pathlib here for typehints - from pathlib import Path -except ImportError: # pragma: no cover - # We only actually need these imports when running the mypy checks - pass - -HERE = os.path.abspath(os.path.dirname(__file__)) -EXAMPLES_SOURCE = os.path.join(HERE, "..", "src") -SINGLE_CMK_ARG = "aws_kms_cmk" -GENERATOR_CMK_ARG = "aws_kms_generator_cmk" -ADDITIONAL_CMKS_ARG = "aws_kms_additional_cmks" -PLAINTEXT_ARG = "source_plaintext" -PLAINTEXT_FILE_ARG = "source_plaintext_filename" os.environ["AWS_ENCRYPTION_SDK_EXAMPLES_TESTING"] = "yes" sys.path.extend([os.sep.join([os.path.dirname(__file__), "..", "..", "test", "integration"])]) -from integration_test_utils import get_all_cmk_arns # noqa pylint: disable=unused-import,import-error static_plaintext = ( b"Lorem ipsum dolor sit amet, consectetur adipiscing elit. " @@ -59,62 +47,4 @@ ) -def all_examples(): - # type: () -> Iterable[pytest.param] - for (dirpath, _dirnames, filenames) in os.walk(EXAMPLES_SOURCE): - for testfile in filenames: - split_path = testfile.rsplit(".", 1) - if len(split_path) != 2: - continue - stem, suffix = split_path - if suffix == "py" and stem != "__init__": - module_parent = dirpath[len(EXAMPLES_SOURCE) + 1 :].replace(os.path.sep, ".") - module_name = stem - if module_parent: - import_path = "..src.{base}.{name}".format(base=module_parent, name=module_name) - else: - import_path = "..src.{name}".format(name=module_name) - - yield pytest.param(import_path, id="{base}.{name}".format(base=module_parent, name=module_name)) - - -def get_arg_names(function): - # type: (Callable) -> List[str] - if six.PY2: - # getargspec was deprecated in CPython 3.0 but 2.7 does not have either of the new options - spec = inspect.getargspec(function) # pylint: disable=deprecated-method - return spec.args - - spec = inspect.getfullargspec(function) - return spec.args - - -def build_kwargs(function, temp_dir): - # type: (Callable, Path) -> Dict[str, str] - - plaintext_file = temp_dir / "plaintext" - plaintext_file.write_bytes(static_plaintext) - - cmk_arns = get_all_cmk_arns() - - args = get_arg_names(function) - possible_kwargs = { - SINGLE_CMK_ARG: cmk_arns[0], - GENERATOR_CMK_ARG: cmk_arns[0], - ADDITIONAL_CMKS_ARG: cmk_arns[1:], - PLAINTEXT_ARG: static_plaintext, - PLAINTEXT_FILE_ARG: str(plaintext_file.absolute()), - } - kwargs = {} - for name in args: - try: - kwargs[name] = possible_kwargs[name] - except KeyError: - pass - return kwargs - - -def default_region(): - # type: () -> str - primary_cmk = get_all_cmk_arns()[0] - return primary_cmk.split(":", 4)[3] +from integration_test_utils import get_cmk_arn # noqa pylint: disable=unused-import,import-error diff --git a/examples/test/test_i_basic_encryption.py b/examples/test/test_i_basic_encryption.py new file mode 100644 index 000000000..f2a4fab51 --- /dev/null +++ b/examples/test/test_i_basic_encryption.py @@ -0,0 +1,27 @@ +# Copyright 2017-2019 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 for the Strings examples in the AWS-hosted documentation.""" +import botocore.session +import pytest + +from ..src.basic_encryption import cycle_string +from .examples_test_utils import get_cmk_arn, static_plaintext + + +pytestmark = [pytest.mark.examples] + + +def test_cycle_string(): + plaintext = static_plaintext + cmk_arn = get_cmk_arn() + cycle_string(key_arn=cmk_arn, source_plaintext=plaintext, botocore_session=botocore.session.Session()) 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 new file mode 100644 index 000000000..282a272ab --- /dev/null +++ b/examples/test/test_i_basic_file_encryption_with_multiple_providers.py @@ -0,0 +1,41 @@ +# Copyright 2017-2019 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 for the Bytes Streams Multiple Providers examples in the AWS-hosted documentation.""" +import os +import tempfile + +import botocore.session +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 + + +pytestmark = [pytest.mark.examples] + + +def test_cycle_file(): + cmk_arn = get_cmk_arn() + handle, filename = tempfile.mkstemp() + with open(filename, "wb") as f: + f.write(static_plaintext) + try: + new_files = cycle_file( + key_arn=cmk_arn, source_plaintext_filename=filename, botocore_session=botocore.session.Session() + ) + for f in new_files: + os.remove(f) + finally: + os.close(handle) + os.remove(filename) 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 new file mode 100644 index 000000000..710c0ccac --- /dev/null +++ b/examples/test/test_i_basic_file_encryption_with_raw_key_provider.py @@ -0,0 +1,36 @@ +# Copyright 2017-2019 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 for the Bytes Streams examples in the AWS-hosted documentation.""" +import os +import tempfile + +import pytest + +from ..src.basic_file_encryption_with_raw_key_provider import cycle_file +from .examples_test_utils import static_plaintext + + +pytestmark = [pytest.mark.examples] + + +def test_cycle_file(): + handle, filename = tempfile.mkstemp() + with open(filename, "wb") as f: + f.write(static_plaintext) + try: + new_files = cycle_file(source_plaintext_filename=filename) + for f in new_files: + os.remove(f) + finally: + os.close(handle) + os.remove(filename) diff --git a/src/aws_encryption_sdk/keyrings/__init__.py b/examples/test/test_i_data_key_caching_basic.py similarity index 50% rename from src/aws_encryption_sdk/keyrings/__init__.py rename to examples/test/test_i_data_key_caching_basic.py index ada03b4d7..734c35692 100644 --- a/src/aws_encryption_sdk/keyrings/__init__.py +++ b/examples/test/test_i_data_key_caching_basic.py @@ -1,4 +1,4 @@ -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# Copyright 2017-2019 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 @@ -10,4 +10,16 @@ # 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. -"""All provided Keyrings.""" +"""Unit test suite for the basic data key caching example in the AWS-hosted documentation.""" +import pytest + +from ..src.data_key_caching_basic import encrypt_with_caching +from .examples_test_utils import get_cmk_arn + + +pytestmark = [pytest.mark.examples] + + +def test_encrypt_with_caching(): + cmk_arn = get_cmk_arn() + encrypt_with_caching(kms_cmk_arn=cmk_arn, max_age_in_cache=10.0, cache_capacity=10) diff --git a/test/functional/internal/__init__.py b/examples/test/test_i_one_kms_cmk.py similarity index 52% rename from test/functional/internal/__init__.py rename to examples/test/test_i_one_kms_cmk.py index ad0e71d6c..71ce74d3d 100644 --- a/test/functional/internal/__init__.py +++ b/examples/test/test_i_one_kms_cmk.py @@ -10,4 +10,20 @@ # 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. -"""Dummy stub to make linters work better.""" +"""Unit test suite for the encryption and decryption using one KMS CMK example.""" + +import botocore.session +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 + + +pytestmark = [pytest.mark.examples] + + +def test_one_kms_cmk(): + plaintext = static_plaintext + cmk_arn = get_cmk_arn() + encrypt_decrypt(key_arn=cmk_arn, source_plaintext=plaintext, botocore_session=botocore.session.Session()) diff --git a/examples/test/test_i_one_kms_cmk_streaming_data.py b/examples/test/test_i_one_kms_cmk_streaming_data.py new file mode 100644 index 000000000..b22fa4232 --- /dev/null +++ b/examples/test/test_i_one_kms_cmk_streaming_data.py @@ -0,0 +1,40 @@ +# Copyright 2017-2019 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 for the encryption and decryption of streaming data using one KMS CMK example.""" +import os +import tempfile + +import botocore.session +import pytest + +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] + + +def test_one_kms_cmk_streaming_data(): + cmk_arn = get_cmk_arn() + handle, filename = tempfile.mkstemp() + with open(filename, "wb") as f: + f.write(static_plaintext) + try: + new_files = encrypt_decrypt_stream( + key_arn=cmk_arn, source_plaintext_filename=filename, botocore_session=botocore.session.Session() + ) + for f in new_files: + os.remove(f) + finally: + os.close(handle) + os.remove(filename) diff --git a/test/functional/keyrings/__init__.py b/examples/test/test_i_one_kms_cmk_unsigned.py similarity index 50% rename from test/functional/keyrings/__init__.py rename to examples/test/test_i_one_kms_cmk_unsigned.py index ad0e71d6c..8a2758c96 100644 --- a/test/functional/keyrings/__init__.py +++ b/examples/test/test_i_one_kms_cmk_unsigned.py @@ -10,4 +10,20 @@ # 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. -"""Dummy stub to make linters work better.""" +"""Unit test suite for the encryption and decryption using one KMS CMK with an unsigned algorithm example.""" + +import botocore.session +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 + + +pytestmark = [pytest.mark.examples] + + +def test_one_kms_cmk_unsigned(): + plaintext = static_plaintext + cmk_arn = get_cmk_arn() + encrypt_decrypt(key_arn=cmk_arn, source_plaintext=plaintext, botocore_session=botocore.session.Session()) diff --git a/examples/test/test_run_examples.py b/examples/test/test_run_examples.py deleted file mode 100644 index 210c0119c..000000000 --- a/examples/test/test_run_examples.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Test all examples.""" -from importlib import import_module - -import pytest - -from .examples_test_utils import all_examples, build_kwargs, default_region - -pytestmark = [pytest.mark.examples] - - -@pytest.mark.parametrize("import_path", all_examples()) -def test_examples(import_path, tmp_path, monkeypatch): - module = import_module(name=import_path, package=__package__) - try: - run_function = module.run - except AttributeError: - pytest.skip("Module lacks 'run' function.") - return - - kwargs = build_kwargs(function=run_function, temp_dir=tmp_path) - - monkeypatch.setenv("AWS_DEFAULT_REGION", default_region()) - - run_function(**kwargs) diff --git a/requirements.txt b/requirements.txt index 08e1d1a72..7f8f0d532 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ -six boto3>=1.4.4 cryptography>=1.8.1 -attrs>=19.1.0 +attrs>=17.4.0 wrapt>=1.10.11 diff --git a/setup.cfg b/setup.cfg index 0671c3c64..038fc5924 100644 --- a/setup.cfg +++ b/setup.cfg @@ -52,4 +52,4 @@ force_grid_wrap = 0 combine_as_imports = True not_skip = __init__.py known_first_party = aws_encryption_sdk -known_third_party = attr,awacs,aws_encryption_sdk_decrypt_oracle,awses_test_vectors,boto3,botocore,chalice,cryptography,integration_test_utils,mock,moto,pytest,pytest_mock,requests,setuptools,six,troposphere,wrapt +known_third_party = attr,awses_test_vectors,basic_encryption,basic_file_encryption_with_multiple_providers,basic_file_encryption_with_raw_key_provider,boto3,botocore,cryptography,data_key_caching_basic,integration_test_utils,mock,pytest,pytest_mock,setuptools,six,typing,wrapt diff --git a/setup.py b/setup.py index 52ef4ff7f..6ceb2d8fb 100644 --- a/setup.py +++ b/setup.py @@ -48,10 +48,10 @@ def get_requirements(): "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Security", "Topic :: Security :: Cryptography", diff --git a/src/aws_encryption_sdk/__init__.py b/src/aws_encryption_sdk/__init__.py index aba067601..3f6d86e2e 100644 --- a/src/aws_encryption_sdk/__init__.py +++ b/src/aws_encryption_sdk/__init__.py @@ -1,5 +1,15 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.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. """High level AWS Encryption SDK client functions.""" # Below are imported for ease of use by implementors from aws_encryption_sdk.caches.local import LocalCryptoMaterialsCache # noqa @@ -14,9 +24,6 @@ StreamDecryptor, StreamEncryptor, ) -from aws_encryption_sdk.structures import CryptoResult - -__all__ = ("encrypt", "decrypt", "stream") def encrypt(**kwargs): @@ -26,41 +33,28 @@ def encrypt(**kwargs): When using this function, the entire ciphertext message is encrypted into memory before returning any data. If streaming is desired, see :class:`aws_encryption_sdk.stream`. - .. versionadded:: 1.5.0 - The *keyring* parameter. - - .. versionadded:: 1.5.0 - - For backwards compatibility, - the new :class:`CryptoResult` return value also unpacks like a 2-member tuple. - This allows for backwards compatibility with the previous outputs - so this change should not break any existing consumers. - .. code:: python >>> import aws_encryption_sdk - >>> from aws_encryption_sdk.keyrings.aws_kms import AwsKmsKeyring - >>> keyring = AwsKmsKeyring( - ... generator_key_id="arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222", - ... key_ids=["arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333"], - ... ) + >>> kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ + ... 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', + ... 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' + ... ]) >>> my_ciphertext, encryptor_header = aws_encryption_sdk.encrypt( ... source=my_plaintext, - ... keyring=keyring, - >>> ) + ... key_provider=kms_key_provider + ... ) :param config: Client configuration object (config or individual parameters required) :type config: aws_encryption_sdk.streaming_client.EncryptorConfig :param source: Source data to encrypt or decrypt :type source: str, bytes, io.IOBase, or file - :param CryptoMaterialsManager materials_manager: - Cryptographic materials manager to use for encryption - (either ``materials_manager``, ``keyring``, ``key_provider`` required) - :param Keyring keyring: Keyring to use for encryption - (either ``materials_manager``, ``keyring``, ``key_provider`` required) - :param MasterKeyProvider key_provider: - Master key provider to use for encryption - (either ``materials_manager``, ``keyring``, ``key_provider`` required) + :param materials_manager: `CryptoMaterialsManager` from which to obtain cryptographic materials + (either `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 encryption + (either `materials_manager` or `key_provider` required) + :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider :param int source_length: Length of source data (optional) .. note:: @@ -78,13 +72,12 @@ def encrypt(**kwargs): :param algorithm: Algorithm to use for encryption :type algorithm: aws_encryption_sdk.identifiers.Algorithm :param int frame_length: Frame length in bytes - :returns: Encrypted message, message metadata (header), and keyring trace - :rtype: CryptoResult + :returns: Tuple containing the encrypted ciphertext and the message header object + :rtype: tuple of bytes and :class:`aws_encryption_sdk.structures.MessageHeader` """ with StreamEncryptor(**kwargs) as encryptor: ciphertext = encryptor.read() - - return CryptoResult(result=ciphertext, header=encryptor.header, keyring_trace=encryptor.keyring_trace) + return ciphertext, encryptor.header def decrypt(**kwargs): @@ -94,41 +87,28 @@ def decrypt(**kwargs): When using this function, the entire ciphertext message is decrypted into memory before returning any data. If streaming is desired, see :class:`aws_encryption_sdk.stream`. - .. versionadded:: 1.5.0 - The *keyring* parameter. - - .. versionadded:: 1.5.0 - - For backwards compatibility, - the new :class:`CryptoResult` return value also unpacks like a 2-member tuple. - This allows for backwards compatibility with the previous outputs - so this change should not break any existing consumers. - .. code:: python >>> import aws_encryption_sdk - >>> from aws_encryption_sdk.keyrings.aws_kms import AwsKmsKeyring - >>> keyring = AwsKmsKeyring( - ... generator_key_id="arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222", - ... key_ids=["arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333"], - ... ) - >>> my_ciphertext, decryptor_header = aws_encryption_sdk.decrypt( + >>> kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ + ... 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', + ... 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' + ... ]) + >>> my_ciphertext, encryptor_header = aws_encryption_sdk.decrypt( ... source=my_ciphertext, - ... keyring=keyring, + ... key_provider=kms_key_provider ... ) :param config: Client configuration object (config or individual parameters required) :type config: aws_encryption_sdk.streaming_client.DecryptorConfig :param source: Source data to encrypt or decrypt :type source: str, bytes, io.IOBase, or file - :param CryptoMaterialsManager materials_manager: - Cryptographic materials manager to use for encryption - (either ``materials_manager``, ``keyring``, ``key_provider`` required) - :param Keyring keyring: Keyring to use for encryption - (either ``materials_manager``, ``keyring``, ``key_provider`` required) - :param MasterKeyProvider key_provider: - Master key provider to use for encryption - (either ``materials_manager``, ``keyring``, ``key_provider`` required) + :param materials_manager: `CryptoMaterialsManager` from which to obtain cryptographic materials + (either `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) + :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider :param int source_length: Length of source data (optional) .. note:: @@ -137,13 +117,12 @@ def decrypt(**kwargs): :param int max_body_length: Maximum frame size (or content length for non-framed messages) in bytes to read from ciphertext message. - :returns: Decrypted plaintext, message metadata (header), and keyring trace - :rtype: CryptoResult + :returns: Tuple containing the decrypted plaintext and the message header object + :rtype: tuple of bytes and :class:`aws_encryption_sdk.structures.MessageHeader` """ with StreamDecryptor(**kwargs) as decryptor: plaintext = decryptor.read() - - return CryptoResult(result=plaintext, header=decryptor.header, keyring_trace=decryptor.keyring_trace) + return plaintext, decryptor.header def stream(**kwargs): @@ -166,18 +145,17 @@ def stream(**kwargs): .. code:: python >>> import aws_encryption_sdk - >>> from aws_encryption_sdk.keyrings.aws_kms import AwsKmsKeyring - >>> keyring = AwsKmsKeyring( - ... generator_key_id="arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222", - ... key_ids=["arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333"], - ... ) + >>> kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ + ... 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', + ... 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' + ... ]) >>> plaintext_filename = 'my-secret-data.dat' >>> ciphertext_filename = 'my-encrypted-data.ct' >>> with open(plaintext_filename, 'rb') as pt_file, open(ciphertext_filename, 'wb') as ct_file: ... with aws_encryption_sdk.stream( ... mode='e', ... source=pt_file, - ... keyring=keyring, + ... key_provider=kms_key_provider ... ) as encryptor: ... for chunk in encryptor: ... ct_file.write(chunk) @@ -186,7 +164,7 @@ def stream(**kwargs): ... with aws_encryption_sdk.stream( ... mode='d', ... source=ct_file, - ... keyring=keyring, + ... key_provider=kms_key_provider ... ) as decryptor: ... for chunk in decryptor: ... pt_file.write(chunk) @@ -204,3 +182,6 @@ def stream(**kwargs): return _stream_map[mode.lower()](**kwargs) except KeyError: raise ValueError("Unsupported mode: {}".format(mode)) + + +__all__ = ("encrypt", "decrypt", "stream") diff --git a/src/aws_encryption_sdk/exceptions.py b/src/aws_encryption_sdk/exceptions.py index 3c58dcea1..a71d414c0 100644 --- a/src/aws_encryption_sdk/exceptions.py +++ b/src/aws_encryption_sdk/exceptions.py @@ -1,5 +1,15 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.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. """Contains exception classes for AWS Encryption SDK.""" @@ -43,13 +53,6 @@ class InvalidDataKeyError(AWSEncryptionSDKClientError): """Exception class for Invalid Data Keys.""" -class InvalidKeyringTraceError(AWSEncryptionSDKClientError): - """Exception class for invalid Keyring Traces. - - .. versionadded:: 1.5.0 - """ - - class InvalidProviderIdError(AWSEncryptionSDKClientError): """Exception class for Invalid Provider IDs.""" @@ -70,20 +73,6 @@ class DecryptKeyError(AWSEncryptionSDKClientError): """Exception class for errors encountered when MasterKeys try to decrypt data keys.""" -class SignatureKeyError(AWSEncryptionSDKClientError): - """Exception class for errors encountered with signing or verification keys. - - .. versionadded:: 1.5.0 - """ - - -class InvalidCryptographicMaterialsError(AWSEncryptionSDKClientError): - """Exception class for errors encountered when attempting to validate cryptographic materials. - - .. versionadded:: 1.5.0 - """ - - class ActionNotAllowedError(AWSEncryptionSDKClientError): """Exception class for errors encountered when attempting to perform unallowed actions.""" diff --git a/src/aws_encryption_sdk/identifiers.py b/src/aws_encryption_sdk/identifiers.py index 269afd702..e3c13c1ea 100644 --- a/src/aws_encryption_sdk/identifiers.py +++ b/src/aws_encryption_sdk/identifiers.py @@ -14,7 +14,6 @@ import struct from enum import Enum -import attr from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa from cryptography.hazmat.primitives.ciphers import algorithms, modes @@ -329,27 +328,3 @@ class ContentAADString(Enum): FRAME_STRING_ID = b"AWSKMSEncryptionClient Frame" FINAL_FRAME_STRING_ID = b"AWSKMSEncryptionClient Final Frame" NON_FRAMED_STRING_ID = b"AWSKMSEncryptionClient Single Block" - - -class KeyringTraceFlag(Enum): - """KeyRing Trace actions.""" - - @attr.s - class KeyringTraceFlagValue(object): - """Keyring trace flags do not have defined serializable values.""" - - name = attr.ib() - - #: A flag to represent that a keyring has generated a plaintext data key. - GENERATED_DATA_KEY = KeyringTraceFlagValue("GENERATED_DATA_KEY") - #: A flag to represent that a keyring has created an encrypted data key. - ENCRYPTED_DATA_KEY = KeyringTraceFlagValue("ENCRYPTED_DATA_KEY") - #: A flag to represent that a keyring has obtained - #: the corresponding plaintext data key from an encrypted data key. - DECRYPTED_DATA_KEY = KeyringTraceFlagValue("DECRYPTED_DATA_KEY") - #: A flag to represent that the keyring has cryptographically - #: bound the encryption context to a newly created encrypted data key. - SIGNED_ENCRYPTION_CONTEXT = KeyringTraceFlagValue("SIGNED_ENCRYPTION_CONTEXT") - #: A flag to represent that the keyring has verified that an encrypted - #: data key was originally created with a particular encryption context. - VERIFIED_ENCRYPTION_CONTEXT = KeyringTraceFlagValue("VERIFIED_ENCRYPTION_CONTEXT") diff --git a/src/aws_encryption_sdk/internal/formatting/serialize.py b/src/aws_encryption_sdk/internal/formatting/serialize.py index bd71b3a1a..e7c86a0cb 100644 --- a/src/aws_encryption_sdk/internal/formatting/serialize.py +++ b/src/aws_encryption_sdk/internal/formatting/serialize.py @@ -316,6 +316,6 @@ def serialize_wrapped_key(key_provider, wrapping_algorithm, wrapping_key_id, enc ) key_ciphertext = encrypted_wrapped_key.ciphertext + encrypted_wrapped_key.tag return EncryptedDataKey( - key_provider=MasterKeyInfo(provider_id=key_provider.provider_id, key_info=key_info, key_name=wrapping_key_id), + key_provider=MasterKeyInfo(provider_id=key_provider.provider_id, key_info=key_info), encrypted_data_key=key_ciphertext, ) diff --git a/src/aws_encryption_sdk/internal/validators.py b/src/aws_encryption_sdk/internal/validators.py deleted file mode 100644 index 6e509a453..000000000 --- a/src/aws_encryption_sdk/internal/validators.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Common ``attrs`` validators.""" -import attr # only used by mypy, so pylint: disable=unused-import -import six - -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Any # noqa pylint: disable=unused-import -except ImportError: # pragma: no cover - # We only actually need these imports when running the mypy checks - pass - - -# The unused-argument check is disabled because -# this function MUST match the function signature -# for attrs validators. -def value_is_not_a_string(instance, attribute, value): # pylint: disable=unused-argument - # type: (Any, attr.Attribute, Any) -> None - """Technically a string is an iterable containing strings. - - This validator lets you accept other iterators but not strings. - """ - if isinstance(value, six.string_types): - raise TypeError("'{}' must not a string".format(attribute.name)) diff --git a/src/aws_encryption_sdk/key_providers/base.py b/src/aws_encryption_sdk/key_providers/base.py index 4ba10585b..3112cba6d 100644 --- a/src/aws_encryption_sdk/key_providers/base.py +++ b/src/aws_encryption_sdk/key_providers/base.py @@ -13,7 +13,6 @@ """Base class interface for Master Key Providers.""" import abc import logging -import warnings import attr import six @@ -27,17 +26,8 @@ MasterKeyProviderError, ) from aws_encryption_sdk.internal.str_ops import to_bytes -from aws_encryption_sdk.structures import DataKey # pylint: disable=unused-import -from aws_encryption_sdk.structures import EncryptedDataKey # pylint: disable=unused-import -from aws_encryption_sdk.structures import RawDataKey # pylint: disable=unused-import from aws_encryption_sdk.structures import MasterKeyInfo -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Iterable, Union # noqa pylint: disable=unused-import -except ImportError: # pragma: no cover - # We only actually need these imports when running the mypy checks - pass - _LOGGER = logging.getLogger(__name__) @@ -52,10 +42,6 @@ class MasterKeyProviderConfig(object): class MasterKeyProvider(object): """Parent interface for Master Key Provider classes. - .. versionadded:: 1.5.0 - Master key providers are deprecated. - Use :class:`aws_encryption_sdk.keyrings.base.Keyring` instead. - :param config: Configuration object :type config: aws_encryption_sdk.key_providers.base.MasterKeyProviderConfig """ @@ -83,16 +69,6 @@ def __new__(cls, **kwargs): """Set key index and member set for all new instances here to avoid requiring child classes to call super init. """ - # DeprecationWarning are ignored by default, - # but because we are not yet removing master key providers, - # I think this is the correct level of visibility. - # - # Once we decide that we are one X or Y version away from removing master key providers, - # we should upgrade this to a UserWarning. - warnings.warn( - "Master key providers are deprecated as of 1.5.0. You should migrate to keyrings.", DeprecationWarning - ) - instance = super(MasterKeyProvider, cls).__new__(cls) config = kwargs.pop("config", None) if not isinstance(config, instance._config_class): # pylint: disable=protected-access @@ -236,35 +212,6 @@ def master_key_for_decrypt(self, key_info): self._decrypt_key_index[key_info] = decrypt_master_key return decrypt_master_key - def master_keys_for_data_key(self, data_key): - # type: (Union[DataKey, EncryptedDataKey, RawDataKey]) -> Iterable[MasterKey] - """Locates the correct master keys from children for the specified data key. - - :param data_key: Data key for which to locate owning master keys - :type data_key: :class:`EncryptedDataKey`, :class:`RawDataKey`, or :class:`DataKey` - :returns: Masters key that own data key - :rtype: iterator of :class:`MasterKey` - :raises UnknownIdentityError: if unable to locate the correct master key - """ - for member in [self] + self._members: - if member.provider_id != data_key.key_provider.provider_id: - continue - - _LOGGER.debug("attempting to locate master key from key provider: %s", member.provider_id) - - if isinstance(member, MasterKey): - if member.owns_data_key(data_key): - _LOGGER.debug("using existing master key") - yield member - - if self.vend_masterkey_on_decrypt: - try: - _LOGGER.debug("attempting to add master key: %s", data_key.key_provider.key_info) - yield member.master_key_for_decrypt(data_key.key_provider.key_info) - except InvalidKeyIdError: - _LOGGER.debug("master key %s not available in provider", data_key.key_provider.key_info) - continue - def decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context): """Iterates through all currently added Master Keys and Master Key Providers to attempt to decrypt data key. @@ -278,25 +225,42 @@ def decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context): :rtype: aws_encryption_sdk.structures.DataKey :raises DecryptKeyError: if unable to decrypt encrypted data key """ + data_key = None + master_key = None _LOGGER.debug("starting decrypt data key attempt") - for master_key in self.master_keys_for_data_key(encrypted_data_key): - try: - _LOGGER.debug( - "attempting to decrypt data key with provider %s", encrypted_data_key.key_provider.key_info - ) - return master_key.decrypt_data_key(encrypted_data_key, algorithm, encryption_context) - - # MasterKeyProvider.decrypt_data_key throws DecryptKeyError - # but MasterKey.decrypt_data_key throws IncorrectMasterKeyError - except (IncorrectMasterKeyError, DecryptKeyError) as error: - _LOGGER.debug( - "%s raised when attempting to decrypt data key with master key %s", - repr(error), - master_key.key_provider, - ) - continue - - raise DecryptKeyError("Unable to decrypt data key") + for member in [self] + self._members: + if member.provider_id == encrypted_data_key.key_provider.provider_id: + _LOGGER.debug("attempting to locate master key from key provider: %s", member.provider_id) + if isinstance(member, MasterKey): + _LOGGER.debug("using existing master key") + master_key = member + elif self.vend_masterkey_on_decrypt: + try: + _LOGGER.debug("attempting to add master key: %s", encrypted_data_key.key_provider.key_info) + master_key = member.master_key_for_decrypt(encrypted_data_key.key_provider.key_info) + except InvalidKeyIdError: + _LOGGER.debug( + "master key %s not available in provider", encrypted_data_key.key_provider.key_info + ) + continue + else: + continue + try: + _LOGGER.debug( + "attempting to decrypt data key with provider %s", encrypted_data_key.key_provider.key_info + ) + data_key = master_key.decrypt_data_key(encrypted_data_key, algorithm, encryption_context) + except (IncorrectMasterKeyError, DecryptKeyError) as error: + _LOGGER.debug( + "%s raised when attempting to decrypt data key with master key %s", + repr(error), + master_key.key_provider, + ) + continue + break # If this point is reached without throwing any errors, the data key has been decrypted + if not data_key: + raise DecryptKeyError("Unable to decrypt data key") + return data_key def decrypt_data_key_from_list(self, encrypted_data_keys, algorithm, encryption_context): """Receives a list of encrypted data keys and returns the first one which this provider is able to decrypt. @@ -344,10 +308,6 @@ def __attrs_post_init__(self): class MasterKey(MasterKeyProvider): """Parent interface for Master Key classes. - .. versionadded:: 1.5.0 - Master key providers are deprecated. - Use :class:`aws_encryption_sdk.keyrings.base.Keyring` instead. - :param bytes key_id: Key ID for Master Key :param config: Configuration object :type config: aws_encryption_sdk.key_providers.base.MasterKeyConfig @@ -396,7 +356,9 @@ def owns_data_key(self, data_key): :returns: Boolean statement of ownership :rtype: bool """ - return data_key.key_provider == self.key_provider + if data_key.key_provider == self.key_provider: + return True + return False def master_keys_for_encryption(self, encryption_context, plaintext_rostream, plaintext_length=None): """Returns self and a list containing self, to match the format of output for a Master Key Provider. diff --git a/src/aws_encryption_sdk/key_providers/kms.py b/src/aws_encryption_sdk/key_providers/kms.py index 83443e40d..c0a2dc46e 100644 --- a/src/aws_encryption_sdk/key_providers/kms.py +++ b/src/aws_encryption_sdk/key_providers/kms.py @@ -78,10 +78,6 @@ class KMSMasterKeyProviderConfig(MasterKeyProviderConfig): class KMSMasterKeyProvider(MasterKeyProvider): """Master Key Provider for KMS. - .. versionadded:: 1.5.0 - Master key providers are deprecated. - Use :class:`aws_encryption_sdk.keyrings.aws_kms.AwsKmsKeyring` instead. - >>> import aws_encryption_sdk >>> kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ ... 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', @@ -230,10 +226,6 @@ def client_default(self): class KMSMasterKey(MasterKey): """Master Key class for KMS CMKs. - .. versionadded:: 1.5.0 - Master key providers are deprecated. - Use :class:`aws_encryption_sdk.keyrings.aws_kms.AwsKmsKeyring` instead. - :param config: Configuration object (config or individual parameters required) :type config: aws_encryption_sdk.key_providers.kms.KMSMasterKeyConfig :param bytes key_id: KMS CMK ID diff --git a/src/aws_encryption_sdk/key_providers/raw.py b/src/aws_encryption_sdk/key_providers/raw.py index fff3487e2..57a1d5edf 100644 --- a/src/aws_encryption_sdk/key_providers/raw.py +++ b/src/aws_encryption_sdk/key_providers/raw.py @@ -49,11 +49,6 @@ class RawMasterKeyConfig(MasterKeyConfig): class RawMasterKey(MasterKey): """Raw Master Key. - .. versionadded:: 1.5.0 - Master key providers are deprecated. - Use :class:`aws_encryption_sdk.keyrings.raw.RawAESKeyring` - or :class:`aws_encryption_sdk.keyrings.raw.RawRSAKeyring` instead. - :param config: Configuration object (config or individual parameters required) :type config: aws_encryption_sdk.key_providers.raw.RawMasterKeyConfig :param bytes key_id: Key ID for Master Key @@ -197,11 +192,6 @@ def _decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context): class RawMasterKeyProvider(MasterKeyProvider): """Raw Master Key Provider. - .. versionadded:: 1.5.0 - Master key providers are deprecated. - Use :class:`aws_encryption_sdk.keyrings.raw.RawAESKeyring` - or :class:`aws_encryption_sdk.keyrings.raw.RawRSAKeyring` instead. - :param config: Configuration object (optional) :type config: aws_encryption_sdk.key_providers.base.MasterKeyProviderConfig """ diff --git a/src/aws_encryption_sdk/keyrings/aws_kms/__init__.py b/src/aws_encryption_sdk/keyrings/aws_kms/__init__.py deleted file mode 100644 index f6340af65..000000000 --- a/src/aws_encryption_sdk/keyrings/aws_kms/__init__.py +++ /dev/null @@ -1,392 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Keyring for use with AWS Key Management Service (KMS). - -.. versionadded:: 1.5.0 - -""" -import logging - -import attr -import six -from attr.validators import deep_iterable, instance_of, is_callable, optional - -from aws_encryption_sdk.exceptions import DecryptKeyError, EncryptKeyError -from aws_encryption_sdk.identifiers import AlgorithmSuite -from aws_encryption_sdk.internal.validators import value_is_not_a_string -from aws_encryption_sdk.keyrings.base import Keyring -from aws_encryption_sdk.keyrings.multi import MultiKeyring -from aws_encryption_sdk.materials_managers import DecryptionMaterials, EncryptionMaterials -from aws_encryption_sdk.structures import EncryptedDataKey, KeyringTrace, KeyringTraceFlag, MasterKeyInfo, RawDataKey - -from .client_suppliers import DefaultClientSupplier - -from .client_suppliers import ClientSupplier # noqa - only used in docstring params; this confuses flake8 - -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Dict, Iterable, Union # noqa pylint: disable=unused-import - from .client_suppliers import ClientSupplierType # noqa pylint: disable=unused-import -except ImportError: # pragma: no cover - # We only actually need these imports when running the mypy checks - pass - -__all__ = ("AwsKmsKeyring", "KEY_NAMESPACE") - -_LOGGER = logging.getLogger(__name__) -_GENERATE_FLAGS = {KeyringTraceFlag.GENERATED_DATA_KEY} -_ENCRYPT_FLAGS = {KeyringTraceFlag.ENCRYPTED_DATA_KEY, KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT} -_DECRYPT_FLAGS = {KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT} - -#: Key namespace used for all encrypted data keys created by the KMS keyring. -KEY_NAMESPACE = "aws-kms" - - -@attr.s -class AwsKmsKeyring(Keyring): - """Keyring that uses AWS Key Management Service (KMS) Customer Master Keys (CMKs) to manage wrapping keys. - - Set ``generator_key_id`` to require that the keyring use that CMK to generate the data key. - If you do not set ``generator_key_id``, the keyring will not generate a data key. - - Set ``key_ids`` to specify additional CMKs that the keyring will use to encrypt the data key. - - The keyring will attempt to use any CMKs - identified by CMK ARN in either ``generator_key_id`` or ``key_ids`` on decrypt. - - You can identify CMKs by any `valid key ID`_ for the keyring to use on encrypt, - but for the keyring to attempt to use them on decrypt - you MUST specify the CMK ARN. - - If you specify ``is_discovery=True`` the keyring will be a KMS discovery keyring, - doing nothing on encrypt and attempting to decrypt any AWS KMS-encrypted data key on decrypt. - - .. note:: - - You must either set ``is_discovery=True`` or provide key IDs. - - You can use the :class:`ClientSupplier` to customize behavior further, - such as to provide different credentials for different regions - or to restrict which regions are allowed. - - See the `AWS KMS Keyring specification`_ for more details. - - .. _AWS KMS Keyring specification: - https://github.com/awslabs/aws-encryption-sdk-specification/blob/master/framework/kms-keyring.md - .. _valid key ID: - https://docs.aws.amazon.com/kms/latest/APIReference/API_GenerateDataKey.html#API_GenerateDataKey_RequestSyntax - .. _discovery mode: - https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#kms-keyring-discovery - - .. versionadded:: 1.5.0 - - :param ClientSupplier client_supplier: Client supplier that provides AWS KMS clients (optional) - :param bool is_discovery: Should this be a discovery keyring (optional) - :param str generator_key_id: Key ID of AWS KMS CMK to use when generating data keys (optional) - :param List[str] key_ids: Key IDs that will be used to encrypt and decrypt data keys (optional) - :param List[str] grant_tokens: AWS KMS grant tokens to include in requests (optional) - """ - - _client_supplier = attr.ib(default=attr.Factory(DefaultClientSupplier), validator=is_callable()) - _is_discovery = attr.ib(default=False, validator=instance_of(bool)) - _generator_key_id = attr.ib(default=None, validator=optional(instance_of(six.string_types))) - _key_ids = attr.ib( - default=attr.Factory(tuple), - validator=(deep_iterable(member_validator=instance_of(six.string_types)), value_is_not_a_string), - ) - _grant_tokens = attr.ib( - default=attr.Factory(tuple), - validator=(deep_iterable(member_validator=instance_of(six.string_types)), value_is_not_a_string), - ) - - def __attrs_post_init__(self): - """Configure internal keyring.""" - key_ids_provided = self._generator_key_id is not None or self._key_ids - both = key_ids_provided and self._is_discovery - neither = not key_ids_provided and not self._is_discovery - - if both: - raise TypeError("is_discovery cannot be True if key IDs are provided") - - if neither: - raise TypeError("is_discovery cannot be False if no key IDs are provided") - - if self._is_discovery: - self._inner_keyring = _AwsKmsDiscoveryKeyring( - client_supplier=self._client_supplier, grant_tokens=self._grant_tokens - ) - return - - if self._generator_key_id is None: - generator_keyring = None - else: - generator_keyring = _AwsKmsSingleCmkKeyring( - key_id=self._generator_key_id, client_supplier=self._client_supplier, grant_tokens=self._grant_tokens - ) - - child_keyrings = [ - _AwsKmsSingleCmkKeyring( - key_id=key_id, client_supplier=self._client_supplier, grant_tokens=self._grant_tokens - ) - for key_id in self._key_ids - ] - - self._inner_keyring = MultiKeyring(generator=generator_keyring, children=child_keyrings) - - def on_encrypt(self, encryption_materials): - # type: (EncryptionMaterials) -> EncryptionMaterials - """Generate a data key using generator keyring - and encrypt it using any available wrapping key in any child keyring. - - :param EncryptionMaterials encryption_materials: Encryption materials for keyring to modify. - :returns: Optionally modified encryption materials. - :rtype: EncryptionMaterials - :raises EncryptKeyError: if unable to encrypt data key. - """ - return self._inner_keyring.on_encrypt(encryption_materials=encryption_materials) - - def on_decrypt(self, decryption_materials, encrypted_data_keys): - # type: (DecryptionMaterials, Iterable[EncryptedDataKey]) -> DecryptionMaterials - """Attempt to decrypt the encrypted data keys. - - :param DecryptionMaterials decryption_materials: Decryption materials for keyring to modify. - :param List[EncryptedDataKey] encrypted_data_keys: List of encrypted data keys. - :returns: Optionally modified decryption materials. - :rtype: DecryptionMaterials - """ - return self._inner_keyring.on_decrypt( - decryption_materials=decryption_materials, encrypted_data_keys=encrypted_data_keys - ) - - -@attr.s -class _AwsKmsSingleCmkKeyring(Keyring): - """AWS KMS keyring that only works with a single AWS KMS CMK. - - This keyring should never be used directly. - It should only ever be used internally by :class:`AwsKmsKeyring`. - - .. versionadded:: 1.5.0 - - :param str key_id: CMK key ID - :param ClientSupplier client_supplier: Client supplier to use when asking for clients - :param List[str] grant_tokens: AWS KMS grant tokens to include in requests (optional) - """ - - _key_id = attr.ib(validator=instance_of(six.string_types)) - _client_supplier = attr.ib(validator=is_callable()) - _grant_tokens = attr.ib( - default=attr.Factory(tuple), - validator=(deep_iterable(member_validator=instance_of(six.string_types)), value_is_not_a_string), - ) - - def on_encrypt(self, encryption_materials): - # type: (EncryptionMaterials) -> EncryptionMaterials - trace_info = MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=self._key_id) - new_materials = encryption_materials - try: - if new_materials.data_encryption_key is None: - plaintext_key, encrypted_key = _do_aws_kms_generate_data_key( - client_supplier=self._client_supplier, - key_name=self._key_id, - encryption_context=new_materials.encryption_context, - algorithm=new_materials.algorithm, - grant_tokens=self._grant_tokens, - ) - new_materials = new_materials.with_data_encryption_key( - data_encryption_key=plaintext_key, - keyring_trace=KeyringTrace(wrapping_key=trace_info, flags=_GENERATE_FLAGS), - ) - else: - encrypted_key = _do_aws_kms_encrypt( - client_supplier=self._client_supplier, - key_name=self._key_id, - plaintext_data_key=new_materials.data_encryption_key, - encryption_context=new_materials.encryption_context, - grant_tokens=self._grant_tokens, - ) - except Exception: # pylint: disable=broad-except - # We intentionally WANT to catch all exceptions here - message = "Unable to generate or encrypt data key using {}".format(trace_info) - _LOGGER.exception(message) - raise EncryptKeyError(message) - - return new_materials.with_encrypted_data_key( - encrypted_data_key=encrypted_key, keyring_trace=KeyringTrace(wrapping_key=trace_info, flags=_ENCRYPT_FLAGS) - ) - - def on_decrypt(self, decryption_materials, encrypted_data_keys): - # type: (DecryptionMaterials, Iterable[EncryptedDataKey]) -> DecryptionMaterials - new_materials = decryption_materials - - for edk in encrypted_data_keys: - if new_materials.data_encryption_key is not None: - return new_materials - - if ( - edk.key_provider.provider_id == KEY_NAMESPACE - and edk.key_provider.key_info.decode("utf-8") == self._key_id - ): - new_materials = _try_aws_kms_decrypt( - client_supplier=self._client_supplier, - decryption_materials=new_materials, - grant_tokens=self._grant_tokens, - encrypted_data_key=edk, - ) - - return new_materials - - -@attr.s -class _AwsKmsDiscoveryKeyring(Keyring): - """AWS KMS discovery keyring that will attempt to decrypt any AWS KMS encrypted data key. - - This keyring should never be used directly. - It should only ever be used internally by :class:`AwsKmsKeyring`. - - .. versionadded:: 1.5.0 - - :param ClientSupplier client_supplier: Client supplier to use when asking for clients - :param List[str] grant_tokens: AWS KMS grant tokens to include in requests (optional) - """ - - _client_supplier = attr.ib(validator=is_callable()) - _grant_tokens = attr.ib( - default=attr.Factory(tuple), - validator=(deep_iterable(member_validator=instance_of(six.string_types)), value_is_not_a_string), - ) - - def on_encrypt(self, encryption_materials): - # type: (EncryptionMaterials) -> EncryptionMaterials - return encryption_materials - - def on_decrypt(self, decryption_materials, encrypted_data_keys): - # type: (DecryptionMaterials, Iterable[EncryptedDataKey]) -> DecryptionMaterials - new_materials = decryption_materials - - for edk in encrypted_data_keys: - if new_materials.data_encryption_key is not None: - return new_materials - - if edk.key_provider.provider_id == KEY_NAMESPACE: - new_materials = _try_aws_kms_decrypt( - client_supplier=self._client_supplier, - decryption_materials=new_materials, - grant_tokens=self._grant_tokens, - encrypted_data_key=edk, - ) - - return new_materials - - -def _try_aws_kms_decrypt(client_supplier, decryption_materials, grant_tokens, encrypted_data_key): - # type: (ClientSupplierType, DecryptionMaterials, Iterable[str], EncryptedDataKey) -> DecryptionMaterials - """Attempt to call ``kms:Decrypt`` and return the resulting plaintext data key. - - Any errors encountered are caught and logged. - - .. versionadded:: 1.5.0 - - """ - try: - plaintext_key = _do_aws_kms_decrypt( - client_supplier=client_supplier, - key_name=encrypted_data_key.key_provider.key_info.decode("utf-8"), - encrypted_data_key=encrypted_data_key, - encryption_context=decryption_materials.encryption_context, - grant_tokens=grant_tokens, - ) - except Exception: # pylint: disable=broad-except - # We intentionally WANT to catch all exceptions here - _LOGGER.exception("Unable to decrypt encrypted data key from %s", encrypted_data_key.key_provider) - return decryption_materials - - return decryption_materials.with_data_encryption_key( - data_encryption_key=plaintext_key, - keyring_trace=KeyringTrace(wrapping_key=encrypted_data_key.key_provider, flags=_DECRYPT_FLAGS), - ) - - -def _do_aws_kms_decrypt(client_supplier, key_name, encrypted_data_key, encryption_context, grant_tokens): - # type: (ClientSupplierType, str, EncryptedDataKey, Dict[str, str], Iterable[str]) -> RawDataKey - """Attempt to call ``kms:Decrypt`` and return the resulting plaintext data key. - - Any errors encountered are passed up the chain without comment. - - .. versionadded:: 1.5.0 - - """ - region = _region_from_key_id(encrypted_data_key.key_provider.key_info.decode("utf-8")) - client = client_supplier(region) - response = client.decrypt( - CiphertextBlob=encrypted_data_key.encrypted_data_key, - EncryptionContext=encryption_context, - GrantTokens=grant_tokens, - ) - response_key_id = response["KeyId"] - if response_key_id != key_name: - raise DecryptKeyError( - "Decryption results from AWS KMS are for an unexpected key ID!" - " actual '{actual}' != expected '{expected}'".format(actual=response_key_id, expected=key_name) - ) - return RawDataKey( - key_provider=MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=response_key_id), data_key=response["Plaintext"] - ) - - -def _do_aws_kms_encrypt(client_supplier, key_name, plaintext_data_key, encryption_context, grant_tokens): - # type: (ClientSupplierType, str, RawDataKey, Dict[str, str], Iterable[str]) -> EncryptedDataKey - """Attempt to call ``kms:Encrypt`` and return the resulting encrypted data key. - - Any errors encountered are passed up the chain without comment. - """ - region = _region_from_key_id(key_name) - client = client_supplier(region) - response = client.encrypt( - KeyId=key_name, - Plaintext=plaintext_data_key.data_key, - EncryptionContext=encryption_context, - GrantTokens=grant_tokens, - ) - return EncryptedDataKey( - key_provider=MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=response["KeyId"]), - encrypted_data_key=response["CiphertextBlob"], - ) - - -def _do_aws_kms_generate_data_key(client_supplier, key_name, encryption_context, algorithm, grant_tokens): - # type: (ClientSupplierType, str, Dict[str, str], AlgorithmSuite, Iterable[str]) -> (RawDataKey, EncryptedDataKey) - """Attempt to call ``kms:GenerateDataKey`` and return the resulting plaintext and encrypted data keys. - - Any errors encountered are passed up the chain without comment. - - .. versionadded:: 1.5.0 - - """ - region = _region_from_key_id(key_name) - client = client_supplier(region) - response = client.generate_data_key( - KeyId=key_name, - NumberOfBytes=algorithm.kdf_input_len, - EncryptionContext=encryption_context, - GrantTokens=grant_tokens, - ) - provider = MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=response["KeyId"]) - plaintext_key = RawDataKey(key_provider=provider, data_key=response["Plaintext"]) - encrypted_key = EncryptedDataKey(key_provider=provider, encrypted_data_key=response["CiphertextBlob"]) - return plaintext_key, encrypted_key - - -def _region_from_key_id(key_id): - # type: (str) -> Union[None, str] - """Attempt to determine the region from the key ID. - - If the region cannot be found, ``None`` is returned instead. - - .. versionadded:: 1.5.0 - - """ - parts = key_id.split(":", 4) - try: - return parts[3] - except IndexError: - return None diff --git a/src/aws_encryption_sdk/keyrings/aws_kms/_client_cache.py b/src/aws_encryption_sdk/keyrings/aws_kms/_client_cache.py deleted file mode 100644 index 9eb8d3e82..000000000 --- a/src/aws_encryption_sdk/keyrings/aws_kms/_client_cache.py +++ /dev/null @@ -1,119 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""boto3 client cache for use by client suppliers. - -.. versionadded:: 1.5.0 - -.. warning:: - No guarantee is provided on the modules and APIs within this - namespace staying consistent. Directly reference at your own risk. - -""" -import functools -import logging - -import attr -from attr.validators import instance_of -from boto3.session import Session as Boto3Session -from botocore.client import BaseClient -from botocore.config import Config as BotocoreConfig -from botocore.exceptions import BotoCoreError -from botocore.session import Session as BotocoreSession - -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Dict # noqa pylint: disable=unused-import -except ImportError: # pragma: no cover - # We only actually need these imports when running the mypy checks - pass - -_LOGGER = logging.getLogger(__name__) -__all__ = ("ClientCache",) - - -@attr.s -class ClientCache(object): - """Provide boto3 clients regional clients, caching by region. - - Any clients that throw an error when used are immediately removed from the cache. - - .. versionadded:: 1.5.0 - - :param botocore_session: Botocore session to use when creating clients - :type botocore_session: botocore.session.Session - :param client_config: Config to use when creating client - :type client_config: botocore.config.Config - """ - - _botocore_session = attr.ib(validator=instance_of(BotocoreSession)) - _client_config = attr.ib(validator=instance_of(BotocoreConfig)) - - def __attrs_post_init__(self): - """Set up internal cache.""" - self._cache = {} # type: Dict[str, BaseClient] - - def _wrap_client_method(self, region_name, method, *args, **kwargs): - """Proxy a call to a boto3 client method and remove any misbehaving clients from the cache. - - :param str region_name: Client region name - :param Callable method: Method on the boto3 client to proxy - :param Tuple args: Positional arguments to pass to ``method`` - :param Dict kwargs: Named arguments to pass to ``method`` - :returns: result of - """ - try: - return method(*args, **kwargs) - except BotoCoreError as error: - try: - del self._cache[region_name] - except KeyError: - pass - _LOGGER.exception( - 'Removing client "%s" from cache due to BotoCoreError on %s call', region_name, method.__name__ - ) - raise error - - def _patch_client(self, client): - # type: (BaseClient) -> BaseClient - """Patch a boto3 client, wrapping every API call in ``_wrap_client_method``. - - :param BaseClient client: boto3 client to patch - :returns: patched client - """ - for method_name in client.meta.method_to_api_mapping: - method = getattr(client, method_name) - wrapped_method = functools.partial(self._wrap_client_method, client.meta.region_name, method) - setattr(client, method_name, wrapped_method) - - return client - - def _add_client(self, region_name, service): - # type: (str, str) -> BaseClient - """Make a new client and add it to the internal cache. - - :param str region_name: Client region - :param str service: Client service - :returns: New client, now in cache - :rtype: botocore.client.BaseClient - """ - client = Boto3Session(botocore_session=self._botocore_session).client( - service_name=service, region_name=region_name, config=self._client_config - ) - patched_client = self._patch_client(client) - self._cache[region_name] = patched_client - return client - - def client(self, region_name, service): - # type: (str, str) -> BaseClient - """Get a client for the specified region and service. - - Generate a new client if needed. - Otherwise, retrieve an existing client from the internal cache. - - :param str region_name: Client region - :param str service: Client service - :rtype: botocore.client.BaseClient - """ - try: - return self._cache[region_name] - except KeyError: - return self._add_client(region_name, service) diff --git a/src/aws_encryption_sdk/keyrings/aws_kms/client_suppliers.py b/src/aws_encryption_sdk/keyrings/aws_kms/client_suppliers.py deleted file mode 100644 index c8a0af696..000000000 --- a/src/aws_encryption_sdk/keyrings/aws_kms/client_suppliers.py +++ /dev/null @@ -1,159 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""AWS KMS client suppliers for use with AWS KMS keyring. - -.. versionadded:: 1.5.0 - -""" -import functools -import logging - -import attr -import six -from attr.validators import deep_iterable, instance_of, is_callable, optional -from botocore.client import BaseClient -from botocore.config import Config as BotocoreConfig -from botocore.session import Session as BotocoreSession - -from aws_encryption_sdk.exceptions import UnknownRegionError -from aws_encryption_sdk.identifiers import USER_AGENT_SUFFIX -from aws_encryption_sdk.internal.validators import value_is_not_a_string - -from ._client_cache import ClientCache - -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Callable, Union # noqa pylint: disable=unused-import - - ClientSupplierType = Callable[[Union[None, str]], BaseClient] -except ImportError: # pragma: no cover - # We only actually need these imports when running the mypy checks - pass - -_LOGGER = logging.getLogger(__name__) -__all__ = ( - "ClientSupplier", - "ClientSupplierType", - "DefaultClientSupplier", - "AllowRegionsClientSupplier", - "DenyRegionsClientSupplier", -) - - -class ClientSupplier(object): - """Base class for client suppliers. - - .. versionadded:: 1.5.0 - - """ - - def __call__(self, region_name): - # type: (Union[None, str]) -> BaseClient - """Return a client for the requested region. - - :rtype: BaseClient - """ - raise NotImplementedError("'ClientSupplier' is not callable") - - -@attr.s -class DefaultClientSupplier(ClientSupplier): - """The default AWS KMS client supplier. - Creates and caches clients for any region. - - .. versionadded:: 1.5.0 - - If you want clients to have special credentials or other configuration, - you can provide those with custom ``botocore`` Session and/or `Config`_ instances. - - .. _Config: https://botocore.amazonaws.com/v1/documentation/api/latest/reference/config.html - - .. code-block:: python - - from aws_encryption_sdk.keyrings.aws_kms.client_supplier import DefaultClientSupplier - from botocore.session import Session - from botocore.config import Config - - my_client_supplier = DefaultClientSupplier( - botocore_session=Session(**_get_custom_credentials()), - client_config=Config(connect_timeout=10), - ) - - :param botocore_session: Botocore session to use when creating clients (optional) - :type botocore_session: botocore.session.Session - :param client_config: Config to use when creating client (optional) - :type client_config: botocore.config.Config - """ - - _botocore_session = attr.ib(default=attr.Factory(BotocoreSession), validator=instance_of(BotocoreSession)) - _client_config = attr.ib( - default=attr.Factory(functools.partial(BotocoreConfig, user_agent_extra=USER_AGENT_SUFFIX)), - validator=instance_of(BotocoreConfig), - ) - - def __attrs_post_init__(self): - """Set up the internal cache.""" - self._client_cache = ClientCache(botocore_session=self._botocore_session, client_config=self._client_config) - - def __call__(self, region_name): - # type: (Union[None, str]) -> BaseClient - """Return a client for the requested region. - - :rtype: BaseClient - """ - return self._client_cache.client(region_name=region_name, service="kms") - - -@attr.s -class AllowRegionsClientSupplier(ClientSupplier): - """AWS KMS client supplier that only supplies clients for the specified regions. - - .. versionadded:: 1.5.0 - - :param List[str] allowed_regions: Regions to allow - :param ClientSupplier client_supplier: Client supplier to wrap (optional) - """ - - allowed_regions = attr.ib( - validator=(deep_iterable(member_validator=instance_of(six.string_types)), value_is_not_a_string) - ) - _client_supplier = attr.ib(default=attr.Factory(DefaultClientSupplier), validator=optional(is_callable())) - - def __call__(self, region_name): - # type: (Union[None, str]) -> BaseClient - """Return a client for the requested region. - - :rtype: BaseClient - :raises UnknownRegionError: if a region is requested that is not in ``allowed_regions`` - """ - if region_name not in self.allowed_regions: - raise UnknownRegionError("Unable to provide client for region '{}'".format(region_name)) - - return self._client_supplier(region_name) - - -@attr.s -class DenyRegionsClientSupplier(ClientSupplier): - """AWS KMS client supplier that supplies clients for any region except for the specified regions. - - .. versionadded:: 1.5.0 - - :param List[str] denied_regions: Regions to deny - :param ClientSupplier client_supplier: Client supplier to wrap (optional) - """ - - denied_regions = attr.ib( - validator=(deep_iterable(member_validator=instance_of(six.string_types)), value_is_not_a_string) - ) - _client_supplier = attr.ib(default=attr.Factory(DefaultClientSupplier), validator=optional(is_callable())) - - def __call__(self, region_name): - # type: (Union[None, str]) -> BaseClient - """Return a client for the requested region. - - :rtype: BaseClient - :raises UnknownRegionError: if a region is requested that is in ``denied_regions`` - """ - if region_name in self.denied_regions: - raise UnknownRegionError("Unable to provide client for region '{}'".format(region_name)) - - return self._client_supplier(region_name) diff --git a/src/aws_encryption_sdk/keyrings/base.py b/src/aws_encryption_sdk/keyrings/base.py deleted file mode 100644 index c854faf27..000000000 --- a/src/aws_encryption_sdk/keyrings/base.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Base class interface for Keyrings.""" -from aws_encryption_sdk.materials_managers import ( # only used for mypy; pylint: disable=unused-import - DecryptionMaterials, - EncryptionMaterials, -) -from aws_encryption_sdk.structures import EncryptedDataKey # only used for mypy; pylint: disable=unused-import - -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - 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 - -__all__ = ("Keyring",) - - -class Keyring(object): - """Parent interface for Keyring classes. - - .. versionadded:: 1.5.0 - """ - - def on_encrypt(self, encryption_materials): - # type: (EncryptionMaterials) -> EncryptionMaterials - """Generate a data key if not present and encrypt it using any available wrapping key. - - :param EncryptionMaterials encryption_materials: Encryption materials for keyring to modify. - :returns: Optionally modified encryption materials. - :rtype: EncryptionMaterials - :raises NotImplementedError: if method is not implemented - """ - raise NotImplementedError("Keyring does not implement on_encrypt function") - - def on_decrypt(self, decryption_materials, encrypted_data_keys): - # type: (DecryptionMaterials, Iterable[EncryptedDataKey]) -> DecryptionMaterials - """Attempt to decrypt the encrypted data keys. - - :param DecryptionMaterials decryption_materials: Decryption materials for keyring to modify. - :param List[EncryptedDataKey] encrypted_data_keys: List of encrypted data keys. - :returns: Optionally modified decryption materials. - :rtype: DecryptionMaterials - :raises NotImplementedError: if method is not implemented - """ - raise NotImplementedError("Keyring does not implement on_decrypt function") diff --git a/src/aws_encryption_sdk/keyrings/multi.py b/src/aws_encryption_sdk/keyrings/multi.py deleted file mode 100644 index 27e90c3c8..000000000 --- a/src/aws_encryption_sdk/keyrings/multi.py +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Resources required for Multi Keyrings.""" -import itertools - -import attr -from attr.validators import deep_iterable, instance_of, optional - -from aws_encryption_sdk.exceptions import EncryptKeyError, GenerateKeyError -from aws_encryption_sdk.keyrings.base import Keyring - -from aws_encryption_sdk.materials_managers import ( # only used for mypy; pylint: disable=unused-import - DecryptionMaterials, - EncryptionMaterials, -) -from aws_encryption_sdk.structures import EncryptedDataKey # only used for mypy; pylint: disable=unused-import - -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - 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 - -__all__ = ("MultiKeyring",) - - -@attr.s -class MultiKeyring(Keyring): - """Public class for Multi Keyring. - - .. versionadded:: 1.5.0 - - :param Keyring generator: Generator keyring used to generate data encryption key (optional) - :param List[Keyring] children: List of keyrings used to encrypt the data encryption key (optional) - :raises EncryptKeyError: if encryption of data key fails for any reason - """ - - generator = attr.ib(default=None, validator=optional(instance_of(Keyring))) - children = attr.ib( - default=attr.Factory(tuple), validator=optional(deep_iterable(member_validator=instance_of(Keyring))) - ) - - def __attrs_post_init__(self): - # type: () -> None - """Prepares initial values not handled by attrs.""" - neither_generator_nor_children = self.generator is None and not self.children - if neither_generator_nor_children: - raise TypeError("At least one of generator or children must be provided") - - _generator = (self.generator,) if self.generator is not None else () - self._decryption_keyrings = list(itertools.chain(_generator, self.children)) - - def on_encrypt(self, encryption_materials): - # type: (EncryptionMaterials) -> EncryptionMaterials - """Generate a data key using generator keyring - and encrypt it using any available wrapping key in any child keyring. - - :param EncryptionMaterials encryption_materials: Encryption materials for keyring to modify. - :returns: Optionally modified encryption materials. - :rtype: EncryptionMaterials - :raises EncryptKeyError: if unable to encrypt data key. - """ - # Check if generator keyring is not provided and data key is not generated - if self.generator is None and encryption_materials.data_encryption_key is None: - raise EncryptKeyError( - "Generator keyring not provided " - "and encryption materials do not already contain a plaintext data key." - ) - - new_materials = encryption_materials - - # Call on_encrypt on the generator keyring if it is provided - if self.generator is not None: - new_materials = self.generator.on_encrypt(encryption_materials=new_materials) - - # Check if data key is generated - if new_materials.data_encryption_key is None: - raise GenerateKeyError("Unable to generate data encryption key.") - - # Call on_encrypt on all other keyrings - for keyring in self.children: - new_materials = keyring.on_encrypt(encryption_materials=new_materials) - - return new_materials - - def on_decrypt(self, decryption_materials, encrypted_data_keys): - # type: (DecryptionMaterials, Iterable[EncryptedDataKey]) -> DecryptionMaterials - """Attempt to decrypt the encrypted data keys. - - :param DecryptionMaterials decryption_materials: Decryption materials for keyring to modify. - :param List[EncryptedDataKey] encrypted_data_keys: List of encrypted data keys. - :returns: Optionally modified decryption materials. - :rtype: DecryptionMaterials - """ - # Call on_decrypt on all keyrings till decryption is successful - new_materials = decryption_materials - for keyring in self._decryption_keyrings: - if new_materials.data_encryption_key is not None: - return new_materials - - new_materials = keyring.on_decrypt( - decryption_materials=new_materials, encrypted_data_keys=encrypted_data_keys - ) - - return new_materials diff --git a/src/aws_encryption_sdk/keyrings/raw.py b/src/aws_encryption_sdk/keyrings/raw.py deleted file mode 100644 index 450111ad7..000000000 --- a/src/aws_encryption_sdk/keyrings/raw.py +++ /dev/null @@ -1,461 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Resources required for Raw Keyrings.""" -import logging -import os - -import attr -import six -from attr.validators import in_, instance_of, optional -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey - -from aws_encryption_sdk.exceptions import EncryptKeyError, GenerateKeyError -from aws_encryption_sdk.identifiers import EncryptionKeyType, KeyringTraceFlag, WrappingAlgorithm -from aws_encryption_sdk.internal.crypto.wrapping_keys import EncryptedData, WrappingKey -from aws_encryption_sdk.internal.formatting.deserialize import deserialize_wrapped_key -from aws_encryption_sdk.internal.formatting.serialize import serialize_raw_master_key_prefix, serialize_wrapped_key -from aws_encryption_sdk.key_providers.raw import RawMasterKey -from aws_encryption_sdk.keyrings.base import Keyring -from aws_encryption_sdk.materials_managers import DecryptionMaterials, EncryptionMaterials -from aws_encryption_sdk.structures import EncryptedDataKey, KeyringTrace, MasterKeyInfo, RawDataKey - -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - 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 - -__all__ = ("RawAESKeyring", "RawRSAKeyring") -_LOGGER = logging.getLogger(__name__) - - -def _generate_data_key( - encryption_materials, # type: EncryptionMaterials - key_provider, # type: MasterKeyInfo -): - # type: (...) -> EncryptionMaterials - """Generates plaintext data key for the keyring. - - :param EncryptionMaterials encryption_materials: Encryption materials for the keyring to modify. - :param MasterKeyInfo key_provider: Information about the key in the keyring. - :rtype: EncryptionMaterials - :returns: Encryption materials containing a data encryption key - """ - # Check if encryption materials contain data encryption key - if encryption_materials.data_encryption_key is not None: - raise TypeError("Data encryption key already exists.") - - # Generate data key - try: - plaintext_data_key = os.urandom(encryption_materials.algorithm.kdf_input_len) - except Exception: # pylint: disable=broad-except - error_message = "Unable to generate data encryption key." - _LOGGER.exception(error_message) - raise GenerateKeyError("Unable to generate data encryption key.") - - # Create a keyring trace - keyring_trace = KeyringTrace(wrapping_key=key_provider, flags={KeyringTraceFlag.GENERATED_DATA_KEY}) - - # plaintext_data_key to RawDataKey - data_encryption_key = RawDataKey(key_provider=key_provider, data_key=plaintext_data_key) - - return encryption_materials.with_data_encryption_key( - data_encryption_key=data_encryption_key, keyring_trace=keyring_trace - ) - - -@attr.s -class RawAESKeyring(Keyring): - """Generate an instance of Raw AES Keyring which encrypts using AES-GCM algorithm using wrapping key provided as a - byte array - - .. versionadded:: 1.5.0 - - :param str key_namespace: String defining the keyring. - :param bytes key_name: Key ID - :param bytes wrapping_key: Encryption key with which to wrap plaintext data key. - - .. note:: - - Only one wrapping key can be specified in a Raw AES Keyring - """ - - key_namespace = attr.ib(validator=instance_of(six.string_types)) - key_name = attr.ib(validator=instance_of(six.binary_type)) - _wrapping_key = attr.ib(repr=False, validator=instance_of(six.binary_type)) - - def __attrs_post_init__(self): - # type: () -> None - """Prepares initial values not handled by attrs.""" - key_size_to_wrapping_algorithm = { - wrapper.algorithm.kdf_input_len: wrapper - for wrapper in ( - WrappingAlgorithm.AES_128_GCM_IV12_TAG16_NO_PADDING, - WrappingAlgorithm.AES_192_GCM_IV12_TAG16_NO_PADDING, - WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING, - ) - } - - try: - self._wrapping_algorithm = key_size_to_wrapping_algorithm[len(self._wrapping_key)] - except KeyError: - raise ValueError( - "Invalid wrapping key length. Must be one of {} bytes.".format( - sorted(key_size_to_wrapping_algorithm.keys()) - ) - ) - - self._key_provider = MasterKeyInfo(provider_id=self.key_namespace, key_info=self.key_name) - - self._wrapping_key_structure = WrappingKey( - wrapping_algorithm=self._wrapping_algorithm, - wrapping_key=self._wrapping_key, - wrapping_key_type=EncryptionKeyType.SYMMETRIC, - ) - - self._key_info_prefix = self._get_key_info_prefix( - key_namespace=self.key_namespace, key_name=self.key_name, wrapping_key=self._wrapping_key_structure - ) - - @staticmethod - def _get_key_info_prefix(key_namespace, key_name, wrapping_key): - # type: (str, bytes, WrappingKey) -> six.binary_type - """Helper function to get key info prefix - - :param str key_namespace: String defining the keyring. - :param bytes key_name: Key ID - :param WrappingKey wrapping_key: Encryption key with which to wrap plaintext data key. - :return: Serialized key_info prefix - :rtype: bytes - """ - key_info_prefix = serialize_raw_master_key_prefix( - RawMasterKey(provider_id=key_namespace, key_id=key_name, wrapping_key=wrapping_key) - ) - return key_info_prefix - - def on_encrypt(self, encryption_materials): - # type: (EncryptionMaterials) -> EncryptionMaterials - """Generate a data key if not present and encrypt it using any available wrapping key - - :param EncryptionMaterials encryption_materials: Encryption materials for the keyring to modify - :returns: Encryption materials containing data key and encrypted data key - :rtype: EncryptionMaterials - """ - new_materials = encryption_materials - - if new_materials.data_encryption_key is None: - # Get encryption materials with a new data key. - new_materials = _generate_data_key(encryption_materials=new_materials, key_provider=self._key_provider) - - try: - # Encrypt data key - encrypted_wrapped_key = self._wrapping_key_structure.encrypt( - plaintext_data_key=new_materials.data_encryption_key.data_key, - encryption_context=new_materials.encryption_context, - ) - - # EncryptedData to EncryptedDataKey - encrypted_data_key = serialize_wrapped_key( - key_provider=self._key_provider, - wrapping_algorithm=self._wrapping_algorithm, - wrapping_key_id=self.key_name, - encrypted_wrapped_key=encrypted_wrapped_key, - ) - except Exception: # pylint: disable=broad-except - error_message = "Raw AES keyring unable to encrypt data key" - _LOGGER.exception(error_message) - raise EncryptKeyError(error_message) - - # Update Keyring Trace - keyring_trace = KeyringTrace( - wrapping_key=self._key_provider, - flags={KeyringTraceFlag.ENCRYPTED_DATA_KEY, KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT}, - ) - - return new_materials.with_encrypted_data_key(encrypted_data_key=encrypted_data_key, keyring_trace=keyring_trace) - - def on_decrypt(self, decryption_materials, encrypted_data_keys): - # type: (DecryptionMaterials, Iterable[EncryptedDataKey]) -> DecryptionMaterials - """Attempt to decrypt the encrypted data keys. - - :param DecryptionMaterials decryption_materials: Decryption materials for the keyring to modify - :param List[EncryptedDataKey] encrypted_data_keys: List of encrypted data keys - :returns: Decryption materials that MAY include a plaintext data key - :rtype: DecryptionMaterials - """ - new_materials = decryption_materials - - if new_materials.data_encryption_key is not None: - return new_materials - - # Decrypt data key - expected_key_info_len = len(self._key_info_prefix) + self._wrapping_algorithm.algorithm.iv_len - for key in encrypted_data_keys: - - if ( - key.key_provider.provider_id != self._key_provider.provider_id - or len(key.key_provider.key_info) != expected_key_info_len - or not key.key_provider.key_info.startswith(self._key_info_prefix) - ): - continue - - # Wrapped EncryptedDataKey to deserialized EncryptedData - encrypted_wrapped_key = deserialize_wrapped_key( - wrapping_algorithm=self._wrapping_algorithm, wrapping_key_id=self.key_name, wrapped_encrypted_key=key - ) - - # EncryptedData to raw key string - try: - plaintext_data_key = self._wrapping_key_structure.decrypt( - encrypted_wrapped_data_key=encrypted_wrapped_key, - encryption_context=new_materials.encryption_context, - ) - - except Exception: # pylint: disable=broad-except - # We intentionally WANT to catch all exceptions here - error_message = "Raw AES Keyring unable to decrypt data key" - _LOGGER.exception(error_message) - # The Raw AES keyring MUST evaluate every encrypted data key - # until it either succeeds or runs out of encrypted data keys. - continue - - # Create a keyring trace - keyring_trace = KeyringTrace( - wrapping_key=self._key_provider, - flags={KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT}, - ) - - # Update decryption materials - data_encryption_key = RawDataKey(key_provider=self._key_provider, data_key=plaintext_data_key) - - return new_materials.with_data_encryption_key( - data_encryption_key=data_encryption_key, keyring_trace=keyring_trace - ) - - return new_materials - - -@attr.s -class RawRSAKeyring(Keyring): - """Generate an instance of Raw RSA Keyring which performs asymmetric encryption and decryption using public - and private keys provided - - .. versionadded:: 1.5.0 - - :param str key_namespace: String defining the keyring ID - :param bytes key_name: Key ID - :param cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey private_wrapping_key: - Private encryption key with which to wrap plaintext data key (optional) - :param cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey public_wrapping_key: - Public encryption key with which to wrap plaintext data key (optional) - :param WrappingAlgorithm wrapping_algorithm: Wrapping Algorithm with which to wrap plaintext data key - :param MasterKeyInfo key_provider: Complete information about the key in the keyring - - .. note:: - - At least one of public wrapping key or private wrapping key must be provided. - """ - - key_namespace = attr.ib(validator=instance_of(six.string_types)) - key_name = attr.ib(validator=instance_of(six.binary_type)) - _wrapping_algorithm = attr.ib( - repr=False, - validator=in_( - ( - WrappingAlgorithm.RSA_PKCS1, - WrappingAlgorithm.RSA_OAEP_SHA1_MGF1, - WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - WrappingAlgorithm.RSA_OAEP_SHA384_MGF1, - WrappingAlgorithm.RSA_OAEP_SHA512_MGF1, - ) - ), - ) - _private_wrapping_key = attr.ib(default=None, repr=False, validator=optional(instance_of(RSAPrivateKey))) - _public_wrapping_key = attr.ib(default=None, repr=False, validator=optional(instance_of(RSAPublicKey))) - - def __attrs_post_init__(self): - # type: () -> None - """Prepares initial values not handled by attrs.""" - self._key_provider = MasterKeyInfo(provider_id=self.key_namespace, key_info=self.key_name) - - if self._public_wrapping_key is None and self._private_wrapping_key is None: - raise TypeError("At least one of public key or private key must be provided.") - - if self._private_wrapping_key is not None and self._public_wrapping_key is None: - self._public_wrapping_key = self._private_wrapping_key.public_key() - - @classmethod - def from_pem_encoding( - cls, - key_namespace, # type: str - key_name, # type: bytes - wrapping_algorithm, # type: WrappingAlgorithm - public_encoded_key=None, # type: bytes - private_encoded_key=None, # type: bytes - password=None, # type: bytes - ): - # type: (...) -> RawRSAKeyring - """Generate a Raw RSA keyring using PEM Encoded public and private keys - - :param str key_namespace: String defining the keyring ID - :param bytes key_name: Key ID - :param WrappingAlgorithm wrapping_algorithm: Wrapping Algorithm with which to wrap plaintext data key - :param bytes public_encoded_key: PEM encoded public key (optional) - :param bytes private_encoded_key: PEM encoded private key (optional) - :param bytes password: Password to load private key (optional) - :return: :class:`RawRSAKeyring` constructed using required parameters - """ - loaded_private_wrapping_key = loaded_public_wrapping_key = None - if private_encoded_key is not None: - loaded_private_wrapping_key = serialization.load_pem_private_key( - data=private_encoded_key, password=password, backend=default_backend() - ) - if public_encoded_key is not None: - loaded_public_wrapping_key = serialization.load_pem_public_key( - data=public_encoded_key, backend=default_backend() - ) - - return cls( - key_namespace=key_namespace, - key_name=key_name, - wrapping_algorithm=wrapping_algorithm, - private_wrapping_key=loaded_private_wrapping_key, - public_wrapping_key=loaded_public_wrapping_key, - ) - - @classmethod - def from_der_encoding( - cls, - key_namespace, # type: str - key_name, # type: bytes - wrapping_algorithm, # type: WrappingAlgorithm - public_encoded_key=None, # type: bytes - private_encoded_key=None, # type: bytes - password=None, # type: bytes - ): - # type: (...) -> RawRSAKeyring - """Generate a raw RSA keyring using DER Encoded public and private keys - - :param str key_namespace: String defining the keyring ID - :param bytes key_name: Key ID - :param WrappingAlgorithm wrapping_algorithm: Wrapping Algorithm with which to wrap plaintext data key - :param bytes public_encoded_key: DER encoded public key (optional) - :param bytes private_encoded_key: DER encoded private key (optional) - :param bytes password: Password to load private key (optional) - :return: :class:`RawRSAKeyring` constructed using required parameters - """ - loaded_private_wrapping_key = loaded_public_wrapping_key = None - if private_encoded_key is not None: - loaded_private_wrapping_key = serialization.load_der_private_key( - data=private_encoded_key, password=password, backend=default_backend() - ) - if public_encoded_key is not None: - loaded_public_wrapping_key = serialization.load_der_public_key( - data=public_encoded_key, backend=default_backend() - ) - - return cls( - key_namespace=key_namespace, - key_name=key_name, - wrapping_algorithm=wrapping_algorithm, - private_wrapping_key=loaded_private_wrapping_key, - public_wrapping_key=loaded_public_wrapping_key, - ) - - def on_encrypt(self, encryption_materials): - # type: (EncryptionMaterials) -> EncryptionMaterials - """Generate a data key using generator keyring - and encrypt it using any available wrapping key in any child keyring. - - :param EncryptionMaterials encryption_materials: Encryption materials for keyring to modify. - :returns: Encryption materials containing data key and encrypted data key - :rtype: EncryptionMaterials - """ - new_materials = encryption_materials - - if new_materials.data_encryption_key is None: - new_materials = _generate_data_key(encryption_materials=new_materials, key_provider=self._key_provider) - - if self._public_wrapping_key is None: - # This should be impossible, but just in case, give a useful error message. - raise EncryptKeyError("Raw RSA keyring unable to encrypt data key: no public key available") - - try: - # Encrypt data key - encrypted_wrapped_key = EncryptedData( - iv=None, - ciphertext=self._public_wrapping_key.encrypt( - plaintext=new_materials.data_encryption_key.data_key, padding=self._wrapping_algorithm.padding, - ), - tag=None, - ) - - # EncryptedData to EncryptedDataKey - encrypted_data_key = serialize_wrapped_key( - key_provider=self._key_provider, - wrapping_algorithm=self._wrapping_algorithm, - wrapping_key_id=self.key_name, - encrypted_wrapped_key=encrypted_wrapped_key, - ) - except Exception: # pylint: disable=broad-except - error_message = "Raw RSA keyring unable to encrypt data key" - _LOGGER.exception(error_message) - raise EncryptKeyError(error_message) - - # Update Keyring Trace - keyring_trace = KeyringTrace(wrapping_key=self._key_provider, flags={KeyringTraceFlag.ENCRYPTED_DATA_KEY}) - - # Add encrypted data key to encryption_materials - return new_materials.with_encrypted_data_key(encrypted_data_key=encrypted_data_key, keyring_trace=keyring_trace) - - def on_decrypt(self, decryption_materials, encrypted_data_keys): - # type: (DecryptionMaterials, Iterable[EncryptedDataKey]) -> DecryptionMaterials - """Attempt to decrypt the encrypted data keys. - - :param DecryptionMaterials decryption_materials: Decryption materials for keyring to modify. - :param encrypted_data_keys: List of encrypted data keys. - :type: List[EncryptedDataKey] - :returns: Decryption materials that MAY include a plaintext data key - :rtype: DecryptionMaterials - """ - new_materials = decryption_materials - - if new_materials.data_encryption_key is not None: - return new_materials - - if self._private_wrapping_key is None: - return new_materials - - # Decrypt data key - for key in encrypted_data_keys: - if key.key_provider != self._key_provider: - continue - - # Wrapped EncryptedDataKey to deserialized EncryptedData - encrypted_wrapped_key = deserialize_wrapped_key( - wrapping_algorithm=self._wrapping_algorithm, wrapping_key_id=self.key_name, wrapped_encrypted_key=key - ) - try: - plaintext_data_key = self._private_wrapping_key.decrypt( - ciphertext=encrypted_wrapped_key.ciphertext, padding=self._wrapping_algorithm.padding - ) - except Exception: # pylint: disable=broad-except - error_message = "Raw RSA Keyring unable to decrypt data key" - _LOGGER.exception(error_message) - # The Raw RSA keyring MUST evaluate every encrypted data key - # until it either succeeds or runs out of encrypted data keys. - continue - - # Create a keyring trace - keyring_trace = KeyringTrace(wrapping_key=self._key_provider, flags={KeyringTraceFlag.DECRYPTED_DATA_KEY}) - - # Update decryption materials - data_encryption_key = RawDataKey(key_provider=self._key_provider, data_key=plaintext_data_key) - - return new_materials.with_data_encryption_key( - data_encryption_key=data_encryption_key, keyring_trace=keyring_trace - ) - - return new_materials diff --git a/src/aws_encryption_sdk/materials_managers/__init__.py b/src/aws_encryption_sdk/materials_managers/__init__.py index 8c8c33886..bc5230c51 100644 --- a/src/aws_encryption_sdk/materials_managers/__init__.py +++ b/src/aws_encryption_sdk/materials_managers/__init__.py @@ -14,23 +14,12 @@ .. versionadded:: 1.3.0 """ -import copy - import attr import six -from attr.validators import deep_iterable, deep_mapping, instance_of, optional - -from aws_encryption_sdk.exceptions import InvalidDataKeyError, InvalidKeyringTraceError, SignatureKeyError -from aws_encryption_sdk.identifiers import Algorithm, KeyringTraceFlag -from aws_encryption_sdk.internal.crypto.authentication import Signer, Verifier -from aws_encryption_sdk.internal.utils.streams import ROStream -from aws_encryption_sdk.structures import DataKey, EncryptedDataKey, KeyringTrace, RawDataKey -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Any, Iterable, Tuple, Union # noqa pylint: disable=unused-import -except ImportError: # pragma: no cover - # We only actually need these imports when running the mypy checks - pass +from ..identifiers import Algorithm +from ..internal.utils.streams import ROStream +from ..structures import DataKey @attr.s(hash=False) @@ -51,324 +40,38 @@ class EncryptionMaterialsRequest(object): :param int plaintext_length: Length of source plaintext (optional) """ - encryption_context = attr.ib( - validator=deep_mapping( - key_validator=instance_of(six.string_types), value_validator=instance_of(six.string_types) - ) - ) - frame_length = attr.ib(validator=instance_of(six.integer_types)) - plaintext_rostream = attr.ib(default=None, validator=optional(instance_of(ROStream))) - algorithm = attr.ib(default=None, validator=optional(instance_of(Algorithm))) - plaintext_length = attr.ib(default=None, validator=optional(instance_of(six.integer_types))) - - -def _data_key_to_raw_data_key(data_key): - # type: (Union[DataKey, RawDataKey, None]) -> Union[RawDataKey, None] - """Convert a :class:`DataKey` into a :class:`RawDataKey`.""" - if isinstance(data_key, RawDataKey) or data_key is None: - return data_key - - return RawDataKey.from_data_key(data_key=data_key) - - -@attr.s -class CryptographicMaterials(object): - """Cryptographic materials core. - - .. versionadded:: 1.5.0 - - :param Algorithm algorithm: Algorithm to use for encrypting message - :param dict encryption_context: Encryption context tied to `encrypted_data_keys` - :param RawDataKey data_encryption_key: Plaintext data key to use for encrypting message - :param keyring_trace: Any KeyRing trace entries - :type keyring_trace: list of :class:`KeyringTrace` - """ - - algorithm = attr.ib(validator=optional(instance_of(Algorithm))) - encryption_context = attr.ib( - validator=optional( - deep_mapping(key_validator=instance_of(six.string_types), value_validator=instance_of(six.string_types)) - ) + encryption_context = attr.ib(validator=attr.validators.instance_of(dict)) + frame_length = attr.ib(validator=attr.validators.instance_of(six.integer_types)) + plaintext_rostream = attr.ib( + default=None, validator=attr.validators.optional(attr.validators.instance_of(ROStream)) ) - data_encryption_key = attr.ib( - default=None, validator=optional(instance_of(RawDataKey)), converter=_data_key_to_raw_data_key + algorithm = attr.ib(default=None, validator=attr.validators.optional(attr.validators.instance_of(Algorithm))) + plaintext_length = attr.ib( + default=None, validator=attr.validators.optional(attr.validators.instance_of(six.integer_types)) ) - _keyring_trace = attr.ib( - default=attr.Factory(list), validator=optional(deep_iterable(member_validator=instance_of(KeyringTrace))) - ) - _initialized = False - - def __attrs_post_init__(self): - """Freeze attributes after initialization.""" - self._initialized = True - - def __setattr__(self, key, value): - # type: (str, Any) -> None - """Do not allow attributes to be changed once an instance is initialized.""" - if self._initialized: - raise AttributeError("can't set attribute") - - self._setattr(key, value) - - def _setattr(self, key, value): - # type: (str, Any) -> None - """Special __setattr__ to avoid having to perform multi-level super calls.""" - super(CryptographicMaterials, self).__setattr__(key, value) - - def _validate_data_encryption_key(self, data_encryption_key, keyring_trace, required_flags): - # type: (Union[DataKey, RawDataKey], KeyringTrace, Iterable[KeyringTraceFlag]) -> None - """Validate that the provided data encryption key and keyring trace match for each other and the materials. - - .. versionadded:: 1.5.0 - - :param RawDataKey data_encryption_key: Data encryption key - :param KeyringTrace keyring_trace: Keyring trace corresponding to data_encryption_key - :param required_flags: Iterable of required flags - :type required_flags: iterable of :class:`KeyringTraceFlag` - :raises AttributeError: if data encryption key is already set - :raises InvalidKeyringTraceError: if keyring trace does not match decrypt action - :raises InvalidKeyringTraceError: if keyring trace does not match data key provider - :raises InvalidDataKeyError: if data key length does not match algorithm suite - """ - if self.data_encryption_key is not None: - raise AttributeError("Data encryption key is already set.") - - for flag in required_flags: - if flag not in keyring_trace.flags: - raise InvalidKeyringTraceError("Keyring flags do not match action.") - - if keyring_trace.wrapping_key != data_encryption_key.key_provider: - raise InvalidKeyringTraceError("Keyring trace does not match data key provider.") - - if len(data_encryption_key.data_key) != self.algorithm.kdf_input_len: - raise InvalidDataKeyError( - "Invalid data key length {actual} must be {expected}.".format( - actual=len(data_encryption_key.data_key), expected=self.algorithm.kdf_input_len - ) - ) - - def _with_data_encryption_key(self, data_encryption_key, keyring_trace, required_flags): - # type: (Union[DataKey, RawDataKey], KeyringTrace, Iterable[KeyringTraceFlag]) -> CryptographicMaterials - """Get new cryptographic materials that include this data encryption key. - - .. versionadded:: 1.5.0 - :param RawDataKey data_encryption_key: Data encryption key - :param KeyringTrace keyring_trace: Trace of actions that a keyring performed - while getting this data encryption key - :param required_flags: Iterable of required flags - :type required_flags: iterable of :class:`KeyringTraceFlag` - :raises AttributeError: if data encryption key is already set - :raises InvalidKeyringTraceError: if keyring trace does not match required actions - :raises InvalidKeyringTraceError: if keyring trace does not match data key provider - :raises InvalidDataKeyError: if data key length does not match algorithm suite - """ - self._validate_data_encryption_key( - data_encryption_key=data_encryption_key, keyring_trace=keyring_trace, required_flags=required_flags - ) - new_materials = copy.copy(self) - - data_key = _data_key_to_raw_data_key(data_key=data_encryption_key) - new_materials._setattr( # simplify access to copies pylint: disable=protected-access - "data_encryption_key", data_key - ) - new_materials._keyring_trace.append(keyring_trace) # simplify access to copies pylint: disable=protected-access - - return new_materials - - @property - def keyring_trace(self): - # type: () -> Tuple[KeyringTrace] - """Return a read-only version of the keyring trace. - - :rtype: tuple - """ - return tuple(self._keyring_trace) - - -@attr.s(hash=False, init=False) -class EncryptionMaterials(CryptographicMaterials): +@attr.s(hash=False) +class EncryptionMaterials(object): """Encryption materials returned by a crypto material manager's `get_encryption_materials` method. .. versionadded:: 1.3.0 - .. versionadded:: 1.5.0 - - The **keyring_trace** parameter. - - .. versionadded:: 1.5.0 - - Most parameters are now optional. - - :param Algorithm algorithm: Algorithm to use for encrypting message - :param RawDataKey data_encryption_key: Plaintext data key to use for encrypting message (optional) - :param encrypted_data_keys: List of encrypted data keys (optional) - :type encrypted_data_keys: list of :class:`EncryptedDataKey` + :param algorithm: Algorithm to use for encrypting message + :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param data_encryption_key: Plaintext data key to use for encrypting message + :type data_encryption_key: aws_encryption_sdk.structures.DataKey + :param encrypted_data_keys: List of encrypted data keys + :type encrypted_data_keys: list of `aws_encryption_sdk.structures.EncryptedDataKey` :param dict encryption_context: Encryption context tied to `encrypted_data_keys` - :param bytes signing_key: Encoded signing key (optional) - :param keyring_trace: Any KeyRing trace entries (optional) - :type keyring_trace: list of :class:`KeyringTrace` + :param bytes signing_key: Encoded signing key """ - _encrypted_data_keys = attr.ib( - default=attr.Factory(list), validator=optional(deep_iterable(member_validator=instance_of(EncryptedDataKey))) - ) - signing_key = attr.ib(default=None, repr=False, validator=optional(instance_of(bytes))) - - def __init__( - self, - algorithm=None, - data_encryption_key=None, - encrypted_data_keys=None, - encryption_context=None, - signing_key=None, - **kwargs - ): # noqa we define this in the class docstring - if algorithm is None: - raise TypeError("algorithm must not be None") - - if encryption_context is None: - raise TypeError("encryption_context must not be None") - - if data_encryption_key is None and encrypted_data_keys: - # If data_encryption_key is not set, encrypted_data_keys MUST be either None or empty - raise TypeError("encrypted_data_keys cannot be provided without data_encryption_key") - - if encrypted_data_keys is None: - encrypted_data_keys = [] - - super(EncryptionMaterials, self).__init__( - algorithm=algorithm, - encryption_context=encryption_context, - data_encryption_key=data_encryption_key, - **kwargs - ) - self._setattr("signing_key", signing_key) - self._setattr("_encrypted_data_keys", encrypted_data_keys) - attr.validate(self) - - def __copy__(self): - # type: () -> EncryptionMaterials - """Do a shallow copy of this instance.""" - return EncryptionMaterials( - algorithm=self.algorithm, - data_encryption_key=self.data_encryption_key, - encrypted_data_keys=copy.copy(self._encrypted_data_keys), - encryption_context=self.encryption_context.copy(), - signing_key=self.signing_key, - keyring_trace=copy.copy(self._keyring_trace), - ) - - @property - def encrypted_data_keys(self): - # type: () -> Tuple[EncryptedDataKey] - """Return a read-only version of the encrypted data keys. - - :rtype: Tuple[EncryptedDataKey] - """ - return tuple(self._encrypted_data_keys) - - @property - def is_complete(self): - # type: () -> bool - """Determine whether these materials are sufficiently complete for use as encryption materials. - - :rtype: bool - """ - if self.data_encryption_key is None: - return False - - if not self.encrypted_data_keys: - return False - - if self.algorithm.signing_algorithm_info is not None and self.signing_key is None: - return False - - return True - - def with_data_encryption_key(self, data_encryption_key, keyring_trace): - # type: (Union[DataKey, RawDataKey], KeyringTrace) -> EncryptionMaterials - """Get new encryption materials that also include this data encryption key. - - .. versionadded:: 1.5.0 - - :param RawDataKey data_encryption_key: Data encryption key - :param KeyringTrace keyring_trace: Trace of actions that a keyring performed - while getting this data encryption key - :rtype: EncryptionMaterials - :raises AttributeError: if data encryption key is already set - :raises InvalidKeyringTraceError: if keyring trace does not match generate action - :raises InvalidKeyringTraceError: if keyring trace does not match data key provider - :raises InvalidDataKeyError: if data key length does not match algorithm suite - """ - return self._with_data_encryption_key( - data_encryption_key=data_encryption_key, - keyring_trace=keyring_trace, - required_flags={KeyringTraceFlag.GENERATED_DATA_KEY}, - ) - - def with_encrypted_data_key(self, encrypted_data_key, keyring_trace): - # type: (EncryptedDataKey, KeyringTrace) -> EncryptionMaterials - """Get new encryption materials that also include this encrypted data key with corresponding keyring trace. - - .. versionadded:: 1.5.0 - - :param EncryptedDataKey encrypted_data_key: Encrypted data key to add - :param KeyringTrace keyring_trace: Trace of actions that a keyring performed - while getting this encrypted data key - :rtype: EncryptionMaterials - :raises AttributeError: if data encryption key is not set - :raises InvalidKeyringTraceError: if keyring trace does not match generate action - :raises InvalidKeyringTraceError: if keyring trace does not match data key encryptor - """ - if self.data_encryption_key is None: - raise AttributeError("Data encryption key is not set.") - - if KeyringTraceFlag.ENCRYPTED_DATA_KEY not in keyring_trace.flags: - raise InvalidKeyringTraceError("Keyring flags do not match action.") - - if not all( - ( - keyring_trace.wrapping_key.provider_id == encrypted_data_key.key_provider.provider_id, - keyring_trace.wrapping_key.key_name == encrypted_data_key.key_provider.key_name, - ) - ): - raise InvalidKeyringTraceError("Keyring trace does not match data key encryptor.") - - new_materials = copy.copy(self) - - new_materials._encrypted_data_keys.append( # simplify access to copies pylint: disable=protected-access - encrypted_data_key - ) - new_materials._keyring_trace.append(keyring_trace) # simplify access to copies pylint: disable=protected-access - return new_materials - - def with_signing_key(self, signing_key): - # type: (bytes) -> EncryptionMaterials - """Get new encryption materials that also include this signing key. - - .. versionadded:: 1.5.0 - - :param bytes signing_key: Signing key - :rtype: EncryptionMaterials - :raises AttributeError: if signing key is already set - :raises SignatureKeyError: if algorithm suite does not support signing keys - """ - if self.signing_key is not None: - raise AttributeError("Signing key is already set.") - - if self.algorithm.signing_algorithm_info is None: - raise SignatureKeyError("Algorithm suite does not support signing keys.") - - new_materials = copy.copy(self) - - # Verify that the signing key matches the algorithm - Signer.from_key_bytes(algorithm=new_materials.algorithm, key_bytes=signing_key) - - new_materials._setattr("signing_key", signing_key) # simplify access to copies pylint: disable=protected-access - - return new_materials + algorithm = attr.ib(validator=attr.validators.instance_of(Algorithm)) + data_encryption_key = attr.ib(validator=attr.validators.instance_of(DataKey)) + encrypted_data_keys = attr.ib(validator=attr.validators.instance_of(set)) + encryption_context = attr.ib(validator=attr.validators.instance_of(dict)) + signing_key = attr.ib(default=None, validator=attr.validators.optional(attr.validators.instance_of(bytes))) @attr.s(hash=False) @@ -384,145 +87,21 @@ class DecryptionMaterialsRequest(object): :param dict encryption_context: Encryption context to provide to master keys for underlying decrypt requests """ - algorithm = attr.ib(validator=instance_of(Algorithm)) - encrypted_data_keys = attr.ib(validator=deep_iterable(member_validator=instance_of(EncryptedDataKey))) - encryption_context = attr.ib( - validator=deep_mapping( - key_validator=instance_of(six.string_types), value_validator=instance_of(six.string_types) - ) - ) - + 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)) -_DEFAULT_SENTINEL = object() - -@attr.s(hash=False, init=False) -class DecryptionMaterials(CryptographicMaterials): +@attr.s(hash=False) +class DecryptionMaterials(object): """Decryption materials returned by a crypto material manager's `decrypt_materials` method. .. versionadded:: 1.3.0 - .. versionadded:: 1.5.0 - - The **algorithm**, **data_encryption_key**, **encryption_context**, and **keyring_trace** parameters. - - .. versionadded:: 1.5.0 - - All parameters are now optional. - - :param Algorithm algorithm: Algorithm to use for encrypting message (optional) - :param RawDataKey data_encryption_key: Plaintext data key to use for encrypting message (optional) - :param dict encryption_context: Encryption context tied to `encrypted_data_keys` (optional) - :param bytes verification_key: Raw signature verification key (optional) - :param keyring_trace: Any KeyRing trace entries (optional) - :type keyring_trace: list of :class:`KeyringTrace` + :param data_key: Plaintext data key to use with message decryption + :type data_key: aws_encryption_sdk.structures.DataKey + :param bytes verification_key: Raw signature verification key """ - verification_key = attr.ib(default=None, repr=False, validator=optional(instance_of(bytes))) - - def __init__( - self, data_key=_DEFAULT_SENTINEL, verification_key=None, **kwargs - ): # noqa we define this in the class docstring - - legacy_data_key_set = data_key is not _DEFAULT_SENTINEL - data_encryption_key_set = "data_encryption_key" in kwargs - - if legacy_data_key_set and data_encryption_key_set: - raise TypeError("Either data_key or data_encryption_key can be used but not both") - - if legacy_data_key_set and not data_encryption_key_set: - kwargs["data_encryption_key"] = data_key - - for legacy_missing in ("algorithm", "encryption_context"): - if legacy_missing not in kwargs: - kwargs[legacy_missing] = None - - super(DecryptionMaterials, self).__init__(**kwargs) - - self._setattr("verification_key", verification_key) - attr.validate(self) - - def __copy__(self): - # type: () -> DecryptionMaterials - """Do a shallow copy of this instance.""" - return DecryptionMaterials( - algorithm=self.algorithm, - data_encryption_key=self.data_encryption_key, - encryption_context=copy.copy(self.encryption_context), - verification_key=self.verification_key, - keyring_trace=copy.copy(self._keyring_trace), - ) - - @property - def is_complete(self): - # type: () -> bool - """Determine whether these materials are sufficiently complete for use as decryption materials. - - :rtype: bool - """ - if None in (self.algorithm, self.encryption_context): - return False - - if self.data_encryption_key is None: - return False - - if self.algorithm.signing_algorithm_info is not None and self.verification_key is None: - return False - - return True - - @property - def data_key(self): - # type: () -> RawDataKey - """Backwards-compatible shim for access to data key.""" - return self.data_encryption_key - - def with_data_encryption_key(self, data_encryption_key, keyring_trace): - # type: (Union[DataKey, RawDataKey], KeyringTrace) -> DecryptionMaterials - """Get new decryption materials that also include this data encryption key. - - .. versionadded:: 1.5.0 - - :param RawDataKey data_encryption_key: Data encryption key - :param KeyringTrace keyring_trace: Trace of actions that a keyring performed - while getting this data encryption key - :rtype: DecryptionMaterials - :raises AttributeError: if data encryption key is already set - :raises InvalidKeyringTraceError: if keyring trace does not match decrypt action - :raises InvalidKeyringTraceError: if keyring trace does not match data key provider - :raises InvalidDataKeyError: if data key length does not match algorithm suite - """ - if self.algorithm is None: - raise AttributeError("Algorithm is not set") - - return self._with_data_encryption_key( - data_encryption_key=data_encryption_key, - keyring_trace=keyring_trace, - required_flags={KeyringTraceFlag.DECRYPTED_DATA_KEY}, - ) - - def with_verification_key(self, verification_key): - # type: (bytes) -> DecryptionMaterials - """Get new decryption materials that also include this verification key. - - .. versionadded:: 1.5.0 - - :param bytes verification_key: Verification key - :rtype: DecryptionMaterials - """ - if self.verification_key is not None: - raise AttributeError("Verification key is already set.") - - if self.algorithm.signing_algorithm_info is None: - raise SignatureKeyError("Algorithm suite does not support signing keys.") - - new_materials = copy.copy(self) - - # Verify that the verification key matches the algorithm - Verifier.from_key_bytes(algorithm=new_materials.algorithm, key_bytes=verification_key) - - new_materials._setattr( # simplify access to copies pylint: disable=protected-access - "verification_key", verification_key - ) - - return new_materials + data_key = attr.ib(validator=attr.validators.instance_of(DataKey)) + verification_key = attr.ib(default=None, validator=attr.validators.optional(attr.validators.instance_of(bytes))) diff --git a/src/aws_encryption_sdk/materials_managers/caching.py b/src/aws_encryption_sdk/materials_managers/caching.py index 5a8e9e9bd..992a39a7a 100644 --- a/src/aws_encryption_sdk/materials_managers/caching.py +++ b/src/aws_encryption_sdk/materials_managers/caching.py @@ -1,25 +1,32 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.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. """Caching crypto material manager.""" import logging import uuid import attr import six -from attr.validators import instance_of, optional -from aws_encryption_sdk.caches import ( +from ..caches import ( CryptoMaterialsCacheEntryHints, build_decryption_materials_cache_key, build_encryption_materials_cache_key, ) -from aws_encryption_sdk.caches.base import CryptoMaterialsCache -from aws_encryption_sdk.exceptions import CacheKeyError -from aws_encryption_sdk.internal.defaults import MAX_BYTES_PER_KEY, MAX_MESSAGES_PER_KEY -from aws_encryption_sdk.internal.str_ops import to_bytes -from aws_encryption_sdk.key_providers.base import MasterKeyProvider -from aws_encryption_sdk.keyrings.base import Keyring - +from ..caches.base import CryptoMaterialsCache +from ..exceptions import CacheKeyError +from ..internal.defaults import MAX_BYTES_PER_KEY, MAX_MESSAGES_PER_KEY +from ..internal.str_ops import to_bytes +from ..key_providers.base import MasterKeyProvider from . import EncryptionMaterialsRequest from .base import CryptoMaterialsManager from .default import DefaultCryptoMaterialsManager @@ -29,14 +36,10 @@ @attr.s(hash=False) class CachingCryptoMaterialsManager(CryptoMaterialsManager): - # pylint: disable=too-many-instance-attributes """Crypto material manager which caches results from an underlying material manager. .. versionadded:: 1.3.0 - .. versionadded:: 1.5.0 - The *keyring* parameter. - >>> import aws_encryption_sdk >>> kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ ... 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', @@ -56,16 +59,17 @@ class CachingCryptoMaterialsManager(CryptoMaterialsManager): value. If no partition name is provided, a random UUID will be used. .. note:: - Exactly one of ``backing_materials_manager``, ``keyring``, or ``master_key_provider`` must be provided. - - :param CryptoMaterialsCache cache: Crypto cache to use with material manager - :param CryptoMaterialsManager backing_materials_manager: - Crypto material manager to back this caching material manager - (either ``backing_materials_manager``, ``keyring``, or ``master_key_provider`` required) - :param MasterKeyProvider master_key_provider: Master key provider to use - (either ``backing_materials_manager``, ``keyring``, or ``master_key_provider`` required) - :param Keyring keyring: Keyring to use - (either ``backing_materials_manager``, ``keyring``, or ``master_key_provider`` required) + Either `backing_materials_manager` or `master_key_provider` must be provided. + `backing_materials_manager` will always be used if present. + + :param cache: Crypto cache to use with material manager + :type cache: aws_encryption_sdk.caches.base.CryptoMaterialsCache + :param backing_materials_manager: Crypto material manager to back this caching material manager + (either `backing_materials_manager` or `master_key_provider` required) + :type backing_materials_manager: aws_encryption_sdk.materials_managers.base.CryptoMaterialsManager + :param master_key_provider: Master key provider to use (either `backing_materials_manager` or + `master_key_provider` required) + :type master_key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider :param float max_age: Maximum time in seconds that a cache entry may be kept in the cache :param int max_messages_encrypted: Maximum number of messages that may be encrypted under a cache entry (optional) @@ -74,14 +78,21 @@ class CachingCryptoMaterialsManager(CryptoMaterialsManager): :param bytes partition_name: Partition name to use for this instance (optional) """ - cache = attr.ib(validator=instance_of(CryptoMaterialsCache)) - max_age = attr.ib(validator=instance_of(float)) - max_messages_encrypted = attr.ib(default=MAX_MESSAGES_PER_KEY, validator=instance_of(six.integer_types)) - max_bytes_encrypted = attr.ib(default=MAX_BYTES_PER_KEY, validator=instance_of(six.integer_types)) - partition_name = attr.ib(default=None, converter=to_bytes, validator=optional(instance_of(bytes))) - master_key_provider = attr.ib(default=None, validator=optional(instance_of(MasterKeyProvider))) - backing_materials_manager = attr.ib(default=None, validator=optional(instance_of(CryptoMaterialsManager))) - keyring = attr.ib(default=None, validator=optional(instance_of(Keyring))) + cache = attr.ib(validator=attr.validators.instance_of(CryptoMaterialsCache)) + max_age = attr.ib(validator=attr.validators.instance_of(float)) + max_messages_encrypted = attr.ib( + default=MAX_MESSAGES_PER_KEY, validator=attr.validators.instance_of(six.integer_types) + ) + max_bytes_encrypted = attr.ib(default=MAX_BYTES_PER_KEY, validator=attr.validators.instance_of(six.integer_types)) + partition_name = attr.ib( + default=None, converter=to_bytes, validator=attr.validators.optional(attr.validators.instance_of(bytes)) + ) + master_key_provider = attr.ib( + default=None, validator=attr.validators.optional(attr.validators.instance_of(MasterKeyProvider)) + ) + backing_materials_manager = attr.ib( + default=None, validator=attr.validators.optional(attr.validators.instance_of(CryptoMaterialsManager)) + ) def __attrs_post_init__(self): """Applies post-processing which cannot be handled by attrs.""" @@ -100,21 +111,10 @@ def __attrs_post_init__(self): if self.max_age <= 0.0: raise ValueError("max_age cannot be less than or equal to 0") - options_provided = [ - option is not None for option in (self.backing_materials_manager, self.keyring, self.master_key_provider) - ] - provided_count = len([is_set for is_set in options_provided if is_set]) - - if provided_count != 1: - raise TypeError("Exactly one of 'backing_materials_manager', 'keyring', or 'key_provider' must be provided") - if self.backing_materials_manager is None: - if self.master_key_provider is not None: - self.backing_materials_manager = DefaultCryptoMaterialsManager( - master_key_provider=self.master_key_provider - ) - else: - self.backing_materials_manager = DefaultCryptoMaterialsManager(keyring=self.keyring) + if self.master_key_provider is None: + raise TypeError("Either backing_materials_manager or master_key_provider must be defined") + self.backing_materials_manager = DefaultCryptoMaterialsManager(self.master_key_provider) if self.partition_name is None: self.partition_name = to_bytes(str(uuid.uuid4())) diff --git a/src/aws_encryption_sdk/materials_managers/default.py b/src/aws_encryption_sdk/materials_managers/default.py index 336e5243f..6d10465a9 100644 --- a/src/aws_encryption_sdk/materials_managers/default.py +++ b/src/aws_encryption_sdk/materials_managers/default.py @@ -1,21 +1,29 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.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. """Default crypto material manager class.""" import logging import attr -from attr.validators import instance_of, optional -from aws_encryption_sdk.exceptions import InvalidCryptographicMaterialsError, MasterKeyProviderError, SerializationError -from aws_encryption_sdk.internal.crypto.authentication import Signer, Verifier -from aws_encryption_sdk.internal.crypto.elliptic_curve import generate_ecc_signing_key -from aws_encryption_sdk.internal.defaults import ALGORITHM, ENCODED_SIGNER_KEY -from aws_encryption_sdk.internal.str_ops import to_str -from aws_encryption_sdk.internal.utils import prepare_data_keys -from aws_encryption_sdk.key_providers.base import MasterKeyProvider -from aws_encryption_sdk.keyrings.base import Keyring -from aws_encryption_sdk.materials_managers import DecryptionMaterials, EncryptionMaterials -from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager +from ..exceptions import MasterKeyProviderError, SerializationError +from ..internal.crypto.authentication import Signer, Verifier +from ..internal.crypto.elliptic_curve import generate_ecc_signing_key +from ..internal.defaults import ALGORITHM, ENCODED_SIGNER_KEY +from ..internal.str_ops import to_str +from ..internal.utils import prepare_data_keys +from ..key_providers.base import MasterKeyProvider +from . import DecryptionMaterials, EncryptionMaterials +from .base import CryptoMaterialsManager _LOGGER = logging.getLogger(__name__) @@ -26,26 +34,12 @@ class DefaultCryptoMaterialsManager(CryptoMaterialsManager): .. versionadded:: 1.3.0 - .. versionadded:: 1.5.0 - The *keyring* parameter. - - :param MasterKeyProvider master_key_provider: Master key provider to use - (either ``keyring`` or ``master_key_provider`` is required) - :param Keyring keyring: Keyring to use - (either ``keyring`` or ``master_key_provider`` is required) + :param master_key_provider: Master key provider to use + :type master_key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider """ algorithm = ALGORITHM - master_key_provider = attr.ib(default=None, validator=optional(instance_of(MasterKeyProvider))) - keyring = attr.ib(default=None, validator=optional(instance_of(Keyring))) - - def __attrs_post_init__(self): - """Apply input requirements.""" - both = self.master_key_provider is not None and self.keyring is not None - neither = self.master_key_provider is None and self.keyring is None - - if both or neither: - raise TypeError("Exactly one of 'keyring' or 'master_key_provider' must be supplied.") + master_key_provider = attr.ib(validator=attr.validators.instance_of(MasterKeyProvider)) def _generate_signing_key_and_update_encryption_context(self, algorithm, encryption_context): """Generates a signing key based on the provided algorithm. @@ -64,7 +58,7 @@ def _generate_signing_key_and_update_encryption_context(self, algorithm, encrypt encryption_context[ENCODED_SIGNER_KEY] = to_str(signer.encoded_public_key()) return signer.key_bytes() - def _get_encryption_materials_using_master_key_provider(self, request): + def get_encryption_materials(self, request): """Creates encryption materials using underlying master key provider. :param request: encryption materials request @@ -107,64 +101,6 @@ def _get_encryption_materials_using_master_key_provider(self, request): signing_key=signing_key, ) - def _get_encryption_materials_using_keyring(self, request): - """Creates encryption materials using underlying keyring. - - :param request: encryption materials request - :type request: aws_encryption_sdk.materials_managers.EncryptionMaterialsRequest - :returns: encryption materials - :rtype: aws_encryption_sdk.materials_managers.EncryptionMaterials - :raises InvalidCryptographicMaterialsError: if keyring cannot complete encryption materials - :raises InvalidCryptographicMaterialsError: - if encryption materials received from keyring do not match request - """ - algorithm = request.algorithm if request.algorithm is not None else self.algorithm - encryption_context = request.encryption_context.copy() - - signing_key = self._generate_signing_key_and_update_encryption_context(algorithm, encryption_context) - - expected_encryption_context = encryption_context.copy() - - encryption_materials = EncryptionMaterials( - algorithm=algorithm, encryption_context=encryption_context, signing_key=signing_key, - ) - - final_materials = self.keyring.on_encrypt(encryption_materials=encryption_materials) - - materials_are_valid = ( - final_materials.algorithm is algorithm, - final_materials.encryption_context == expected_encryption_context, - final_materials.signing_key is signing_key, - ) - if not all(materials_are_valid): - raise InvalidCryptographicMaterialsError("Encryption materials do not match request!") - - if not final_materials.is_complete: - raise InvalidCryptographicMaterialsError("Encryption materials are incomplete!") - - _LOGGER.debug("Post-encrypt encryption context: %s", encryption_context) - - return final_materials - - def get_encryption_materials(self, request): - """Creates encryption materials using underlying master key provider. - - :param request: encryption materials request - :type request: aws_encryption_sdk.materials_managers.EncryptionMaterialsRequest - :returns: encryption materials - :rtype: aws_encryption_sdk.materials_managers.EncryptionMaterials - :raises InvalidCryptographicMaterialsError: if keyring cannot complete encryption materials - :raises InvalidCryptographicMaterialsError: - if encryption materials received from keyring do not match request - :raises MasterKeyProviderError: if no master keys are available from the underlying master key provider - :raises MasterKeyProviderError: if the primary master key provided by the underlying master key provider - is not included in the full set of master keys provided by that provider - """ - if self.master_key_provider is not None: - return self._get_encryption_materials_using_master_key_provider(request) - - return self._get_encryption_materials_using_keyring(request) - def _load_verification_key_from_encryption_context(self, algorithm, encryption_context): """Loads the verification key from the encryption context if used by algorithm suite. @@ -188,7 +124,7 @@ def _load_verification_key_from_encryption_context(self, algorithm, encryption_c verifier = Verifier.from_encoded_point(algorithm=algorithm, encoded_point=encoded_verification_key) return verifier.key_bytes() - def _decrypt_materials_using_master_key_provider(self, request): + def decrypt_materials(self, request): """Obtains a plaintext data key from one or more encrypted data keys using underlying master key provider. @@ -207,55 +143,3 @@ def _decrypt_materials_using_master_key_provider(self, request): ) return DecryptionMaterials(data_key=data_key, verification_key=verification_key) - - def _decrypt_materials_using_keyring(self, request): - """Obtains a plaintext data key from one or more encrypted data keys - using underlying keyring. - - :param request: decrypt materials request - :type request: aws_encryption_sdk.materials_managers.DecryptionMaterialsRequest - :returns: decryption materials - :rtype: aws_encryption_sdk.materials_managers.DecryptionMaterials - :raises InvalidCryptographicMaterialsError: if keyring cannot complete decryption materials - :raises InvalidCryptographicMaterialsError: - if decryption materials received from keyring do not match request - """ - verification_key = self._load_verification_key_from_encryption_context( - algorithm=request.algorithm, encryption_context=request.encryption_context - ) - decryption_materials = DecryptionMaterials( - algorithm=request.algorithm, - encryption_context=request.encryption_context.copy(), - verification_key=verification_key, - ) - - final_materials = self.keyring.on_decrypt( - decryption_materials=decryption_materials, encrypted_data_keys=request.encrypted_data_keys - ) - - materials_are_valid = ( - final_materials.algorithm is request.algorithm, - final_materials.encryption_context == request.encryption_context, - final_materials.verification_key is verification_key, - ) - if not all(materials_are_valid): - raise InvalidCryptographicMaterialsError("Decryption materials do not match request!") - - if not final_materials.is_complete: - raise InvalidCryptographicMaterialsError("Decryption materials are incomplete!") - - return final_materials - - def decrypt_materials(self, request): - """Obtains a plaintext data key from one or more encrypted data keys - using underlying master key provider. - - :param request: decrypt materials request - :type request: aws_encryption_sdk.materials_managers.DecryptionMaterialsRequest - :returns: decryption materials - :rtype: aws_encryption_sdk.materials_managers.DecryptionMaterials - """ - if self.master_key_provider is not None: - return self._decrypt_materials_using_master_key_provider(request) - - return self._decrypt_materials_using_keyring(request) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index ff549563d..504f68977 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -1,5 +1,15 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.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. """High level AWS Encryption SDK client for streaming objects.""" from __future__ import division @@ -10,7 +20,6 @@ import attr import six -from attr.validators import instance_of, optional import aws_encryption_sdk.internal.utils from aws_encryption_sdk.exceptions import ( @@ -45,7 +54,6 @@ serialize_non_framed_open, ) from aws_encryption_sdk.key_providers.base import MasterKeyProvider -from aws_encryption_sdk.keyrings.base import Keyring from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager @@ -59,19 +67,14 @@ class _ClientConfig(object): """Parent configuration object for StreamEncryptor and StreamDecryptor objects. - .. versionadded:: 1.5.0 - The *keyring* parameter. - :param source: Source data to encrypt or decrypt :type source: str, bytes, io.IOBase, or file - :param CryptoMaterialsManager materials_manager: - Cryptographic materials manager to use for encryption - (either ``materials_manager``, ``keyring``, ``key_provider`` required) - :param Keyring keyring: Keyring to use for encryption - (either ``materials_manager``, ``keyring``, ``key_provider`` required) - :param MasterKeyProvider key_provider: - Master key provider to use for encryption - (either ``materials_manager``, ``keyring``, ``key_provider`` required) + :param materials_manager: `CryptoMaterialsManager` from which to obtain cryptographic materials + (either `materials_manager` or `key_provider` required) + :type materials_manager: aws_encryption_sdk.materials_manager.base.CryptoMaterialsManager + :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 int source_length: Length of source data (optional) .. note:: @@ -80,27 +83,28 @@ class _ClientConfig(object): """ source = attr.ib(hash=True, converter=aws_encryption_sdk.internal.utils.prep_stream_data) - materials_manager = attr.ib(hash=True, default=None, validator=optional(instance_of(CryptoMaterialsManager))) - keyring = attr.ib(default=None, validator=optional(instance_of(Keyring))) - key_provider = attr.ib(hash=True, default=None, validator=optional(instance_of(MasterKeyProvider))) - source_length = attr.ib(hash=True, default=None, validator=optional(instance_of(six.integer_types))) + 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)) + ) + source_length = attr.ib( + hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(six.integer_types)) + ) line_length = attr.ib( - hash=True, default=LINE_LENGTH, validator=instance_of(six.integer_types) + 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 __attrs_post_init__(self): """Normalize inputs to crypto material manager.""" - options_provided = [option is not None for option in (self.materials_manager, self.keyring, self.key_provider)] - provided_count = len([is_set for is_set in options_provided if is_set]) - - if provided_count != 1: - raise TypeError("Exactly one of 'materials_manager', 'keyring', or 'key_provider' must be provided") + 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: - if self.key_provider is not None: - self.materials_manager = DefaultCryptoMaterialsManager(master_key_provider=self.key_provider) - else: - self.materials_manager = DefaultCryptoMaterialsManager(keyring=self.keyring) + self.materials_manager = DefaultCryptoMaterialsManager(master_key_provider=self.key_provider) class _EncryptionStream(io.IOBase): @@ -137,7 +141,6 @@ class _EncryptionStream(io.IOBase): _message_prepped = None # type: bool source_stream = None _stream_length = None # type: int - keyring_trace = () def __new__(cls, **kwargs): """Perform necessary handling for _EncryptionStream instances that should be @@ -308,19 +311,14 @@ def next(self): class EncryptorConfig(_ClientConfig): """Configuration object for StreamEncryptor class. - .. versionadded:: 1.5.0 - The *keyring* parameter. - :param source: Source data to encrypt or decrypt :type source: str, bytes, io.IOBase, or file - :param CryptoMaterialsManager materials_manager: - Cryptographic materials manager to use for encryption - (either ``materials_manager``, ``keyring``, ``key_provider`` required) - :param Keyring keyring: Keyring to use for encryption - (either ``materials_manager``, ``keyring``, ``key_provider`` required) - :param MasterKeyProvider key_provider: - Master key provider to use for encryption - (either ``materials_manager``, ``keyring``, ``key_provider`` required) + :param materials_manager: `CryptoMaterialsManager` from which to obtain cryptographic materials + (either `materials_manager` or `key_provider` required) + :type materials_manager: aws_encryption_sdk.materials_manager.base.CryptoMaterialsManager + :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 int source_length: Length of source data (optional) .. note:: @@ -362,21 +360,16 @@ class StreamEncryptor(_EncryptionStream): # pylint: disable=too-many-instance-a .. note:: If config is provided, all other parameters are ignored. - .. versionadded:: 1.5.0 - The *keyring* parameter. - :param config: Client configuration object (config or individual parameters required) :type config: aws_encryption_sdk.streaming_client.EncryptorConfig :param source: Source data to encrypt or decrypt :type source: str, bytes, io.IOBase, or file - :param CryptoMaterialsManager materials_manager: - Cryptographic materials manager to use for encryption - (either ``materials_manager``, ``keyring``, ``key_provider`` required) - :param Keyring keyring: Keyring to use for encryption - (either ``materials_manager``, ``keyring``, ``key_provider`` required) - :param MasterKeyProvider key_provider: - Master key provider to use for encryption - (either ``materials_manager``, ``keyring``, ``key_provider`` required) + :param materials_manager: `CryptoMaterialsManager` from which to obtain cryptographic materials + (either `materials_manager` or `key_provider` required) + :type materials_manager: aws_encryption_sdk.materials_manager.base.CryptoMaterialsManager + :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 int source_length: Length of source data (optional) .. note:: @@ -444,7 +437,6 @@ def _prep_message(self): self._encryption_materials = self.config.materials_manager.get_encryption_materials( request=encryption_materials_request ) - self.keyring_trace = self._encryption_materials.keyring_trace if self.config.algorithm is not None and self._encryption_materials.algorithm != self.config.algorithm: raise ActionNotAllowedError( @@ -675,19 +667,14 @@ def close(self): class DecryptorConfig(_ClientConfig): """Configuration object for StreamDecryptor class. - .. versionadded:: 1.5.0 - The *keyring* parameter. - :param source: Source data to encrypt or decrypt :type source: str, bytes, io.IOBase, or file - :param CryptoMaterialsManager materials_manager: - Cryptographic materials manager to use for encryption - (either ``materials_manager``, ``keyring``, ``key_provider`` required) - :param Keyring keyring: Keyring to use for encryption - (either ``materials_manager``, ``keyring``, ``key_provider`` required) - :param MasterKeyProvider key_provider: - Master key provider to use for encryption - (either ``materials_manager``, ``keyring``, ``key_provider`` required) + :param materials_manager: `CryptoMaterialsManager` from which to obtain cryptographic materials + (either `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) + :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider :param int source_length: Length of source data (optional) .. note:: @@ -714,21 +701,16 @@ class StreamDecryptor(_EncryptionStream): # pylint: disable=too-many-instance-a .. note:: If config is provided, all other parameters are ignored. - .. versionadded:: 1.5.0 - The *keyring* parameter. - :param config: Client configuration object (config or individual parameters required) :type config: aws_encryption_sdk.streaming_client.DecryptorConfig :param source: Source data to encrypt or decrypt :type source: str, bytes, io.IOBase, or file - :param CryptoMaterialsManager materials_manager: - Cryptographic materials manager to use for encryption - (either ``materials_manager``, ``keyring``, ``key_provider`` required) - :param Keyring keyring: Keyring to use for encryption - (either ``materials_manager``, ``keyring``, ``key_provider`` required) - :param MasterKeyProvider key_provider: - Master key provider to use for encryption - (either ``materials_manager``, ``keyring``, ``key_provider`` required) + :param materials_manager: `CryptoMaterialsManager` from which to obtain cryptographic materials + (either `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) + :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider :param int source_length: Length of source data (optional) .. note:: @@ -781,8 +763,6 @@ def _read_header(self): encryption_context=header.encryption_context, ) decryption_materials = self.config.materials_manager.decrypt_materials(request=decrypt_materials_request) - self.keyring_trace = decryption_materials.keyring_trace - if decryption_materials.verification_key is None: self.verifier = None else: diff --git a/src/aws_encryption_sdk/structures.py b/src/aws_encryption_sdk/structures.py index 4e8275a2c..97e4c1d13 100644 --- a/src/aws_encryption_sdk/structures.py +++ b/src/aws_encryption_sdk/structures.py @@ -11,59 +11,59 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Public data structures for aws_encryption_sdk.""" -import copy - import attr import six -from attr.validators import deep_iterable, deep_mapping, instance_of, optional -from aws_encryption_sdk.identifiers import Algorithm, ContentType, KeyringTraceFlag, ObjectType, SerializationVersion +import aws_encryption_sdk.identifiers from aws_encryption_sdk.internal.str_ops import to_bytes, to_str -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Tuple # noqa pylint: disable=unused-import -except ImportError: # pragma: no cover - # We only actually need these imports when running the mypy checks - pass - @attr.s(hash=True) -class MasterKeyInfo(object): - """Contains information necessary to identify a Master Key. +class MessageHeader(object): + # pylint: disable=too-many-instance-attributes + """Deserialized message header object. - .. note:: + :param version: Message format version, per spec + :type version: aws_encryption_sdk.identifiers.SerializationVersion + :param type: Message content type, per spec + :type type: aws_encryption_sdk.identifiers.ObjectType + :param algorithm: Algorithm to use for encryption + :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param bytes message_id: Message ID + :param dict encryption_context: Dictionary defining encryption context + :param encrypted_data_keys: Encrypted data keys + :type encrypted_data_keys: set of :class:`aws_encryption_sdk.structures.EncryptedDataKey` + :param content_type: Message content framing type (framed/non-framed) + :type content_type: aws_encryption_sdk.identifiers.ContentType + :param bytes content_aad_length: empty + :param int header_iv_length: Bytes in Initialization Vector value found in header + :param int frame_length: Length of message frame in bytes + """ - The only keyring or master key that should need to set ``key_name`` is the Raw AES keyring/master key. - For all other keyrings and master keys, ``key_info`` and ``key_name`` should always be the same. + version = attr.ib( + hash=True, validator=attr.validators.instance_of(aws_encryption_sdk.identifiers.SerializationVersion) + ) + type = attr.ib(hash=True, validator=attr.validators.instance_of(aws_encryption_sdk.identifiers.ObjectType)) + algorithm = attr.ib(hash=True, validator=attr.validators.instance_of(aws_encryption_sdk.identifiers.Algorithm)) + message_id = attr.ib(hash=True, validator=attr.validators.instance_of(bytes)) + encryption_context = attr.ib(hash=True, validator=attr.validators.instance_of(dict)) + encrypted_data_keys = attr.ib(hash=True, validator=attr.validators.instance_of(set)) + content_type = attr.ib(hash=True, validator=attr.validators.instance_of(aws_encryption_sdk.identifiers.ContentType)) + content_aad_length = attr.ib(hash=True, validator=attr.validators.instance_of(six.integer_types)) + header_iv_length = attr.ib(hash=True, validator=attr.validators.instance_of(six.integer_types)) + frame_length = attr.ib(hash=True, validator=attr.validators.instance_of(six.integer_types)) - .. versionadded:: 1.5.0 - ``key_name`` +@attr.s(hash=True) +class MasterKeyInfo(object): + """Contains information necessary to identify a Master Key. :param str provider_id: MasterKey provider_id value :param bytes key_info: MasterKey key_info value - :param bytes key_name: Key name if different than key_info (optional) """ - provider_id = attr.ib(hash=True, validator=instance_of((six.string_types, bytes)), converter=to_str) - key_info = attr.ib(hash=True, validator=instance_of((six.string_types, bytes)), converter=to_bytes) - key_name = attr.ib( - hash=True, default=None, validator=optional(instance_of((six.string_types, bytes))), converter=to_bytes - ) - - def __attrs_post_init__(self): - """Set ``key_name`` if not already set.""" - if self.key_name is None: - self.key_name = self.key_info - - @property - def key_namespace(self): - """Access the key namespace value (previously, provider ID). - - .. versionadded:: 1.5.0 - - """ - return self.provider_id + provider_id = attr.ib(hash=True, validator=attr.validators.instance_of((six.string_types, bytes)), converter=to_str) + key_info = attr.ib(hash=True, validator=attr.validators.instance_of((six.string_types, bytes)), converter=to_bytes) @attr.s(hash=True) @@ -75,20 +75,8 @@ class RawDataKey(object): :param bytes data_key: Plaintext data key """ - key_provider = attr.ib(hash=True, validator=instance_of(MasterKeyInfo)) - data_key = attr.ib(hash=True, repr=False, validator=instance_of(bytes)) - - @classmethod - def from_data_key(cls, data_key): - # type: (DataKey) -> RawDataKey - """Build an :class:`RawDataKey` from a :class:`DataKey`. - - .. versionadded:: 1.5.0 - """ - if not isinstance(data_key, DataKey): - raise TypeError("data_key must be type DataKey not {}".format(type(data_key).__name__)) - - return RawDataKey(key_provider=copy.copy(data_key.key_provider), data_key=copy.copy(data_key.data_key)) + key_provider = attr.ib(hash=True, validator=attr.validators.instance_of(MasterKeyInfo)) + data_key = attr.ib(hash=True, repr=False, validator=attr.validators.instance_of(bytes)) @attr.s(hash=True) @@ -101,9 +89,9 @@ class DataKey(object): :param bytes encrypted_data_key: Encrypted data key """ - key_provider = attr.ib(hash=True, validator=instance_of(MasterKeyInfo)) - data_key = attr.ib(hash=True, repr=False, validator=instance_of(bytes)) - encrypted_data_key = attr.ib(hash=True, validator=instance_of(bytes)) + key_provider = attr.ib(hash=True, validator=attr.validators.instance_of(MasterKeyInfo)) + data_key = attr.ib(hash=True, repr=False, validator=attr.validators.instance_of(bytes)) + encrypted_data_key = attr.ib(hash=True, validator=attr.validators.instance_of(bytes)) @attr.s(hash=True) @@ -115,105 +103,5 @@ class EncryptedDataKey(object): :param bytes encrypted_data_key: Encrypted data key """ - key_provider = attr.ib(hash=True, validator=instance_of(MasterKeyInfo)) - encrypted_data_key = attr.ib(hash=True, validator=instance_of(bytes)) - - @classmethod - def from_data_key(cls, data_key): - # type: (DataKey) -> EncryptedDataKey - """Build an :class:`EncryptedDataKey` from a :class:`DataKey`. - - .. versionadded:: 1.5.0 - """ - if not isinstance(data_key, DataKey): - raise TypeError("data_key must be type DataKey not {}".format(type(data_key).__name__)) - - return EncryptedDataKey( - key_provider=copy.copy(data_key.key_provider), encrypted_data_key=copy.copy(data_key.encrypted_data_key) - ) - - -@attr.s -class KeyringTrace(object): - """Record of all actions that a KeyRing performed with a wrapping key. - - .. versionadded:: 1.5.0 - - :param MasterKeyInfo wrapping_key: Wrapping key used - :param Set[KeyringTraceFlag] flags: Actions performed - """ - - wrapping_key = attr.ib(validator=instance_of(MasterKeyInfo)) - flags = attr.ib(validator=deep_iterable(member_validator=instance_of(KeyringTraceFlag))) - - -@attr.s(hash=True) -class MessageHeader(object): - # pylint: disable=too-many-instance-attributes - """Deserialized message header object. - - :param SerializationVersion version: Message format version, per spec - :param ObjectType type: Message content type, per spec - :param AlgorithmSuite algorithm: Algorithm to use for encryption - :param bytes message_id: Message ID - :param Dict[str,str] encryption_context: Dictionary defining encryption context - :param Sequence[EncryptedDataKey] encrypted_data_keys: Encrypted data keys - :param ContentType content_type: Message content framing type (framed/non-framed) - :param int content_aad_length: empty - :param int header_iv_length: Bytes in Initialization Vector value found in header - :param int frame_length: Length of message frame in bytes - """ - - version = attr.ib(hash=True, validator=instance_of(SerializationVersion)) - type = attr.ib(hash=True, validator=instance_of(ObjectType)) - algorithm = attr.ib(hash=True, validator=instance_of(Algorithm)) - message_id = attr.ib(hash=True, validator=instance_of(bytes)) - encryption_context = attr.ib( - hash=True, - validator=deep_mapping( - key_validator=instance_of(six.string_types), value_validator=instance_of(six.string_types) - ), - ) - encrypted_data_keys = attr.ib(hash=True, validator=deep_iterable(member_validator=instance_of(EncryptedDataKey))) - content_type = attr.ib(hash=True, validator=instance_of(ContentType)) - content_aad_length = attr.ib(hash=True, validator=instance_of(six.integer_types)) - header_iv_length = attr.ib(hash=True, validator=instance_of(six.integer_types)) - frame_length = attr.ib(hash=True, validator=instance_of(six.integer_types)) - - -@attr.s -class CryptoResult(object): - """Result container for one-shot cryptographic API results. - - .. versionadded:: 1.5.0 - - .. note:: - - For backwards compatibility, - this container also unpacks like a 2-member tuple. - This allows for backwards compatibility with the previous outputs. - - :param bytes result: Binary results of the cryptographic operation - :param MessageHeader header: Encrypted message metadata - :param Tuple[KeyringTrace] keyring_trace: Keyring trace entries - """ - - result = attr.ib(validator=instance_of(bytes)) - header = attr.ib(validator=instance_of(MessageHeader)) - keyring_trace = attr.ib(validator=deep_iterable(member_validator=instance_of(KeyringTrace))) - - def __attrs_post_init__(self): - """Construct the inner tuple for backwards compatibility.""" - self._legacy_container = (self.result, self.header) - - def __len__(self): - """Emulate the inner tuple.""" - return self._legacy_container.__len__() - - def __iter__(self): - """Emulate the inner tuple.""" - return self._legacy_container.__iter__() - - def __getitem__(self, key): - """Emulate the inner tuple.""" - return self._legacy_container.__getitem__(key) + key_provider = attr.ib(hash=True, validator=attr.validators.instance_of(MasterKeyInfo)) + encrypted_data_key = attr.ib(hash=True, validator=attr.validators.instance_of(bytes)) diff --git a/src/pylintrc b/src/pylintrc index 1722f208c..af00ced56 100644 --- a/src/pylintrc +++ b/src/pylintrc @@ -33,9 +33,6 @@ additional-builtins = raw_input [DESIGN] max-args = 10 -[SIMILARITIES] -ignore-imports = yes - [FORMAT] max-line-length = 120 diff --git a/test/__init__.py b/test/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/functional/__init__.py b/test/functional/__init__.py index ad0e71d6c..53a960891 100644 --- a/test/functional/__init__.py +++ b/test/functional/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 @@ -10,4 +10,3 @@ # 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. -"""Dummy stub to make linters work better.""" diff --git a/test/functional/functional_test_utils.py b/test/functional/functional_test_utils.py deleted file mode 100644 index 3822ef5fa..000000000 --- a/test/functional/functional_test_utils.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Utility functions to handle configuration and credentials setup for functional tests.""" -import boto3 -import pytest -from moto.kms import mock_kms - -FAKE_REGION = "us-west-2" - - -def _create_cmk(): - # type: () -> str - kms = boto3.client("kms", region_name=FAKE_REGION) - response = kms.create_key() - return response["KeyMetadata"]["Arn"] - - -@pytest.fixture -def fake_generator(): - with mock_kms(): - yield _create_cmk() - - -@pytest.fixture -def fake_generator_and_child(): - with mock_kms(): - generator = _create_cmk() - child = _create_cmk() - yield generator, child diff --git a/test/functional/internal/crypto/__init__.py b/test/functional/internal/crypto/__init__.py deleted file mode 100644 index ad0e71d6c..000000000 --- a/test/functional/internal/crypto/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2019 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. -"""Dummy stub to make linters work better.""" diff --git a/test/functional/keyrings/aws_kms/__init__.py b/test/functional/keyrings/aws_kms/__init__.py deleted file mode 100644 index d548f9b1f..000000000 --- a/test/functional/keyrings/aws_kms/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Dummy stub to make linters work better.""" diff --git a/test/functional/keyrings/aws_kms/test_aws_kms.py b/test/functional/keyrings/aws_kms/test_aws_kms.py deleted file mode 100644 index 2aca1c15a..000000000 --- a/test/functional/keyrings/aws_kms/test_aws_kms.py +++ /dev/null @@ -1,494 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Functional tests for ``aws_encryption_sdk.keyrings.aws_kms``.""" -import itertools -import logging -import os - -import boto3 -import pytest -from moto.kms import mock_kms - -from aws_encryption_sdk.exceptions import DecryptKeyError, EncryptKeyError -from aws_encryption_sdk.identifiers import KeyringTraceFlag -from aws_encryption_sdk.internal.defaults import ALGORITHM -from aws_encryption_sdk.keyrings.aws_kms import ( - KEY_NAMESPACE, - AwsKmsKeyring, - _AwsKmsDiscoveryKeyring, - _AwsKmsSingleCmkKeyring, - _do_aws_kms_decrypt, - _do_aws_kms_encrypt, - _do_aws_kms_generate_data_key, - _try_aws_kms_decrypt, -) -from aws_encryption_sdk.keyrings.aws_kms.client_suppliers import DefaultClientSupplier -from aws_encryption_sdk.materials_managers import DecryptionMaterials, EncryptionMaterials -from aws_encryption_sdk.structures import EncryptedDataKey, KeyringTrace, MasterKeyInfo, RawDataKey - -# used as fixtures -from ...functional_test_utils import fake_generator # noqa pylint: disable=unused-import -from ...functional_test_utils import fake_generator_and_child # noqa pylint: disable=unused-import -from ...functional_test_utils import FAKE_REGION - -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Iterable, List # noqa pylint: disable=unused-import -except ImportError: # pragma: no cover - # We only actually need these imports when running the mypy checks - pass - -pytestmark = [pytest.mark.functional, pytest.mark.local] - - -def _matching_flags(wrapping_key, keyring_trace): - # type: (MasterKeyInfo, Iterable[KeyringTrace]) -> List[KeyringTraceFlag] - return list( - itertools.chain.from_iterable([entry.flags for entry in keyring_trace if entry.wrapping_key == wrapping_key]) - ) - - -def test_aws_kms_single_cmk_keyring_on_encrypt_empty_materials(fake_generator): - keyring = _AwsKmsSingleCmkKeyring(key_id=fake_generator, client_supplier=DefaultClientSupplier()) - - initial_materials = EncryptionMaterials(algorithm=ALGORITHM, encryption_context={}) - - result_materials = keyring.on_encrypt(initial_materials) - - assert result_materials.data_encryption_key is not None - assert len(result_materials.encrypted_data_keys) == 1 - - generator_flags = _matching_flags( - MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=fake_generator), result_materials.keyring_trace - ) - - assert KeyringTraceFlag.GENERATED_DATA_KEY in generator_flags - assert KeyringTraceFlag.ENCRYPTED_DATA_KEY in generator_flags - assert KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT in generator_flags - - -def test_aws_kms_single_cmk_keyring_on_encrypt_existing_data_key(fake_generator): - keyring = _AwsKmsSingleCmkKeyring(key_id=fake_generator, client_supplier=DefaultClientSupplier()) - - initial_materials = EncryptionMaterials( - algorithm=ALGORITHM, - encryption_context={}, - data_encryption_key=RawDataKey( - key_provider=MasterKeyInfo(provider_id="foo", key_info=b"bar"), data_key=os.urandom(ALGORITHM.kdf_input_len) - ), - ) - - result_materials = keyring.on_encrypt(initial_materials) - - assert result_materials is not initial_materials - assert result_materials.data_encryption_key is not None - assert len(result_materials.encrypted_data_keys) == 1 - - generator_flags = _matching_flags( - MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=fake_generator), result_materials.keyring_trace - ) - - assert KeyringTraceFlag.GENERATED_DATA_KEY not in generator_flags - assert KeyringTraceFlag.ENCRYPTED_DATA_KEY in generator_flags - assert KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT in generator_flags - - -@mock_kms -def test_aws_kms_single_cmk_keyring_on_encrypt_fail(): - # In this context there are no KMS CMKs, so any calls to KMS will fail. - keyring = _AwsKmsSingleCmkKeyring(key_id="foo", client_supplier=DefaultClientSupplier()) - - initial_materials = EncryptionMaterials(algorithm=ALGORITHM, encryption_context={}) - - with pytest.raises(EncryptKeyError) as excinfo: - keyring.on_encrypt(initial_materials) - - excinfo.match(r"Unable to generate or encrypt data key using *") - - -@mock_kms -def test_aws_kms_single_cmk_keyring_on_decrypt_existing_datakey(caplog): - # In this context there are no KMS CMKs, so any calls to KMS will fail. - caplog.set_level(logging.DEBUG) - keyring = _AwsKmsSingleCmkKeyring(key_id="foo", client_supplier=DefaultClientSupplier()) - - initial_materials = DecryptionMaterials( - algorithm=ALGORITHM, - encryption_context={}, - data_encryption_key=RawDataKey( - key_provider=MasterKeyInfo(provider_id="foo", key_info=b"bar"), data_key=os.urandom(ALGORITHM.kdf_input_len) - ), - ) - - result_materials = keyring.on_decrypt( - decryption_materials=initial_materials, - encrypted_data_keys=( - EncryptedDataKey( - key_provider=MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=b"foo"), encrypted_data_key=b"bar" - ), - ), - ) - - assert result_materials.data_encryption_key == initial_materials.data_encryption_key - - log_data = caplog.text - # This means that it did NOT try to decrypt the EDK. - assert "Unable to decrypt encrypted data key from" not in log_data - - -def test_aws_kms_single_cmk_keyring_on_decrypt_single_cmk(fake_generator): - keyring = _AwsKmsSingleCmkKeyring(key_id=fake_generator, client_supplier=DefaultClientSupplier()) - - initial_encryption_materials = EncryptionMaterials(algorithm=ALGORITHM, encryption_context={}) - - encryption_materials = keyring.on_encrypt(initial_encryption_materials) - - initial_decryption_materials = DecryptionMaterials( - algorithm=encryption_materials.algorithm, encryption_context=encryption_materials.encryption_context - ) - - result_materials = keyring.on_decrypt( - decryption_materials=initial_decryption_materials, encrypted_data_keys=encryption_materials.encrypted_data_keys - ) - - assert result_materials is not initial_decryption_materials - assert result_materials.data_encryption_key is not None - - generator_flags = _matching_flags( - MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=fake_generator), result_materials.keyring_trace - ) - - assert KeyringTraceFlag.DECRYPTED_DATA_KEY in generator_flags - assert KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT in generator_flags - - -def test_aws_kms_single_cmk_keyring_on_decrypt_multiple_cmk(fake_generator_and_child): - generator, child = fake_generator_and_child - - encrypting_keyring = AwsKmsKeyring(generator_key_id=generator, key_ids=(child,)) - decrypting_keyring = _AwsKmsSingleCmkKeyring(key_id=child, client_supplier=DefaultClientSupplier()) - - initial_encryption_materials = EncryptionMaterials(algorithm=ALGORITHM, encryption_context={}) - - encryption_materials = encrypting_keyring.on_encrypt(initial_encryption_materials) - - initial_decryption_materials = DecryptionMaterials( - algorithm=encryption_materials.algorithm, encryption_context=encryption_materials.encryption_context - ) - - result_materials = decrypting_keyring.on_decrypt( - decryption_materials=initial_decryption_materials, encrypted_data_keys=encryption_materials.encrypted_data_keys - ) - - generator_flags = _matching_flags( - MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=generator), result_materials.keyring_trace - ) - assert len(generator_flags) == 0 - - child_flags = _matching_flags( - MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=child), result_materials.keyring_trace - ) - - assert KeyringTraceFlag.DECRYPTED_DATA_KEY in child_flags - assert KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT in child_flags - - -def test_aws_kms_single_cmk_keyring_on_decrypt_no_match(fake_generator_and_child): - generator, child = fake_generator_and_child - - encrypting_keyring = _AwsKmsSingleCmkKeyring(key_id=generator, client_supplier=DefaultClientSupplier()) - decrypting_keyring = _AwsKmsSingleCmkKeyring(key_id=child, client_supplier=DefaultClientSupplier()) - - initial_encryption_materials = EncryptionMaterials(algorithm=ALGORITHM, encryption_context={}) - - encryption_materials = encrypting_keyring.on_encrypt(initial_encryption_materials) - - initial_decryption_materials = DecryptionMaterials( - algorithm=encryption_materials.algorithm, encryption_context=encryption_materials.encryption_context - ) - - result_materials = decrypting_keyring.on_decrypt( - decryption_materials=initial_decryption_materials, encrypted_data_keys=encryption_materials.encrypted_data_keys - ) - - assert result_materials.data_encryption_key is None - - -@mock_kms -def test_aws_kms_single_cmk_keyring_on_decrypt_fail(caplog): - # In this context there are no KMS CMKs, so any calls to KMS will fail. - caplog.set_level(logging.DEBUG) - keyring = _AwsKmsSingleCmkKeyring(key_id="foo", client_supplier=DefaultClientSupplier()) - - initial_materials = DecryptionMaterials(algorithm=ALGORITHM, encryption_context={}) - - result_materials = keyring.on_decrypt( - decryption_materials=initial_materials, - encrypted_data_keys=( - EncryptedDataKey( - key_provider=MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=b"foo"), encrypted_data_key=b"bar" - ), - ), - ) - - assert not result_materials.data_encryption_key - - log_data = caplog.text - - # This means that it did actually try to decrypt the EDK but encountered an error talking to KMS. - assert "Unable to decrypt encrypted data key from" in log_data - - -def test_aws_kms_discovery_keyring_on_encrypt(): - keyring = _AwsKmsDiscoveryKeyring(client_supplier=DefaultClientSupplier()) - - initial_materials = EncryptionMaterials(algorithm=ALGORITHM, encryption_context={}) - - result_materials = keyring.on_encrypt(initial_materials) - - assert result_materials is initial_materials - assert len(result_materials.encrypted_data_keys) == 0 - - -@pytest.fixture -def encryption_materials_for_discovery_decrypt(fake_generator): - encrypting_keyring = _AwsKmsSingleCmkKeyring(key_id=fake_generator, client_supplier=DefaultClientSupplier()) - - initial_encryption_materials = EncryptionMaterials(algorithm=ALGORITHM, encryption_context={}) - - return fake_generator, encrypting_keyring.on_encrypt(initial_encryption_materials) - - -def test_aws_kms_discovery_keyring_on_decrypt(encryption_materials_for_discovery_decrypt): - generator_key_id, encryption_materials = encryption_materials_for_discovery_decrypt - - decrypting_keyring = _AwsKmsDiscoveryKeyring(client_supplier=DefaultClientSupplier()) - - initial_decryption_materials = DecryptionMaterials( - algorithm=encryption_materials.algorithm, encryption_context=encryption_materials.encryption_context - ) - - result_materials = decrypting_keyring.on_decrypt( - decryption_materials=initial_decryption_materials, encrypted_data_keys=encryption_materials.encrypted_data_keys - ) - - assert result_materials is not initial_decryption_materials - assert result_materials.data_encryption_key is not None - - generator_flags = _matching_flags( - MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=generator_key_id), result_materials.keyring_trace - ) - - assert KeyringTraceFlag.DECRYPTED_DATA_KEY in generator_flags - assert KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT in generator_flags - - -@mock_kms -def test_aws_kms_discovery_keyring_on_decrypt_existing_data_key(caplog): - # In this context there are no KMS CMKs, so any calls to KMS will fail. - caplog.set_level(logging.DEBUG) - keyring = _AwsKmsDiscoveryKeyring(client_supplier=DefaultClientSupplier()) - - initial_materials = DecryptionMaterials( - algorithm=ALGORITHM, - encryption_context={}, - data_encryption_key=RawDataKey( - key_provider=MasterKeyInfo(provider_id="foo", key_info=b"bar"), data_key=os.urandom(ALGORITHM.kdf_input_len) - ), - ) - - result_materials = keyring.on_decrypt( - decryption_materials=initial_materials, - encrypted_data_keys=( - EncryptedDataKey( - key_provider=MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=b"foo"), encrypted_data_key=b"bar" - ), - ), - ) - - assert result_materials.data_encryption_key == initial_materials.data_encryption_key - - log_data = caplog.text - # This means that it did NOT try to decrypt the EDK. - assert "Unable to decrypt encrypted data key from" not in log_data - - -@mock_kms -def test_aws_kms_discovery_keyring_on_decrypt_no_matching_edk(caplog): - # In this context there are no KMS CMKs, so any calls to KMS will fail. - caplog.set_level(logging.DEBUG) - keyring = _AwsKmsDiscoveryKeyring(client_supplier=DefaultClientSupplier()) - - initial_materials = DecryptionMaterials(algorithm=ALGORITHM, encryption_context={},) - - result_materials = keyring.on_decrypt( - decryption_materials=initial_materials, - encrypted_data_keys=( - EncryptedDataKey(key_provider=MasterKeyInfo(provider_id="foo", key_info=b"bar"), encrypted_data_key=b"bar"), - ), - ) - - assert result_materials.data_encryption_key is None - - log_data = caplog.text - # This means that it did NOT try to decrypt the EDK. - assert "Unable to decrypt encrypted data key from" not in log_data - - -@mock_kms -def test_aws_kms_discovery_keyring_on_decrypt_fail(caplog): - # In this context there are no KMS CMKs, so any calls to KMS will fail. - caplog.set_level(logging.DEBUG) - keyring = _AwsKmsDiscoveryKeyring(client_supplier=DefaultClientSupplier()) - - initial_materials = DecryptionMaterials(algorithm=ALGORITHM, encryption_context={},) - - result_materials = keyring.on_decrypt( - decryption_materials=initial_materials, - encrypted_data_keys=( - EncryptedDataKey( - key_provider=MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=b"bar"), encrypted_data_key=b"bar" - ), - ), - ) - - assert result_materials.data_encryption_key is None - - log_data = caplog.text - # This means that it did actually try to decrypt the EDK but encountered an error talking to KMS. - assert "Unable to decrypt encrypted data key from" in log_data - - -def test_try_aws_kms_decrypt_succeed(fake_generator): - encryption_context = {"foo": "bar"} - kms = boto3.client("kms", region_name=FAKE_REGION) - plaintext = b"0123" * 8 - response = kms.encrypt(KeyId=fake_generator, Plaintext=plaintext, EncryptionContext=encryption_context) - - encrypted_data_key = EncryptedDataKey( - key_provider=MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=response["KeyId"]), - encrypted_data_key=response["CiphertextBlob"], - ) - - initial_decryption_materials = DecryptionMaterials(algorithm=ALGORITHM, encryption_context=encryption_context,) - - result_materials = _try_aws_kms_decrypt( - client_supplier=DefaultClientSupplier(), - decryption_materials=initial_decryption_materials, - grant_tokens=[], - encrypted_data_key=encrypted_data_key, - ) - - assert result_materials.data_encryption_key.data_key == plaintext - - generator_flags = _matching_flags( - MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=fake_generator), result_materials.keyring_trace - ) - - assert KeyringTraceFlag.DECRYPTED_DATA_KEY in generator_flags - assert KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT in generator_flags - - -@mock_kms -def test_try_aws_kms_decrypt_error(caplog): - # In this context there are no KMS CMKs, so any calls to KMS will fail. - caplog.set_level(logging.DEBUG) - - encrypted_data_key = EncryptedDataKey( - key_provider=MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=b"foo"), encrypted_data_key=b"bar" - ) - - initial_decryption_materials = DecryptionMaterials(algorithm=ALGORITHM, encryption_context={},) - - result_materials = _try_aws_kms_decrypt( - client_supplier=DefaultClientSupplier(), - decryption_materials=initial_decryption_materials, - grant_tokens=[], - encrypted_data_key=encrypted_data_key, - ) - - assert result_materials.data_encryption_key is None - - log_data = caplog.text - # This means that it did actually try to decrypt the EDK but encountered an error talking to KMS. - assert "Unable to decrypt encrypted data key from" in log_data - - -def test_do_aws_kms_decrypt(fake_generator): - encryption_context = {"foo": "bar"} - kms = boto3.client("kms", region_name=FAKE_REGION) - plaintext = b"0123" * 8 - response = kms.encrypt(KeyId=fake_generator, Plaintext=plaintext, EncryptionContext=encryption_context) - - encrypted_data_key = EncryptedDataKey( - key_provider=MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=response["KeyId"]), - encrypted_data_key=response["CiphertextBlob"], - ) - - decrypted_data_key = _do_aws_kms_decrypt( - client_supplier=DefaultClientSupplier(), - key_name=fake_generator, - encrypted_data_key=encrypted_data_key, - encryption_context=encryption_context, - grant_tokens=[], - ) - assert decrypted_data_key.data_key == plaintext - - -def test_do_aws_kms_decrypt_unexpected_key_id(fake_generator_and_child): - encryptor, decryptor = fake_generator_and_child - encryption_context = {"foo": "bar"} - kms = boto3.client("kms", region_name=FAKE_REGION) - plaintext = b"0123" * 8 - response = kms.encrypt(KeyId=encryptor, Plaintext=plaintext, EncryptionContext=encryption_context) - - encrypted_data_key = EncryptedDataKey( - key_provider=MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=response["KeyId"]), - encrypted_data_key=response["CiphertextBlob"], - ) - - with pytest.raises(DecryptKeyError) as excinfo: - _do_aws_kms_decrypt( - client_supplier=DefaultClientSupplier(), - key_name=decryptor, - encrypted_data_key=encrypted_data_key, - encryption_context=encryption_context, - grant_tokens=[], - ) - - excinfo.match(r"Decryption results from AWS KMS are for an unexpected key ID*") - - -def test_do_aws_kms_encrypt(fake_generator): - encryption_context = {"foo": "bar"} - plaintext = b"0123" * 8 - - encrypted_key = _do_aws_kms_encrypt( - client_supplier=DefaultClientSupplier(), - key_name=fake_generator, - plaintext_data_key=RawDataKey( - key_provider=MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=fake_generator), data_key=plaintext - ), - encryption_context=encryption_context, - grant_tokens=[], - ) - - kms = boto3.client("kms", region_name=FAKE_REGION) - response = kms.decrypt(CiphertextBlob=encrypted_key.encrypted_data_key, EncryptionContext=encryption_context) - - assert response["Plaintext"] == plaintext - - -def test_do_aws_kms_generate_data_key(fake_generator): - encryption_context = {"foo": "bar"} - plaintext_key, encrypted_key = _do_aws_kms_generate_data_key( - client_supplier=DefaultClientSupplier(), - key_name=fake_generator, - encryption_context=encryption_context, - algorithm=ALGORITHM, - grant_tokens=[], - ) - - kms = boto3.client("kms", region_name=FAKE_REGION) - response = kms.decrypt(CiphertextBlob=encrypted_key.encrypted_data_key, EncryptionContext=encryption_context) - - assert response["Plaintext"] == plaintext_key.data_key diff --git a/test/functional/keyrings/aws_kms/test_client_cache.py b/test/functional/keyrings/aws_kms/test_client_cache.py deleted file mode 100644 index 06c6c51c0..000000000 --- a/test/functional/keyrings/aws_kms/test_client_cache.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Functional tests for ``aws_encryption_sdk.keyrings.aws_kms.client_cache``.""" -import pytest -from botocore.config import Config -from botocore.session import Session - -from aws_encryption_sdk.keyrings.aws_kms._client_cache import ClientCache - -pytestmark = [pytest.mark.functional, pytest.mark.local] - - -def test_client_cache_caches_clients(): - cache = ClientCache(botocore_session=Session(), client_config=Config()) - - initial_client = cache.client("us-west-2", "kms") - - test = cache.client("us-west-2", "kms") - - assert "us-west-2" in cache._cache - assert test is initial_client - - -def test_client_cache_new_client(): - cache = ClientCache(botocore_session=Session(), client_config=Config()) - - initial_client = cache.client("us-west-2", "kms") - - cache._cache.pop("us-west-2") - - test = cache.client("us-west-2", "kms") - - assert test is not initial_client diff --git a/test/functional/keyrings/aws_kms/test_client_suppliers.py b/test/functional/keyrings/aws_kms/test_client_suppliers.py deleted file mode 100644 index 2d63fcc02..000000000 --- a/test/functional/keyrings/aws_kms/test_client_suppliers.py +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Functional tests for ``aws_encryption_sdk.keyrings.aws_kms.client_suppliers``.""" -import pytest -from botocore.config import Config -from botocore.session import Session - -from aws_encryption_sdk.exceptions import UnknownRegionError -from aws_encryption_sdk.keyrings.aws_kms.client_suppliers import ( - AllowRegionsClientSupplier, - ClientSupplier, - DefaultClientSupplier, - DenyRegionsClientSupplier, -) - -pytestmark = [pytest.mark.functional, pytest.mark.local] - - -def test_default_supplier_not_implemented(): - test = ClientSupplier() - - with pytest.raises(NotImplementedError) as excinfo: - test("region") - - excinfo.match("'ClientSupplier' is not callable") - - -def test_default_supplier_uses_cache(): - supplier = DefaultClientSupplier() - - region = "us-west-2" - expected = supplier._client_cache.client(region_name=region, service="kms") - - test = supplier(region) - - assert test is expected - - -def test_default_supplier_passes_through_configs(): - session = Session() - config = Config() - - test = DefaultClientSupplier(botocore_session=session, client_config=config) - - assert test._client_cache._botocore_session is session - assert test._client_cache._client_config is config - - -@pytest.mark.parametrize( - "kwargs", - ( - pytest.param(dict(allowed_regions="foo"), id="allowed_regions is a string"), - pytest.param(dict(allowed_regions=["foo", 5]), id="allowed_regions contains invalid type"), - ), -) -def test_allow_regions_supplier_invalid_parameters(kwargs): - with pytest.raises(TypeError): - AllowRegionsClientSupplier(**kwargs) - - -def test_allow_regions_supplier_allows_allowed_region(): - test = AllowRegionsClientSupplier(allowed_regions=["us-west-2", "us-east-2"]) - - assert test("us-west-2") - - -def test_allow_regions_supplier_denied_not_allowed_region(): - test = AllowRegionsClientSupplier(allowed_regions=["us-west-2", "us-east-2"]) - - with pytest.raises(UnknownRegionError) as excinfo: - test("ap-northeast-2") - - excinfo.match("Unable to provide client for region 'ap-northeast-2'") - - -@pytest.mark.parametrize( - "kwargs", - ( - pytest.param(dict(denied_regions="foo"), id="denied_regions is a string"), - pytest.param(dict(denied_regions=["foo", 5]), id="denied_regions contains invalid type"), - ), -) -def test_deny_regions_supplier_invalid_parameters(kwargs): - with pytest.raises(TypeError): - DenyRegionsClientSupplier(**kwargs) - - -def test_deny_regions_supplier_denies_denied_region(): - test = DenyRegionsClientSupplier(denied_regions=["us-west-2", "us-east-2"]) - - with pytest.raises(UnknownRegionError) as excinfo: - test("us-west-2") - - excinfo.match("Unable to provide client for region 'us-west-2'") - - -def test_deny_regions_supplier_allows_not_denied_region(): - test = DenyRegionsClientSupplier(denied_regions=["us-west-2", "us-east-2"]) - - assert test("ap-northeast-2") - - -def test_allow_deny_nested_supplier(): - test_allow = AllowRegionsClientSupplier( - allowed_regions=["us-west-2", "us-east-2"], client_supplier=DefaultClientSupplier() - ) - test_deny = DenyRegionsClientSupplier(denied_regions=["us-west-2"], client_supplier=test_allow) - - # test_allow allows us-west-2 - test_allow("us-west-2") - - # test_deny denies us-west-2 even though its internal supplier (test_allow) allows it - with pytest.raises(UnknownRegionError) as excinfo: - test_deny("us-west-2") - - excinfo.match("Unable to provide client for region 'us-west-2'") diff --git a/test/functional/keyrings/raw/__init__.py b/test/functional/keyrings/raw/__init__.py deleted file mode 100644 index ad0e71d6c..000000000 --- a/test/functional/keyrings/raw/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2019 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. -"""Dummy stub to make linters work better.""" diff --git a/test/functional/keyrings/raw/test_raw_aes.py b/test/functional/keyrings/raw/test_raw_aes.py deleted file mode 100644 index 9759f2ce9..000000000 --- a/test/functional/keyrings/raw/test_raw_aes.py +++ /dev/null @@ -1,181 +0,0 @@ -# Copyright 2019 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. -"""Functional tests for Raw AES keyring encryption decryption path.""" - -import pytest - -from aws_encryption_sdk.identifiers import ( - Algorithm, - EncryptionKeyType, - EncryptionType, - KeyringTraceFlag, - WrappingAlgorithm, -) -from aws_encryption_sdk.internal.crypto import WrappingKey -from aws_encryption_sdk.internal.formatting.serialize import serialize_raw_master_key_prefix -from aws_encryption_sdk.key_providers.raw import RawMasterKey -from aws_encryption_sdk.keyrings.raw import RawAESKeyring -from aws_encryption_sdk.materials_managers import DecryptionMaterials, EncryptionMaterials -from aws_encryption_sdk.structures import KeyringTrace, MasterKeyInfo, RawDataKey - -pytestmark = [pytest.mark.functional, pytest.mark.local] - -_ENCRYPTION_CONTEXT = {"encryption": "context", "values": "here"} -_PROVIDER_ID = "Random Raw Keys" -_KEY_ID = b"5325b043-5843-4629-869c-64794af77ada" -_WRAPPING_KEY = b"12345678901234567890123456789012" -_SIGNING_KEY = b"aws-crypto-public-key" - -_WRAPPING_ALGORITHM = [alg for alg in WrappingAlgorithm if alg.encryption_type is EncryptionType.SYMMETRIC] - - -def sample_encryption_materials(): - return [ - EncryptionMaterials( - algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, - encryption_context=_ENCRYPTION_CONTEXT, - signing_key=_SIGNING_KEY, - ), - EncryptionMaterials( - algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, - data_encryption_key=RawDataKey( - key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_KEY_ID), - data_key=b'*!\xa1"^-(\xf3\x105\x05i@B\xc2\xa2\xb7\xdd\xd5\xd5\xa9\xddm\xfae\xa8\\$\xf9d\x1e(', - ), - encryption_context=_ENCRYPTION_CONTEXT, - signing_key=_SIGNING_KEY, - keyring_trace=[ - KeyringTrace( - wrapping_key=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_KEY_ID), - flags={KeyringTraceFlag.GENERATED_DATA_KEY}, - ) - ], - ), - ] - - -@pytest.mark.parametrize("encryption_materials_samples", sample_encryption_materials()) -def test_raw_aes_encryption_decryption(encryption_materials_samples): - - # Initializing attributes - key_namespace = _PROVIDER_ID - key_name = _KEY_ID - - # Creating an instance of a raw AES keyring - test_raw_aes_keyring = RawAESKeyring(key_namespace=key_namespace, key_name=key_name, wrapping_key=_WRAPPING_KEY,) - - # Call on_encrypt function for the keyring - encryption_materials = test_raw_aes_keyring.on_encrypt(encryption_materials=encryption_materials_samples) - - # Generate decryption materials - decryption_materials = DecryptionMaterials( - algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, - verification_key=b"ex_verification_key", - encryption_context=_ENCRYPTION_CONTEXT, - ) - - # Call on_decrypt function for the keyring - decryption_materials = test_raw_aes_keyring.on_decrypt( - decryption_materials=decryption_materials, encrypted_data_keys=encryption_materials.encrypted_data_keys - ) - - # Check if the data keys match - assert encryption_materials.data_encryption_key.data_key == decryption_materials.data_encryption_key.data_key - - -@pytest.mark.parametrize("encryption_materials_samples", sample_encryption_materials()) -def test_raw_master_key_decrypts_what_raw_keyring_encrypts(encryption_materials_samples): - - # Initializing attributes - key_namespace = _PROVIDER_ID - key_name = _KEY_ID - - # Creating an instance of a raw AES keyring - test_raw_aes_keyring = RawAESKeyring(key_namespace=key_namespace, key_name=key_name, wrapping_key=_WRAPPING_KEY,) - - # Creating an instance of a raw master key - test_raw_master_key = RawMasterKey( - key_id=test_raw_aes_keyring.key_name, - provider_id=test_raw_aes_keyring.key_namespace, - wrapping_key=test_raw_aes_keyring._wrapping_key_structure, - ) - - # Encrypt using raw AES keyring - encryption_materials = test_raw_aes_keyring.on_encrypt(encryption_materials=encryption_materials_samples) - - # Check if plaintext data key encrypted by raw keyring is decrypted by raw master key - - raw_mkp_decrypted_data_key = test_raw_master_key.decrypt_data_key_from_list( - encrypted_data_keys=encryption_materials._encrypted_data_keys, - algorithm=encryption_materials.algorithm, - encryption_context=encryption_materials.encryption_context, - ).data_key - - assert encryption_materials.data_encryption_key.data_key == raw_mkp_decrypted_data_key - - -@pytest.mark.parametrize("encryption_materials_samples", sample_encryption_materials()) -def test_raw_keyring_decrypts_what_raw_master_key_encrypts(encryption_materials_samples): - - # Initializing attributes - key_namespace = _PROVIDER_ID - key_name = _KEY_ID - - # Creating an instance of a raw AES keyring - test_raw_aes_keyring = RawAESKeyring(key_namespace=key_namespace, key_name=key_name, wrapping_key=_WRAPPING_KEY,) - - # Creating an instance of a raw master key - test_raw_master_key = RawMasterKey( - key_id=test_raw_aes_keyring.key_name, - provider_id=test_raw_aes_keyring.key_namespace, - wrapping_key=test_raw_aes_keyring._wrapping_key_structure, - ) - - if encryption_materials_samples.data_encryption_key is None: - return - raw_master_key_encrypted_data_key = test_raw_master_key.encrypt_data_key( - data_key=encryption_materials_samples.data_encryption_key, - algorithm=encryption_materials_samples.algorithm, - encryption_context=encryption_materials_samples.encryption_context, - ) - - # Check if plaintext data key encrypted by raw master key is decrypted by raw keyring - - raw_aes_keyring_decrypted_data_key = test_raw_aes_keyring.on_decrypt( - decryption_materials=DecryptionMaterials( - algorithm=encryption_materials_samples.algorithm, - encryption_context=encryption_materials_samples.encryption_context, - verification_key=b"ex_verification_key", - ), - encrypted_data_keys=[raw_master_key_encrypted_data_key], - ).data_encryption_key.data_key - - assert encryption_materials_samples.data_encryption_key.data_key == raw_aes_keyring_decrypted_data_key - - -@pytest.mark.parametrize("wrapping_algorithm", _WRAPPING_ALGORITHM) -def test_key_info_prefix_vectors(wrapping_algorithm): - assert ( - serialize_raw_master_key_prefix( - raw_master_key=RawMasterKey( - provider_id=_PROVIDER_ID, - key_id=_KEY_ID, - wrapping_key=WrappingKey( - wrapping_algorithm=wrapping_algorithm, - wrapping_key=_WRAPPING_KEY, - wrapping_key_type=EncryptionKeyType.SYMMETRIC, - ), - ) - ) - == _KEY_ID + b"\x00\x00\x00\x80\x00\x00\x00\x0c" - ) diff --git a/test/functional/keyrings/raw/test_raw_rsa.py b/test/functional/keyrings/raw/test_raw_rsa.py deleted file mode 100644 index b7a278d2b..000000000 --- a/test/functional/keyrings/raw/test_raw_rsa.py +++ /dev/null @@ -1,297 +0,0 @@ -# Copyright 2019 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. -"""Functional tests for Raw AES keyring encryption decryption path.""" - -import pytest -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import rsa - -from aws_encryption_sdk.identifiers import ( - Algorithm, - EncryptionKeyType, - EncryptionType, - KeyringTraceFlag, - WrappingAlgorithm, -) -from aws_encryption_sdk.internal.crypto import WrappingKey -from aws_encryption_sdk.key_providers.raw import RawMasterKey -from aws_encryption_sdk.keyrings.raw import RawRSAKeyring -from aws_encryption_sdk.materials_managers import DecryptionMaterials, EncryptionMaterials -from aws_encryption_sdk.structures import KeyringTrace, MasterKeyInfo, RawDataKey - -pytestmark = [pytest.mark.functional, pytest.mark.local] - -_ENCRYPTION_CONTEXT = {"encryption": "context", "values": "here"} -_PROVIDER_ID = "Random Raw Keys" -_KEY_ID = b"5325b043-5843-4629-869c-64794af77ada" -_WRAPPING_ALGORITHM = WrappingAlgorithm.RSA_OAEP_SHA256_MGF1 - -_PUBLIC_EXPONENT = 65537 -_KEY_SIZE = 2048 -_BACKEND = default_backend() - -_PRIVATE_WRAPPING_KEY = rsa.generate_private_key(public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND) - -_PRIVATE_WRAPPING_KEY_PEM = ( - b"-----BEGIN RSA PRIVATE KEY-----\n" - b"MIIEowIBAAKCAQEAo8uCyhiO4JUGZV+rtNq5DBA9Lm4xkw5kTA3v6EPybs8bVXL2\n" - b"ZE6jkbo+xT4Jg/bKzUpnp1fE+T1ruGPtsPdoEmhY/P64LDNIs3sRq5U4QV9IETU1\n" - b"vIcbNNkgGhRjV8J87YNY0tV0H7tuWuZRpqnS+gjV6V9lUMkbvjMCc5IBqQc3heut\n" - b"/+fH4JwpGlGxOVXI8QAapnSy1XpCr3+PT29kydVJnIMuAoFrurojRpOQbOuVvhtA\n" - b"gARhst1Ji4nfROGYkj6eZhvkz2Bkud4/+3lGvVU5LO1vD8oY7WoGtpin3h50VcWe\n" - b"aBT4kejx4s9/G9C4R24lTH09J9HO2UUsuCqZYQIDAQABAoIBAQCfC90bCk+qaWqF\n" - b"gymC+qOWwCn4bM28gswHQb1D5r6AtKBRD8mKywVvWs7azguFVV3Fi8sspkBA2FBC\n" - b"At5p6ULoJOTL/TauzLl6djVJTCMM701WUDm2r+ZOIctXJ5bzP4n5Q4I7b0NMEL7u\n" - b"ixib4elYGr5D1vrVQAKtZHCr8gmkqyx8Mz7wkJepzBP9EeVzETCHsmiQDd5WYlO1\n" - b"C2IQYgw6MJzgM4entJ0V/GPytkodblGY95ORVK7ZhyNtda+r5BZ6/jeMW+hA3VoK\n" - b"tHSWjHt06ueVCCieZIATmYzBNt+zEz5UA2l7ksg3eWfVORJQS7a6Ef4VvbJLM9Ca\n" - b"m1kdsjelAoGBANKgvRf39i3bSuvm5VoyJuqinSb/23IH3Zo7XOZ5G164vh49E9Cq\n" - b"dOXXVxox74ppj/kbGUoOk+AvaB48zzfzNvac0a7lRHExykPH2kVrI/NwH/1OcT/x\n" - b"2e2DnFYocXcb4gbdZQ+m6X3zkxOYcONRzPVW1uMrFTWHcJveMUm4PGx7AoGBAMcU\n" - b"IRvrT6ye5se0s27gHnPweV+3xjsNtXZcK82N7duXyHmNjxrwOAv0SOhUmTkRXArM\n" - b"6aN5D8vyZBSWma2TgUKwpQYFTI+4Sp7sdkkyojGAEixJ+c5TZJNxZFrUe0FwAoic\n" - b"c2kb7ntaiEj5G+qHvykJJro5hy6uLnjiMVbAiJDTAoGAKb67241EmHAXGEwp9sdr\n" - b"2SMjnIAnQSF39UKAthkYqJxa6elXDQtLoeYdGE7/V+J2K3wIdhoPiuY6b4vD0iX9\n" - b"JcGM+WntN7YTjX2FsC588JmvbWfnoDHR7HYiPR1E58N597xXdFOzgUgORVr4PMWQ\n" - b"pqtwaZO3X2WZlvrhr+e46hMCgYBfdIdrm6jYXFjL6RkgUNZJQUTxYGzsY+ZemlNm\n" - b"fGdQo7a8kePMRuKY2MkcnXPaqTg49YgRmjq4z8CtHokRcWjJUWnPOTs8rmEZUshk\n" - b"0KJ0mbQdCFt/Uv0mtXgpFTkEZ3DPkDTGcV4oR4CRfOCl0/EU/A5VvL/U4i/mRo7h\n" - b"ye+xgQKBgD58b+9z+PR5LAJm1tZHIwb4tnyczP28PzwknxFd2qylR4ZNgvAUqGtU\n" - b"xvpUDpzMioz6zUH9YV43YNtt+5Xnzkqj+u9Mr27/H2v9XPwORGfwQ5XPwRJz/2oC\n" - b"EnPmP1SZoY9lXKUpQXHXSpDZ2rE2Klt3RHMUMHt8Zpy36E8Vwx8o\n" - b"-----END RSA PRIVATE KEY-----\n" -) - -_RAW_RSA_PRIVATE_KEY_PEM_ENCODED_WITHOUT_PASSWORD = rsa.generate_private_key( - public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND -).private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.NoEncryption(), -) - -_RAW_RSA_PRIVATE_KEY_PEM_ENCODED_WITH_PASSWORD = rsa.generate_private_key( - public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND -).private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.PKCS8, - encryption_algorithm=serialization.BestAvailableEncryption(b"mypassword"), -) - -_RAW_RSA_PUBLIC_KEY_PEM_ENCODED = ( - rsa.generate_private_key(public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND) - .public_key() - .public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo) -) - -_RAW_RSA_PRIVATE_KEY_DER_ENCODED_WITHOUT_PASSWORD = rsa.generate_private_key( - public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND -).private_bytes( - encoding=serialization.Encoding.DER, - format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.NoEncryption(), -) - -_RAW_RSA_PRIVATE_KEY_DER_ENCODED_WITH_PASSWORD = rsa.generate_private_key( - public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND -).private_bytes( - encoding=serialization.Encoding.DER, - format=serialization.PrivateFormat.PKCS8, - encryption_algorithm=serialization.BestAvailableEncryption(b"mypassword"), -) - -_RAW_RSA_PUBLIC_KEY_DER_ENCODED = ( - rsa.generate_private_key(public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND) - .public_key() - .public_bytes(encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo) -) - - -def sample_encryption_materials(): - return [ - EncryptionMaterials( - algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, encryption_context=_ENCRYPTION_CONTEXT - ), - EncryptionMaterials( - algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, - data_encryption_key=RawDataKey( - key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_KEY_ID), - data_key=b'*!\xa1"^-(\xf3\x105\x05i@B\xc2\xa2\xb7\xdd\xd5\xd5\xa9\xddm\xfae\xa8\\$\xf9d\x1e(', - ), - encryption_context=_ENCRYPTION_CONTEXT, - keyring_trace=[ - KeyringTrace( - wrapping_key=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_KEY_ID), - flags={KeyringTraceFlag.GENERATED_DATA_KEY}, - ) - ], - ), - ] - - -def sample_raw_rsa_keyring_using_different_wrapping_algorithm(): - for alg in WrappingAlgorithm: - if alg.encryption_type is EncryptionType.ASYMMETRIC: - yield RawRSAKeyring( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - wrapping_algorithm=alg, - private_wrapping_key=_PRIVATE_WRAPPING_KEY, - ) - pem_and_der_encoded_raw_rsa_keyring = [ - RawRSAKeyring.from_pem_encoding( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - private_encoded_key=_RAW_RSA_PRIVATE_KEY_PEM_ENCODED_WITHOUT_PASSWORD, - wrapping_algorithm=_WRAPPING_ALGORITHM, - ), - RawRSAKeyring.from_pem_encoding( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - private_encoded_key=_RAW_RSA_PRIVATE_KEY_PEM_ENCODED_WITH_PASSWORD, - password=b"mypassword", - wrapping_algorithm=_WRAPPING_ALGORITHM, - ), - RawRSAKeyring.from_pem_encoding( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - public_encoded_key=_RAW_RSA_PUBLIC_KEY_PEM_ENCODED, - wrapping_algorithm=_WRAPPING_ALGORITHM, - ), - RawRSAKeyring.from_der_encoding( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - private_encoded_key=_RAW_RSA_PRIVATE_KEY_DER_ENCODED_WITHOUT_PASSWORD, - wrapping_algorithm=_WRAPPING_ALGORITHM, - ), - RawRSAKeyring.from_der_encoding( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - private_encoded_key=_RAW_RSA_PRIVATE_KEY_DER_ENCODED_WITH_PASSWORD, - password=b"mypassword", - wrapping_algorithm=_WRAPPING_ALGORITHM, - ), - RawRSAKeyring.from_der_encoding( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - public_encoded_key=_RAW_RSA_PUBLIC_KEY_DER_ENCODED, - password=b"mypassword", - wrapping_algorithm=_WRAPPING_ALGORITHM, - ), - ] - for keyring in pem_and_der_encoded_raw_rsa_keyring: - yield keyring - - -@pytest.mark.parametrize("encryption_materials_samples", sample_encryption_materials()) -@pytest.mark.parametrize("test_raw_rsa_keyring", sample_raw_rsa_keyring_using_different_wrapping_algorithm()) -def test_raw_rsa_encryption_decryption(encryption_materials_samples, test_raw_rsa_keyring): - - # Call on_encrypt function for the keyring - encryption_materials = test_raw_rsa_keyring.on_encrypt(encryption_materials=encryption_materials_samples) - - assert encryption_materials.encrypted_data_keys is not None - - # Generate decryption materials - decryption_materials = DecryptionMaterials( - algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, - verification_key=b"ex_verification_key", - encryption_context=_ENCRYPTION_CONTEXT, - ) - - # Call on_decrypt function for the keyring - decryption_materials = test_raw_rsa_keyring.on_decrypt( - decryption_materials=decryption_materials, encrypted_data_keys=encryption_materials.encrypted_data_keys - ) - - if test_raw_rsa_keyring._private_wrapping_key is not None: - # Check if the data keys match - assert encryption_materials.data_encryption_key.data_key == decryption_materials.data_encryption_key.data_key - - -@pytest.mark.parametrize("encryption_materials_samples", sample_encryption_materials()) -def test_raw_master_key_decrypts_what_raw_keyring_encrypts(encryption_materials_samples): - test_raw_rsa_keyring = RawRSAKeyring.from_pem_encoding( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - wrapping_algorithm=_WRAPPING_ALGORITHM, - private_encoded_key=_PRIVATE_WRAPPING_KEY_PEM, - ) - - # Creating an instance of a raw master key - test_raw_master_key = RawMasterKey( - key_id=_KEY_ID, - provider_id=_PROVIDER_ID, - wrapping_key=WrappingKey( - wrapping_algorithm=_WRAPPING_ALGORITHM, - wrapping_key=_PRIVATE_WRAPPING_KEY_PEM, - wrapping_key_type=EncryptionKeyType.PRIVATE, - ), - ) - - # Call on_encrypt function for the keyring - encryption_materials = test_raw_rsa_keyring.on_encrypt(encryption_materials=encryption_materials_samples) - - # Check if plaintext data key encrypted by raw keyring is decrypted by raw master key - raw_mkp_decrypted_data_key = test_raw_master_key.decrypt_data_key_from_list( - encrypted_data_keys=encryption_materials._encrypted_data_keys, - algorithm=encryption_materials.algorithm, - encryption_context=encryption_materials.encryption_context, - ).data_key - - assert encryption_materials.data_encryption_key.data_key == raw_mkp_decrypted_data_key - - -@pytest.mark.parametrize("encryption_materials_samples", sample_encryption_materials()) -def test_raw_keyring_decrypts_what_raw_master_key_encrypts(encryption_materials_samples): - - # Create instance of raw master key - test_raw_master_key = RawMasterKey( - key_id=_KEY_ID, - provider_id=_PROVIDER_ID, - wrapping_key=WrappingKey( - wrapping_algorithm=_WRAPPING_ALGORITHM, - wrapping_key=_PRIVATE_WRAPPING_KEY_PEM, - wrapping_key_type=EncryptionKeyType.PRIVATE, - ), - ) - - test_raw_rsa_keyring = RawRSAKeyring.from_pem_encoding( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - wrapping_algorithm=_WRAPPING_ALGORITHM, - private_encoded_key=_PRIVATE_WRAPPING_KEY_PEM, - ) - - raw_mkp_generated_data_key = test_raw_master_key.generate_data_key( - algorithm=encryption_materials_samples.algorithm, - encryption_context=encryption_materials_samples.encryption_context, - ) - - raw_mkp_encrypted_data_key = test_raw_master_key.encrypt_data_key( - data_key=raw_mkp_generated_data_key, - algorithm=encryption_materials_samples.algorithm, - encryption_context=encryption_materials_samples.encryption_context, - ) - - decryption_materials = test_raw_rsa_keyring.on_decrypt( - decryption_materials=DecryptionMaterials( - algorithm=encryption_materials_samples.algorithm, - encryption_context=encryption_materials_samples.encryption_context, - verification_key=b"ex_verification_key", - ), - encrypted_data_keys=[raw_mkp_encrypted_data_key], - ) - - assert raw_mkp_generated_data_key.data_key == decryption_materials.data_encryption_key.data_key diff --git a/test/functional/keyrings/test_multi.py b/test/functional/keyrings/test_multi.py deleted file mode 100644 index ff9eb2440..000000000 --- a/test/functional/keyrings/test_multi.py +++ /dev/null @@ -1,122 +0,0 @@ -# Copyright 2019 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. -"""Functional tests for Multi keyring encryption decryption path.""" - -import pytest -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.asymmetric import rsa - -from aws_encryption_sdk.identifiers import KeyringTraceFlag, WrappingAlgorithm -from aws_encryption_sdk.internal.defaults import ALGORITHM -from aws_encryption_sdk.keyrings.multi import MultiKeyring -from aws_encryption_sdk.keyrings.raw import RawAESKeyring, RawRSAKeyring -from aws_encryption_sdk.materials_managers import DecryptionMaterials, EncryptionMaterials -from aws_encryption_sdk.structures import KeyringTrace, MasterKeyInfo, RawDataKey - -pytestmark = [pytest.mark.functional, pytest.mark.local] - -_ENCRYPTION_CONTEXT = {"encryption": "context", "values": "here"} -_PROVIDER_ID = "Random Raw Keys" -_KEY_ID = b"5325b043-5843-4629-869c-64794af77ada" -_WRAPPING_KEY_AES = b"\xeby-\x80A6\x15rA8\x83#,\xe4\xab\xac`\xaf\x99Z\xc1\xce\xdb\xb6\x0f\xb7\x805\xb2\x14J3" - -_ENCRYPTION_MATERIALS_WITHOUT_DATA_KEY = EncryptionMaterials( - algorithm=ALGORITHM, encryption_context=_ENCRYPTION_CONTEXT -) - -_ENCRYPTION_MATERIALS_WITH_DATA_KEY = EncryptionMaterials( - algorithm=ALGORITHM, - data_encryption_key=RawDataKey( - key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_KEY_ID), - data_key=b'*!\xa1"^-(\xf3\x105\x05i@B\xc2\xa2\xb7\xdd\xd5\xd5\xa9\xddm\xfae\xa8\\$\xf9d\x1e(', - ), - encryption_context=_ENCRYPTION_CONTEXT, - keyring_trace=[ - KeyringTrace( - wrapping_key=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_KEY_ID), - flags={KeyringTraceFlag.GENERATED_DATA_KEY}, - ) - ], -) - -_MULTI_KEYRING_WITH_GENERATOR_AND_CHILDREN = MultiKeyring( - generator=RawAESKeyring(key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_key=_WRAPPING_KEY_AES,), - children=[ - RawRSAKeyring( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=rsa.generate_private_key( - public_exponent=65537, key_size=2048, backend=default_backend() - ), - ), - RawRSAKeyring( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=rsa.generate_private_key( - public_exponent=65537, key_size=2048, backend=default_backend() - ), - ), - ], -) - -_MULTI_KEYRING_WITHOUT_CHILDREN = MultiKeyring( - generator=RawRSAKeyring( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()), - ) -) - -_MULTI_KEYRING_WITHOUT_GENERATOR = MultiKeyring( - children=[ - RawRSAKeyring( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=rsa.generate_private_key( - public_exponent=65537, key_size=2048, backend=default_backend() - ), - ), - RawAESKeyring(key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_key=_WRAPPING_KEY_AES,), - ] -) - - -@pytest.mark.parametrize( - "multi_keyring, encryption_materials", - [ - (_MULTI_KEYRING_WITH_GENERATOR_AND_CHILDREN, _ENCRYPTION_MATERIALS_WITHOUT_DATA_KEY), - (_MULTI_KEYRING_WITH_GENERATOR_AND_CHILDREN, _ENCRYPTION_MATERIALS_WITH_DATA_KEY), - (_MULTI_KEYRING_WITHOUT_CHILDREN, _ENCRYPTION_MATERIALS_WITH_DATA_KEY), - (_MULTI_KEYRING_WITHOUT_GENERATOR, _ENCRYPTION_MATERIALS_WITH_DATA_KEY), - ], -) -def test_multi_keyring_encryption_decryption(multi_keyring, encryption_materials): - # Call on_encrypt function for the keyring - encryption_materials = multi_keyring.on_encrypt(encryption_materials) - - # Generate decryption materials - decryption_materials = DecryptionMaterials( - algorithm=ALGORITHM, verification_key=b"ex_verification_key", encryption_context=_ENCRYPTION_CONTEXT - ) - - # Call on_decrypt function for the keyring - decryption_materials = multi_keyring.on_decrypt( - decryption_materials=decryption_materials, encrypted_data_keys=encryption_materials.encrypted_data_keys - ) - - # Check if the data keys match - assert encryption_materials.data_encryption_key == decryption_materials.data_encryption_key diff --git a/test/functional/test_client.py b/test/functional/test_f_aws_encryption_sdk_client.py similarity index 80% rename from test/functional/test_client.py rename to test/functional/test_f_aws_encryption_sdk_client.py index ebe7e14d1..fb19e868a 100644 --- a/test/functional/test_client.py +++ b/test/functional/test_f_aws_encryption_sdk_client.py @@ -1,10 +1,19 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.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. """Functional test suite for aws_encryption_sdk.kms_thick_client""" from __future__ import division import io -import itertools import logging import attr @@ -25,19 +34,10 @@ from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey from aws_encryption_sdk.internal.defaults import LINE_LENGTH from aws_encryption_sdk.internal.formatting.encryption_context import serialize_encryption_context -from aws_encryption_sdk.key_providers.base import MasterKeyProvider, MasterKeyProviderConfig +from aws_encryption_sdk.key_providers.base import MasterKeyProviderConfig from aws_encryption_sdk.key_providers.raw import RawMasterKeyProvider -from aws_encryption_sdk.keyrings.base import Keyring -from aws_encryption_sdk.keyrings.raw import RawRSAKeyring from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest -from ..unit.unit_test_utils import ( - ephemeral_raw_aes_keyring, - ephemeral_raw_aes_master_key, - ephemeral_raw_rsa_keyring, - raw_rsa_mkps_from_keyring, -) - pytestmark = [pytest.mark.functional, pytest.mark.local] VALUES = { @@ -315,211 +315,69 @@ def test_encrypt_ciphertext_message(frame_length, algorithm, encryption_context) assert len(ciphertext) == results_length -def _raw_aes(include_mkp=True): - for symmetric_algorithm in ( - WrappingAlgorithm.AES_128_GCM_IV12_TAG16_NO_PADDING, - WrappingAlgorithm.AES_192_GCM_IV12_TAG16_NO_PADDING, - WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING, - ): - keyring = ephemeral_raw_aes_keyring(symmetric_algorithm) - yield pytest.param( - "keyring", keyring, "keyring", keyring, id="raw AES keyring -- {}".format(symmetric_algorithm.name) - ) - - if not include_mkp: - continue - - yield pytest.param( - "key_provider", - build_fake_raw_key_provider(symmetric_algorithm, EncryptionKeyType.SYMMETRIC), - "key_provider", - build_fake_raw_key_provider(symmetric_algorithm, EncryptionKeyType.SYMMETRIC), - id="raw AES master key provider -- {}".format(symmetric_algorithm.name), - ) - - mkp = ephemeral_raw_aes_master_key(wrapping_algorithm=symmetric_algorithm, key=keyring._wrapping_key) - yield pytest.param( - "key_provider", - mkp, - "keyring", - keyring, - id="raw AES -- encrypt with master key provider and decrypt with keyring -- {}".format(symmetric_algorithm), - ) - yield pytest.param( - "keyring", - keyring, - "key_provider", - mkp, - id="raw AES -- encrypt with keyring and decrypt with master key provider -- {}".format(symmetric_algorithm), - ) - - -def _raw_rsa(include_pre_sha2=True, include_sha2=True, include_mkp=True): - wrapping_algorithms = [] - if include_pre_sha2: - wrapping_algorithms.extend([WrappingAlgorithm.RSA_PKCS1, WrappingAlgorithm.RSA_OAEP_SHA1_MGF1]) - if include_sha2: - wrapping_algorithms.extend( - [ - WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - WrappingAlgorithm.RSA_OAEP_SHA384_MGF1, - WrappingAlgorithm.RSA_OAEP_SHA512_MGF1, - ] - ) - for wrapping_algorithm in wrapping_algorithms: - private_keyring = ephemeral_raw_rsa_keyring(wrapping_algorithm=wrapping_algorithm) - public_keyring = RawRSAKeyring( - key_namespace=private_keyring.key_namespace, - key_name=private_keyring.key_name, - wrapping_algorithm=wrapping_algorithm, - public_wrapping_key=private_keyring._private_wrapping_key.public_key(), - ) - yield pytest.param( - "keyring", - private_keyring, - "keyring", - private_keyring, - id="raw RSA keyring -- private encrypt, private decrypt -- {}".format(wrapping_algorithm.name), - ) - yield pytest.param( - "keyring", - public_keyring, - "keyring", - private_keyring, - id="raw RSA keyring -- public encrypt, private decrypt -- {}".format(wrapping_algorithm.name), - ) - - if not include_mkp: - continue - - private_mkp, public_mkp = raw_rsa_mkps_from_keyring(private_keyring) - - yield pytest.param( - "key_provider", - build_fake_raw_key_provider(wrapping_algorithm, EncryptionKeyType.PRIVATE), - "key_provider", - build_fake_raw_key_provider(wrapping_algorithm, EncryptionKeyType.PRIVATE), - id="raw RSA master key provider -- private encrypt, private decrypt -- {}".format(wrapping_algorithm.name), - ) - yield pytest.param( - "key_provider", - build_fake_raw_key_provider(wrapping_algorithm, EncryptionKeyType.PUBLIC), - "key_provider", - build_fake_raw_key_provider(wrapping_algorithm, EncryptionKeyType.PRIVATE), - id="raw RSA master key provider -- public encrypt, private decrypt -- {}".format(wrapping_algorithm.name), - ) - - yield pytest.param( - "key_provider", - private_mkp, - "keyring", - private_keyring, - id="raw RSA keyring -- private master key provider encrypt and private keyring decrypt -- {}".format( - wrapping_algorithm - ), - ) - yield pytest.param( - "key_provider", - public_mkp, - "keyring", - private_keyring, - id="raw RSA keyring -- public master key provider encrypt and private keyring decrypt -- {}".format( - wrapping_algorithm - ), - ) - yield pytest.param( - "keyring", - private_keyring, - "key_provider", - private_mkp, - id="raw RSA keyring -- private keyring encrypt and private master key provider decrypt -- {}".format( - wrapping_algorithm - ), - ) - yield pytest.param( - "keyring", - public_keyring, - "key_provider", - private_mkp, - id="raw RSA keyring -- public keyring encrypt and private master key provider decrypt -- {}".format( - wrapping_algorithm - ), - ) - - -def assert_key_not_logged(provider, log_capture): - if isinstance(provider, MasterKeyProvider): - for member in provider._members: - assert repr(member.config.wrapping_key._wrapping_key)[2:-1] not in log_capture - - -def run_raw_provider_check( - log_capturer, encrypt_param_name, encrypting_provider, decrypt_param_name, decrypting_provider -): - log_capturer.set_level(logging.DEBUG) - - encrypt_kwargs = {encrypt_param_name: encrypting_provider} - decrypt_kwargs = {decrypt_param_name: decrypting_provider} +@pytest.mark.parametrize( + "wrapping_algorithm, encryption_key_type, decryption_key_type", + ( + (WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING, EncryptionKeyType.SYMMETRIC, EncryptionKeyType.SYMMETRIC), + (WrappingAlgorithm.RSA_PKCS1, EncryptionKeyType.PRIVATE, EncryptionKeyType.PRIVATE), + (WrappingAlgorithm.RSA_PKCS1, EncryptionKeyType.PUBLIC, EncryptionKeyType.PRIVATE), + (WrappingAlgorithm.RSA_OAEP_SHA1_MGF1, EncryptionKeyType.PRIVATE, EncryptionKeyType.PRIVATE), + (WrappingAlgorithm.RSA_OAEP_SHA1_MGF1, EncryptionKeyType.PUBLIC, EncryptionKeyType.PRIVATE), + ), +) +def test_encryption_cycle_raw_mkp(caplog, wrapping_algorithm, encryption_key_type, decryption_key_type): + caplog.set_level(logging.DEBUG) - encrypt_result = aws_encryption_sdk.encrypt( + encrypting_key_provider = build_fake_raw_key_provider(wrapping_algorithm, encryption_key_type) + decrypting_key_provider = build_fake_raw_key_provider(wrapping_algorithm, decryption_key_type) + ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], + key_provider=encrypting_key_provider, encryption_context=VALUES["encryption_context"], frame_length=0, - **encrypt_kwargs ) - decrypt_result = aws_encryption_sdk.decrypt(source=encrypt_result.result, **decrypt_kwargs) - - if isinstance(encrypting_provider, Keyring): - trace_entries = ( - entry - for entry in encrypt_result.keyring_trace - if ( - entry.wrapping_key.provider_id == encrypting_provider.key_namespace - and entry.wrapping_key.key_info == encrypting_provider.key_name - ) - ) - assert trace_entries - - assert decrypt_result.result == VALUES["plaintext_128"] - assert_key_not_logged(encrypting_provider, log_capturer.text) - - if isinstance(decrypting_provider, Keyring): - trace_entries = ( - entry - for entry in decrypt_result.keyring_trace - if ( - entry.wrapping_key.provider_id == decrypting_provider.key_namespace - and entry.wrapping_key.key_info == decrypting_provider.key_name - ) - ) - assert trace_entries - + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=decrypting_key_provider) -@pytest.mark.parametrize( - "encrypt_param_name, encrypting_provider, decrypt_param_name, decrypting_provider", - itertools.chain.from_iterable((_raw_aes(), _raw_rsa(include_sha2=False))), -) -def test_encryption_cycle_raw_mkp( - caplog, encrypt_param_name, encrypting_provider, decrypt_param_name, decrypting_provider -): - run_raw_provider_check(caplog, encrypt_param_name, encrypting_provider, decrypt_param_name, decrypting_provider) + assert plaintext == VALUES["plaintext_128"] + for member in encrypting_key_provider._members: + assert repr(member.config.wrapping_key._wrapping_key)[2:-1] not in caplog.text @pytest.mark.skipif( not _mgf1_sha256_supported(), reason="MGF1-SHA2 not supported by this backend: OpenSSL required v1.0.2+" ) @pytest.mark.parametrize( - "encrypt_param_name, encrypting_provider, decrypt_param_name, decrypting_provider", _raw_rsa(include_pre_sha2=False) + "wrapping_algorithm", + ( + WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, + WrappingAlgorithm.RSA_OAEP_SHA384_MGF1, + WrappingAlgorithm.RSA_OAEP_SHA512_MGF1, + ), ) -def test_encryption_cycle_raw_mkp_openssl_102_plus( - caplog, encrypt_param_name, encrypting_provider, decrypt_param_name, decrypting_provider -): - run_raw_provider_check(caplog, encrypt_param_name, encrypting_provider, decrypt_param_name, decrypting_provider) +@pytest.mark.parametrize("encryption_key_type", (EncryptionKeyType.PUBLIC, EncryptionKeyType.PRIVATE)) +def test_encryption_cycle_raw_mkp_openssl_102_plus(wrapping_algorithm, encryption_key_type): + decryption_key_type = EncryptionKeyType.PRIVATE + encrypting_key_provider = build_fake_raw_key_provider(wrapping_algorithm, encryption_key_type) + decrypting_key_provider = build_fake_raw_key_provider(wrapping_algorithm, decryption_key_type) + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=encrypting_key_provider, + encryption_context=VALUES["encryption_context"], + frame_length=0, + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=decrypting_key_provider) + assert plaintext == VALUES["plaintext_128"] -@pytest.mark.parametrize("frame_length", VALUES["frame_lengths"]) -@pytest.mark.parametrize("algorithm", Algorithm) -@pytest.mark.parametrize("encryption_context", [{}, VALUES["encryption_context"]]) +@pytest.mark.parametrize( + "frame_length, algorithm, encryption_context", + [ + [frame_length, algorithm_suite, encryption_context] + for frame_length in VALUES["frame_lengths"] + for algorithm_suite in Algorithm + for encryption_context in [{}, VALUES["encryption_context"]] + ], +) def test_encryption_cycle_oneshot_kms(frame_length, algorithm, encryption_context): key_provider = fake_kms_key_provider(algorithm.kdf_input_len) @@ -536,9 +394,15 @@ def test_encryption_cycle_oneshot_kms(frame_length, algorithm, encryption_contex assert plaintext == VALUES["plaintext_128"] * 10 -@pytest.mark.parametrize("frame_length", VALUES["frame_lengths"]) -@pytest.mark.parametrize("algorithm", Algorithm) -@pytest.mark.parametrize("encryption_context", [{}, VALUES["encryption_context"]]) +@pytest.mark.parametrize( + "frame_length, algorithm, encryption_context", + [ + [frame_length, algorithm_suite, encryption_context] + for frame_length in VALUES["frame_lengths"] + for algorithm_suite in Algorithm + for encryption_context in [{}, VALUES["encryption_context"]] + ], +) def test_encryption_cycle_stream_kms(frame_length, algorithm, encryption_context): key_provider = fake_kms_key_provider(algorithm.kdf_input_len) diff --git a/test/functional/internal/crypto/test_crypto.py b/test/functional/test_f_crypto.py similarity index 100% rename from test/functional/internal/crypto/test_crypto.py rename to test/functional/test_f_crypto.py diff --git a/test/functional/internal/crypto/test_iv.py b/test/functional/test_f_crypto_iv.py similarity index 100% rename from test/functional/internal/crypto/test_iv.py rename to test/functional/test_f_crypto_iv.py diff --git a/test/integration/README.rst b/test/integration/README.rst index a7dcdd5ac..33ecbbedd 100644 --- a/test/integration/README.rst +++ b/test/integration/README.rst @@ -5,11 +5,8 @@ aws-encryption-sdk Integration Tests In order to run these integration tests successfully, these things must be configured. #. Ensure that AWS credentials are available in one of the `automatically discoverable credential locations`_. -#. Set environment variable ``AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID`` - and ``AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2`` to valid +#. Set environment variable ``AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID`` to valid `AWS KMS key id`_ to use for integration tests. - These should be AWS KMS CMK ARNs in two different regions. - They will be used for integration tests. .. _automatically discoverable credential locations: http://boto3.readthedocs.io/en/latest/guide/configuration.html .. _AWS KMS key id: http://docs.aws.amazon.com/kms/latest/APIReference/API_Encrypt.html diff --git a/test/integration/__init__.py b/test/integration/__init__.py index ad0e71d6c..53a960891 100644 --- a/test/integration/__init__.py +++ b/test/integration/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 @@ -10,4 +10,3 @@ # 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. -"""Dummy stub to make linters work better.""" diff --git a/test/integration/integration_test_utils.py b/test/integration/integration_test_utils.py index 8edc125f8..b65d93570 100644 --- a/test/integration/integration_test_utils.py +++ b/test/integration/integration_test_utils.py @@ -14,50 +14,37 @@ import os import botocore.session -import pytest from aws_encryption_sdk.key_providers.kms import KMSMasterKeyProvider -from aws_encryption_sdk.keyrings.aws_kms import AwsKmsKeyring AWS_KMS_KEY_ID = "AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID" -AWS_KMS_KEY_ID_2 = "AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2" _KMS_MKP = None _KMS_MKP_BOTO = None -_KMS_KEYRING = None -def _get_single_cmk_arn(name): - # type: (str) -> str - """Retrieve a single target AWS KMS CMK ARN from the specified environment variable name.""" - arn = os.environ.get(name, None) +def get_cmk_arn(): + """Retrieves the target CMK ARN from environment variable.""" + arn = os.environ.get(AWS_KMS_KEY_ID, None) if arn is None: raise ValueError( - 'Environment variable "{}" must be set to a valid KMS CMK ARN for integration tests to run'.format(name) + 'Environment variable "{}" must be set to a valid KMS CMK ARN for integration tests to run'.format( + AWS_KMS_KEY_ID + ) ) if arn.startswith("arn:") and ":alias/" not in arn: return arn raise ValueError("KMS CMK ARN provided for integration tests much be a key not an alias") -def get_cmk_arn(): - """Retrieves the target AWS KMS CMK ARN from environment variable.""" - return _get_single_cmk_arn(AWS_KMS_KEY_ID) - - -def get_all_cmk_arns(): - """Retrieve all known target AWS KMS CMK ARNs from environment variables.""" - return [_get_single_cmk_arn(AWS_KMS_KEY_ID), _get_single_cmk_arn(AWS_KMS_KEY_ID_2)] - - def setup_kms_master_key_provider(cache=True): - """Build an AWS KMS Master Key Provider.""" + """Reads the test_values config file and builds the requested KMS Master Key Provider.""" global _KMS_MKP # pylint: disable=global-statement if cache and _KMS_MKP is not None: return _KMS_MKP cmk_arn = get_cmk_arn() kms_master_key_provider = KMSMasterKeyProvider() - kms_master_key_provider.add_master_key(cmk_arn.encode("utf-8")) + kms_master_key_provider.add_master_key(cmk_arn) if cache: _KMS_MKP = kms_master_key_provider @@ -66,42 +53,16 @@ def setup_kms_master_key_provider(cache=True): def setup_kms_master_key_provider_with_botocore_session(cache=True): - """Build an AWS KMS Master Key Provider with an explicit botocore_session.""" + """Reads the test_values config file and builds the requested KMS Master Key Provider with botocore_session.""" global _KMS_MKP_BOTO # pylint: disable=global-statement if cache and _KMS_MKP_BOTO is not None: return _KMS_MKP_BOTO cmk_arn = get_cmk_arn() kms_master_key_provider = KMSMasterKeyProvider(botocore_session=botocore.session.Session()) - kms_master_key_provider.add_master_key(cmk_arn.encode("utf-8")) + kms_master_key_provider.add_master_key(cmk_arn) if cache: _KMS_MKP_BOTO = kms_master_key_provider return kms_master_key_provider - - -def build_aws_kms_keyring(generate=True, cache=True): - """Build an AWS KMS keyring.""" - global _KMS_KEYRING # pylint: disable=global-statement - if cache and _KMS_KEYRING is not None: - return _KMS_KEYRING - - cmk_arn = get_cmk_arn() - - if generate: - kwargs = dict(generator_key_id=cmk_arn) - else: - kwargs = dict(key_ids=[cmk_arn]) - - keyring = AwsKmsKeyring(**kwargs) - - if cache: - _KMS_KEYRING = keyring - - return keyring - - -@pytest.fixture -def aws_kms_keyring(): - return build_aws_kms_keyring() diff --git a/test/integration/key_providers/__init__.py b/test/integration/key_providers/__init__.py deleted file mode 100644 index d548f9b1f..000000000 --- a/test/integration/key_providers/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Dummy stub to make linters work better.""" diff --git a/test/integration/key_providers/test_kms.py b/test/integration/key_providers/test_kms.py deleted file mode 100644 index 59c699f70..000000000 --- a/test/integration/key_providers/test_kms.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Integration tests for ``aws_encryption_sdk.key_provider.kms``.""" -from test.integration.integration_test_utils import setup_kms_master_key_provider_with_botocore_session - -import pytest -from botocore.exceptions import BotoCoreError - -from aws_encryption_sdk.key_providers.kms import KMSMasterKeyProvider - -pytestmark = [pytest.mark.integ] - - -def test_remove_bad_client(): - test = KMSMasterKeyProvider() - fake_region = "us-fakey-12" - test.add_regional_client(fake_region) - - with pytest.raises(BotoCoreError): - test._regional_clients[fake_region].list_keys() - - assert fake_region not in test._regional_clients - - -def test_regional_client_does_not_modify_botocore_session(caplog): - mkp = setup_kms_master_key_provider_with_botocore_session() - fake_region = "us-fakey-12" - - assert mkp.config.botocore_session.get_config_variable("region") != fake_region - mkp.add_regional_client(fake_region) - assert mkp.config.botocore_session.get_config_variable("region") != fake_region diff --git a/test/integration/keyrings/__init__.py b/test/integration/keyrings/__init__.py deleted file mode 100644 index d548f9b1f..000000000 --- a/test/integration/keyrings/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Dummy stub to make linters work better.""" diff --git a/test/integration/keyrings/aws_kms/__init__.py b/test/integration/keyrings/aws_kms/__init__.py deleted file mode 100644 index d548f9b1f..000000000 --- a/test/integration/keyrings/aws_kms/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Dummy stub to make linters work better.""" diff --git a/test/integration/keyrings/aws_kms/test_client_cache.py b/test/integration/keyrings/aws_kms/test_client_cache.py deleted file mode 100644 index 6ab1a05d5..000000000 --- a/test/integration/keyrings/aws_kms/test_client_cache.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Integration tests for ``aws_encryption_sdk.keyrings.aws_kms.client_cache``.""" -import pytest -from botocore.config import Config -from botocore.exceptions import BotoCoreError -from botocore.session import Session - -from aws_encryption_sdk.keyrings.aws_kms._client_cache import ClientCache - -pytestmark = [pytest.mark.integ] - - -def test_client_cache_removes_bad_client(): - cache = ClientCache(botocore_session=Session(), client_config=Config()) - fake_region = "us-fake-1" - - initial_client = cache.client(fake_region, "kms") - - assert fake_region in cache._cache - - with pytest.raises(BotoCoreError): - initial_client.encrypt(KeyId="foo", Plaintext=b"bar") - - assert fake_region not in cache._cache - - -def test_regional_client_does_not_modify_botocore_session(): - cache = ClientCache(botocore_session=Session(), client_config=Config()) - fake_region = "us-fake-1" - - assert cache._botocore_session.get_config_variable("region") != fake_region - cache.client(fake_region, "kms") - assert cache._botocore_session.get_config_variable("region") != fake_region - - -def test_client_cache_remove_bad_client_when_already_removed(): - cache = ClientCache(botocore_session=Session(), client_config=Config()) - fake_region = "us-fake-1" - - initial_client = cache.client(fake_region, "kms") - - assert fake_region in cache._cache - del cache._cache[fake_region] - - with pytest.raises(BotoCoreError): - initial_client.encrypt(KeyId="foo", Plaintext=b"bar") - - assert fake_region not in cache._cache diff --git a/test/integration/test_client.py b/test/integration/test_client.py deleted file mode 100644 index 44f43a95c..000000000 --- a/test/integration/test_client.py +++ /dev/null @@ -1,128 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Integration test suite for `aws_encryption_sdk`.""" -import io -import logging - -import pytest - -import aws_encryption_sdk -from aws_encryption_sdk.identifiers import USER_AGENT_SUFFIX, Algorithm - -from .integration_test_utils import build_aws_kms_keyring, get_cmk_arn, setup_kms_master_key_provider - -pytestmark = [pytest.mark.integ] - - -VALUES = { - "plaintext_128": ( - b"\xa3\xf6\xbc\x89\x95\x15(\xc8}\\\x8d=zu^{JA\xc1\xe9\xf0&m\xe6TD\x03" - b"\x165F\x85\xae\x96\xd9~ \xa6\x13\x88\xf8\xdb\xc9\x0c\xd8\xd8\xd4\xe0" - b"\x02\xe9\xdb+\xd4l\xeaq\xf6\xba.cg\xda\xe4V\xd9\x9a\x96\xe8\xf4:\xf5" - b"\xfd\xd7\xa6\xfa\xd1\x85\xa7o\xf5\x94\xbcE\x14L\xa1\x87\xd9T\xa6\x95" - b"eZVv\xfe[\xeeJ$a<9\x1f\x97\xe1\xd6\x9dQc\x8b7n\x0f\x1e\xbd\xf5\xba" - b"\x0e\xae|%\xd8L]\xa2\xa2\x08\x1f" - ), - "encryption_context": {"key_a": "value_a", "key_b": "value_b", "key_c": "value_c"}, -} - - -def _generate_mkp(): - """Isolated inside a function to avoid calling get_cmk_arn during test discovery.""" - return setup_kms_master_key_provider().master_key(get_cmk_arn()) - - -@pytest.mark.parametrize( - "parameter_name, value_partial", - ( - pytest.param("key_provider", setup_kms_master_key_provider, id="AWS KMS master key provider"), - pytest.param("key_provider", _generate_mkp, id="AWS KMS master key"), - pytest.param("keyring", build_aws_kms_keyring, id="AWS KMS keyring"), - ), -) -def test_encrypt_verify_user_agent_in_logs(caplog, parameter_name, value_partial): - caplog.set_level(level=logging.DEBUG) - - aws_encryption_sdk.encrypt(source=VALUES["plaintext_128"], **{parameter_name: value_partial()}) - - assert USER_AGENT_SUFFIX in caplog.text - - -@pytest.mark.parametrize("frame_size", (pytest.param(0, id="unframed"), pytest.param(1024, id="1024 byte frame"))) -@pytest.mark.parametrize("algorithm_suite", Algorithm) -@pytest.mark.parametrize( - "encrypt_key_provider_param, encrypt_key_provider_partial", - ( - pytest.param("key_provider", setup_kms_master_key_provider, id="encrypt with MKP"), - pytest.param("keyring", build_aws_kms_keyring, id="encrypt with keyring"), - ), -) -@pytest.mark.parametrize( - "decrypt_key_provider_param, decrypt_key_provider_partial", - ( - pytest.param("key_provider", setup_kms_master_key_provider, id="decrypt with MKP"), - pytest.param("keyring", build_aws_kms_keyring, id="decrypt with keyring"), - ), -) -@pytest.mark.parametrize( - "encryption_context", - ( - pytest.param({}, id="empty encryption context"), - pytest.param(VALUES["encryption_context"], id="non-empty encryption context"), - ), -) -@pytest.mark.parametrize( - "plaintext", - ( - pytest.param(VALUES["plaintext_128"], id="plaintext smaller than frame"), - pytest.param(VALUES["plaintext_128"] * 100, id="plaintext larger than frame"), - ), -) -def test_encrypt_decrypt_cycle_aws_kms( - frame_size, - algorithm_suite, - encrypt_key_provider_param, - encrypt_key_provider_partial, - decrypt_key_provider_param, - decrypt_key_provider_partial, - encryption_context, - plaintext, -): - ciphertext, _ = aws_encryption_sdk.encrypt( - source=plaintext, - encryption_context=encryption_context, - frame_length=frame_size, - algorithm=algorithm_suite, - **{encrypt_key_provider_param: encrypt_key_provider_partial()} - ) - decrypted, _ = aws_encryption_sdk.decrypt( - source=ciphertext, **{decrypt_key_provider_param: decrypt_key_provider_partial()} - ) - assert decrypted == plaintext - - -@pytest.mark.parametrize( - "plaintext", - ( - pytest.param(VALUES["plaintext_128"], id="plaintext smaller than frame"), - pytest.param(VALUES["plaintext_128"] * 100, id="plaintext larger than frame"), - ), -) -def test_encrypt_decrypt_cycle_aws_kms_streaming(plaintext): - keyring = build_aws_kms_keyring() - ciphertext = b"" - with aws_encryption_sdk.stream( - source=io.BytesIO(plaintext), keyring=keyring, mode="e", encryption_context=VALUES["encryption_context"], - ) as encryptor: - for chunk in encryptor: - ciphertext += chunk - header_1 = encryptor.header - - decrypted = b"" - with aws_encryption_sdk.stream(source=io.BytesIO(ciphertext), keyring=keyring, mode="d") as decryptor: - for chunk in decryptor: - decrypted += chunk - header_2 = decryptor.header - - assert decrypted == plaintext - assert header_1.encryption_context == header_2.encryption_context diff --git a/test/integration/test_i_aws_encrytion_sdk_client.py b/test/integration/test_i_aws_encrytion_sdk_client.py new file mode 100644 index 000000000..26df431dc --- /dev/null +++ b/test/integration/test_i_aws_encrytion_sdk_client.py @@ -0,0 +1,452 @@ +# 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. +"""Integration test suite for `aws_encryption_sdk`.""" +import io +import logging + +import pytest +from botocore.exceptions import BotoCoreError + +import aws_encryption_sdk +from aws_encryption_sdk.identifiers import USER_AGENT_SUFFIX, Algorithm +from aws_encryption_sdk.key_providers.kms import KMSMasterKey, KMSMasterKeyProvider + +from .integration_test_utils import ( + get_cmk_arn, + setup_kms_master_key_provider, + setup_kms_master_key_provider_with_botocore_session, +) + +pytestmark = [pytest.mark.integ] + + +VALUES = { + "plaintext_128": ( + b"\xa3\xf6\xbc\x89\x95\x15(\xc8}\\\x8d=zu^{JA\xc1\xe9\xf0&m\xe6TD\x03" + b"\x165F\x85\xae\x96\xd9~ \xa6\x13\x88\xf8\xdb\xc9\x0c\xd8\xd8\xd4\xe0" + b"\x02\xe9\xdb+\xd4l\xeaq\xf6\xba.cg\xda\xe4V\xd9\x9a\x96\xe8\xf4:\xf5" + b"\xfd\xd7\xa6\xfa\xd1\x85\xa7o\xf5\x94\xbcE\x14L\xa1\x87\xd9T\xa6\x95" + b"eZVv\xfe[\xeeJ$a<9\x1f\x97\xe1\xd6\x9dQc\x8b7n\x0f\x1e\xbd\xf5\xba" + b"\x0e\xae|%\xd8L]\xa2\xa2\x08\x1f" + ), + "encryption_context": {"key_a": "value_a", "key_b": "value_b", "key_c": "value_c"}, +} + + +def test_encrypt_verify_user_agent_kms_master_key_provider(caplog): + caplog.set_level(level=logging.DEBUG) + mkp = setup_kms_master_key_provider() + mk = mkp.master_key(get_cmk_arn()) + + mk.generate_data_key(algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, encryption_context={}) + + assert USER_AGENT_SUFFIX in caplog.text + + +def test_encrypt_verify_user_agent_kms_master_key(caplog): + caplog.set_level(level=logging.DEBUG) + mk = KMSMasterKey(key_id=get_cmk_arn()) + + mk.generate_data_key(algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, encryption_context={}) + + assert USER_AGENT_SUFFIX in caplog.text + + +def test_remove_bad_client(): + test = KMSMasterKeyProvider() + test.add_regional_client("us-fakey-12") + + with pytest.raises(BotoCoreError): + test._regional_clients["us-fakey-12"].list_keys() + + assert not test._regional_clients + + +def test_regional_client_does_not_modify_botocore_session(caplog): + mkp = setup_kms_master_key_provider_with_botocore_session() + fake_region = "us-fakey-12" + + assert mkp.config.botocore_session.get_config_variable("region") != fake_region + mkp.add_regional_client(fake_region) + assert mkp.config.botocore_session.get_config_variable("region") != fake_region + + +class TestKMSThickClientIntegration(object): + @pytest.fixture(autouse=True) + def apply_fixtures(self): + self.kms_master_key_provider = setup_kms_master_key_provider() + + def test_encryption_cycle_default_algorithm_framed_stream(self): + """Test that the enrypt/decrypt cycle completes successfully + for a framed message using the default algorithm. + """ + with aws_encryption_sdk.stream( + source=io.BytesIO(VALUES["plaintext_128"]), + key_provider=self.kms_master_key_provider, + mode="e", + encryption_context=VALUES["encryption_context"], + ) as encryptor: + ciphertext = encryptor.read() + header_1 = encryptor.header + with aws_encryption_sdk.stream( + source=io.BytesIO(ciphertext), key_provider=self.kms_master_key_provider, mode="d" + ) as decryptor: + plaintext = decryptor.read() + header_2 = decryptor.header + assert plaintext == VALUES["plaintext_128"] + assert header_1.encryption_context == header_2.encryption_context + + def test_encryption_cycle_default_algorithm_framed_stream_many_lines(self): + """Test that the enrypt/decrypt cycle completes successfully + for a framed message with many frames using the default algorithm. + """ + ciphertext = b"" + with aws_encryption_sdk.stream( + source=io.BytesIO(VALUES["plaintext_128"] * 10), + key_provider=self.kms_master_key_provider, + mode="e", + encryption_context=VALUES["encryption_context"], + frame_length=128, + ) as encryptor: + for chunk in encryptor: + ciphertext += chunk + header_1 = encryptor.header + plaintext = b"" + with aws_encryption_sdk.stream( + source=io.BytesIO(ciphertext), key_provider=self.kms_master_key_provider, mode="d" + ) as decryptor: + for chunk in decryptor: + plaintext += chunk + header_2 = decryptor.header + assert plaintext == VALUES["plaintext_128"] * 10 + assert header_1.encryption_context == header_2.encryption_context + + def test_encryption_cycle_default_algorithm_non_framed(self): + """Test that the enrypt/decrypt cycle completes successfully + for a non-framed message using the default algorithm. + """ + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=self.kms_master_key_provider, + encryption_context=VALUES["encryption_context"], + frame_length=0, + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_encryption_cycle_default_algorithm_non_framed_no_encryption_context(self): + """Test that the enrypt/decrypt cycle completes successfully + for a non-framed message using the default algorithm. + """ + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], key_provider=self.kms_master_key_provider, frame_length=0 + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_encryption_cycle_default_algorithm_single_frame(self): + """Test that the enrypt/decrypt cycle completes successfully + for a single frame message using the default algorithm. + """ + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=self.kms_master_key_provider, + encryption_context=VALUES["encryption_context"], + frame_length=1024, + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_encryption_cycle_default_algorithm_multiple_frames(self): + """Test that the enrypt/decrypt cycle completes successfully + for a framed message with multiple frames using the + default algorithm. + """ + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"] * 100, + key_provider=self.kms_master_key_provider, + encryption_context=VALUES["encryption_context"], + frame_length=1024, + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) + assert plaintext == VALUES["plaintext_128"] * 100 + + def test_encryption_cycle_aes_128_gcm_iv12_tag16_single_frame(self): + """Test that the enrypt/decrypt cycle completes successfully + for a single frame message using the aes_128_gcm_iv12_tag16 + algorithm. + """ + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=self.kms_master_key_provider, + encryption_context=VALUES["encryption_context"], + frame_length=1024, + algorithm=Algorithm.AES_128_GCM_IV12_TAG16, + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_encryption_cycle_aes_128_gcm_iv12_tag16_non_framed(self): + """Test that the enrypt/decrypt cycle completes successfully + for a non-framed message using the aes_128_gcm_iv12_tag16 + algorithm. + """ + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=self.kms_master_key_provider, + encryption_context=VALUES["encryption_context"], + frame_length=0, + algorithm=Algorithm.AES_128_GCM_IV12_TAG16, + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_encryption_cycle_aes_192_gcm_iv12_tag16_single_frame(self): + """Test that the enrypt/decrypt cycle completes successfully + for a single frame message using the aes_192_gcm_iv12_tag16 + algorithm. + """ + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=self.kms_master_key_provider, + encryption_context=VALUES["encryption_context"], + frame_length=1024, + algorithm=Algorithm.AES_192_GCM_IV12_TAG16, + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_encryption_cycle_aes_192_gcm_iv12_tag16_non_framed(self): + """Test that the enrypt/decrypt cycle completes successfully + for a non-framed message using the aes_192_gcm_iv12_tag16 + algorithm. + """ + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=self.kms_master_key_provider, + encryption_context=VALUES["encryption_context"], + frame_length=0, + algorithm=Algorithm.AES_192_GCM_IV12_TAG16, + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_encryption_cycle_aes_256_gcm_iv12_tag16_single_frame(self): + """Test that the enrypt/decrypt cycle completes successfully + for a single frame message using the aes_256_gcm_iv12_tag16 + algorithm. + """ + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=self.kms_master_key_provider, + encryption_context=VALUES["encryption_context"], + frame_length=1024, + algorithm=Algorithm.AES_256_GCM_IV12_TAG16, + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_encryption_cycle_aes_256_gcm_iv12_tag16_non_framed(self): + """Test that the enrypt/decrypt cycle completes successfully + for a non-framed message using the aes_256_gcm_iv12_tag16 + algorithm. + """ + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=self.kms_master_key_provider, + encryption_context=VALUES["encryption_context"], + frame_length=0, + algorithm=Algorithm.AES_256_GCM_IV12_TAG16, + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_encryption_cycle_aes_128_gcm_iv12_tag16_hkdf_sha256_single_frame(self): + """Test that the enrypt/decrypt cycle completes successfully for a + single frame message using the aes_128_gcm_iv12_tag16_hkdf_sha256 + algorithm. + """ + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=self.kms_master_key_provider, + encryption_context=VALUES["encryption_context"], + frame_length=1024, + algorithm=Algorithm.AES_128_GCM_IV12_TAG16_HKDF_SHA256, + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_encryption_cycle_aes_128_gcm_iv12_tag16_hkdf_sha256_non_framed(self): + """Test that the enrypt/decrypt cycle completes successfully for a + non-framed message using the aes_128_gcm_iv12_tag16_hkdf_sha256 + algorithm. + """ + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=self.kms_master_key_provider, + encryption_context=VALUES["encryption_context"], + frame_length=0, + algorithm=Algorithm.AES_128_GCM_IV12_TAG16_HKDF_SHA256, + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_encryption_cycle_aes_192_gcm_iv12_tag16_hkdf_sha256_single_frame(self): + """Test that the enrypt/decrypt cycle completes successfully for a + single frame message using the aes_192_gcm_iv12_tag16_hkdf_sha256 + algorithm. + """ + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=self.kms_master_key_provider, + encryption_context=VALUES["encryption_context"], + frame_length=1024, + algorithm=Algorithm.AES_192_GCM_IV12_TAG16_HKDF_SHA256, + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_encryption_cycle_aes_192_gcm_iv12_tag16_hkdf_sha256_non_framed(self): + """Test that the enrypt/decrypt cycle completes successfully for a + non-framed message using the aes_192_gcm_iv12_tag16_hkdf_sha256 + algorithm. + """ + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=self.kms_master_key_provider, + encryption_context=VALUES["encryption_context"], + frame_length=0, + algorithm=Algorithm.AES_192_GCM_IV12_TAG16_HKDF_SHA256, + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_encryption_cycle_aes_256_gcm_iv12_tag16_hkdf_sha256_single_frame(self): + """Test that the enrypt/decrypt cycle completes successfully for a + single frame message using the aes_256_gcm_iv12_tag16_hkdf_sha256 + algorithm. + """ + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=self.kms_master_key_provider, + encryption_context=VALUES["encryption_context"], + frame_length=1024, + algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA256, + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_encryption_cycle_aes_256_gcm_iv12_tag16_hkdf_sha256_non_framed(self): + """Test that the enrypt/decrypt cycle completes successfully for a + non-framed message using the aes_256_gcm_iv12_tag16_hkdf_sha256 + algorithm. + """ + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=self.kms_master_key_provider, + encryption_context=VALUES["encryption_context"], + frame_length=0, + algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA256, + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_encryption_cycle_aes_128_gcm_iv12_tag16_hkdf_sha256_ecdsa_p256_single_frame(self): + """Test that the enrypt/decrypt cycle completes successfully for a single + frame message using the aes_128_gcm_iv12_tag16_hkdf_sha256_ecdsa_p256 + algorithm. + """ + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=self.kms_master_key_provider, + encryption_context=VALUES["encryption_context"], + frame_length=1024, + algorithm=Algorithm.AES_128_GCM_IV12_TAG16_HKDF_SHA256_ECDSA_P256, + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_encryption_cycle_aes_128_gcm_iv12_tag16_hkdf_sha256_ecdsa_p256_non_framed(self): + """Test that the enrypt/decrypt cycle completes successfully for a single + block message using the aes_128_gcm_iv12_tag16_hkdf_sha256_ecdsa_p256 + algorithm. + """ + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=self.kms_master_key_provider, + encryption_context=VALUES["encryption_context"], + frame_length=0, + algorithm=Algorithm.AES_128_GCM_IV12_TAG16_HKDF_SHA256_ECDSA_P256, + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_encryption_cycle_aes_192_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384_single_frame(self): + """Test that the enrypt/decrypt cycle completes successfully for a single + frame message using the aes_192_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384 + algorithm. + """ + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=self.kms_master_key_provider, + encryption_context=VALUES["encryption_context"], + frame_length=1024, + algorithm=Algorithm.AES_192_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_encryption_cycle_aes_192_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384_non_framed(self): + """Test that the enrypt/decrypt cycle completes successfully for a single + block message using the aes_192_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384 + algorithm. + """ + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=self.kms_master_key_provider, + encryption_context=VALUES["encryption_context"], + frame_length=0, + algorithm=Algorithm.AES_192_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_encryption_cycle_aes_256_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384_single_frame(self): + """Test that the enrypt/decrypt cycle completes successfully for a single + frame message using the aes_256_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384 + algorithm. + """ + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=self.kms_master_key_provider, + encryption_context=VALUES["encryption_context"], + frame_length=1024, + algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_encryption_cycle_aes_256_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384_non_framed(self): + """Test that the enrypt/decrypt cycle completes successfully for a single + block message using the aes_256_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384 + algorithm. + """ + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=self.kms_master_key_provider, + encryption_context=VALUES["encryption_context"], + frame_length=0, + algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, + ) + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) + assert plaintext == VALUES["plaintext_128"] diff --git a/test/integration/test_thread_safety.py b/test/integration/test_i_thread_safety.py similarity index 100% rename from test/integration/test_thread_safety.py rename to test/integration/test_i_thread_safety.py diff --git a/test/requirements.txt b/test/requirements.txt index ff9311dc4..152b5dbf4 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -2,4 +2,3 @@ mock pytest>=3.3.1 pytest-cov pytest-mock -moto>=1.3.14 diff --git a/test/unit/__init__.py b/test/unit/__init__.py index ad0e71d6c..53a960891 100644 --- a/test/unit/__init__.py +++ b/test/unit/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# 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 @@ -10,4 +10,3 @@ # 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. -"""Dummy stub to make linters work better.""" diff --git a/test/unit/caches/__init__.py b/test/unit/caches/__init__.py deleted file mode 100644 index ad0e71d6c..000000000 --- a/test/unit/caches/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2019 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. -"""Dummy stub to make linters work better.""" diff --git a/test/unit/internal/__init__.py b/test/unit/internal/__init__.py deleted file mode 100644 index ad0e71d6c..000000000 --- a/test/unit/internal/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2019 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. -"""Dummy stub to make linters work better.""" diff --git a/test/unit/internal/crypto/__init__.py b/test/unit/internal/crypto/__init__.py deleted file mode 100644 index ad0e71d6c..000000000 --- a/test/unit/internal/crypto/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2019 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. -"""Dummy stub to make linters work better.""" diff --git a/test/unit/internal/crypto/authentication/__init__.py b/test/unit/internal/crypto/authentication/__init__.py deleted file mode 100644 index ad0e71d6c..000000000 --- a/test/unit/internal/crypto/authentication/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2019 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. -"""Dummy stub to make linters work better.""" diff --git a/test/unit/internal/crypto/encryption/__init__.py b/test/unit/internal/crypto/encryption/__init__.py deleted file mode 100644 index ad0e71d6c..000000000 --- a/test/unit/internal/crypto/encryption/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2019 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. -"""Dummy stub to make linters work better.""" diff --git a/test/unit/internal/formatting/__init__.py b/test/unit/internal/formatting/__init__.py deleted file mode 100644 index ad0e71d6c..000000000 --- a/test/unit/internal/formatting/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2019 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. -"""Dummy stub to make linters work better.""" diff --git a/test/unit/internal/utils/__init__.py b/test/unit/internal/utils/__init__.py deleted file mode 100644 index ad0e71d6c..000000000 --- a/test/unit/internal/utils/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2019 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. -"""Dummy stub to make linters work better.""" diff --git a/test/unit/key_providers/__init__.py b/test/unit/key_providers/__init__.py deleted file mode 100644 index ad0e71d6c..000000000 --- a/test/unit/key_providers/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2019 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. -"""Dummy stub to make linters work better.""" diff --git a/test/unit/key_providers/base/__init__.py b/test/unit/key_providers/base/__init__.py deleted file mode 100644 index ad0e71d6c..000000000 --- a/test/unit/key_providers/base/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2019 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. -"""Dummy stub to make linters work better.""" diff --git a/test/unit/key_providers/kms/__init__.py b/test/unit/key_providers/kms/__init__.py deleted file mode 100644 index ad0e71d6c..000000000 --- a/test/unit/key_providers/kms/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2019 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. -"""Dummy stub to make linters work better.""" diff --git a/test/unit/key_providers/raw/__init__.py b/test/unit/key_providers/raw/__init__.py deleted file mode 100644 index ad0e71d6c..000000000 --- a/test/unit/key_providers/raw/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2019 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. -"""Dummy stub to make linters work better.""" diff --git a/test/unit/keyrings/__init__.py b/test/unit/keyrings/__init__.py deleted file mode 100644 index ad0e71d6c..000000000 --- a/test/unit/keyrings/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2019 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. -"""Dummy stub to make linters work better.""" diff --git a/test/unit/keyrings/raw/__init__.py b/test/unit/keyrings/raw/__init__.py deleted file mode 100644 index ad0e71d6c..000000000 --- a/test/unit/keyrings/raw/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2019 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. -"""Dummy stub to make linters work better.""" diff --git a/test/unit/keyrings/raw/test_raw_aes.py b/test/unit/keyrings/raw/test_raw_aes.py deleted file mode 100644 index 72961c7d4..000000000 --- a/test/unit/keyrings/raw/test_raw_aes.py +++ /dev/null @@ -1,318 +0,0 @@ -# Copyright 2019 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 tests for Raw AES keyring.""" - -import os - -import mock -import pytest - -import aws_encryption_sdk.key_providers.raw -import aws_encryption_sdk.keyrings.raw -from aws_encryption_sdk.exceptions import EncryptKeyError -from aws_encryption_sdk.identifiers import Algorithm, KeyringTraceFlag, WrappingAlgorithm -from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey -from aws_encryption_sdk.keyrings.base import Keyring -from aws_encryption_sdk.keyrings.raw import GenerateKeyError, RawAESKeyring, _generate_data_key -from aws_encryption_sdk.materials_managers import EncryptionMaterials -from aws_encryption_sdk.structures import MasterKeyInfo - -from ...unit_test_utils import ( - _DATA_KEY, - _ENCRYPTED_DATA_KEY_AES, - _ENCRYPTED_DATA_KEY_NOT_IN_KEYRING, - _ENCRYPTION_CONTEXT, - _KEY_ID, - _PROVIDER_ID, - _SIGNING_KEY, - _WRAPPING_KEY, - get_decryption_materials_with_data_encryption_key, - get_decryption_materials_without_data_encryption_key, - get_encryption_materials_with_data_encryption_key, - get_encryption_materials_without_data_encryption_key, -) - -pytestmark = [pytest.mark.unit, pytest.mark.local] - - -@pytest.fixture -def raw_aes_keyring(): - return RawAESKeyring(key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_key=_WRAPPING_KEY,) - - -@pytest.fixture -def patch_generate_data_key(mocker): - mocker.patch.object(aws_encryption_sdk.keyrings.raw, "_generate_data_key") - return aws_encryption_sdk.keyrings.raw._generate_data_key - - -@pytest.fixture -def patch_decrypt_on_wrapping_key(mocker): - mocker.patch.object(WrappingKey, "decrypt") - return WrappingKey.decrypt - - -@pytest.fixture -def patch_encrypt_on_wrapping_key(mocker): - mocker.patch.object(WrappingKey, "encrypt") - return WrappingKey.encrypt - - -@pytest.fixture -def patch_os_urandom(mocker): - mocker.patch.object(os, "urandom") - return os.urandom - - -def test_parent(): - assert issubclass(RawAESKeyring, Keyring) - - -def test_valid_parameters(raw_aes_keyring): - test = raw_aes_keyring - assert test.key_name == _KEY_ID - assert test.key_namespace == _PROVIDER_ID - assert test._wrapping_algorithm == WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING - assert test._wrapping_key == _WRAPPING_KEY - - -@pytest.mark.parametrize( - "key_namespace, key_name, wrapping_algorithm, wrapping_key", - ( - (_PROVIDER_ID, None, WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING, None), - (None, None, None, None), - ( - _PROVIDER_ID, - _KEY_ID, - WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING, - WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING, - ), - ( - Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA256, - Algorithm.AES_256_GCM_IV12_TAG16, - Algorithm.AES_128_GCM_IV12_TAG16, - Algorithm.AES_128_GCM_IV12_TAG16, - ), - ), -) -def test_invalid_parameters(key_namespace, key_name, wrapping_algorithm, wrapping_key): - with pytest.raises(TypeError): - RawAESKeyring( - key_namespace=key_namespace, key_name=key_name, wrapping_key=wrapping_key, - ) - - -def test_invalid_key_length(): - with pytest.raises(ValueError) as excinfo: - RawAESKeyring( - key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_key=b"012345", - ) - - excinfo.match(r"Invalid wrapping key length. Must be one of \[16, 24, 32\] bytes.") - - -def test_on_encrypt_when_data_encryption_key_given(raw_aes_keyring, patch_generate_data_key): - test_raw_aes_keyring = raw_aes_keyring - - test_raw_aes_keyring.on_encrypt(encryption_materials=get_encryption_materials_with_data_encryption_key()) - # Check if keyring is generated - assert not patch_generate_data_key.called - - -def test_keyring_trace_on_encrypt_when_data_encryption_key_given(raw_aes_keyring): - test_raw_aes_keyring = raw_aes_keyring - - test = test_raw_aes_keyring.on_encrypt(encryption_materials=get_encryption_materials_with_data_encryption_key()) - - trace_entries = [entry for entry in test.keyring_trace if entry.wrapping_key == raw_aes_keyring._key_provider] - assert len(trace_entries) == 1 - - generate_traces = [entry for entry in trace_entries if entry.flags == {KeyringTraceFlag.GENERATED_DATA_KEY}] - assert len(generate_traces) == 0 - - encrypt_traces = [ - entry - for entry in trace_entries - if entry.flags == {KeyringTraceFlag.ENCRYPTED_DATA_KEY, KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT} - ] - assert len(encrypt_traces) == 1 - - -def test_on_encrypt_when_data_encryption_key_not_given(raw_aes_keyring): - - test_raw_aes_keyring = raw_aes_keyring - - original_number_of_encrypted_data_keys = len( - get_encryption_materials_without_data_encryption_key().encrypted_data_keys - ) - - test = test_raw_aes_keyring.on_encrypt(encryption_materials=get_encryption_materials_without_data_encryption_key()) - - # Check if data key is generated - assert test.data_encryption_key is not None - - trace_entries = [entry for entry in test.keyring_trace if entry.wrapping_key == raw_aes_keyring._key_provider] - assert len(trace_entries) == 2 - - generate_traces = [entry for entry in trace_entries if entry.flags == {KeyringTraceFlag.GENERATED_DATA_KEY}] - assert len(generate_traces) == 1 - - encrypt_traces = [ - entry - for entry in trace_entries - if entry.flags == {KeyringTraceFlag.ENCRYPTED_DATA_KEY, KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT} - ] - assert len(encrypt_traces) == 1 - - assert len(test.encrypted_data_keys) == original_number_of_encrypted_data_keys + 1 - - -def test_on_encrypt_cannot_encrypt(patch_encrypt_on_wrapping_key, raw_aes_keyring): - patch_encrypt_on_wrapping_key.side_effect = Exception("ENCRYPT FAIL") - - with pytest.raises(EncryptKeyError) as excinfo: - raw_aes_keyring.on_encrypt(get_encryption_materials_without_data_encryption_key()) - - excinfo.match("Raw AES keyring unable to encrypt data key") - - -@pytest.mark.parametrize( - "decryption_materials, edk", - ( - (get_decryption_materials_with_data_encryption_key(), [_ENCRYPTED_DATA_KEY_AES]), - (get_decryption_materials_with_data_encryption_key(), []), - ), -) -def test_on_decrypt_when_data_key_given(raw_aes_keyring, decryption_materials, edk, patch_decrypt_on_wrapping_key): - test_raw_aes_keyring = raw_aes_keyring - test_raw_aes_keyring.on_decrypt(decryption_materials=decryption_materials, encrypted_data_keys=edk) - assert not patch_decrypt_on_wrapping_key.called - - -def test_on_decrypt_keyring_trace_when_data_key_given(raw_aes_keyring): - test_raw_aes_keyring = raw_aes_keyring - test = test_raw_aes_keyring.on_decrypt( - decryption_materials=get_decryption_materials_with_data_encryption_key(), - encrypted_data_keys=[_ENCRYPTED_DATA_KEY_AES], - ) - - trace_entries = [entry for entry in test.keyring_trace if entry.wrapping_key == raw_aes_keyring._key_provider] - assert len(trace_entries) == 0 - - -@pytest.mark.parametrize( - "decryption_materials, edk", - ( - (get_decryption_materials_without_data_encryption_key(), []), - (get_encryption_materials_without_data_encryption_key(), [_ENCRYPTED_DATA_KEY_NOT_IN_KEYRING]), - ), -) -def test_on_decrypt_when_data_key_and_edk_not_provided( - raw_aes_keyring, decryption_materials, edk, patch_decrypt_on_wrapping_key -): - test_raw_aes_keyring = raw_aes_keyring - - test = test_raw_aes_keyring.on_decrypt(decryption_materials=decryption_materials, encrypted_data_keys=edk) - assert not patch_decrypt_on_wrapping_key.called - - trace_entries = [entry for entry in test.keyring_trace if entry.wrapping_key == raw_aes_keyring._key_provider] - assert len(trace_entries) == 0 - - assert test.data_encryption_key is None - - -def test_on_decrypt_when_data_key_not_provided_and_edk_provided(raw_aes_keyring, patch_decrypt_on_wrapping_key): - patch_decrypt_on_wrapping_key.return_value = _DATA_KEY - test_raw_aes_keyring = raw_aes_keyring - test_raw_aes_keyring.on_decrypt( - decryption_materials=get_decryption_materials_without_data_encryption_key(), - encrypted_data_keys=[_ENCRYPTED_DATA_KEY_AES], - ) - patch_decrypt_on_wrapping_key.assert_called_once_with( - encrypted_wrapped_data_key=mock.ANY, encryption_context=mock.ANY - ) - - -def test_on_decrypt_keyring_trace_when_data_key_not_provided_and_edk_provided(raw_aes_keyring): - test_raw_aes_keyring = raw_aes_keyring - - test = test_raw_aes_keyring.on_decrypt( - decryption_materials=get_decryption_materials_without_data_encryption_key(), - encrypted_data_keys=[_ENCRYPTED_DATA_KEY_AES], - ) - - trace_entries = [entry for entry in test.keyring_trace if entry.wrapping_key == raw_aes_keyring._key_provider] - assert len(trace_entries) == 1 - - decrypt_traces = [ - entry - for entry in trace_entries - if entry.flags == {KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT} - ] - assert len(decrypt_traces) == 1 - - -def test_on_decrypt_continues_through_edks_on_failure(raw_aes_keyring, patch_decrypt_on_wrapping_key): - patch_decrypt_on_wrapping_key.side_effect = (Exception("DECRYPT FAIL"), _DATA_KEY) - - test = raw_aes_keyring.on_decrypt( - decryption_materials=get_decryption_materials_without_data_encryption_key(), - encrypted_data_keys=(_ENCRYPTED_DATA_KEY_AES, _ENCRYPTED_DATA_KEY_AES), - ) - - assert test.data_encryption_key is not None - assert patch_decrypt_on_wrapping_key.call_count == 2 - - -def test_generate_data_key_error_when_data_key_not_generated(patch_os_urandom): - patch_os_urandom.side_effect = NotImplementedError - with pytest.raises(GenerateKeyError) as exc_info: - _generate_data_key( - encryption_materials=get_encryption_materials_without_data_encryption_key(), - key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_KEY_ID), - ) - assert exc_info.match("Unable to generate data encryption key.") - - -def test_generate_data_key_error_when_data_key_exists(): - with pytest.raises(TypeError) as exc_info: - _generate_data_key( - encryption_materials=get_encryption_materials_with_data_encryption_key(), - key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_KEY_ID), - ) - assert exc_info.match("Data encryption key already exists.") - - -def test_generate_data_key_keyring_trace(): - encryption_materials_without_data_key = EncryptionMaterials( - algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, - encryption_context=_ENCRYPTION_CONTEXT, - signing_key=_SIGNING_KEY, - ) - key_provider_info = MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_KEY_ID) - new_materials = _generate_data_key( - encryption_materials=encryption_materials_without_data_key, key_provider=key_provider_info, - ) - - assert new_materials is not encryption_materials_without_data_key - assert encryption_materials_without_data_key.data_encryption_key is None - assert not encryption_materials_without_data_key.keyring_trace - - assert new_materials.data_encryption_key is not None - assert new_materials.data_encryption_key.key_provider == key_provider_info - - trace_entries = [entry for entry in new_materials.keyring_trace if entry.wrapping_key == key_provider_info] - assert len(trace_entries) == 1 - - generate_traces = [entry for entry in trace_entries if entry.flags == {KeyringTraceFlag.GENERATED_DATA_KEY}] - assert len(generate_traces) == 1 diff --git a/test/unit/keyrings/raw/test_raw_rsa.py b/test/unit/keyrings/raw/test_raw_rsa.py deleted file mode 100644 index 5416ae24d..000000000 --- a/test/unit/keyrings/raw/test_raw_rsa.py +++ /dev/null @@ -1,321 +0,0 @@ -# Copyright 2019 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 tests for Raw AES keyring.""" - -import pytest -from cryptography.hazmat.primitives.asymmetric import rsa - -import aws_encryption_sdk.key_providers.raw -import aws_encryption_sdk.keyrings.raw -from aws_encryption_sdk.exceptions import EncryptKeyError -from aws_encryption_sdk.identifiers import KeyringTraceFlag, WrappingAlgorithm -from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey -from aws_encryption_sdk.keyrings.base import Keyring -from aws_encryption_sdk.keyrings.raw import RawRSAKeyring - -from ...unit_test_utils import ( - _BACKEND, - _DATA_KEY, - _ENCRYPTED_DATA_KEY_AES, - _ENCRYPTED_DATA_KEY_RSA, - _ENCRYPTION_CONTEXT, - _KEY_ID, - _KEY_SIZE, - _PROVIDER_ID, - _PUBLIC_EXPONENT, - get_decryption_materials_with_data_encryption_key, - get_decryption_materials_without_data_encryption_key, - get_encryption_materials_with_data_encryption_key, - get_encryption_materials_without_data_encryption_key, -) -from ...vectors import VALUES - -pytestmark = [pytest.mark.unit, pytest.mark.local] - - -@pytest.fixture -def raw_rsa_keyring(): - return RawRSAKeyring.from_pem_encoding( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_encoded_key=VALUES["private_rsa_key_bytes"][1], - ) - - -def raw_rsa_private_key(): - return rsa.generate_private_key(public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND) - - -@pytest.fixture -def patch_generate_data_key(mocker): - mocker.patch.object(aws_encryption_sdk.keyrings.raw, "_generate_data_key") - return aws_encryption_sdk.keyrings.raw._generate_data_key - - -@pytest.fixture -def patch_decrypt_on_wrapping_key(mocker): - mocker.patch.object(WrappingKey, "decrypt") - return WrappingKey.decrypt - - -@pytest.fixture -def patch_os_urandom(mocker): - mocker.patch.object(aws_encryption_sdk.key_providers.raw.os, "urandom") - return aws_encryption_sdk.key_providers.raw.os.urandom - - -def test_parent(): - assert issubclass(RawRSAKeyring, Keyring) - - -def test_valid_parameters(raw_rsa_keyring): - test = raw_rsa_keyring - assert test.key_namespace == _PROVIDER_ID - assert test.key_name == _KEY_ID - assert test._wrapping_algorithm == WrappingAlgorithm.RSA_OAEP_SHA256_MGF1 - assert isinstance(test._private_wrapping_key, rsa.RSAPrivateKey) - - -@pytest.mark.parametrize( - "key_namespace, key_name, wrapping_algorithm, private_wrapping_key, public_wrapping_key", - ( - (_PROVIDER_ID, None, WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, raw_rsa_private_key(), None), - (None, None, None, None, None), - (_PROVIDER_ID, _KEY_ID, WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, None), - (None, None, None, raw_rsa_private_key(), raw_rsa_private_key().public_key()), - (len(_PROVIDER_ID), len(_KEY_ID), _PROVIDER_ID, _PROVIDER_ID, _KEY_ID), - ), -) -def test_invalid_parameters(key_namespace, key_name, wrapping_algorithm, private_wrapping_key, public_wrapping_key): - with pytest.raises(TypeError): - RawRSAKeyring( - key_namespace=key_namespace, - key_name=key_name, - wrapping_algorithm=wrapping_algorithm, - private_wrapping_key=private_wrapping_key, - public_wrapping_key=public_wrapping_key, - ) - - -@pytest.mark.parametrize( - "wrapping_algorithm", - ( - WrappingAlgorithm.AES_128_GCM_IV12_TAG16_NO_PADDING, - WrappingAlgorithm.AES_192_GCM_IV12_TAG16_NO_PADDING, - WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING, - ), -) -def test_invalid_wrapping_algorithm_suite(wrapping_algorithm): - with pytest.raises(ValueError): - RawRSAKeyring( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - wrapping_algorithm=wrapping_algorithm, - private_wrapping_key=raw_rsa_private_key(), - ) - - -def test_public_and_private_key_not_provided(): - with pytest.raises(TypeError) as exc_info: - RawRSAKeyring( - key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1 - ) - assert exc_info.match("At least one of public key or private key must be provided.") - - -def test_on_encrypt_when_data_encryption_key_given(raw_rsa_keyring, patch_generate_data_key): - test_raw_rsa_keyring = raw_rsa_keyring - - test_raw_rsa_keyring.on_encrypt(encryption_materials=get_encryption_materials_with_data_encryption_key()) - # Check if keyring is generated - assert not patch_generate_data_key.called - - -def test_on_encrypt_no_public_key(raw_rsa_keyring): - raw_rsa_keyring._public_wrapping_key = None - - with pytest.raises(EncryptKeyError) as excinfo: - raw_rsa_keyring.on_encrypt(encryption_materials=get_encryption_materials_without_data_encryption_key()) - - excinfo.match("Raw RSA keyring unable to encrypt data key: no public key available") - - -def test_on_encrypt_keyring_trace_when_data_encryption_key_given(raw_rsa_keyring): - materials = get_encryption_materials_with_data_encryption_key() - test = raw_rsa_keyring.on_encrypt(encryption_materials=materials) - assert test is not materials - - trace_entries = [entry for entry in test.keyring_trace if entry.wrapping_key == raw_rsa_keyring._key_provider] - assert len(trace_entries) == 1 - - encrypt_traces = [entry for entry in trace_entries if entry.flags == {KeyringTraceFlag.ENCRYPTED_DATA_KEY}] - assert len(encrypt_traces) == 1 - - generate_traces = [entry for entry in trace_entries if entry.flags == {KeyringTraceFlag.GENERATED_DATA_KEY}] - assert len(generate_traces) == 0 - - -def test_on_encrypt_when_data_encryption_key_not_given(raw_rsa_keyring): - test_raw_rsa_keyring = raw_rsa_keyring - - original_number_of_encrypted_data_keys = len( - get_encryption_materials_without_data_encryption_key().encrypted_data_keys - ) - - test = test_raw_rsa_keyring.on_encrypt(encryption_materials=get_encryption_materials_without_data_encryption_key()) - - trace_entries = [entry for entry in test.keyring_trace if entry.wrapping_key == raw_rsa_keyring._key_provider] - assert len(trace_entries) == 2 - - encrypt_traces = [entry for entry in trace_entries if entry.flags == {KeyringTraceFlag.ENCRYPTED_DATA_KEY}] - assert len(encrypt_traces) == 1 - - generate_traces = [entry for entry in trace_entries if entry.flags == {KeyringTraceFlag.GENERATED_DATA_KEY}] - assert len(generate_traces) == 1 - - assert test.data_encryption_key.data_key is not None - - assert len(test.encrypted_data_keys) == original_number_of_encrypted_data_keys + 1 - - -def test_on_encrypt_cannot_encrypt(raw_rsa_keyring, mocker): - encrypt_patch = mocker.patch.object(raw_rsa_keyring._public_wrapping_key, "encrypt") - encrypt_patch.side_effect = Exception("ENCRYPT FAIL") - - with pytest.raises(EncryptKeyError) as excinfo: - raw_rsa_keyring.on_encrypt(encryption_materials=get_encryption_materials_without_data_encryption_key()) - - excinfo.match("Raw RSA keyring unable to encrypt data key") - - -def test_on_decrypt_when_data_key_given(raw_rsa_keyring, patch_decrypt_on_wrapping_key): - test_raw_rsa_keyring = raw_rsa_keyring - test_raw_rsa_keyring.on_decrypt( - decryption_materials=get_decryption_materials_with_data_encryption_key(), - encrypted_data_keys=[_ENCRYPTED_DATA_KEY_RSA], - ) - assert not patch_decrypt_on_wrapping_key.called - - -def test_on_decrypt_no_private_key(raw_rsa_keyring): - raw_rsa_keyring._private_wrapping_key = None - - materials = get_decryption_materials_without_data_encryption_key() - test = raw_rsa_keyring.on_decrypt(decryption_materials=materials, encrypted_data_keys=[_ENCRYPTED_DATA_KEY_RSA],) - - assert test is materials - - -def test_on_decrypt_keyring_trace_when_data_key_given(raw_rsa_keyring): - test_raw_rsa_keyring = raw_rsa_keyring - test = test_raw_rsa_keyring.on_decrypt( - decryption_materials=get_decryption_materials_with_data_encryption_key(), - encrypted_data_keys=[_ENCRYPTED_DATA_KEY_RSA], - ) - trace_entries = [entry for entry in test.keyring_trace if entry.wrapping_key == raw_rsa_keyring._key_provider] - assert len(trace_entries) == 0 - - -def test_on_decrypt_when_data_key_and_edk_not_provided(raw_rsa_keyring, patch_decrypt_on_wrapping_key): - test_raw_rsa_keyring = raw_rsa_keyring - - test = test_raw_rsa_keyring.on_decrypt( - decryption_materials=get_decryption_materials_without_data_encryption_key(), encrypted_data_keys=[] - ) - assert not patch_decrypt_on_wrapping_key.called - - trace_entries = [entry for entry in test.keyring_trace if entry.wrapping_key == raw_rsa_keyring._key_provider] - assert len(trace_entries) == 0 - - assert test.data_encryption_key is None - - -def test_on_decrypt_when_data_key_not_provided_and_no_know_edks(raw_rsa_keyring, mocker): - patched_wrapping_key_decrypt = mocker.patch.object(raw_rsa_keyring._private_wrapping_key, "decrypt") - - test = raw_rsa_keyring.on_decrypt( - decryption_materials=get_decryption_materials_without_data_encryption_key(), - encrypted_data_keys=[_ENCRYPTED_DATA_KEY_AES], - ) - - assert not patched_wrapping_key_decrypt.called - - assert test.data_encryption_key is None - - -def test_on_decrypt_when_data_key_not_provided_and_edk_not_in_keyring(raw_rsa_keyring, patch_decrypt_on_wrapping_key): - test_raw_rsa_keyring = raw_rsa_keyring - - test = test_raw_rsa_keyring.on_decrypt( - decryption_materials=get_decryption_materials_without_data_encryption_key(), - encrypted_data_keys=[_ENCRYPTED_DATA_KEY_RSA], - ) - assert not patch_decrypt_on_wrapping_key.called - - trace_entries = [entry for entry in test.keyring_trace if entry.wrapping_key == raw_rsa_keyring._key_provider] - assert not trace_entries - - assert test.data_encryption_key is None - - -def test_on_decrypt_when_data_key_not_provided_and_edk_provided(raw_rsa_keyring, patch_decrypt_on_wrapping_key): - patch_decrypt_on_wrapping_key.return_value = _DATA_KEY - test_raw_rsa_keyring = raw_rsa_keyring - - test_raw_rsa_keyring.on_decrypt( - decryption_materials=get_decryption_materials_without_data_encryption_key(), - encrypted_data_keys=[_ENCRYPTED_DATA_KEY_RSA], - ) - assert patch_decrypt_on_wrapping_key.called_once_with( - encrypted_wrapped_data_key=_ENCRYPTED_DATA_KEY_RSA, encryption_context=_ENCRYPTION_CONTEXT - ) - - -def test_on_decrypt_keyring_trace_when_data_key_not_provided_and_edk_provided(raw_rsa_keyring): - test_raw_rsa_keyring = raw_rsa_keyring - - test = test_raw_rsa_keyring.on_decrypt( - decryption_materials=get_decryption_materials_without_data_encryption_key(), - encrypted_data_keys=test_raw_rsa_keyring.on_encrypt( - encryption_materials=get_encryption_materials_without_data_encryption_key() - ).encrypted_data_keys, - ) - - trace_entries = [entry for entry in test.keyring_trace if entry.wrapping_key == raw_rsa_keyring._key_provider] - assert len(trace_entries) == 1 - - decrypt_traces = [entry for entry in trace_entries if entry.flags == {KeyringTraceFlag.DECRYPTED_DATA_KEY}] - assert len(decrypt_traces) == 1 - - assert test.data_encryption_key is not None - - -def test_on_decrypt_continues_through_edks_on_failure(raw_rsa_keyring, mocker): - patched_wrapping_key_decrypt = mocker.patch.object(raw_rsa_keyring._private_wrapping_key, "decrypt") - patched_wrapping_key_decrypt.side_effect = (Exception("DECRYPT FAIL"), _DATA_KEY) - - test = raw_rsa_keyring.on_decrypt( - decryption_materials=get_decryption_materials_without_data_encryption_key(), - encrypted_data_keys=(_ENCRYPTED_DATA_KEY_RSA, _ENCRYPTED_DATA_KEY_RSA), - ) - - assert patched_wrapping_key_decrypt.call_count == 2 - - trace_entries = [entry for entry in test.keyring_trace if entry.wrapping_key == raw_rsa_keyring._key_provider] - assert len(trace_entries) == 1 - - decrypt_traces = [entry for entry in trace_entries if entry.flags == {KeyringTraceFlag.DECRYPTED_DATA_KEY}] - assert len(decrypt_traces) == 1 - - assert test.data_encryption_key.data_key == _DATA_KEY diff --git a/test/unit/keyrings/test_aws_kms.py b/test/unit/keyrings/test_aws_kms.py deleted file mode 100644 index d1710193b..000000000 --- a/test/unit/keyrings/test_aws_kms.py +++ /dev/null @@ -1,198 +0,0 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Unit tests for ``aws_encryption_sdk.keyrings.aws_kms``.""" -import pytest - -from aws_encryption_sdk.keyrings.aws_kms import ( - AwsKmsKeyring, - _AwsKmsDiscoveryKeyring, - _AwsKmsSingleCmkKeyring, - _region_from_key_id, -) -from aws_encryption_sdk.keyrings.aws_kms.client_suppliers import DefaultClientSupplier -from aws_encryption_sdk.keyrings.multi import MultiKeyring - -pytestmark = [pytest.mark.unit, pytest.mark.local] - - -@pytest.mark.parametrize( - "kwargs", - ( - pytest.param(dict(client_supplier=None), id="client_supplier is invalid"), - pytest.param(dict(generator_key_id=5), id="generator_id is invalid"), - pytest.param(dict(key_ids=("foo", 5)), id="key_ids contains invalid values"), - pytest.param(dict(key_ids="some stuff"), id="key_ids is a string"), - pytest.param(dict(grant_tokens=("foo", 5)), id="grant_tokens contains invalid values"), - pytest.param(dict(grant_tokens="some stuff"), id="grant_tokens is a string"), - pytest.param(dict(generator_key_id="foo", is_discovery=True), id="generator and discovery"), - pytest.param(dict(key_ids=("foo",), is_discovery=True), id="key_ids and discovery"), - pytest.param(dict(), id="nothing"), - ), -) -def test_kms_keyring_invalid_parameters(kwargs): - with pytest.raises(TypeError): - AwsKmsKeyring(**kwargs) - - -def test_kms_keyring_builds_correct_inner_keyring_multikeyring(): - generator_id = "foo" - child_id_1 = "bar" - child_id_2 = "baz" - grants = ("asdf", "fdsa") - supplier = DefaultClientSupplier() - - test = AwsKmsKeyring( - generator_key_id=generator_id, key_ids=(child_id_1, child_id_2), grant_tokens=grants, client_supplier=supplier, - ) - - # We specified a generator and child IDs, so the inner keyring MUST be a multikeyring - assert isinstance(test._inner_keyring, MultiKeyring) - - # Verify that the generator is configured correctly - assert isinstance(test._inner_keyring.generator, _AwsKmsSingleCmkKeyring) - assert test._inner_keyring.generator._key_id == generator_id - assert test._inner_keyring.generator._grant_tokens == grants - assert test._inner_keyring.generator._client_supplier is supplier - - # We specified two child IDs, so there MUST be exactly two children - assert len(test._inner_keyring.children) == 2 - - # Verify that the first child is configured correctly - assert isinstance(test._inner_keyring.children[0], _AwsKmsSingleCmkKeyring) - assert test._inner_keyring.children[0]._key_id == child_id_1 - assert test._inner_keyring.children[0]._grant_tokens == grants - assert test._inner_keyring.children[0]._client_supplier is supplier - - # Verify that the second child is configured correctly - assert isinstance(test._inner_keyring.children[1], _AwsKmsSingleCmkKeyring) - assert test._inner_keyring.children[1]._key_id == child_id_2 - assert test._inner_keyring.children[1]._grant_tokens == grants - assert test._inner_keyring.children[1]._client_supplier is supplier - - -def test_kms_keyring_builds_correct_inner_keyring_multikeyring_no_generator(): - test = AwsKmsKeyring(key_ids=("bar", "baz")) - - # We specified child IDs, so the inner keyring MUST be a multikeyring - assert isinstance(test._inner_keyring, MultiKeyring) - - # We did not specify a generator ID, so the generator MUST NOT be set - assert test._inner_keyring.generator is None - - # We specified two child IDs, so there MUST be exactly two children - assert len(test._inner_keyring.children) == 2 - - -def test_kms_keyring_builds_correct_inner_keyring_multikeyring_no_children(): - test = AwsKmsKeyring(generator_key_id="foo") - - # We specified a generator ID, so the inner keyring MUST be a multikeyring - assert isinstance(test._inner_keyring, MultiKeyring) - - # We specified a generator ID, so the generator MUST be set - assert test._inner_keyring.generator is not None - - # We did not specify any child IDs, so the multikeyring MUST NOT contain any children - assert len(test._inner_keyring.children) == 0 - - -def test_kms_keyring_builds_correct_inner_keyring_discovery(): - grants = ("asdf", "fdas") - supplier = DefaultClientSupplier() - - test = AwsKmsKeyring(is_discovery=True, grant_tokens=grants, client_supplier=supplier) - - # We specified neither a generator nor children, so the inner keyring MUST be a discovery keyring - assert isinstance(test._inner_keyring, _AwsKmsDiscoveryKeyring) - - # Verify that the discovery keyring is configured correctly - assert test._inner_keyring._grant_tokens == grants - assert test._inner_keyring._client_supplier is supplier - - -def test_kms_keyring_inner_keyring_on_encrypt(mocker): - mock_keyring = mocker.Mock() - - keyring = AwsKmsKeyring(is_discovery=True) - keyring._inner_keyring = mock_keyring - - test = keyring.on_encrypt(encryption_materials=mocker.sentinel.encryption_materials) - - # on_encrypt MUST be a straight passthrough to the inner keyring - assert mock_keyring.on_encrypt.called_once_with(encryption_materials=mocker.sentinel.encryption_materials) - assert test is mock_keyring.on_encrypt.return_value - - -def test_kms_keyring_inner_keyring_on_decrypt(mocker): - mock_keyring = mocker.Mock() - - keyring = AwsKmsKeyring(is_discovery=True) - keyring._inner_keyring = mock_keyring - - test = keyring.on_decrypt( - decryption_materials=mocker.sentinel.decryption_materials, - encrypted_data_keys=mocker.sentinel.encrypted_data_keys, - ) - - # on_decrypt MUST be a straight passthrough to the inner keyring - assert mock_keyring.on_decrypt.called_once_with( - decryption_materials=mocker.sentinel.decryption_materials, - encrypted_data_keys=mocker.sentinel.encrypted_data_keys, - ) - assert test is mock_keyring.on_decrypt.return_value - - -@pytest.mark.parametrize( - "kwargs", - ( - pytest.param(dict(key_id=None, client_supplier=DefaultClientSupplier()), id="key_id is invalid"), - pytest.param(dict(key_id="foo", client_supplier=None), id="client_supplier is invalid"), - pytest.param( - dict(key_id="foo", client_supplier=DefaultClientSupplier(), grant_tokens=("bar", 5)), - id="grant_tokens contains invalid values", - ), - pytest.param( - dict(key_id="foo", client_supplier=DefaultClientSupplier(), grant_tokens="some stuff"), - id="grant_tokens is a string", - ), - ), -) -def test_aws_kms_single_cmk_keyring_invalid_parameters(kwargs): - with pytest.raises(TypeError): - _AwsKmsSingleCmkKeyring(**kwargs) - - -@pytest.mark.parametrize( - "kwargs", - ( - pytest.param(dict(client_supplier=None), id="client_supplier is invalid"), - pytest.param( - dict(client_supplier=DefaultClientSupplier(), grant_tokens=("bar", 5)), - id="grant_tokens contains invalid values", - ), - pytest.param( - dict(client_supplier=DefaultClientSupplier(), grant_tokens="some stuff"), id="grant_tokens is a string", - ), - ), -) -def test_aws_kms_discovery_keyring_invalid_parameters(kwargs): - with pytest.raises(TypeError): - _AwsKmsDiscoveryKeyring(**kwargs) - - -@pytest.mark.parametrize( - "key_id, expected", - ( - pytest.param("foo", None, id="invalid format"), - pytest.param("alias/foo", None, id="alias name"), - pytest.param("880e7651-6f87-4c68-b84b-3220da5a7a02", None, id="key ID"), - pytest.param("arn:aws:kms:moon-base-1:111222333444:alias/foo", "moon-base-1", id="alias ARN"), - pytest.param( - "arn:aws:kms:moon-base-1:111222333444:key/880e7651-6f87-4c68-b84b-3220da5a7a02", "moon-base-1", id="CMK ARN" - ), - ), -) -def test_region_from_key_id(key_id, expected): - actual = _region_from_key_id(key_id=key_id) - - assert actual == expected diff --git a/test/unit/keyrings/test_base.py b/test/unit/keyrings/test_base.py deleted file mode 100644 index 08522de0a..000000000 --- a/test/unit/keyrings/test_base.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2019 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 tests for base keyring.""" - -import pytest - -from aws_encryption_sdk.identifiers import Algorithm -from aws_encryption_sdk.keyrings.base import Keyring -from aws_encryption_sdk.materials_managers import DecryptionMaterials, EncryptionMaterials - -pytestmark = [pytest.mark.unit, pytest.mark.local] - -_encryption_materials = EncryptionMaterials( - algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, - encryption_context={"encryption": "context", "values": "here"}, - signing_key=b"aws-crypto-public-key", -) - -_decryption_materials = DecryptionMaterials( - algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, verification_key=b"ex_verification_key" -) - -_encrypted_data_keys = [] - - -def test_keyring_no_encrypt(): - with pytest.raises(NotImplementedError) as exc_info: - Keyring().on_encrypt(encryption_materials=_encryption_materials) - assert exc_info.match("Keyring does not implement on_encrypt function") - - -def test_keyring_no_decrypt(): - with pytest.raises(NotImplementedError) as exc_info: - Keyring().on_decrypt(decryption_materials=_decryption_materials, encrypted_data_keys=_encrypted_data_keys) - assert exc_info.match("Keyring does not implement on_decrypt function") diff --git a/test/unit/keyrings/test_multi.py b/test/unit/keyrings/test_multi.py deleted file mode 100644 index 97948ef63..000000000 --- a/test/unit/keyrings/test_multi.py +++ /dev/null @@ -1,244 +0,0 @@ -# Copyright 2019 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 tests for Multi keyring.""" - -import pytest -from mock import MagicMock -from pytest_mock import mocker # noqa pylint: disable=unused-import - -from aws_encryption_sdk.exceptions import EncryptKeyError, GenerateKeyError -from aws_encryption_sdk.identifiers import WrappingAlgorithm -from aws_encryption_sdk.internal.formatting import serialize -from aws_encryption_sdk.keyrings.base import Keyring -from aws_encryption_sdk.keyrings.multi import MultiKeyring -from aws_encryption_sdk.keyrings.raw import RawAESKeyring - -from ..unit_test_utils import ( - IdentityKeyring, - OnlyGenerateKeyring, - get_decryption_materials_with_data_key, - get_decryption_materials_without_data_key, - get_encryption_materials_with_data_key, - get_encryption_materials_with_encrypted_data_key, - get_encryption_materials_without_data_key, - get_multi_keyring_with_generator_and_children, - get_multi_keyring_with_no_children, - get_multi_keyring_with_no_generator, -) - -pytestmark = [pytest.mark.unit, pytest.mark.local] - - -_ENCRYPTION_CONTEXT = {"encryption": "context", "values": "here"} -_PROVIDER_ID = "Random Raw Keys" -_KEY_ID = b"5325b043-5843-4629-869c-64794af77ada" -_WRAPPING_KEY_AES = b"\xeby-\x80A6\x15rA8\x83#,\xe4\xab\xac`\xaf\x99Z\xc1\xce\xdb\xb6\x0f\xb7\x805\xb2\x14J3" -_SIGNING_KEY = b"aws-crypto-public-key" - - -@pytest.fixture -def identity_keyring(): - return IdentityKeyring() - - -@pytest.fixture -def keyring_which_only_generates(): - return OnlyGenerateKeyring() - - -@pytest.fixture -def mock_generator(): - mock_generator_keyring = MagicMock() - mock_generator_keyring.__class__ = RawAESKeyring - return mock_generator_keyring - - -@pytest.fixture -def mock_child_1(): - mock_child_1_keyring = MagicMock() - mock_child_1_keyring.__class__ = RawAESKeyring - return mock_child_1_keyring - - -@pytest.fixture -def mock_child_2(): - mock_child_2_keyring = MagicMock() - mock_child_2_keyring.__class__ = RawAESKeyring - return mock_child_2_keyring - - -@pytest.fixture -def mock_child_3(): - mock_child_3_keyring = MagicMock() - mock_child_3_keyring.__class__ = RawAESKeyring - mock_child_3_keyring.on_decrypt.return_value = get_decryption_materials_with_data_key() - return mock_child_3_keyring - - -@pytest.fixture -def patch_encrypt(mocker): - mocker.patch.object(serialize, "serialize_raw_master_key_prefix") - return serialize.serialize_raw_master_key_prefix - - -def test_parent(): - assert issubclass(MultiKeyring, Keyring) - - -def test_keyring_with_generator_but_no_children(): - generator_keyring = RawAESKeyring(key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_key=_WRAPPING_KEY_AES,) - test_multi_keyring = MultiKeyring(generator=generator_keyring) - assert test_multi_keyring.generator is generator_keyring - assert not test_multi_keyring.children - - -def test_keyring_with_children_but_no_generator(): - children_keyring = [RawAESKeyring(key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_key=_WRAPPING_KEY_AES,)] - test_multi_keyring = MultiKeyring(children=children_keyring) - assert test_multi_keyring.children is children_keyring - assert test_multi_keyring.generator is None - - -def test_keyring_with_no_generator_no_children(): - with pytest.raises(TypeError) as exc_info: - MultiKeyring() - assert exc_info.match("At least one of generator or children must be provided") - - -@pytest.mark.parametrize( - "generator, children", - ( - (WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING, None), - (WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING, get_multi_keyring_with_no_generator().children), - (None, [WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING]), - (get_multi_keyring_with_no_children().generator, [WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING]), - ), -) -def test_keyring_with_invalid_parameters(generator, children): - with pytest.raises(TypeError) as exc_info: - MultiKeyring(generator=generator, children=children) - assert exc_info.match("('children'|'generator') must be .*") - - -def test_decryption_keyring(): - test_multi_keyring = get_multi_keyring_with_generator_and_children() - assert test_multi_keyring.generator in test_multi_keyring._decryption_keyrings - for child_keyring in test_multi_keyring.children: - assert child_keyring in test_multi_keyring._decryption_keyrings - assert len(test_multi_keyring._decryption_keyrings) == len(test_multi_keyring.children) + 1 - - -def test_on_encrypt_with_no_generator_no_data_encryption_key(): - test_multi_keyring = get_multi_keyring_with_no_generator() - with pytest.raises(EncryptKeyError) as exc_info: - test_multi_keyring.on_encrypt(encryption_materials=get_encryption_materials_without_data_key()) - assert exc_info.match( - "Generator keyring not provided and encryption materials do not already contain a plaintext data key." - ) - - -def test_identity_keyring_as_generator_and_no_data_encryption_key(identity_keyring): - test_multi_keyring = MultiKeyring(generator=identity_keyring) - with pytest.raises(GenerateKeyError) as exc_info: - test_multi_keyring.on_encrypt(encryption_materials=get_encryption_materials_without_data_key()) - assert exc_info.match("Unable to generate data encryption key.") - - -def test_number_of_encrypted_data_keys_without_generator_with_children(): - test_multi_keyring = get_multi_keyring_with_no_generator() - test = test_multi_keyring.on_encrypt(encryption_materials=get_encryption_materials_with_data_key()) - assert len(test.encrypted_data_keys) == len(test_multi_keyring.children) - - -def test_number_of_encrypted_data_keys_without_children_with_generator(): - test_multi_keyring = get_multi_keyring_with_no_children() - test = test_multi_keyring.on_encrypt(encryption_materials=get_encryption_materials_with_data_key()) - assert len(test.encrypted_data_keys) == 1 - - -def test_number_of_encrypted_data_keys_with_generator_and_children(): - test_multi_keyring = get_multi_keyring_with_generator_and_children() - number_of_children = len(test_multi_keyring.children) - test = test_multi_keyring.on_encrypt(encryption_materials=get_encryption_materials_with_data_key()) - assert len(test.encrypted_data_keys) == number_of_children + 1 - - -def test_on_encrypt_when_data_encryption_key_given(mock_generator, mock_child_1, mock_child_2): - test_multi_keyring = MultiKeyring(generator=mock_generator, children=[mock_child_1, mock_child_2]) - initial_materials = get_encryption_materials_with_data_key() - new_materials = test_multi_keyring.on_encrypt(encryption_materials=initial_materials) - - assert new_materials is not initial_materials - - for keyring in test_multi_keyring._decryption_keyrings: - keyring.on_encrypt.assert_called_once() - - -def test_on_encrypt_edk_length_when_keyring_generates_but_does_not_encrypt_encryption_materials_without_data_key(): - test_multi_keyring = MultiKeyring(generator=OnlyGenerateKeyring()) - len_edk_before_encrypt = len(get_encryption_materials_without_data_key().encrypted_data_keys) - test = test_multi_keyring.on_encrypt(encryption_materials=get_encryption_materials_without_data_key()) - assert test.data_encryption_key is not None - assert len(test.encrypted_data_keys) == len_edk_before_encrypt - - -def test_on_encrypt_edk_length_when_keyring_generates_but_does_not_encrypt_encryption_materials_with_data_key(): - test_multi_keyring = MultiKeyring(generator=OnlyGenerateKeyring()) - test = test_multi_keyring.on_encrypt(encryption_materials=get_encryption_materials_with_encrypted_data_key()) - assert len(test.encrypted_data_keys) == len(get_encryption_materials_with_encrypted_data_key().encrypted_data_keys) - - -def test_on_decrypt_when_data_encryption_key_given(mock_generator, mock_child_1, mock_child_2): - test_multi_keyring = MultiKeyring(generator=mock_generator, children=[mock_child_1, mock_child_2]) - initial_materials = get_decryption_materials_with_data_key() - new_materials = test_multi_keyring.on_decrypt(decryption_materials=initial_materials, encrypted_data_keys=[]) - - assert new_materials is initial_materials - - for keyring in test_multi_keyring._decryption_keyrings: - assert not keyring.on_decrypt.called - - -def test_on_decrypt_every_keyring_called_when_data_encryption_key_not_added(mock_generator, mock_child_1, mock_child_2): - mock_generator.on_decrypt.side_effect = ( - lambda decryption_materials, encrypted_data_keys: get_decryption_materials_without_data_key() - ) - mock_child_1.on_decrypt.return_value = get_decryption_materials_without_data_key() - mock_child_2.on_decrypt.return_value = get_decryption_materials_without_data_key() - - test_multi_keyring = MultiKeyring(generator=mock_generator, children=[mock_child_1, mock_child_2]) - test_multi_keyring.on_decrypt( - decryption_materials=get_decryption_materials_without_data_key(), encrypted_data_keys=[] - ) - - for keyring in test_multi_keyring._decryption_keyrings: - assert keyring.on_decrypt.called - - -def test_no_keyring_called_after_data_encryption_key_added_when_data_encryption_key_not_given( - mock_generator, mock_child_1, mock_child_2, mock_child_3 -): - - mock_generator.on_decrypt.side_effect = ( - lambda decryption_materials, encrypted_data_keys: get_decryption_materials_without_data_key() - ) - - test_multi_keyring = MultiKeyring(generator=mock_generator, children=[mock_child_3, mock_child_1, mock_child_2]) - initial_materials = get_decryption_materials_without_data_key() - new_materials = test_multi_keyring.on_decrypt(decryption_materials=initial_materials, encrypted_data_keys=[]) - - assert new_materials is not initial_materials - assert mock_generator.on_decrypt.called - assert mock_child_3.on_decrypt.called - assert not mock_child_1.called - assert not mock_child_2.called diff --git a/test/unit/materials_managers/__init__.py b/test/unit/materials_managers/__init__.py deleted file mode 100644 index ad0e71d6c..000000000 --- a/test/unit/materials_managers/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2019 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. -"""Dummy stub to make linters work better.""" diff --git a/test/unit/materials_managers/test_material_managers.py b/test/unit/materials_managers/test_material_managers.py deleted file mode 100644 index 62314298e..000000000 --- a/test/unit/materials_managers/test_material_managers.py +++ /dev/null @@ -1,484 +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 for aws_encryption_sdk.materials_managers""" - -import pytest -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.asymmetric import ec -from mock import MagicMock - -from aws_encryption_sdk.exceptions import InvalidDataKeyError, InvalidKeyringTraceError, SignatureKeyError -from aws_encryption_sdk.identifiers import AlgorithmSuite, KeyringTraceFlag -from aws_encryption_sdk.internal.crypto.authentication import Signer, Verifier -from aws_encryption_sdk.internal.defaults import ALGORITHM -from aws_encryption_sdk.internal.utils.streams import ROStream -from aws_encryption_sdk.materials_managers import ( - CryptographicMaterials, - DecryptionMaterials, - DecryptionMaterialsRequest, - EncryptionMaterials, - EncryptionMaterialsRequest, - _data_key_to_raw_data_key, -) -from aws_encryption_sdk.structures import DataKey, EncryptedDataKey, KeyringTrace, MasterKeyInfo, RawDataKey - -pytestmark = [pytest.mark.unit, pytest.mark.local] - -_DATA_KEY = DataKey( - key_provider=MasterKeyInfo(provider_id="Provider", key_info=b"Info"), - data_key=b"1234567890123456789012", - encrypted_data_key=b"asdf", -) -_RAW_DATA_KEY = RawDataKey.from_data_key(_DATA_KEY) -_ENCRYPTED_DATA_KEY = EncryptedDataKey.from_data_key(_DATA_KEY) -_SIGNATURE_PRIVATE_KEY = ec.generate_private_key(ALGORITHM.signing_algorithm_info(), default_backend()) -_SIGNING_KEY = Signer(algorithm=ALGORITHM, key=_SIGNATURE_PRIVATE_KEY) -_VERIFICATION_KEY = Verifier(algorithm=ALGORITHM, key=_SIGNATURE_PRIVATE_KEY.public_key()) - -_VALID_KWARGS = { - "CryptographicMaterials": dict( - algorithm=ALGORITHM, - encryption_context={"additional": "data"}, - data_encryption_key=_DATA_KEY, - keyring_trace=[ - KeyringTrace( - wrapping_key=MasterKeyInfo(provider_id="Provider", key_info=b"Info"), - flags={KeyringTraceFlag.GENERATED_DATA_KEY}, - ) - ], - ), - "EncryptionMaterialsRequest": dict( - encryption_context={}, - plaintext_rostream=MagicMock(__class__=ROStream), - frame_length=5, - algorithm=ALGORITHM, - plaintext_length=5, - ), - "EncryptionMaterials": dict( - algorithm=ALGORITHM, - data_encryption_key=_DATA_KEY, - encrypted_data_keys=[], - encryption_context={}, - signing_key=_SIGNING_KEY.key_bytes(), - ), - "DecryptionMaterialsRequest": dict(algorithm=ALGORITHM, encrypted_data_keys=[], encryption_context={}), - "DecryptionMaterials": dict( - data_key=_DATA_KEY, verification_key=_VERIFICATION_KEY.key_bytes(), algorithm=ALGORITHM, encryption_context={} - ), -} -_REMOVE = object() - - -def _copy_and_update_kwargs(class_name, mod_kwargs): - kwargs = _VALID_KWARGS[class_name].copy() - kwargs.update(mod_kwargs) - purge_keys = [key for key, val in kwargs.items() if val is _REMOVE] - for key in purge_keys: - del kwargs[key] - return kwargs - - -@pytest.mark.parametrize( - "attr_class, invalid_kwargs", - ( - (CryptographicMaterials, dict(algorithm=1234)), - (CryptographicMaterials, dict(encryption_context=1234)), - (CryptographicMaterials, dict(data_encryption_key=1234)), - (CryptographicMaterials, dict(encrypted_data_keys=1234)), - (CryptographicMaterials, dict(keyring_trace=1234)), - (EncryptionMaterialsRequest, dict(encryption_context=None)), - (EncryptionMaterialsRequest, dict(frame_length="not an int")), - (EncryptionMaterialsRequest, dict(algorithm="not an Algorithm or None")), - (EncryptionMaterialsRequest, dict(plaintext_length="not an int or None")), - (EncryptionMaterials, dict(algorithm=None)), - (EncryptionMaterials, dict(encryption_context=None)), - (EncryptionMaterials, dict(signing_key=u"not bytes or None")), - (DecryptionMaterialsRequest, dict(algorithm=None)), - (DecryptionMaterialsRequest, dict(encrypted_data_keys=None)), - (DecryptionMaterialsRequest, dict(encryption_context=None)), - (DecryptionMaterials, dict(verification_key=5555)), - (DecryptionMaterials, dict(data_key=_DATA_KEY, data_encryption_key=_DATA_KEY)), - ), -) -def test_attributes_fails(attr_class, invalid_kwargs): - kwargs = _copy_and_update_kwargs(attr_class.__name__, invalid_kwargs) - with pytest.raises(TypeError): - attr_class(**kwargs) - - -@pytest.mark.parametrize( - "attr_class, kwargs_modification", - ( - (CryptographicMaterials, {}), - (EncryptionMaterials, {}), - (EncryptionMaterials, dict(data_encryption_key=_REMOVE, encrypted_data_keys=[])), - (EncryptionMaterials, dict(data_encryption_key=_REMOVE, encrypted_data_keys=_REMOVE)), - (DecryptionMaterials, {}), - (DecryptionMaterials, dict(data_key=_REMOVE, data_encryption_key=_REMOVE)), - (DecryptionMaterials, dict(data_key=_REMOVE, data_encryption_key=_RAW_DATA_KEY)), - (DecryptionMaterials, dict(data_key=_RAW_DATA_KEY, data_encryption_key=_REMOVE)), - ), -) -def test_attributes_good(attr_class, kwargs_modification): - kwargs = _copy_and_update_kwargs(attr_class.__name__, kwargs_modification) - attr_class(**kwargs) - - -def test_encryption_materials_request_attributes_defaults(): - test = EncryptionMaterialsRequest(encryption_context={}, frame_length=5) - assert test.plaintext_rostream is None - assert test.algorithm is None - assert test.plaintext_length is None - - -def test_encryption_materials_defaults(): - test = EncryptionMaterials( - algorithm=ALGORITHM, data_encryption_key=_DATA_KEY, encrypted_data_keys=[], encryption_context={} - ) - assert test.signing_key is None - - -def test_decryption_materials_defaults(): - test = DecryptionMaterials(data_key=_DATA_KEY) - assert test.verification_key is None - assert test.algorithm is None - assert test.encryption_context is None - - -def test_decryption_materials_legacy_data_key_get(): - test = DecryptionMaterials(data_encryption_key=_DATA_KEY) - - assert test.data_encryption_key == _RAW_DATA_KEY - assert test.data_key == _RAW_DATA_KEY - - -@pytest.mark.parametrize( - "data_key, expected", ((_DATA_KEY, _RAW_DATA_KEY), (_RAW_DATA_KEY, _RAW_DATA_KEY), (None, None)) -) -def test_data_key_to_raw_data_key_success(data_key, expected): - test = _data_key_to_raw_data_key(data_key=data_key) - - assert test == expected - - -def test_data_key_to_raw_data_key_fail(): - with pytest.raises(TypeError) as excinfo: - _data_key_to_raw_data_key(data_key="not a data key") - - excinfo.match("data_key must be type DataKey not str") - - -def _cryptographic_materials_attributes(): - for material in (CryptographicMaterials, EncryptionMaterials, DecryptionMaterials): - for attribute in ( - "algorithm", - "encryption_context", - "data_encryption_key", - "_keyring_trace", - "keyring_trace", - "_initialized", - ): - yield material, attribute - - for attribute in ("_encrypted_data_keys", "encrypted_data_keys", "signing_key"): - yield EncryptionMaterials, attribute - - for attribute in ("data_key", "verification_key"): - yield DecryptionMaterials, attribute - - -@pytest.mark.parametrize("material_class, attribute_name", _cryptographic_materials_attributes()) -def test_cryptographic_materials_cannot_change_attribute(material_class, attribute_name): - test = material_class(algorithm=ALGORITHM, encryption_context={}) - - with pytest.raises(AttributeError) as excinfo: - setattr(test, attribute_name, 42) - - excinfo.match("can't set attribute") - - -@pytest.mark.parametrize("material_class", (CryptographicMaterials, EncryptionMaterials, DecryptionMaterials)) -def test_immutable_keyring_trace(material_class): - materials = material_class(**_VALID_KWARGS[material_class.__name__]) - - with pytest.raises(AttributeError): - materials.keyring_trace.append(42) - - -@pytest.mark.parametrize("material_class", (CryptographicMaterials, EncryptionMaterials, DecryptionMaterials)) -def test_empty_keyring_trace(material_class): - materials = material_class(**_copy_and_update_kwargs(material_class.__name__, dict(keyring_trace=_REMOVE))) - - trace = materials.keyring_trace - - assert isinstance(trace, tuple) - assert not trace - - -def test_immutable_encrypted_data_keys(): - materials = EncryptionMaterials(**_VALID_KWARGS["EncryptionMaterials"]) - - with pytest.raises(AttributeError): - materials.encrypted_data_keys.append(42) - - -def test_empty_encrypted_data_keys(): - materials = EncryptionMaterials(**_copy_and_update_kwargs("EncryptionMaterials", dict(encrypted_data_keys=_REMOVE))) - - edks = materials.encrypted_data_keys - - assert isinstance(edks, tuple) - assert not edks - - -@pytest.mark.parametrize( - "material_class, flag", - ( - (EncryptionMaterials, KeyringTraceFlag.GENERATED_DATA_KEY), - (DecryptionMaterials, KeyringTraceFlag.DECRYPTED_DATA_KEY), - ), -) -def test_with_data_encryption_key_success(material_class, flag): - kwargs = _copy_and_update_kwargs( - material_class.__name__, dict(data_encryption_key=_REMOVE, data_key=_REMOVE, encrypted_data_keys=_REMOVE) - ) - materials = material_class(**kwargs) - - new_materials = materials.with_data_encryption_key( - data_encryption_key=RawDataKey( - key_provider=MasterKeyInfo(provider_id="a", key_info=b"b"), data_key=b"1" * ALGORITHM.kdf_input_len - ), - keyring_trace=KeyringTrace(wrapping_key=MasterKeyInfo(provider_id="a", key_info=b"b"), flags={flag}), - ) - assert new_materials is not materials - - -def _add_data_encryption_key_test_cases(): - for material_class, required_flags in ( - (EncryptionMaterials, KeyringTraceFlag.GENERATED_DATA_KEY), - (DecryptionMaterials, KeyringTraceFlag.DECRYPTED_DATA_KEY), - ): - yield ( - material_class, - dict(data_encryption_key=_RAW_DATA_KEY, data_key=_REMOVE, encrypted_data_keys=_REMOVE), - _RAW_DATA_KEY, - KeyringTrace(wrapping_key=_RAW_DATA_KEY.key_provider, flags={required_flags}), - AttributeError, - "Data encryption key is already set.", - ) - yield ( - material_class, - dict(data_encryption_key=_REMOVE, data_key=_REMOVE, encrypted_data_keys=_REMOVE), - _RAW_DATA_KEY, - KeyringTrace(wrapping_key=_RAW_DATA_KEY.key_provider, flags=set()), - InvalidKeyringTraceError, - "Keyring flags do not match action.", - ) - yield ( - material_class, - dict(data_encryption_key=_REMOVE, data_key=_REMOVE, encrypted_data_keys=_REMOVE), - RawDataKey(key_provider=MasterKeyInfo(provider_id="a", key_info=b"b"), data_key=b"asdf"), - KeyringTrace(wrapping_key=MasterKeyInfo(provider_id="c", key_info=b"d"), flags={required_flags}), - InvalidKeyringTraceError, - "Keyring trace does not match data key provider.", - ) - yield ( - material_class, - dict(data_encryption_key=_REMOVE, data_key=_REMOVE, encrypted_data_keys=_REMOVE), - RawDataKey(key_provider=_RAW_DATA_KEY.key_provider, data_key=b"1234"), - KeyringTrace(wrapping_key=_RAW_DATA_KEY.key_provider, flags={required_flags}), - InvalidDataKeyError, - r"Invalid data key length *", - ) - yield ( - DecryptionMaterials, - dict(data_encryption_key=_REMOVE, data_key=_REMOVE, encrypted_data_keys=_REMOVE, algorithm=_REMOVE), - RawDataKey(key_provider=_RAW_DATA_KEY.key_provider, data_key=b"1234"), - KeyringTrace(wrapping_key=_RAW_DATA_KEY.key_provider, flags={required_flags}), - AttributeError, - "Algorithm is not set", - ) - - -@pytest.mark.parametrize( - "material_class, mod_kwargs, data_encryption_key, keyring_trace, exception_type, exception_message", - _add_data_encryption_key_test_cases(), -) -def test_with_data_encryption_key_fail( - material_class, mod_kwargs, data_encryption_key, keyring_trace, exception_type, exception_message -): - kwargs = _copy_and_update_kwargs(material_class.__name__, mod_kwargs) - materials = material_class(**kwargs) - - with pytest.raises(exception_type) as excinfo: - materials.with_data_encryption_key(data_encryption_key=data_encryption_key, keyring_trace=keyring_trace) - - excinfo.match(exception_message) - - -def test_with_encrypted_data_key_success(): - kwargs = _copy_and_update_kwargs("EncryptionMaterials", {}) - materials = EncryptionMaterials(**kwargs) - - new_materials = materials.with_encrypted_data_key( - _ENCRYPTED_DATA_KEY, - keyring_trace=KeyringTrace( - wrapping_key=_ENCRYPTED_DATA_KEY.key_provider, flags={KeyringTraceFlag.ENCRYPTED_DATA_KEY} - ), - ) - assert new_materials is not materials - - -@pytest.mark.parametrize( - "mod_kwargs, encrypted_data_key, keyring_trace, exception_type, exception_message", - ( - ( - {}, - _ENCRYPTED_DATA_KEY, - KeyringTrace(wrapping_key=_ENCRYPTED_DATA_KEY.key_provider, flags=set()), - InvalidKeyringTraceError, - "Keyring flags do not match action.", - ), - ( - {}, - EncryptedDataKey(key_provider=MasterKeyInfo(provider_id="a", key_info=b"b"), encrypted_data_key=b"asdf"), - KeyringTrace( - wrapping_key=MasterKeyInfo(provider_id="not a match", key_info=b"really not a match"), - flags={KeyringTraceFlag.ENCRYPTED_DATA_KEY}, - ), - InvalidKeyringTraceError, - "Keyring trace does not match data key encryptor.", - ), - ( - dict(data_encryption_key=_REMOVE, encrypted_data_keys=_REMOVE), - _ENCRYPTED_DATA_KEY, - KeyringTrace(wrapping_key=_ENCRYPTED_DATA_KEY.key_provider, flags={KeyringTraceFlag.ENCRYPTED_DATA_KEY}), - AttributeError, - "Data encryption key is not set.", - ), - ), -) -def test_with_encrypted_data_key_fail(mod_kwargs, encrypted_data_key, keyring_trace, exception_type, exception_message): - kwargs = _copy_and_update_kwargs("EncryptionMaterials", mod_kwargs) - materials = EncryptionMaterials(**kwargs) - - with pytest.raises(exception_type) as excinfo: - materials.with_encrypted_data_key(encrypted_data_key=encrypted_data_key, keyring_trace=keyring_trace) - - excinfo.match(exception_message) - - -def test_with_signing_key_success(): - kwargs = _copy_and_update_kwargs("EncryptionMaterials", dict(signing_key=_REMOVE)) - materials = EncryptionMaterials(**kwargs) - - new_materials = materials.with_signing_key(signing_key=_SIGNING_KEY.key_bytes()) - assert new_materials is not materials - - -@pytest.mark.parametrize( - "mod_kwargs, signing_key, exception_type, exception_message", - ( - ({}, b"", AttributeError, "Signing key is already set."), - ( - dict(signing_key=_REMOVE, algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16), - b"", - SignatureKeyError, - "Algorithm suite does not support signing keys.", - ), - ), -) -def test_with_signing_key_fail(mod_kwargs, signing_key, exception_type, exception_message): - kwargs = _copy_and_update_kwargs("EncryptionMaterials", mod_kwargs) - materials = EncryptionMaterials(**kwargs) - - with pytest.raises(exception_type) as excinfo: - materials.with_signing_key(signing_key=signing_key) - - excinfo.match(exception_message) - - -def test_with_verification_key_success(): - kwargs = _copy_and_update_kwargs("DecryptionMaterials", dict(verification_key=_REMOVE)) - materials = DecryptionMaterials(**kwargs) - - new_materials = materials.with_verification_key(verification_key=_VERIFICATION_KEY.key_bytes()) - assert new_materials is not materials - - -@pytest.mark.parametrize( - "mod_kwargs, verification_key, exception_type, exception_message", - ( - ({}, b"", AttributeError, "Verification key is already set."), - ( - dict(verification_key=_REMOVE, algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16), - b"", - SignatureKeyError, - "Algorithm suite does not support signing keys.", - ), - ), -) -def test_with_verification_key_fail(mod_kwargs, verification_key, exception_type, exception_message): - kwargs = _copy_and_update_kwargs("DecryptionMaterials", mod_kwargs) - materials = DecryptionMaterials(**kwargs) - - with pytest.raises(exception_type) as excinfo: - materials.with_verification_key(verification_key=verification_key) - - excinfo.match(exception_message) - - -def test_decryption_materials_is_complete(): - materials = DecryptionMaterials(**_copy_and_update_kwargs("DecryptionMaterials", {})) - - assert materials.is_complete - - -@pytest.mark.parametrize( - "mod_kwargs", - ( - dict(algorithm=_REMOVE), - dict(encryption_context=_REMOVE), - dict(data_encryption_key=_REMOVE, data_key=_REMOVE), - dict(verification_key=_REMOVE), - ), -) -def test_decryption_materials_is_not_complete(mod_kwargs): - kwargs = _copy_and_update_kwargs("DecryptionMaterials", mod_kwargs) - materials = DecryptionMaterials(**kwargs) - - assert not materials.is_complete - - -def test_encryption_materials_is_complete(): - materials = EncryptionMaterials( - **_copy_and_update_kwargs("EncryptionMaterials", dict(encrypted_data_keys=[_ENCRYPTED_DATA_KEY])) - ) - - assert materials.is_complete - - -@pytest.mark.parametrize( - "mod_kwargs", - ( - dict(data_encryption_key=_REMOVE, encrypted_data_keys=_REMOVE), - dict(encrypted_data_keys=[]), - dict(encrypted_data_keys=_REMOVE), - dict(signing_key=_REMOVE), - ), -) -def test_encryption_materials_is_not_complete(mod_kwargs): - kwargs = _copy_and_update_kwargs("EncryptionMaterials", mod_kwargs) - materials = EncryptionMaterials(**kwargs) - - assert not materials.is_complete diff --git a/test/unit/streaming_client/__init__.py b/test/unit/streaming_client/__init__.py deleted file mode 100644 index ad0e71d6c..000000000 --- a/test/unit/streaming_client/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2019 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. -"""Dummy stub to make linters work better.""" diff --git a/test/unit/test_client.py b/test/unit/test_aws_encryption_sdk.py similarity index 89% rename from test/unit/test_client.py rename to test/unit/test_aws_encryption_sdk.py index d6b763b49..38dfff85a 100644 --- a/test/unit/test_client.py +++ b/test/unit/test_aws_encryption_sdk.py @@ -17,12 +17,7 @@ import aws_encryption_sdk import aws_encryption_sdk.internal.defaults -from .vectors import VALUES - pytestmark = [pytest.mark.unit, pytest.mark.local] -_CIPHERTEXT = b"CIPHERTEXT" -_PLAINTEXT = b"PLAINTEXT" -_HEADER = VALUES["deserialized_header_frame"] class TestAwsEncryptionSdk(object): @@ -32,16 +27,16 @@ def apply_fixtures(self): self.mock_stream_encryptor_patcher = patch("aws_encryption_sdk.StreamEncryptor") self.mock_stream_encryptor = self.mock_stream_encryptor_patcher.start() self.mock_stream_encryptor_instance = MagicMock() - self.mock_stream_encryptor_instance.read.return_value = _CIPHERTEXT - self.mock_stream_encryptor_instance.header = _HEADER + self.mock_stream_encryptor_instance.read.return_value = sentinel.ciphertext + self.mock_stream_encryptor_instance.header = sentinel.header self.mock_stream_encryptor.return_value = self.mock_stream_encryptor_instance self.mock_stream_encryptor_instance.__enter__.return_value = self.mock_stream_encryptor_instance # Set up StreamDecryptor patch self.mock_stream_decryptor_patcher = patch("aws_encryption_sdk.StreamDecryptor") self.mock_stream_decryptor = self.mock_stream_decryptor_patcher.start() self.mock_stream_decryptor_instance = MagicMock() - self.mock_stream_decryptor_instance.read.return_value = _PLAINTEXT - self.mock_stream_decryptor_instance.header = _HEADER + self.mock_stream_decryptor_instance.read.return_value = sentinel.plaintext + self.mock_stream_decryptor_instance.header = sentinel.header self.mock_stream_decryptor.return_value = self.mock_stream_decryptor_instance self.mock_stream_decryptor_instance.__enter__.return_value = self.mock_stream_decryptor_instance yield @@ -52,14 +47,14 @@ def apply_fixtures(self): def test_encrypt(self): test_ciphertext, test_header = aws_encryption_sdk.encrypt(a=sentinel.a, b=sentinel.b, c=sentinel.b) self.mock_stream_encryptor.called_once_with(a=sentinel.a, b=sentinel.b, c=sentinel.b) - assert test_ciphertext is _CIPHERTEXT - assert test_header is _HEADER + assert test_ciphertext is sentinel.ciphertext + assert test_header is sentinel.header def test_decrypt(self): test_plaintext, test_header = aws_encryption_sdk.decrypt(a=sentinel.a, b=sentinel.b, c=sentinel.b) self.mock_stream_encryptor.called_once_with(a=sentinel.a, b=sentinel.b, c=sentinel.b) - assert test_plaintext is _PLAINTEXT - assert test_header is _HEADER + assert test_plaintext is sentinel.plaintext + assert test_header is sentinel.header def test_stream_encryptor_e(self): test = aws_encryption_sdk.stream(mode="e", a=sentinel.a, b=sentinel.b, c=sentinel.b) diff --git a/test/unit/caches/test_caches.py b/test/unit/test_caches.py similarity index 97% rename from test/unit/caches/test_caches.py rename to test/unit/test_caches.py index 58c1b4944..250ad6d5b 100644 --- a/test/unit/caches/test_caches.py +++ b/test/unit/test_caches.py @@ -27,7 +27,7 @@ ) from aws_encryption_sdk.identifiers import Algorithm from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest -from aws_encryption_sdk.structures import EncryptedDataKey, MasterKeyInfo +from aws_encryption_sdk.structures import DataKey, MasterKeyInfo pytestmark = [pytest.mark.unit, pytest.mark.local] @@ -47,17 +47,19 @@ }, "encrypted_data_keys": [ { - "key": EncryptedDataKey( + "key": DataKey( key_provider=MasterKeyInfo(provider_id="this is a provider ID", key_info=b"this is some key info"), + data_key=b"super secret key!", encrypted_data_key=b"super secret key, now with encryption!", ), "hash": b"TYoFeYuxns/FBlaw4dsRDOv25OCEKuZG9iXt5iEdJ8LU7n5glgkDAVxWUEYC4JKKykJdHkaVpxcDvNqS6UswiQ==", }, { - "key": EncryptedDataKey( + "key": DataKey( key_provider=MasterKeyInfo( provider_id="another provider ID!", key_info=b"this is some different key info" ), + data_key=b"better super secret key!", encrypted_data_key=b"better super secret key, now with encryption!", ), "hash": b"wSrDlPM2ocIj9MAtD94ULSR0Qrt1muBovBDRL+DsSTNphJEM3CZ/h3OyvYL8BR2EIXx0m7GYwv8dGtyZL2D87w==", diff --git a/test/unit/caches/test_base.py b/test/unit/test_caches_base.py similarity index 100% rename from test/unit/caches/test_base.py rename to test/unit/test_caches_base.py diff --git a/test/unit/caches/test_crypto_cache_entry.py b/test/unit/test_caches_crypto_cache_entry.py similarity index 100% rename from test/unit/caches/test_crypto_cache_entry.py rename to test/unit/test_caches_crypto_cache_entry.py diff --git a/test/unit/caches/test_local.py b/test/unit/test_caches_local.py similarity index 100% rename from test/unit/caches/test_local.py rename to test/unit/test_caches_local.py diff --git a/test/unit/caches/test_null.py b/test/unit/test_caches_null.py similarity index 100% rename from test/unit/caches/test_null.py rename to test/unit/test_caches_null.py diff --git a/test/unit/internal/crypto/vectors.py b/test/unit/test_crypto.py similarity index 100% rename from test/unit/internal/crypto/vectors.py rename to test/unit/test_crypto.py diff --git a/test/unit/internal/crypto/authentication/test_signer.py b/test/unit/test_crypto_authentication_signer.py similarity index 99% rename from test/unit/internal/crypto/authentication/test_signer.py rename to test/unit/test_crypto_authentication_signer.py index 0a55b2e48..eae064130 100644 --- a/test/unit/internal/crypto/authentication/test_signer.py +++ b/test/unit/test_crypto_authentication_signer.py @@ -19,7 +19,7 @@ from aws_encryption_sdk.internal.crypto.authentication import Signer from aws_encryption_sdk.internal.defaults import ALGORITHM -from ..vectors import VALUES +from .test_crypto import VALUES pytestmark = [pytest.mark.unit, pytest.mark.local] diff --git a/test/unit/internal/crypto/authentication/test_verifier.py b/test/unit/test_crypto_authentication_verifier.py similarity index 99% rename from test/unit/internal/crypto/authentication/test_verifier.py rename to test/unit/test_crypto_authentication_verifier.py index e25fb78f3..a55e8f517 100644 --- a/test/unit/internal/crypto/authentication/test_verifier.py +++ b/test/unit/test_crypto_authentication_verifier.py @@ -19,7 +19,7 @@ from aws_encryption_sdk.internal.crypto.authentication import Verifier from aws_encryption_sdk.internal.defaults import ALGORITHM -from ..vectors import VALUES +from .test_crypto import VALUES pytestmark = [pytest.mark.unit, pytest.mark.local] diff --git a/test/unit/internal/crypto/test_data_keys.py b/test/unit/test_crypto_data_keys.py similarity index 100% rename from test/unit/internal/crypto/test_data_keys.py rename to test/unit/test_crypto_data_keys.py diff --git a/test/unit/internal/crypto/test_elliptic_curve.py b/test/unit/test_crypto_elliptic_curve.py similarity index 99% rename from test/unit/internal/crypto/test_elliptic_curve.py rename to test/unit/test_crypto_elliptic_curve.py index 16dcd2686..b030db5c2 100644 --- a/test/unit/internal/crypto/test_elliptic_curve.py +++ b/test/unit/test_crypto_elliptic_curve.py @@ -17,6 +17,7 @@ from cryptography.hazmat.primitives.asymmetric import ec from cryptography.utils import InterfaceNotImplemented from mock import MagicMock, sentinel +from pytest_mock import mocker # noqa pylint: disable=unused-import import aws_encryption_sdk.internal.crypto.elliptic_curve from aws_encryption_sdk.exceptions import NotSupportedError @@ -30,7 +31,7 @@ generate_ecc_signing_key, ) -from .vectors import VALUES +from .test_crypto import VALUES pytestmark = [pytest.mark.unit, pytest.mark.local] diff --git a/test/unit/internal/crypto/encryption/test_decryptor.py b/test/unit/test_crypto_encryption_decryptor.py similarity index 100% rename from test/unit/internal/crypto/encryption/test_decryptor.py rename to test/unit/test_crypto_encryption_decryptor.py diff --git a/test/unit/internal/crypto/encryption/test_encryptor.py b/test/unit/test_crypto_encryption_encryptor.py similarity index 100% rename from test/unit/internal/crypto/encryption/test_encryptor.py rename to test/unit/test_crypto_encryption_encryptor.py diff --git a/test/unit/internal/crypto/authentication/test_prehashing_authenticator.py b/test/unit/test_crypto_prehashing_authenticator.py similarity index 100% rename from test/unit/internal/crypto/authentication/test_prehashing_authenticator.py rename to test/unit/test_crypto_prehashing_authenticator.py diff --git a/test/unit/internal/crypto/test_wrapping_keys.py b/test/unit/test_crypto_wrapping_keys.py similarity index 99% rename from test/unit/internal/crypto/test_wrapping_keys.py rename to test/unit/test_crypto_wrapping_keys.py index 2bdcb0983..cb7b4489a 100644 --- a/test/unit/internal/crypto/test_wrapping_keys.py +++ b/test/unit/test_crypto_wrapping_keys.py @@ -21,7 +21,7 @@ from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey from aws_encryption_sdk.internal.structures import EncryptedData -from .vectors import VALUES +from .test_crypto import VALUES pytestmark = [pytest.mark.unit, pytest.mark.local] diff --git a/test/unit/internal/test_defaults.py b/test/unit/test_defaults.py similarity index 100% rename from test/unit/internal/test_defaults.py rename to test/unit/test_defaults.py diff --git a/test/unit/internal/formatting/test_deserialize.py b/test/unit/test_deserialize.py similarity index 99% rename from test/unit/internal/formatting/test_deserialize.py rename to test/unit/test_deserialize.py index d19093320..8a96ea4ca 100644 --- a/test/unit/internal/formatting/test_deserialize.py +++ b/test/unit/test_deserialize.py @@ -23,7 +23,7 @@ from aws_encryption_sdk.identifiers import AlgorithmSuite from aws_encryption_sdk.internal.structures import EncryptedData -from ...vectors import VALUES +from .test_values import VALUES pytestmark = [pytest.mark.unit, pytest.mark.local] diff --git a/test/unit/internal/formatting/test_encryption_context.py b/test/unit/test_encryption_context.py similarity index 99% rename from test/unit/internal/formatting/test_encryption_context.py rename to test/unit/test_encryption_context.py index 443df4065..187365783 100644 --- a/test/unit/internal/formatting/test_encryption_context.py +++ b/test/unit/test_encryption_context.py @@ -18,7 +18,7 @@ from aws_encryption_sdk.exceptions import SerializationError from aws_encryption_sdk.identifiers import ContentAADString -from ...vectors import VALUES +from .test_values import VALUES pytestmark = [pytest.mark.unit, pytest.mark.local] diff --git a/test/unit/internal/test_structures.py b/test/unit/test_internal_structures.py similarity index 97% rename from test/unit/internal/test_structures.py rename to test/unit/test_internal_structures.py index 04f4e737a..d57166982 100644 --- a/test/unit/internal/test_structures.py +++ b/test/unit/test_internal_structures.py @@ -21,7 +21,7 @@ MessageNoFrameBody, ) -from ..unit_test_utils import all_invalid_kwargs, all_valid_kwargs +from .unit_test_utils import all_invalid_kwargs, all_valid_kwargs pytestmark = [pytest.mark.unit, pytest.mark.local] diff --git a/test/unit/test_material_managers.py b/test/unit/test_material_managers.py new file mode 100644 index 000000000..fcd4977f5 --- /dev/null +++ b/test/unit/test_material_managers.py @@ -0,0 +1,98 @@ +# 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 for aws_encryption_sdk.materials_managers""" +import pytest +from mock import MagicMock +from pytest_mock import mocker # noqa pylint: disable=unused-import + +from aws_encryption_sdk.identifiers import Algorithm +from aws_encryption_sdk.internal.utils.streams import ROStream +from aws_encryption_sdk.materials_managers import ( + DecryptionMaterials, + DecryptionMaterialsRequest, + EncryptionMaterials, + EncryptionMaterialsRequest, +) +from aws_encryption_sdk.structures import DataKey + +pytestmark = [pytest.mark.unit, pytest.mark.local] + + +_VALID_KWARGS = { + "EncryptionMaterialsRequest": dict( + encryption_context={}, + plaintext_rostream=MagicMock(__class__=ROStream), + frame_length=5, + algorithm=MagicMock(__class__=Algorithm), + plaintext_length=5, + ), + "EncryptionMaterials": dict( + algorithm=MagicMock(__class__=Algorithm), + data_encryption_key=MagicMock(__class__=DataKey), + encrypted_data_keys=set([]), + encryption_context={}, + signing_key=b"", + ), + "DecryptionMaterialsRequest": dict( + algorithm=MagicMock(__class__=Algorithm), encrypted_data_keys=set([]), encryption_context={} + ), + "DecryptionMaterials": dict(data_key=MagicMock(__class__=DataKey), verification_key=b"ex_verification_key"), +} + + +@pytest.mark.parametrize( + "attr_class, invalid_kwargs", + ( + (EncryptionMaterialsRequest, dict(encryption_context=None)), + (EncryptionMaterialsRequest, dict(frame_length="not an int")), + (EncryptionMaterialsRequest, dict(algorithm="not an Algorithm or None")), + (EncryptionMaterialsRequest, dict(plaintext_length="not an int or None")), + (EncryptionMaterials, dict(algorithm=None)), + (EncryptionMaterials, dict(data_encryption_key=None)), + (EncryptionMaterials, dict(encrypted_data_keys=None)), + (EncryptionMaterials, dict(encryption_context=None)), + (EncryptionMaterials, dict(signing_key=u"not bytes or None")), + (DecryptionMaterialsRequest, dict(algorithm=None)), + (DecryptionMaterialsRequest, dict(encrypted_data_keys=None)), + (DecryptionMaterialsRequest, dict(encryption_context=None)), + (DecryptionMaterials, dict(data_key=None)), + (DecryptionMaterials, dict(verification_key=5555)), + ), +) +def test_attributes_fails(attr_class, invalid_kwargs): + kwargs = _VALID_KWARGS[attr_class.__name__].copy() + kwargs.update(invalid_kwargs) + with pytest.raises(TypeError): + attr_class(**kwargs) + + +def test_encryption_materials_request_attributes_defaults(): + test = EncryptionMaterialsRequest(encryption_context={}, frame_length=5) + assert test.plaintext_rostream is None + assert test.algorithm is None + assert test.plaintext_length is None + + +def test_encryption_materials_defaults(): + test = EncryptionMaterials( + algorithm=MagicMock(__class__=Algorithm), + data_encryption_key=MagicMock(__class__=DataKey), + encrypted_data_keys=set([]), + encryption_context={}, + ) + assert test.signing_key is None + + +def test_decryption_materials_defaults(): + test = DecryptionMaterials(data_key=MagicMock(__class__=DataKey)) + assert test.verification_key is None diff --git a/test/unit/materials_managers/test_base.py b/test/unit/test_material_managers_base.py similarity index 100% rename from test/unit/materials_managers/test_base.py rename to test/unit/test_material_managers_base.py diff --git a/test/unit/materials_managers/test_caching.py b/test/unit/test_material_managers_caching.py similarity index 92% rename from test/unit/materials_managers/test_caching.py rename to test/unit/test_material_managers_caching.py index cea3e86b6..833d6aa53 100644 --- a/test/unit/materials_managers/test_caching.py +++ b/test/unit/test_material_managers_caching.py @@ -1,20 +1,28 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.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 for CachingCryptoMaterialsManager""" import pytest from mock import MagicMock, sentinel +from pytest_mock import mocker # noqa pylint: disable=unused-import import aws_encryption_sdk.materials_managers.caching from aws_encryption_sdk.caches.base import CryptoMaterialsCache -from aws_encryption_sdk.caches.local import LocalCryptoMaterialsCache from aws_encryption_sdk.exceptions import CacheKeyError from aws_encryption_sdk.internal.defaults import MAX_BYTES_PER_KEY, MAX_MESSAGES_PER_KEY from aws_encryption_sdk.internal.str_ops import to_bytes +from aws_encryption_sdk.key_providers.base import MasterKeyProvider 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 ..unit_test_utils import ephemeral_raw_aes_keyring, ephemeral_raw_aes_master_key pytestmark = [pytest.mark.unit, pytest.mark.local] @@ -46,7 +54,7 @@ def fake_encryption_request(): dict(max_messages_encrypted=None), dict(max_bytes_encrypted=None), dict(partition_name=55), - dict(master_key_provider=None, backing_materials_manager=None, keyring=None), + dict(master_key_provider=None, backing_materials_manager=None), ), ) def test_attrs_fail(invalid_kwargs): @@ -80,26 +88,20 @@ def test_custom_partition_name(patch_uuid4): assert test.partition_name == custom_partition_name -def test_mkp_to_default_cmm(): - mkp = ephemeral_raw_aes_master_key() - +def test_mkp_to_default_cmm(mocker): + mocker.patch.object(aws_encryption_sdk.materials_managers.caching, "DefaultCryptoMaterialsManager") + mock_mkp = MagicMock(__class__=MasterKeyProvider) test = CachingCryptoMaterialsManager( - cache=LocalCryptoMaterialsCache(capacity=10), max_age=10.0, master_key_provider=mkp + cache=MagicMock(__class__=CryptoMaterialsCache), max_age=10.0, master_key_provider=mock_mkp ) - assert isinstance(test.backing_materials_manager, DefaultCryptoMaterialsManager) - assert test.backing_materials_manager.master_key_provider is mkp - assert test.backing_materials_manager.keyring is None - - -def test_keyring_to_default_cmm(): - keyring = ephemeral_raw_aes_keyring() - - test = CachingCryptoMaterialsManager(cache=LocalCryptoMaterialsCache(capacity=10), max_age=10.0, keyring=keyring) - - assert isinstance(test.backing_materials_manager, DefaultCryptoMaterialsManager) - assert test.backing_materials_manager.keyring is keyring - assert test.backing_materials_manager.master_key_provider is None + aws_encryption_sdk.materials_managers.caching.DefaultCryptoMaterialsManager.assert_called_once_with( + mock_mkp + ) # noqa pylint: disable=line-too-long + assert ( + test.backing_materials_manager + is aws_encryption_sdk.materials_managers.caching.DefaultCryptoMaterialsManager.return_value + ) # noqa pylint: disable=line-too-long @pytest.mark.parametrize( diff --git a/test/unit/materials_managers/test_default.py b/test/unit/test_material_managers_default.py similarity index 59% rename from test/unit/materials_managers/test_default.py rename to test/unit/test_material_managers_default.py index 9a86e59b8..9d6bd949f 100644 --- a/test/unit/materials_managers/test_default.py +++ b/test/unit/test_material_managers_default.py @@ -1,38 +1,31 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.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 for aws_encryption_sdk.materials_managers.default""" import pytest from mock import MagicMock, sentinel +from pytest_mock import mocker # noqa pylint: disable=unused-import import aws_encryption_sdk.materials_managers.default -from aws_encryption_sdk.exceptions import InvalidCryptographicMaterialsError, MasterKeyProviderError, SerializationError -from aws_encryption_sdk.identifiers import Algorithm, WrappingAlgorithm +from aws_encryption_sdk.exceptions import MasterKeyProviderError, SerializationError +from aws_encryption_sdk.identifiers import Algorithm from aws_encryption_sdk.internal.defaults import ALGORITHM, ENCODED_SIGNER_KEY from aws_encryption_sdk.key_providers.base import MasterKeyProvider -from aws_encryption_sdk.materials_managers import ( - DecryptionMaterialsRequest, - EncryptionMaterials, - EncryptionMaterialsRequest, -) +from aws_encryption_sdk.materials_managers import EncryptionMaterials from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager -from aws_encryption_sdk.structures import DataKey, EncryptedDataKey, MasterKeyInfo, RawDataKey - -from ..unit_test_utils import ( - BrokenKeyring, - NoEncryptedDataKeysKeyring, - ephemeral_raw_aes_keyring, - ephemeral_raw_aes_master_key, -) +from aws_encryption_sdk.structures import DataKey pytestmark = [pytest.mark.unit, pytest.mark.local] -_DATA_KEY = DataKey( - key_provider=MasterKeyInfo(provider_id="Provider", key_info=b"Info"), - data_key=b"1234567890123456789012", - encrypted_data_key=b"asdf", -) -_ENCRYPTED_DATA_KEY = EncryptedDataKey.from_data_key(_DATA_KEY) - @pytest.fixture def patch_for_dcmm_encrypt(mocker): @@ -40,8 +33,8 @@ def patch_for_dcmm_encrypt(mocker): mock_signing_key = b"ex_signing_key" DefaultCryptoMaterialsManager._generate_signing_key_and_update_encryption_context.return_value = mock_signing_key mocker.patch.object(aws_encryption_sdk.materials_managers.default, "prepare_data_keys") - mock_data_encryption_key = _DATA_KEY - mock_encrypted_data_keys = (_ENCRYPTED_DATA_KEY,) + mock_data_encryption_key = MagicMock(__class__=DataKey) + mock_encrypted_data_keys = set([mock_data_encryption_key]) result_pair = mock_data_encryption_key, mock_encrypted_data_keys aws_encryption_sdk.materials_managers.default.prepare_data_keys.return_value = result_pair yield result_pair, mock_signing_key @@ -57,28 +50,17 @@ def patch_for_dcmm_decrypt(mocker): def build_cmm(): mock_mkp = MagicMock(__class__=MasterKeyProvider) - mock_mkp.decrypt_data_key_from_list.return_value = _DATA_KEY + mock_mkp.decrypt_data_key_from_list.return_value = MagicMock(__class__=DataKey) mock_mkp.master_keys_for_encryption.return_value = ( sentinel.primary_mk, - {sentinel.primary_mk, sentinel.mk_a, sentinel.mk_b}, + set([sentinel.primary_mk, sentinel.mk_a, sentinel.mk_b]), ) return DefaultCryptoMaterialsManager(master_key_provider=mock_mkp) -@pytest.mark.parametrize( - "kwargs", - ( - pytest.param(dict(), id="no parameters"), - pytest.param(dict(master_key_provider=None, keyring=None), id="explicit None for both"), - pytest.param( - dict(master_key_provider=ephemeral_raw_aes_master_key(), keyring=ephemeral_raw_aes_keyring()), - id="both provided", - ), - ), -) -def test_attributes_fail(kwargs): +def test_attributes_fail(): with pytest.raises(TypeError): - DefaultCryptoMaterialsManager(**kwargs) + DefaultCryptoMaterialsManager(master_key_provider=None) def test_attributes_default(): @@ -145,8 +127,8 @@ def test_get_encryption_materials(patch_for_dcmm_encrypt): ) assert isinstance(test, EncryptionMaterials) assert test.algorithm is cmm.algorithm - assert test.data_encryption_key == RawDataKey.from_data_key(patch_for_dcmm_encrypt[0][0]) - assert test.encrypted_data_keys == patch_for_dcmm_encrypt[0][1] + assert test.data_encryption_key is patch_for_dcmm_encrypt[0][0] + assert test.encrypted_data_keys is patch_for_dcmm_encrypt[0][1] assert test.encryption_context == encryption_context assert test.signing_key == patch_for_dcmm_encrypt[1] @@ -176,7 +158,7 @@ def test_get_encryption_materials_primary_mk_not_in_mks(patch_for_dcmm_encrypt): cmm = build_cmm() cmm.master_key_provider.master_keys_for_encryption.return_value = ( sentinel.primary_mk, - {sentinel.mk_a, sentinel.mk_b}, + set([sentinel.mk_a, sentinel.mk_b]), ) with pytest.raises(MasterKeyProviderError) as excinfo: @@ -250,94 +232,5 @@ def test_decrypt_materials(mocker, patch_for_dcmm_decrypt): cmm._load_verification_key_from_encryption_context.assert_called_once_with( algorithm=mock_request.algorithm, encryption_context=mock_request.encryption_context ) - assert test.data_key == RawDataKey.from_data_key(cmm.master_key_provider.decrypt_data_key_from_list.return_value) + assert test.data_key is cmm.master_key_provider.decrypt_data_key_from_list.return_value assert test.verification_key == patch_for_dcmm_decrypt - - -@pytest.mark.parametrize("algorithm_suite", Algorithm) -def test_encrypt_with_keyring_materials_incomplete(algorithm_suite): - raw_aes256_keyring = ephemeral_raw_aes_keyring(WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING) - - encrypt_cmm = DefaultCryptoMaterialsManager(keyring=NoEncryptedDataKeysKeyring(inner_keyring=raw_aes256_keyring)) - - encryption_materials_request = EncryptionMaterialsRequest( - encryption_context={}, frame_length=1024, algorithm=algorithm_suite - ) - - with pytest.raises(InvalidCryptographicMaterialsError) as excinfo: - encrypt_cmm.get_encryption_materials(encryption_materials_request) - - excinfo.match("Encryption materials are incomplete!") - - -def _broken_materials_scenarios(): - yield pytest.param(dict(break_algorithm=True), id="broken algorithm") - yield pytest.param(dict(break_encryption_context=True), id="broken encryption context") - yield pytest.param(dict(break_signing=True), id="broken signing/verification key") - - -@pytest.mark.parametrize("algorithm_suite", Algorithm) -@pytest.mark.parametrize("kwargs", _broken_materials_scenarios()) -def test_encrypt_with_keyring_materials_do_not_match_request(kwargs, algorithm_suite): - raw_aes256_keyring = ephemeral_raw_aes_keyring(WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING) - - encrypt_cmm = DefaultCryptoMaterialsManager(keyring=BrokenKeyring(inner_keyring=raw_aes256_keyring, **kwargs)) - - encryption_materials_request = EncryptionMaterialsRequest( - encryption_context={}, frame_length=1024, algorithm=algorithm_suite - ) - - with pytest.raises(InvalidCryptographicMaterialsError) as excinfo: - encrypt_cmm.get_encryption_materials(encryption_materials_request) - - excinfo.match("Encryption materials do not match request!") - - -@pytest.mark.parametrize("algorithm_suite", Algorithm) -def test_decrypt_with_keyring_materials_incomplete(algorithm_suite): - raw_aes256_keyring = ephemeral_raw_aes_keyring(WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING) - raw_aes128_keyring = ephemeral_raw_aes_keyring(WrappingAlgorithm.AES_128_GCM_IV12_TAG16_NO_PADDING) - - encrypt_cmm = DefaultCryptoMaterialsManager(keyring=raw_aes256_keyring) - decrypt_cmm = DefaultCryptoMaterialsManager(keyring=raw_aes128_keyring) - - encryption_materials_request = EncryptionMaterialsRequest( - encryption_context={}, frame_length=1024, algorithm=algorithm_suite - ) - encryption_materials = encrypt_cmm.get_encryption_materials(encryption_materials_request) - - decryption_materials_request = DecryptionMaterialsRequest( - algorithm=encryption_materials.algorithm, - encrypted_data_keys=encryption_materials.encrypted_data_keys, - encryption_context=encryption_materials.encryption_context, - ) - - with pytest.raises(InvalidCryptographicMaterialsError) as excinfo: - decrypt_cmm.decrypt_materials(decryption_materials_request) - - excinfo.match("Decryption materials are incomplete!") - - -@pytest.mark.parametrize("algorithm_suite", Algorithm) -@pytest.mark.parametrize("kwargs", _broken_materials_scenarios()) -def test_decrypt_with_keyring_materials_do_not_match_request(kwargs, algorithm_suite): - raw_aes256_keyring = ephemeral_raw_aes_keyring(WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING) - - encrypt_cmm = DefaultCryptoMaterialsManager(keyring=raw_aes256_keyring) - decrypt_cmm = DefaultCryptoMaterialsManager(keyring=BrokenKeyring(inner_keyring=raw_aes256_keyring, **kwargs)) - - encryption_materials_request = EncryptionMaterialsRequest( - encryption_context={}, frame_length=1024, algorithm=algorithm_suite - ) - encryption_materials = encrypt_cmm.get_encryption_materials(encryption_materials_request) - - decryption_materials_request = DecryptionMaterialsRequest( - algorithm=encryption_materials.algorithm, - encrypted_data_keys=encryption_materials.encrypted_data_keys, - encryption_context=encryption_materials.encryption_context, - ) - - with pytest.raises(InvalidCryptographicMaterialsError) as excinfo: - decrypt_cmm.decrypt_materials(decryption_materials_request) - - excinfo.match("Decryption materials do not match request!") diff --git a/test/unit/key_providers/base/test_base_master_key.py b/test/unit/test_providers_base_master_key.py similarity index 99% rename from test/unit/key_providers/base/test_base_master_key.py rename to test/unit/test_providers_base_master_key.py index 4ee6c2661..26a90ced8 100644 --- a/test/unit/key_providers/base/test_base_master_key.py +++ b/test/unit/test_providers_base_master_key.py @@ -20,7 +20,7 @@ from aws_encryption_sdk.key_providers.base import MasterKey, MasterKeyConfig, MasterKeyProvider from aws_encryption_sdk.structures import MasterKeyInfo -from ...vectors import VALUES +from .test_values import VALUES pytestmark = [pytest.mark.unit, pytest.mark.local] diff --git a/test/unit/key_providers/base/test_base_master_key_config.py b/test/unit/test_providers_base_master_key_config.py similarity index 96% rename from test/unit/key_providers/base/test_base_master_key_config.py rename to test/unit/test_providers_base_master_key_config.py index 3eb0ce406..8b6c8731a 100644 --- a/test/unit/key_providers/base/test_base_master_key_config.py +++ b/test/unit/test_providers_base_master_key_config.py @@ -15,7 +15,7 @@ from aws_encryption_sdk.key_providers.base import MasterKeyConfig -from ...unit_test_utils import all_invalid_kwargs, all_valid_kwargs +from .unit_test_utils import all_invalid_kwargs, all_valid_kwargs pytestmark = [pytest.mark.unit, pytest.mark.local] diff --git a/test/unit/key_providers/base/test_base_master_key_provider.py b/test/unit/test_providers_base_master_key_provider.py similarity index 99% rename from test/unit/key_providers/base/test_base_master_key_provider.py rename to test/unit/test_providers_base_master_key_provider.py index 87e9a924c..44385ea17 100644 --- a/test/unit/key_providers/base/test_base_master_key_provider.py +++ b/test/unit/test_providers_base_master_key_provider.py @@ -23,7 +23,7 @@ ) from aws_encryption_sdk.key_providers.base import MasterKeyProvider, MasterKeyProviderConfig -from ...vectors import VALUES +from .test_values import VALUES pytestmark = [pytest.mark.unit, pytest.mark.local] @@ -60,12 +60,6 @@ def test_repr(): ) -def test_deprecated(): - - with pytest.warns(DeprecationWarning): - MockMasterKeyProvider(provider_id="ex_provider_id", mock_new_master_key="ex_new_master_key") - - class TestBaseMasterKeyProvider(object): def test_provider_id_enforcement(self): class TestProvider(MasterKeyProvider): diff --git a/test/unit/key_providers/base/test_base_master_key_provider_config.py b/test/unit/test_providers_base_master_key_provider_config.py similarity index 93% rename from test/unit/key_providers/base/test_base_master_key_provider_config.py rename to test/unit/test_providers_base_master_key_provider_config.py index 9604b84a8..0e21ded80 100644 --- a/test/unit/key_providers/base/test_base_master_key_provider_config.py +++ b/test/unit/test_providers_base_master_key_provider_config.py @@ -11,11 +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 to validate aws_encryption_sdk.key_providers.base.MasterKeyProviderConfig""" -import pytest - from aws_encryption_sdk.key_providers.base import MasterKeyProviderConfig # noqa pylint: disable=unused-import -pytestmark = [pytest.mark.unit, pytest.mark.local] - # Nothing to test at this time, but import will ensure that it exists. # If this MasterKeyProviderConfig has attributes added in the future, they should be tested here. diff --git a/test/unit/key_providers/kms/test_kms_master_key.py b/test/unit/test_providers_kms_master_key.py similarity index 99% rename from test/unit/key_providers/kms/test_kms_master_key.py rename to test/unit/test_providers_kms_master_key.py index 862888ad8..c0ab9a968 100644 --- a/test/unit/key_providers/kms/test_kms_master_key.py +++ b/test/unit/test_providers_kms_master_key.py @@ -22,7 +22,7 @@ from aws_encryption_sdk.key_providers.kms import KMSMasterKey, KMSMasterKeyConfig from aws_encryption_sdk.structures import DataKey, EncryptedDataKey, MasterKeyInfo -from ...vectors import VALUES +from .test_values import VALUES pytestmark = [pytest.mark.unit, pytest.mark.local] diff --git a/test/unit/key_providers/kms/test_kms_master_key_config.py b/test/unit/test_providers_kms_master_key_config.py similarity index 97% rename from test/unit/key_providers/kms/test_kms_master_key_config.py rename to test/unit/test_providers_kms_master_key_config.py index 224e43c7c..1501c951f 100644 --- a/test/unit/key_providers/kms/test_kms_master_key_config.py +++ b/test/unit/test_providers_kms_master_key_config.py @@ -17,7 +17,7 @@ from aws_encryption_sdk.key_providers.base import MasterKeyConfig from aws_encryption_sdk.key_providers.kms import _PROVIDER_ID, KMSMasterKeyConfig -from ...unit_test_utils import all_invalid_kwargs, all_valid_kwargs +from .unit_test_utils import all_invalid_kwargs, all_valid_kwargs pytestmark = [pytest.mark.unit, pytest.mark.local] diff --git a/test/unit/key_providers/kms/test_kms_master_key_provider.py b/test/unit/test_providers_kms_master_key_provider.py similarity index 98% rename from test/unit/key_providers/kms/test_kms_master_key_provider.py rename to test/unit/test_providers_kms_master_key_provider.py index f8d8dc453..b99a8bb94 100644 --- a/test/unit/key_providers/kms/test_kms_master_key_provider.py +++ b/test/unit/test_providers_kms_master_key_provider.py @@ -110,7 +110,9 @@ def test_add_regional_client_new(self): test.add_regional_client("ex_region_name") self.mock_boto3_session.assert_called_with(botocore_session=ANY) self.mock_boto3_session_instance.client.assert_called_with( - "kms", region_name="ex_region_name", config=test._user_agent_adding_config + "kms", + region_name="ex_region_name", + config=test._user_agent_adding_config, ) assert test._regional_clients["ex_region_name"] is self.mock_boto3_client_instance diff --git a/test/unit/key_providers/kms/test_kms_master_key_provider_config.py b/test/unit/test_providers_kms_master_key_provider_config.py similarity index 97% rename from test/unit/key_providers/kms/test_kms_master_key_provider_config.py rename to test/unit/test_providers_kms_master_key_provider_config.py index 9b8f9fd74..affa74102 100644 --- a/test/unit/key_providers/kms/test_kms_master_key_provider_config.py +++ b/test/unit/test_providers_kms_master_key_provider_config.py @@ -17,7 +17,7 @@ from aws_encryption_sdk.key_providers.base import MasterKeyProviderConfig from aws_encryption_sdk.key_providers.kms import KMSMasterKeyProviderConfig -from ...unit_test_utils import all_invalid_kwargs, all_valid_kwargs +from .unit_test_utils import all_invalid_kwargs, all_valid_kwargs pytestmark = [pytest.mark.unit, pytest.mark.local] diff --git a/test/unit/key_providers/raw/test_raw_master_key.py b/test/unit/test_providers_raw_master_key.py similarity index 99% rename from test/unit/key_providers/raw/test_raw_master_key.py rename to test/unit/test_providers_raw_master_key.py index 8b9ba658d..9abcd14c6 100644 --- a/test/unit/key_providers/raw/test_raw_master_key.py +++ b/test/unit/test_providers_raw_master_key.py @@ -20,7 +20,7 @@ from aws_encryption_sdk.key_providers.raw import RawMasterKey, RawMasterKeyConfig from aws_encryption_sdk.structures import DataKey, EncryptedDataKey, MasterKeyInfo, RawDataKey -from ...vectors import VALUES +from .test_values import VALUES pytestmark = [pytest.mark.unit, pytest.mark.local] diff --git a/test/unit/key_providers/raw/test_raw_master_key_config.py b/test/unit/test_providers_raw_master_key_config.py similarity index 96% rename from test/unit/key_providers/raw/test_raw_master_key_config.py rename to test/unit/test_providers_raw_master_key_config.py index bbdbc5bef..d06feae87 100644 --- a/test/unit/key_providers/raw/test_raw_master_key_config.py +++ b/test/unit/test_providers_raw_master_key_config.py @@ -19,7 +19,7 @@ from aws_encryption_sdk.key_providers.base import MasterKeyConfig from aws_encryption_sdk.key_providers.raw import RawMasterKeyConfig -from ...unit_test_utils import all_invalid_kwargs, all_valid_kwargs +from .unit_test_utils import all_invalid_kwargs, all_valid_kwargs pytestmark = [pytest.mark.unit, pytest.mark.local] diff --git a/test/unit/key_providers/raw/test_raw_master_key_provider.py b/test/unit/test_providers_raw_master_key_provider.py similarity index 98% rename from test/unit/key_providers/raw/test_raw_master_key_provider.py rename to test/unit/test_providers_raw_master_key_provider.py index 9205d3563..5128b1e22 100644 --- a/test/unit/key_providers/raw/test_raw_master_key_provider.py +++ b/test/unit/test_providers_raw_master_key_provider.py @@ -18,7 +18,7 @@ from aws_encryption_sdk.key_providers.base import MasterKeyProvider, MasterKeyProviderConfig from aws_encryption_sdk.key_providers.raw import RawMasterKeyProvider -from ...vectors import VALUES +from .test_values import VALUES pytestmark = [pytest.mark.unit, pytest.mark.local] diff --git a/test/unit/internal/formatting/test_serialize.py b/test/unit/test_serialize.py similarity index 98% rename from test/unit/internal/formatting/test_serialize.py rename to test/unit/test_serialize.py index 7a4063472..511048d80 100644 --- a/test/unit/internal/formatting/test_serialize.py +++ b/test/unit/test_serialize.py @@ -21,7 +21,7 @@ from aws_encryption_sdk.internal.structures import EncryptedData from aws_encryption_sdk.structures import EncryptedDataKey, MasterKeyInfo -from ...vectors import VALUES +from .test_values import VALUES pytestmark = [pytest.mark.unit, pytest.mark.local] @@ -325,9 +325,7 @@ def test_serialize_wrapped_key_symmetric(self): ) assert test == EncryptedDataKey( key_provider=MasterKeyInfo( - provider_id=self.mock_key_provider.provider_id, - key_info=VALUES["wrapped_keys"]["serialized"]["key_info"], - key_name=VALUES["wrapped_keys"]["raw"]["key_info"], + provider_id=VALUES["provider_id"], key_info=VALUES["wrapped_keys"]["serialized"]["key_info"] ), encrypted_data_key=VALUES["wrapped_keys"]["serialized"]["key_ciphertext"], ) diff --git a/test/unit/streaming_client/test_configs.py b/test/unit/test_streaming_client_configs.py similarity index 98% rename from test/unit/streaming_client/test_configs.py rename to test/unit/test_streaming_client_configs.py index a98a38957..98e5cb13c 100644 --- a/test/unit/streaming_client/test_configs.py +++ b/test/unit/test_streaming_client_configs.py @@ -22,7 +22,7 @@ from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager from aws_encryption_sdk.streaming_client import DecryptorConfig, EncryptorConfig, _ClientConfig -from ..unit_test_utils import all_invalid_kwargs, all_valid_kwargs, build_valid_kwargs_list +from .unit_test_utils import all_invalid_kwargs, all_valid_kwargs, build_valid_kwargs_list pytestmark = [pytest.mark.unit, pytest.mark.local] diff --git a/test/unit/streaming_client/test_encryption_stream.py b/test/unit/test_streaming_client_encryption_stream.py similarity index 99% rename from test/unit/streaming_client/test_encryption_stream.py rename to test/unit/test_streaming_client_encryption_stream.py index 345e9939e..e3a06347a 100644 --- a/test/unit/streaming_client/test_encryption_stream.py +++ b/test/unit/test_streaming_client_encryption_stream.py @@ -23,8 +23,8 @@ from aws_encryption_sdk.key_providers.base import MasterKeyProvider from aws_encryption_sdk.streaming_client import _ClientConfig, _EncryptionStream -from ..unit_test_utils import assert_prepped_stream_identity -from ..vectors import VALUES +from .test_values import VALUES +from .unit_test_utils import assert_prepped_stream_identity pytestmark = [pytest.mark.unit, pytest.mark.local] diff --git a/test/unit/streaming_client/test_stream_decryptor.py b/test/unit/test_streaming_client_stream_decryptor.py similarity index 99% rename from test/unit/streaming_client/test_stream_decryptor.py rename to test/unit/test_streaming_client_stream_decryptor.py index 06e7f0816..6a3ccb56d 100644 --- a/test/unit/streaming_client/test_stream_decryptor.py +++ b/test/unit/test_streaming_client_stream_decryptor.py @@ -22,7 +22,7 @@ from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager from aws_encryption_sdk.streaming_client import StreamDecryptor -from ..vectors import VALUES +from .test_values import VALUES pytestmark = [pytest.mark.unit, pytest.mark.local] diff --git a/test/unit/streaming_client/test_stream_encryptor.py b/test/unit/test_streaming_client_stream_encryptor.py similarity index 99% rename from test/unit/streaming_client/test_stream_encryptor.py rename to test/unit/test_streaming_client_stream_encryptor.py index 42516444a..501214e9f 100644 --- a/test/unit/streaming_client/test_stream_encryptor.py +++ b/test/unit/test_streaming_client_stream_encryptor.py @@ -30,7 +30,7 @@ from aws_encryption_sdk.streaming_client import StreamEncryptor from aws_encryption_sdk.structures import MessageHeader -from ..vectors import VALUES +from .test_values import VALUES pytestmark = [pytest.mark.unit, pytest.mark.local] @@ -247,7 +247,7 @@ def test_prep_message_framed_message( encryption_context=VALUES["encryption_context"], ) test_encryptor.content_type = ContentType.FRAMED_DATA - test_encryption_context = {aws_encryption_sdk.internal.defaults.ENCODED_SIGNER_KEY: "DECODED_BYTES"} + test_encryption_context = {aws_encryption_sdk.internal.defaults.ENCODED_SIGNER_KEY: sentinel.decoded_bytes} self.mock_encryption_materials.encryption_context = test_encryption_context self.mock_encryption_materials.encrypted_data_keys = self.mock_encrypted_data_keys diff --git a/test/unit/test_structures.py b/test/unit/test_structures.py index 26cef17ec..1a9caa01d 100644 --- a/test/unit/test_structures.py +++ b/test/unit/test_structures.py @@ -13,16 +13,8 @@ """Unit test suite for aws_encryption_sdk.structures""" import pytest -from aws_encryption_sdk.identifiers import Algorithm, ContentType, KeyringTraceFlag, ObjectType, SerializationVersion -from aws_encryption_sdk.structures import ( - CryptoResult, - DataKey, - EncryptedDataKey, - KeyringTrace, - MasterKeyInfo, - MessageHeader, - RawDataKey, -) +from aws_encryption_sdk.identifiers import Algorithm, ContentType, ObjectType, SerializationVersion +from aws_encryption_sdk.structures import DataKey, EncryptedDataKey, MasterKeyInfo, MessageHeader, RawDataKey from .unit_test_utils import all_invalid_kwargs, all_valid_kwargs @@ -65,34 +57,6 @@ key_provider=MasterKeyInfo(provider_id="asjnoa", key_info=b"aosjfoaiwej"), encrypted_data_key=b"aisofiawjef" ) ], - KeyringTrace: [ - dict( - wrapping_key=MasterKeyInfo(provider_id="foo", key_info=b"bar"), flags={KeyringTraceFlag.ENCRYPTED_DATA_KEY}, - ) - ], - CryptoResult: [ - dict( - result=b"super secret stuff", - header=MessageHeader( - version=SerializationVersion.V1, - type=ObjectType.CUSTOMER_AE_DATA, - algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, - message_id=b"aosiejfoaiwej", - encryption_context={}, - encrypted_data_keys=set([]), - content_type=ContentType.FRAMED_DATA, - content_aad_length=32456, - header_iv_length=32456, - frame_length=234567, - ), - keyring_trace=( - KeyringTrace( - wrapping_key=MasterKeyInfo(provider_id="foo", key_info=b"bar"), - flags={KeyringTraceFlag.ENCRYPTED_DATA_KEY}, - ), - ), - ) - ], } @@ -143,60 +107,3 @@ def test_data_key_repr_str(cls, params): assert data_key_check not in str(test) assert data_key_check not in repr(test) - - -@pytest.fixture -def ex_data_key(): - return DataKey(**VALID_KWARGS[DataKey][0]) - - -def test_encrypted_data_key_from_data_key_success(ex_data_key): - test = EncryptedDataKey.from_data_key(ex_data_key) - - assert test.key_provider == ex_data_key.key_provider - assert test.encrypted_data_key == ex_data_key.encrypted_data_key - - -def test_raw_data_key_from_data_key_success(ex_data_key): - test = RawDataKey.from_data_key(ex_data_key) - - assert test.key_provider == ex_data_key.key_provider - assert test.data_key == ex_data_key.data_key - - -@pytest.mark.parametrize("data_key_class", (EncryptedDataKey, RawDataKey)) -def test_raw_and_encrypted_data_key_from_data_key_fail(data_key_class): - with pytest.raises(TypeError) as excinfo: - data_key_class.from_data_key(b"ahjseofij") - - excinfo.match(r"data_key must be type DataKey not *") - - -@pytest.fixture -def ex_result(): - return CryptoResult(**VALID_KWARGS[CryptoResult][0]) - - -def test_cryptoresult_len(ex_result): - assert len(ex_result) == 2 - - -def test_cryptoresult_unpack(ex_result): - data, header = ex_result - - assert data is ex_result.result - assert header is ex_result.header - - -def test_cryptoresult_getitem(ex_result): - data = ex_result[0] - header = ex_result[1] - - assert data is ex_result.result - assert header is ex_result.header - - -def test_cryptoresult_to_tuple(ex_result): - test = tuple(ex_result) - - assert test == ex_result._legacy_container diff --git a/test/unit/internal/utils/test_str_ops.py b/test/unit/test_util_str_ops.py similarity index 100% rename from test/unit/internal/utils/test_str_ops.py rename to test/unit/test_util_str_ops.py diff --git a/test/unit/internal/utils/test_streams.py b/test/unit/test_util_streams.py similarity index 96% rename from test/unit/internal/utils/test_streams.py rename to test/unit/test_util_streams.py index 660e4623c..ab7b05152 100644 --- a/test/unit/internal/utils/test_streams.py +++ b/test/unit/test_util_streams.py @@ -19,7 +19,7 @@ from aws_encryption_sdk.internal.str_ops import to_bytes, to_str from aws_encryption_sdk.internal.utils.streams import InsistentReaderBytesIO, ROStream, TeeStream -from ...unit_test_utils import ExactlyTwoReads, NothingButRead, SometimesIncompleteReaderIO +from .unit_test_utils import ExactlyTwoReads, NothingButRead, SometimesIncompleteReaderIO pytestmark = [pytest.mark.unit, pytest.mark.local] diff --git a/test/unit/internal/utils/test_utils.py b/test/unit/test_utils.py similarity index 98% rename from test/unit/internal/utils/test_utils.py rename to test/unit/test_utils.py index c118ba375..b1374a09d 100644 --- a/test/unit/internal/utils/test_utils.py +++ b/test/unit/test_utils.py @@ -21,11 +21,10 @@ import aws_encryption_sdk.internal.utils from aws_encryption_sdk.exceptions import InvalidDataKeyError, SerializationError, UnknownIdentityError from aws_encryption_sdk.internal.defaults import MAX_FRAME_SIZE, MESSAGE_ID_LENGTH -from aws_encryption_sdk.keyrings.base import EncryptedDataKey -from aws_encryption_sdk.structures import DataKey, MasterKeyInfo, RawDataKey +from aws_encryption_sdk.structures import DataKey, EncryptedDataKey, MasterKeyInfo, RawDataKey -from ...unit_test_utils import assert_prepped_stream_identity -from ...vectors import VALUES +from .test_values import VALUES +from .unit_test_utils import assert_prepped_stream_identity pytestmark = [pytest.mark.unit, pytest.mark.local] diff --git a/test/unit/vectors.py b/test/unit/test_values.py similarity index 88% rename from test/unit/vectors.py rename to test/unit/test_values.py index b9aba0c02..26eff1341 100644 --- a/test/unit/vectors.py +++ b/test/unit/test_values.py @@ -187,55 +187,6 @@ def array_byte(source): "\xff\x8fn\x95\xf0\xf0E\x91Uj\xb0E3=\x0e\x1a\xf1'4\xf6" ), "signature_len": b"\x00h", - "private_rsa_key_bytes": [ - ( - b"-----BEGIN RSA PRIVATE KEY-----" - b"MIICXgIBAAKBgQCUjhI8YRPXV8Gfofbg/" - b"PLjWw2AzowQTPErLU2z3+xGqElMdzdiC4Ta43DFWZg34Eg0X8kQPAeoe8h3cRSMo" - b"77eSOHt2dPo7OfTfZqsH8766fivHIKVxBYPX8SZYIUhMtRnlg3uqch9BksfRop+h" - b"f8h/H3lfervJoevS2CXYB9/iwIDAQABAoGBAIqeGzQOHbaGI51yQ2zjez1dPDdiB" - b"F49fZideHEM1GuGIodgguRQ/VJGgncUSC5zcMy2SGaGrVqwznltohAtxy4rZp0eh" - b"2O3aHYi9Wehd0SPLh+qwu7mJDuh0z15hmCOue070FnUtyuSwhXLwDrbot2+5HbmF" - b"9clJLI5tv92gvIpAkEA+Bv5i8XJNPN1rao31aQFoi9bFIOEclk3b1RbLX6mpZBFS" - b"U9CNUy0RQNC0+H3KZ5CTvsyFGpMfTdiFc/Qdesk3QJBAJlHjrvoadP+PU3zXYrWR" - b"D5EryyTxaP1bOjrp9xLuQBeU8x7EVJdpoul9OmwcT3NrAqvxDE9okjha2tjCI6O2" - b"4cCQQDMyOJPYL/zaaPO5LlTKB/SPv4RT4BplYPw6xKa2XeZHhxiJv5B2f7NG6T0G" - b"AWWn16hrCoouZhKngTidfXc7motAkA/KiTgvKr3yHp86AAxWZDv1CAYD6FPqrDB3" - b"3LiLnZDd5uy1ThTJ/Kc87vUnXhdDqeKE9qWrB53SCWbMElzbd17AkEA4DMp+6ngM" - b"o6sS0dY1X6nTLqgvK3B0z5GCAdSEy3Y8jh995Lrl+hy88HzuwUkQwwPlZkFhUNCx" - b"edrC6cTKE5xLA==" - b"-----END RSA PRIVATE KEY-----" - ), - ( - b"-----BEGIN RSA PRIVATE KEY-----\n" - b"MIIEowIBAAKCAQEAo8uCyhiO4JUGZV+rtNq5DBA9Lm4xkw5kTA3v6EPybs8bVXL2\n" - b"ZE6jkbo+xT4Jg/bKzUpnp1fE+T1ruGPtsPdoEmhY/P64LDNIs3sRq5U4QV9IETU1\n" - b"vIcbNNkgGhRjV8J87YNY0tV0H7tuWuZRpqnS+gjV6V9lUMkbvjMCc5IBqQc3heut\n" - b"/+fH4JwpGlGxOVXI8QAapnSy1XpCr3+PT29kydVJnIMuAoFrurojRpOQbOuVvhtA\n" - b"gARhst1Ji4nfROGYkj6eZhvkz2Bkud4/+3lGvVU5LO1vD8oY7WoGtpin3h50VcWe\n" - b"aBT4kejx4s9/G9C4R24lTH09J9HO2UUsuCqZYQIDAQABAoIBAQCfC90bCk+qaWqF\n" - b"gymC+qOWwCn4bM28gswHQb1D5r6AtKBRD8mKywVvWs7azguFVV3Fi8sspkBA2FBC\n" - b"At5p6ULoJOTL/TauzLl6djVJTCMM701WUDm2r+ZOIctXJ5bzP4n5Q4I7b0NMEL7u\n" - b"ixib4elYGr5D1vrVQAKtZHCr8gmkqyx8Mz7wkJepzBP9EeVzETCHsmiQDd5WYlO1\n" - b"C2IQYgw6MJzgM4entJ0V/GPytkodblGY95ORVK7ZhyNtda+r5BZ6/jeMW+hA3VoK\n" - b"tHSWjHt06ueVCCieZIATmYzBNt+zEz5UA2l7ksg3eWfVORJQS7a6Ef4VvbJLM9Ca\n" - b"m1kdsjelAoGBANKgvRf39i3bSuvm5VoyJuqinSb/23IH3Zo7XOZ5G164vh49E9Cq\n" - b"dOXXVxox74ppj/kbGUoOk+AvaB48zzfzNvac0a7lRHExykPH2kVrI/NwH/1OcT/x\n" - b"2e2DnFYocXcb4gbdZQ+m6X3zkxOYcONRzPVW1uMrFTWHcJveMUm4PGx7AoGBAMcU\n" - b"IRvrT6ye5se0s27gHnPweV+3xjsNtXZcK82N7duXyHmNjxrwOAv0SOhUmTkRXArM\n" - b"6aN5D8vyZBSWma2TgUKwpQYFTI+4Sp7sdkkyojGAEixJ+c5TZJNxZFrUe0FwAoic\n" - b"c2kb7ntaiEj5G+qHvykJJro5hy6uLnjiMVbAiJDTAoGAKb67241EmHAXGEwp9sdr\n" - b"2SMjnIAnQSF39UKAthkYqJxa6elXDQtLoeYdGE7/V+J2K3wIdhoPiuY6b4vD0iX9\n" - b"JcGM+WntN7YTjX2FsC588JmvbWfnoDHR7HYiPR1E58N597xXdFOzgUgORVr4PMWQ\n" - b"pqtwaZO3X2WZlvrhr+e46hMCgYBfdIdrm6jYXFjL6RkgUNZJQUTxYGzsY+ZemlNm\n" - b"fGdQo7a8kePMRuKY2MkcnXPaqTg49YgRmjq4z8CtHokRcWjJUWnPOTs8rmEZUshk\n" - b"0KJ0mbQdCFt/Uv0mtXgpFTkEZ3DPkDTGcV4oR4CRfOCl0/EU/A5VvL/U4i/mRo7h\n" - b"ye+xgQKBgD58b+9z+PR5LAJm1tZHIwb4tnyczP28PzwknxFd2qylR4ZNgvAUqGtU\n" - b"xvpUDpzMioz6zUH9YV43YNtt+5Xnzkqj+u9Mr27/H2v9XPwORGfwQ5XPwRJz/2oC\n" - b"EnPmP1SZoY9lXKUpQXHXSpDZ2rE2Klt3RHMUMHt8Zpy36E8Vwx8o\n" - b"-----END RSA PRIVATE KEY-----\n" - ), - ], } VALUES["updated_encryption_context"] = copy.deepcopy(VALUES["encryption_context"]) VALUES["updated_encryption_context"]["aws-crypto-public-key"] = VALUES["encoded_curve_point"] diff --git a/test/unit/unit_test_utils.py b/test/unit/unit_test_utils.py index d175f0b3e..6b0a84bdc 100644 --- a/test/unit/unit_test_utils.py +++ b/test/unit/unit_test_utils.py @@ -1,310 +1,21 @@ -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.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. """Utility functions to handle common test framework functions.""" -import base64 import copy import io import itertools -import os -import attr -from attr.validators import instance_of -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import rsa - -from aws_encryption_sdk.exceptions import DecryptKeyError -from aws_encryption_sdk.identifiers import AlgorithmSuite, EncryptionKeyType, KeyringTraceFlag, WrappingAlgorithm -from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey from aws_encryption_sdk.internal.utils.streams import InsistentReaderBytesIO -from aws_encryption_sdk.key_providers.base import MasterKeyProvider, MasterKeyProviderConfig -from aws_encryption_sdk.key_providers.raw import RawMasterKey, RawMasterKeyProvider -from aws_encryption_sdk.keyrings.base import Keyring -from aws_encryption_sdk.keyrings.multi import MultiKeyring -from aws_encryption_sdk.keyrings.raw import RawAESKeyring, RawRSAKeyring -from aws_encryption_sdk.materials_managers import DecryptionMaterials, EncryptionMaterials -from aws_encryption_sdk.structures import EncryptedDataKey, KeyringTrace, MasterKeyInfo, RawDataKey - -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import 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 - -_ENCRYPTION_CONTEXT = {"encryption": "context", "values": "here"} -_PROVIDER_ID = "Random Raw Keys" -_EXISTING_KEY_ID = b"pre-seeded key id" -_KEY_ID = b"5325b043-5843-4629-869c-64794af77ada" -_WRAPPING_KEY = b"\xeby-\x80A6\x15rA8\x83#,\xe4\xab\xac`\xaf\x99Z\xc1\xce\xdb\xb6\x0f\xb7\x805\xb2\x14J3" -_SIGNING_KEY = b"aws-crypto-public-key" -_DATA_KEY = ( - b"\x00\xfa\x8c\xdd\x08Au\xc6\x92_4\xc5\xfb\x90\xaf\x8f\xa1D\xaf\xcc\xd25" b"\xa8\x0b\x0b\x16\x92\x91W\x01\xb7\x84" -) -_WRAPPING_KEY_AES = b"\xeby-\x80A6\x15rA8\x83#,\xe4\xab\xac`\xaf\x99Z\xc1\xce\xdb\xb6\x0f\xb7\x805\xb2\x14J3" - -_PUBLIC_EXPONENT = 65537 -_KEY_SIZE = 2048 -_BACKEND = default_backend() - -_ENCRYPTED_DATA_KEY_AES = EncryptedDataKey( - key_provider=MasterKeyInfo( - provider_id="Random Raw Keys", - key_info=b"5325b043-5843-4629-869c-64794af77ada\x00\x00\x00\x80" - b"\x00\x00\x00\x0c\xc7\xd5d\xc9\xc5\xf21\x8d\x8b\xf9H" - b"\xbb", - ), - encrypted_data_key=b"\xf3+\x15n\xe6`\xbe\xfe\xf0\x9e1\xe5\x9b" - b"\xaf\xfe\xdaT\xbb\x17\x14\xfd} o\xdd\xf1" - b"\xbc\xe1C\xa5J\xd8\xc7\x15\xc2\x90t=\xb9" - b"\xfd;\x94lTu/6\xfe", -) - -_ENCRYPTED_DATA_KEY_NOT_IN_KEYRING = EncryptedDataKey( - key_provider=MasterKeyInfo( - provider_id="Random Raw Keys", - key_info=b"5430b043-5843-4629-869c-64794af77ada\x00\x00\x00\x80" - b"\x00\x00\x00\x0c\xc7\xd5d\xc9\xc5\xf21\x8d\x8b\xf9H" - b"\xbb", - ), - encrypted_data_key=b"\xf3+\x15n\xe6`\xbe\xfe\xf0\x9e1\xe5\x9b" - b"\xaf\xfe\xdaT\xbb\x17\x14\xfd} o\xdd\xf1" - b"\xbc\xe1C\xa5J\xd8\xc7\x15\xc2\x90t=\xb9" - b"\xfd;\x94lTu/6\xfe", -) - -_ENCRYPTED_DATA_KEY_RSA = EncryptedDataKey( - key_provider=MasterKeyInfo(provider_id="Random Raw Keys", key_info=_KEY_ID), - encrypted_data_key=b"\xf3+\x15n\xe6`\xbe\xfe\xf0\x9e1\xe5\x9b" - b"\xaf\xfe\xdaT\xbb\x17\x14\xfd} o\xdd\xf1" - b"\xbc\xe1C\xa5J\xd8\xc7\x15\xc2\x90t=\xb9" - b"\xfd;\x94lTu/6\xfe", -) - - -class IdentityKeyring(Keyring): - def on_encrypt(self, encryption_materials): - # type: (EncryptionMaterials) -> EncryptionMaterials - return encryption_materials - - def on_decrypt(self, decryption_materials, encrypted_data_keys): - # type: (DecryptionMaterials, Iterable[EncryptedDataKey]) -> DecryptionMaterials - return decryption_materials - - -class OnlyGenerateKeyring(Keyring): - def on_encrypt(self, encryption_materials): - # type: (EncryptionMaterials) -> EncryptionMaterials - if encryption_materials.data_encryption_key is None: - key_provider = MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_KEY_ID) - data_encryption_key = RawDataKey( - key_provider=key_provider, data_key=os.urandom(encryption_materials.algorithm.kdf_input_len) - ) - encryption_materials = encryption_materials.with_data_encryption_key( - data_encryption_key=data_encryption_key, - keyring_trace=KeyringTrace(wrapping_key=key_provider, flags={KeyringTraceFlag.GENERATED_DATA_KEY}), - ) - return encryption_materials - - def on_decrypt(self, decryption_materials, encrypted_data_keys): - # type: (DecryptionMaterials, Iterable[EncryptedDataKey]) -> DecryptionMaterials - return decryption_materials - - -def get_encryption_materials_with_data_key(): - return EncryptionMaterials( - algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, - data_encryption_key=RawDataKey( - key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_EXISTING_KEY_ID), - data_key=b'*!\xa1"^-(\xf3\x105\x05i@B\xc2\xa2\xb7\xdd\xd5\xd5\xa9\xddm\xfae\xa8\\$\xf9d\x1e(', - ), - encryption_context=_ENCRYPTION_CONTEXT, - signing_key=_SIGNING_KEY, - keyring_trace=[ - KeyringTrace( - wrapping_key=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_EXISTING_KEY_ID), - flags={KeyringTraceFlag.GENERATED_DATA_KEY}, - ) - ], - ) - - -def get_encryption_materials_with_data_encryption_key(): - return EncryptionMaterials( - algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, - data_encryption_key=RawDataKey( - key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_EXISTING_KEY_ID), - data_key=b'*!\xa1"^-(\xf3\x105\x05i@B\xc2\xa2\xb7\xdd\xd5\xd5\xa9\xddm\xfae\xa8\\$\xf9d\x1e(', - ), - encryption_context=_ENCRYPTION_CONTEXT, - signing_key=_SIGNING_KEY, - keyring_trace=[ - KeyringTrace( - wrapping_key=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_EXISTING_KEY_ID), - flags={KeyringTraceFlag.GENERATED_DATA_KEY}, - ) - ], - ) - - -def get_encryption_materials_without_data_key(): - return EncryptionMaterials( - algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, - encryption_context=_ENCRYPTION_CONTEXT, - signing_key=_SIGNING_KEY, - ) - - -def get_encryption_materials_with_encrypted_data_key(): - return EncryptionMaterials( - algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, - data_encryption_key=RawDataKey( - key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_EXISTING_KEY_ID), - data_key=b'*!\xa1"^-(\xf3\x105\x05i@B\xc2\xa2\xb7\xdd\xd5\xd5\xa9\xddm\xfae\xa8\\$\xf9d\x1e(', - ), - encrypted_data_keys=[ - EncryptedDataKey( - key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_EXISTING_KEY_ID), - encrypted_data_key=b"\xde^\x97\x7f\x84\xe9\x9e\x98\xd0\xe2\xf8\xd5\xcb\xe9\x7f.}\x87\x16,\x11n#\xc8p" - b"\xdb\xbf\x94\x86*Q\x06\xd2\xf5\xdah\x08\xa4p\x81\xf7\xf4G\x07FzE\xde", - ) - ], - encryption_context=_ENCRYPTION_CONTEXT, - signing_key=_SIGNING_KEY, - keyring_trace=[ - KeyringTrace( - wrapping_key=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_EXISTING_KEY_ID), - flags={KeyringTraceFlag.GENERATED_DATA_KEY, KeyringTraceFlag.ENCRYPTED_DATA_KEY}, - ) - ], - ) - - -def get_encryption_materials_with_encrypted_data_key_aes(): - return EncryptionMaterials( - algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, - data_encryption_key=RawDataKey( - key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_EXISTING_KEY_ID), - data_key=b'*!\xa1"^-(\xf3\x105\x05i@B\xc2\xa2\xb7\xdd\xd5\xd5\xa9\xddm\xfae\xa8\\$\xf9d\x1e(', - ), - encrypted_data_keys=[_ENCRYPTED_DATA_KEY_AES], - encryption_context=_ENCRYPTION_CONTEXT, - signing_key=_SIGNING_KEY, - keyring_trace=[ - KeyringTrace( - wrapping_key=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_EXISTING_KEY_ID), - flags={KeyringTraceFlag.GENERATED_DATA_KEY, KeyringTraceFlag.ENCRYPTED_DATA_KEY}, - ) - ], - ) - - -def get_encryption_materials_without_data_encryption_key(): - return EncryptionMaterials( - algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, - encryption_context=_ENCRYPTION_CONTEXT, - signing_key=_SIGNING_KEY, - ) - - -def get_decryption_materials_without_data_encryption_key(): - return DecryptionMaterials( - algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, - verification_key=b"ex_verification_key", - encryption_context=_ENCRYPTION_CONTEXT, - ) - - -def get_decryption_materials_with_data_key(): - return DecryptionMaterials( - algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, - data_encryption_key=RawDataKey( - key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_EXISTING_KEY_ID), - data_key=b'*!\xa1"^-(\xf3\x105\x05i@B\xc2\xa2\xb7\xdd\xd5\xd5\xa9\xddm\xfae\xa8\\$\xf9d\x1e(', - ), - encryption_context=_ENCRYPTION_CONTEXT, - verification_key=b"ex_verification_key", - keyring_trace=[ - KeyringTrace( - wrapping_key=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_EXISTING_KEY_ID), - flags={KeyringTraceFlag.DECRYPTED_DATA_KEY}, - ) - ], - ) - - -def get_decryption_materials_with_data_encryption_key(): - return DecryptionMaterials( - algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, - data_encryption_key=RawDataKey( - key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_EXISTING_KEY_ID), - data_key=b'*!\xa1"^-(\xf3\x105\x05i@B\xc2\xa2\xb7\xdd\xd5\xd5\xa9\xddm\xfae\xa8\\$\xf9d\x1e(', - ), - encryption_context=_ENCRYPTION_CONTEXT, - verification_key=b"ex_verification_key", - keyring_trace=[ - KeyringTrace( - wrapping_key=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_EXISTING_KEY_ID), - flags={KeyringTraceFlag.DECRYPTED_DATA_KEY}, - ) - ], - ) - - -def get_decryption_materials_without_data_key(): - return DecryptionMaterials(encryption_context=_ENCRYPTION_CONTEXT, verification_key=b"ex_verification_key") - - -def get_multi_keyring_with_generator_and_children(): - return MultiKeyring( - generator=RawAESKeyring(key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_key=_WRAPPING_KEY_AES,), - children=[ - RawRSAKeyring( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=rsa.generate_private_key( - public_exponent=65537, key_size=2048, backend=default_backend() - ), - ), - RawRSAKeyring( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=rsa.generate_private_key( - public_exponent=65537, key_size=2048, backend=default_backend() - ), - ), - ], - ) - - -def get_multi_keyring_with_no_children(): - return MultiKeyring( - generator=RawRSAKeyring( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=rsa.generate_private_key( - public_exponent=65537, key_size=2048, backend=default_backend() - ), - ) - ) - - -def get_multi_keyring_with_no_generator(): - return MultiKeyring( - children=[ - RawRSAKeyring( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - private_wrapping_key=rsa.generate_private_key( - public_exponent=65537, key_size=2048, backend=default_backend() - ), - ), - RawAESKeyring(key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_key=_WRAPPING_KEY_AES,), - ] - ) def all_valid_kwargs(valid_kwargs): @@ -382,252 +93,3 @@ def assert_prepped_stream_identity(prepped_stream, wrapped_type): assert isinstance(prepped_stream, wrapped_type) # Check the wrapping streams assert isinstance(prepped_stream, InsistentReaderBytesIO) - - -def _generate_rsa_key_bytes(size): - # type: (int) -> bytes - private_key = rsa.generate_private_key(public_exponent=65537, key_size=size, backend=default_backend()) - return private_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.PKCS8, - encryption_algorithm=serialization.NoEncryption(), - ) - - -def ephemeral_raw_rsa_master_key(size=4096): - # type: (int) -> RawMasterKey - key_bytes = _generate_rsa_key_bytes(size) - return RawMasterKey( - provider_id="fake", - key_id="rsa-{}".format(size).encode("utf-8"), - wrapping_key=WrappingKey( - wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, - wrapping_key=key_bytes, - wrapping_key_type=EncryptionKeyType.PRIVATE, - ), - ) - - -def ephemeral_raw_rsa_keyring(size=4096, wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1): - # type: (int, WrappingAlgorithm) -> RawRSAKeyring - key_bytes = _generate_rsa_key_bytes(size) - return RawRSAKeyring.from_pem_encoding( - key_namespace="fake", - key_name="rsa-{}".format(size).encode("utf-8"), - wrapping_algorithm=wrapping_algorithm, - private_encoded_key=key_bytes, - ) - - -def raw_rsa_mkps_from_keyring(keyring): - # type: (RawRSAKeyring) -> (MasterKeyProvider, MasterKeyProvider) - """Constructs a private and public raw RSA MKP using the private key in the raw RSA keyring.""" - private_key = keyring._private_wrapping_key - private_pem = private_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.PKCS8, - encryption_algorithm=serialization.NoEncryption(), - ) - public_pem = private_key.public_key().public_bytes( - encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo - ) - private_key_mkp = RawMasterKey( - provider_id=keyring.key_namespace, - key_id=keyring.key_name, - wrapping_key=WrappingKey( - wrapping_algorithm=keyring._wrapping_algorithm, - wrapping_key=private_pem, - wrapping_key_type=EncryptionKeyType.PRIVATE, - ), - ) - public_key_mkp = RawMasterKey( - provider_id=keyring.key_namespace, - key_id=keyring.key_name, - wrapping_key=WrappingKey( - wrapping_algorithm=keyring._wrapping_algorithm, - wrapping_key=public_pem, - wrapping_key_type=EncryptionKeyType.PUBLIC, - ), - ) - return private_key_mkp, public_key_mkp - - -def ephemeral_raw_aes_master_key(wrapping_algorithm=WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING, key=None): - # type: (WrappingAlgorithm, Optional[bytes]) -> RawMasterKey - key_length = wrapping_algorithm.algorithm.data_key_len - if key is None: - key = os.urandom(key_length) - return RawMasterKey( - provider_id="fake", - key_id="aes-{}".format(key_length * 8).encode("utf-8"), - wrapping_key=WrappingKey( - wrapping_algorithm=wrapping_algorithm, wrapping_key=key, wrapping_key_type=EncryptionKeyType.SYMMETRIC, - ), - ) - - -def ephemeral_raw_aes_keyring(wrapping_algorithm=WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING, key=None): - # type: (WrappingAlgorithm, Optional[bytes]) -> RawAESKeyring - key_length = wrapping_algorithm.algorithm.data_key_len - if key is None: - key = os.urandom(key_length) - return RawAESKeyring( - key_namespace="fake", key_name="aes-{}".format(key_length * 8).encode("utf-8"), wrapping_key=key, - ) - - -class EphemeralRawMasterKeyProvider(RawMasterKeyProvider): - """Master key provider with raw master keys that are generated on each initialization.""" - - provider_id = "fake" - - def __init__(self): - self.__keys = { - b"aes-256": ephemeral_raw_aes_master_key(WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING), - b"rsa-4096": ephemeral_raw_rsa_master_key(4096), - } - - def _get_raw_key(self, key_id): - return self.__keys[key_id].config.wrapping_key - - -class EmptyMasterKeyProvider(MasterKeyProvider): - """Master key provider that provides no master keys.""" - - provider_id = "empty" - _config_class = MasterKeyProviderConfig - vend_masterkey_on_decrypt = False - - def _new_master_key(self, key_id): - raise Exception("How did this happen??") - - def master_keys_for_encryption(self, encryption_context, plaintext_rostream, plaintext_length=None): - return ephemeral_raw_aes_master_key(), [] - - -class DisjointMasterKeyProvider(MasterKeyProvider): - """Master key provider that does not provide the primary master key in the additional master keys.""" - - provider_id = "disjoint" - _config_class = MasterKeyProviderConfig - vend_masterkey_on_decrypt = False - - def _new_master_key(self, key_id): - raise Exception("How did this happen??") - - def master_keys_for_encryption(self, encryption_context, plaintext_rostream, plaintext_length=None): - return ephemeral_raw_aes_master_key(), [ephemeral_raw_rsa_master_key()] - - -class FailingDecryptMasterKeyProvider(EphemeralRawMasterKeyProvider): - """EphemeralRawMasterKeyProvider that cannot decrypt.""" - - def decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context): - raise DecryptKeyError("FailingDecryptMasterKeyProvider cannot decrypt!") - - -@attr.s -class BrokenKeyring(Keyring): - """Keyring that wraps another keyring and selectively breaks the returned values.""" - - _inner_keyring = attr.ib(validator=instance_of(Keyring)) - _break_algorithm = attr.ib(default=False, validator=instance_of(bool)) - _break_encryption_context = attr.ib(default=False, validator=instance_of(bool)) - _break_signing = attr.ib(default=False, validator=instance_of(bool)) - - @staticmethod - def _random_string(bytes_len): - # type: (int) -> str - return base64.b64encode(os.urandom(bytes_len)).decode("utf-8") - - def _broken_algorithm(self, algorithm): - # type: (AlgorithmSuite) -> AlgorithmSuite - if not self._break_algorithm: - return algorithm - - # We want to make sure that we return something different, - # so find this suite in all suites and grab the next one, - # whatever that is. - all_suites = list(AlgorithmSuite) - suite_index = all_suites.index(algorithm) - next_index = (suite_index + 1) % (len(all_suites) - 1) - - return all_suites[next_index] - - def _broken_encryption_context(self, encryption_context): - # type: (Dict[str, str]) -> Dict[str, str] - broken_ec = encryption_context.copy() - - if not self._break_encryption_context: - return broken_ec - - # Remove a random value - try: - broken_ec.popitem() - except KeyError: - pass - - # add a random value - broken_ec[self._random_string(5)] = self._random_string(10) - - return broken_ec - - def _broken_key(self, key): - # type: (bytes) -> bytes - if not self._break_signing: - return key - - return self._random_string(32).encode("utf-8") - - def _break_encryption_materials(self, encryption_materials): - # type: (EncryptionMaterials) -> EncryptionMaterials - return EncryptionMaterials( - algorithm=self._broken_algorithm(encryption_materials.algorithm), - data_encryption_key=encryption_materials.data_encryption_key, - encrypted_data_keys=encryption_materials.encrypted_data_keys, - encryption_context=self._broken_encryption_context(encryption_materials.encryption_context), - signing_key=self._broken_key(encryption_materials.signing_key), - keyring_trace=encryption_materials.keyring_trace, - ) - - def _break_decryption_materials(self, decryption_materials): - # type: (DecryptionMaterials) -> DecryptionMaterials - return DecryptionMaterials( - algorithm=self._broken_algorithm(decryption_materials.algorithm), - data_encryption_key=decryption_materials.data_encryption_key, - encryption_context=self._broken_encryption_context(decryption_materials.encryption_context), - verification_key=self._broken_key(decryption_materials.verification_key), - keyring_trace=decryption_materials.keyring_trace, - ) - - def on_encrypt(self, encryption_materials): - # type: (EncryptionMaterials) -> EncryptionMaterials - return self._break_encryption_materials(self._inner_keyring.on_encrypt(encryption_materials)) - - def on_decrypt(self, decryption_materials, encrypted_data_keys): - # type: (DecryptionMaterials, Iterable[EncryptedDataKey]) -> DecryptionMaterials - return self._break_decryption_materials( - self._inner_keyring.on_decrypt(decryption_materials, encrypted_data_keys) - ) - - -@attr.s -class NoEncryptedDataKeysKeyring(Keyring): - """Keyring that wraps another keyring and removes any encrypted data keys.""" - - _inner_keyring = attr.ib(validator=instance_of(Keyring)) - - def on_encrypt(self, encryption_materials): - # type: (EncryptionMaterials) -> EncryptionMaterials - materials = self._inner_keyring.on_encrypt(encryption_materials) - return EncryptionMaterials( - algorithm=materials.algorithm, - data_encryption_key=materials.data_encryption_key, - encryption_context=materials.encryption_context, - signing_key=materials.signing_key, - keyring_trace=materials.keyring_trace, - ) - - def on_decrypt(self, decryption_materials, encrypted_data_keys): - # type: (DecryptionMaterials, Iterable[EncryptedDataKey]) -> DecryptionMaterials - return self._inner_keyring.on_decrypt(decryption_materials, encrypted_data_keys) diff --git a/test/upstream-requirements-py27.txt b/test/upstream-requirements-py27.txt index 92e723b9c..082a6198e 100644 --- a/test/upstream-requirements-py27.txt +++ b/test/upstream-requirements-py27.txt @@ -1,72 +1,30 @@ +asn1crypto==0.24.0 atomicwrites==1.3.0 -attrs==19.3.0 -aws-sam-translator==1.21.0 -aws-xray-sdk==2.4.3 -backports.ssl-match-hostname==3.7.0.1 -backports.tempfile==1.0 -backports.weakref==1.0.post1 -boto==2.49.0 -boto3==1.12.16 -botocore==1.15.16 -certifi==2019.11.28 -cffi==1.14.0 -cfn-lint==0.28.3 -chardet==3.0.4 -configparser==4.0.2 -contextlib2==0.6.0.post1 -cookies==2.2.1 -coverage==5.0.3 -cryptography==2.8 -docker==4.2.0 -docutils==0.15.2 -ecdsa==0.15 -enum34==1.1.9 +attrs==19.1.0 +boto3==1.9.133 +botocore==1.12.133 +cffi==1.12.3 +coverage==4.5.3 +cryptography==2.6.1 +docutils==0.14 +enum34==1.1.6 funcsigs==1.0.2 -functools32==3.2.3.post2 -future==0.18.2 -futures==3.3.0 -idna==2.8 -importlib-metadata==1.5.0 -importlib-resources==1.0.2 -ipaddress==1.0.23 -Jinja2==2.11.1 -jmespath==0.9.5 -jsondiff==1.1.2 -jsonpatch==1.25 -jsonpickle==1.3 -jsonpointer==2.0 -jsonschema==3.2.0 -MarkupSafe==1.1.1 -mock==3.0.5 +futures==3.2.0 +ipaddress==1.0.22 +jmespath==0.9.4 +mock==2.0.0 more-itertools==5.0.0 -moto==1.3.14 -packaging==20.3 -pathlib2==2.3.5 -pluggy==0.13.1 -py==1.8.1 -pyasn1==0.4.8 -pycparser==2.20 -pyparsing==2.4.6 -pyrsistent==0.15.7 -pytest==4.6.9 -pytest-cov==2.8.1 -pytest-mock==2.0.0 -python-dateutil==2.8.1 -python-jose==3.1.0 -pytz==2019.3 -PyYAML==5.3 -requests==2.23.0 -responses==0.10.12 -rsa==4.0 -s3transfer==0.3.3 +pathlib2==2.3.3 +pbr==5.1.3 +pluggy==0.9.0 +py==1.8.0 +pycparser==2.19 +pytest==4.4.1 +pytest-cov==2.6.1 +pytest-mock==1.10.4 +python-dateutil==2.8.0 +s3transfer==0.2.0 scandir==1.10.0 -six==1.14.0 -sshpubkeys==3.1.0 -typing==3.7.4.1 -urllib3==1.25.8 -wcwidth==0.1.8 -websocket-client==0.57.0 -Werkzeug==1.0.0 -wrapt==1.12.0 -xmltodict==0.12.0 -zipp==1.2.0 +six==1.12.0 +urllib3==1.24.2 +wrapt==1.11.1 diff --git a/test/upstream-requirements-py37.txt b/test/upstream-requirements-py37.txt index 03a6eb36e..238746675 100644 --- a/test/upstream-requirements-py37.txt +++ b/test/upstream-requirements-py37.txt @@ -1,56 +1,24 @@ -attrs==19.3.0 -aws-sam-translator==1.21.0 -aws-xray-sdk==2.4.3 -boto==2.49.0 -boto3==1.12.16 -botocore==1.15.16 -certifi==2019.11.28 -cffi==1.14.0 -cfn-lint==0.28.3 -chardet==3.0.4 -coverage==5.0.3 -cryptography==2.8 -docker==4.2.0 -docutils==0.15.2 -ecdsa==0.15 -future==0.18.2 -idna==2.8 -importlib-metadata==1.5.0 -Jinja2==2.11.1 -jmespath==0.9.5 -jsondiff==1.1.2 -jsonpatch==1.25 -jsonpickle==1.3 -jsonpointer==2.0 -jsonschema==3.2.0 -MarkupSafe==1.1.1 -mock==4.0.1 -more-itertools==8.2.0 -moto==1.3.14 -packaging==20.3 -pluggy==0.13.1 -py==1.8.1 -pyasn1==0.4.8 -pycparser==2.20 -pyparsing==2.4.6 -pyrsistent==0.15.7 -pytest==5.3.5 -pytest-cov==2.8.1 -pytest-mock==2.0.0 -python-dateutil==2.8.1 -python-jose==3.1.0 -pytz==2019.3 -PyYAML==5.3 -requests==2.23.0 -responses==0.10.12 -rsa==4.0 -s3transfer==0.3.3 -six==1.14.0 -sshpubkeys==3.1.0 -urllib3==1.25.8 -wcwidth==0.1.8 -websocket-client==0.57.0 -Werkzeug==1.0.0 -wrapt==1.12.0 -xmltodict==0.12.0 -zipp==3.1.0 +asn1crypto==0.24.0 +atomicwrites==1.3.0 +attrs==19.1.0 +boto3==1.9.133 +botocore==1.12.133 +cffi==1.12.3 +coverage==4.5.3 +cryptography==2.6.1 +docutils==0.14 +jmespath==0.9.4 +mock==2.0.0 +more-itertools==7.0.0 +pbr==5.1.3 +pluggy==0.9.0 +py==1.8.0 +pycparser==2.19 +pytest==4.4.1 +pytest-cov==2.6.1 +pytest-mock==1.10.4 +python-dateutil==2.8.0 +s3transfer==0.2.0 +six==1.12.0 +urllib3==1.24.2 +wrapt==1.11.1 diff --git a/test_vector_handlers/README.rst b/test_vector_handlers/README.rst index b1d10c2c5..a2d188dd6 100644 --- a/test_vector_handlers/README.rst +++ b/test_vector_handlers/README.rst @@ -12,7 +12,7 @@ Getting Started Required Prerequisites ====================== -* Python 2.7 or 3.5+ +* Python 2.7 or 3.4+ * aws-encryption-sdk Use diff --git a/test_vector_handlers/setup.py b/test_vector_handlers/setup.py index c7ad742ad..bd16c76c8 100644 --- a/test_vector_handlers/setup.py +++ b/test_vector_handlers/setup.py @@ -48,10 +48,10 @@ def get_requirements(): "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Security", "Topic :: Security :: Cryptography", diff --git a/test_vector_handlers/tox.ini b/test_vector_handlers/tox.ini index 4c6e4edf9..420032dd0 100644 --- a/test_vector_handlers/tox.ini +++ b/test_vector_handlers/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - py{27,35,36,37,38}-awses_{1.3.3,1.3.max,latest}, + py{27,34,35,36,37}-awses_{1.3.3,1.3.max,latest}, # 1.2.0 and 1.2.max are being difficult because of attrs bandit, doc8, readme, docs, {flake8,pylint}{,-tests}, diff --git a/tox.ini b/tox.ini index 0785c62f7..362130b47 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - py{27,35,36,37,38}-{local,integ,accept,examples}, nocmk, + py{27,34,35,36,37}-{local,integ,accept,examples}, nocmk, bandit, doc8, readme, docs, {flake8,pylint}{,-tests,-examples}, isort-check, black-check, @@ -40,9 +40,8 @@ commands = pytest --basetemp={envtmpdir} -l --cov aws_encryption_sdk {posargs} [testenv] passenv = - # Identifies AWS KMS key ids to use in integration tests + # Identifies AWS KMS key id to use in integration tests AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID \ - AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2 \ # Pass through AWS credentials AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN \ # Pass through AWS profile name (useful for local testing) @@ -145,10 +144,6 @@ commands = [testenv:flake8-examples] basepython = {[testenv:flake8]basepython} -# This does not actually ignore errors, -# it just runs all commands regardless of whether any fail. -# If any fail, the final result will still fail. -ignore_errors = true deps = {[testenv:flake8]deps} commands = flake8 examples/src/ @@ -173,10 +168,6 @@ commands = [testenv:pylint-examples] basepython = {[testenv:pylint]basepython} -# This does not actually ignore errors, -# it just runs all commands regardless of whether any fail. -# If any fail, the final result will still fail. -ignore_errors = true deps = {[testenv:pylint]deps} commands = pylint --rcfile=examples/src/pylintrc examples/src/ @@ -245,11 +236,9 @@ commands = {[testenv:isort]commands} -c [testenv:autoformat] basepython = python3 deps = - {[testenv:isort-seed]deps} {[testenv:blacken]deps} {[testenv:isort]deps} commands = - {[testenv:isort-seed]commands} {[testenv:blacken]commands} {[testenv:isort]commands} From 268af660b6c943b38043af0894d3d696d6f2a2a3 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Fri, 22 May 2020 14:25:15 -0700 Subject: [PATCH 6/7] chore: add test/__init__.py to fix pylint module loading --- test/__init__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 test/__init__.py diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 000000000..53a960891 --- /dev/null +++ b/test/__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. From bc00e9b8199ab52efbb271516dfdeb75cf543ef1 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Fri, 22 May 2020 14:32:04 -0700 Subject: [PATCH 7/7] chore: cherry-pick CI configuration from keyring work --- .github/workflows/ci_decrypt-oracle.yaml | 53 +++++ .github/workflows/ci_static-analysis.yaml | 40 ++++ .github/workflows/ci_test-vector-handler.yaml | 88 ++++++++ .github/workflows/ci_tests.yaml | 100 ++++++++ .travis.yml | 213 +++--------------- ci-requirements.txt | 1 + 6 files changed, 308 insertions(+), 187 deletions(-) create mode 100644 .github/workflows/ci_decrypt-oracle.yaml create mode 100644 .github/workflows/ci_static-analysis.yaml create mode 100644 .github/workflows/ci_test-vector-handler.yaml create mode 100644 .github/workflows/ci_tests.yaml create mode 100644 ci-requirements.txt diff --git a/.github/workflows/ci_decrypt-oracle.yaml b/.github/workflows/ci_decrypt-oracle.yaml new file mode 100644 index 000000000..d8ecff117 --- /dev/null +++ b/.github/workflows/ci_decrypt-oracle.yaml @@ -0,0 +1,53 @@ +name: Continuous Integration tests for the decrypt oracle + +on: + pull_request: + push: + # Run once a day + schedule: + - cron: '0 0 * * *' + +jobs: + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v1 + with: + # The oracle runs in a Python 3.6 Lamba + python-version: 3.6 + - run: | + python -m pip install --upgrade pip + pip install --upgrade -r ci-requirements.txt + - name: run test + env: + TOXENV: local + run: | + cd decrypt_oracle + tox -- -vv + static-analysis: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + category: + - bandit + - readme + - flake8 + - pylint + - flake8-tests + - pylint-tests + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v1 + with: + python-version: 3.x + - run: | + python -m pip install --upgrade pip + pip install --upgrade -r ci-requirements.txt + - name: run test + env: + TOXENV: ${{ matrix.category }} + run: | + cd decrypt_oracle + tox -- -vv diff --git a/.github/workflows/ci_static-analysis.yaml b/.github/workflows/ci_static-analysis.yaml new file mode 100644 index 000000000..f80c429fe --- /dev/null +++ b/.github/workflows/ci_static-analysis.yaml @@ -0,0 +1,40 @@ +name: Static analysis checks + +on: + pull_request: + push: + # Run once a day + schedule: + - cron: '0 0 * * *' + +jobs: + analysis: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + category: + - bandit + - doc8 + - docs + - readme + - flake8 + - pylint + - flake8-tests + - pylint-tests + - flake8-examples + - pylint-examples + - black-check + - isort-check + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v1 + with: + python-version: 3.x + - run: | + python -m pip install --upgrade pip + pip install --upgrade -r ci-requirements.txt + - name: run test + env: + TOXENV: ${{ matrix.category }} + run: tox -- -vv diff --git a/.github/workflows/ci_test-vector-handler.yaml b/.github/workflows/ci_test-vector-handler.yaml new file mode 100644 index 000000000..570133231 --- /dev/null +++ b/.github/workflows/ci_test-vector-handler.yaml @@ -0,0 +1,88 @@ +name: Continuous Integration tests for the test vector handler + +on: + pull_request: + push: + # Run once a day + schedule: + - cron: '0 0 * * *' + +jobs: + tests: + # Leaving this defined but disabled + # until we address the credentials problem. + if: 1 == 0 + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + python: + - 2.7 + - 3.5 + - 3.6 + - 3.7 + - 3.8 + - 3.x + architecture: + - x64 + - x86 + category: + - awses_1.3.3 + - awses_1.3.max + - awses_latest + exclude: + # x86 builds are only meaningful for Windows + - os: ubuntu-latest + architecture: x86 + - os: macos-latest + architecture: x86 + steps: + - uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.INTEG_AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.INTEG_AWS_SECRET_ACCESS_KEY }} + aws-region: us-west-2 + - uses: actions/checkout@v2 + - uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python }} + architecture: ${{ matrix.architecture }} + - run: | + python -m pip install --upgrade pip + pip install --upgrade -r ci-requirements.txt + - name: run test + env: + TOXENV: ${{ matrix.category }} + run: | + cd test_vector_handlers + tox -- -vv + static-analysis: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + category: + - bandit + - readme + - flake8 + - pylint + - flake8-tests + - pylint-tests + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v1 + with: + python-version: 3.x + - run: | + python -m pip install --upgrade pip + pip install --upgrade -r ci-requirements.txt + - name: run test + env: + TOXENV: ${{ matrix.category }} + run: | + cd test_vector_handlers + tox -- -vv diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml new file mode 100644 index 000000000..c2f297ea2 --- /dev/null +++ b/.github/workflows/ci_tests.yaml @@ -0,0 +1,100 @@ +name: Continuous Integration tests + +on: + pull_request: + push: + # Run once a day + schedule: + - cron: '0 0 * * *' + +env: + 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 + +jobs: + tests: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + python: + - 2.7 + - 3.5 + - 3.6 + - 3.7 + - 3.8 + - 3.x + architecture: + - x64 + - x86 + category: + - local + - accept +# These require credentials. +# Enable them once we sort how to provide them. +# - integ +# - examples + exclude: + # x86 builds are only meaningful for Windows + - os: ubuntu-latest + architecture: x86 + - os: macos-latest + architecture: x86 + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python }} + architecture: ${{ matrix.architecture }} + - run: | + python -m pip install --upgrade pip + pip install --upgrade -r ci-requirements.txt + - name: run test + env: + TOXENV: ${{ matrix.category }} + run: tox -- -vv + upstream-py3: + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + category: + - nocmk + - test-upstream-requirements-py37 + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v1 + with: + python-version: 3.7 + - run: | + python -m pip install --upgrade pip + pip install --upgrade -r ci-requirements.txt + - name: run test + env: + TOXENV: ${{ matrix.category }} + run: tox -- -vv + upstream-py2: + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + category: + - test-upstream-requirements-py27 + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v1 + with: + python-version: 2.7 + - run: | + python -m pip install --upgrade pip + pip install --upgrade -r ci-requirements.txt + - name: run test + env: + TOXENV: ${{ matrix.category }} + run: tox -- -vv diff --git a/.travis.yml b/.travis.yml index 86702c536..a8ca00f68 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,132 +3,52 @@ language: python matrix: include: # CPython 2.7 - - python: 2.7 - env: TOXENV=py27-local - stage: Client Tests - python: 2.7 env: TOXENV=py27-integ stage: Client Tests - - python: 2.7 - env: TOXENV=py27-accept - stage: Client Tests - python: 2.7 env: TOXENV=py27-examples stage: Client Tests - # CPython 3.4 - - python: 3.4 - env: TOXENV=py34-local - stage: Client Tests - - python: 3.4 - env: TOXENV=py34-integ - stage: Client Tests - - python: 3.4 - env: TOXENV=py34-accept - stage: Client Tests - - python: 3.4 - env: TOXENV=py34-examples - stage: Client Tests # CPython 3.5 - - python: 3.5 - env: TOXENV=py35-local - stage: Client Tests - python: 3.5 env: TOXENV=py35-integ stage: Client Tests - - python: 3.5 - env: TOXENV=py35-accept - stage: Client Tests - python: 3.5 env: TOXENV=py35-examples stage: Client Tests # CPython 3.6 - - python: 3.6 - env: TOXENV=py36-local - stage: Client Tests - python: 3.6 env: TOXENV=py36-integ stage: Client Tests - - python: 3.6 - env: TOXENV=py36-accept - stage: Client Tests - python: 3.6 env: TOXENV=py36-examples stage: Client Tests # CPython 3.7 # xenial + sudo are currently needed to get 3.7 # https://github.com/travis-ci/travis-ci/issues/9815 - - python: 3.7 - env: TOXENV=py37-local - dist: xenial - sudo: true - stage: Client Tests - python: 3.7 env: TOXENV=py37-integ dist: xenial sudo: true stage: Client Tests - python: 3.7 - env: TOXENV=py37-accept + env: TOXENV=py37-examples dist: xenial sudo: true stage: Client Tests - - python: 3.7 - env: TOXENV=py37-examples + # CPython 3.8 + # xenial + sudo are currently needed to get 3.8 + # https://github.com/travis-ci/travis-ci/issues/9815 + - python: 3.8 + env: TOXENV=py38-integ dist: xenial sudo: true stage: Client Tests - # Upstream tests - - python: 3.6 - env: TOXENV=nocmk - stage: Upstream Tests - - python: 2.7 - env: TOXENV=test-upstream-requirements-py27 - stage: Upstream Tests - # xenial + sudo are currently needed to get 3.7 - # https://github.com/travis-ci/travis-ci/issues/9815 - - python: 3.7 - env: TOXENV=test-upstream-requirements-py37 + - python: 3.8 + env: TOXENV=py38-examples dist: xenial sudo: true - stage: Upstream Tests - # Security - - python: 3.6 - env: TOXENV=bandit - stage: Security Checks - # Linting and autoformatting - - python: 3.6 - env: TOXENV=doc8 - stage: Formatting Checks - - python: 3.6 - env: TOXENV=docs - stage: Formatting Checks - - python: 3.6 - env: TOXENV=readme - stage: Formatting Checks - - python: 3.6 - env: TOXENV=flake8 - stage: Formatting Checks - - python: 3.6 - env: TOXENV=pylint - stage: Formatting Checks - - python: 3.6 - env: TOXENV=flake8-tests - stage: Formatting Checks - - python: 3.6 - env: TOXENV=pylint-tests - stage: Formatting Checks - - python: 3.6 - env: TOXENV=flake8-examples - stage: Formatting Checks - - python: 3.6 - env: TOXENV=pylint-examples - stage: Formatting Checks - - python: 3.6 - env: TOXENV=black-check - stage: Formatting Checks - - python: 3.6 - env: TOXENV=isort-check - stage: Formatting Checks + stage: Client Tests ######################## # Test Vector Handlers # ######################## @@ -148,22 +68,6 @@ matrix: TEST_VECTOR_HANDLERS=1 TOXENV=py27-awses_latest stage: Test Vector Handler Tests - # CPython 3.4 - - python: 3.4 - env: - TEST_VECTOR_HANDLERS=1 - TOXENV=py34-awses_1.3.3 - stage: Test Vector Handler Tests - - python: 3.4 - env: - TEST_VECTOR_HANDLERS=1 - TOXENV=py34-awses_1.3.max - stage: Test Vector Handler Tests - - python: 3.4 - env: - TEST_VECTOR_HANDLERS=1 - TOXENV=py34-awses_latest - stage: Test Vector Handler Tests # CPython 3.5 - python: 3.5 env: @@ -218,93 +122,28 @@ matrix: dist: xenial sudo: true stage: Test Vector Handler Tests - # Linters - - python: 3.6 + # CPython 3.8 + - python: 3.8 env: TEST_VECTOR_HANDLERS=1 - TOXENV=bandit - stage: Test Vector Handler Formatting Checks - - python: 3.6 + TOXENV=py38-awses_1.3.3 + dist: xenial + sudo: true + stage: Test Vector Handler Tests + - python: 3.8 env: TEST_VECTOR_HANDLERS=1 - TOXENV=readme - stage: Test Vector Handler Formatting Checks - # Pending buildout of docs - #- python: 3.6 - # env: - # TEST_VECTOR_HANDLERS=1 - # TOXENV=docs - #- python: 3.6 - # env: - # TEST_VECTOR_HANDLERS=1 - # TOXENV=doc8 - # Pending linting cleanup - #- python: 3.6 - # env: - # TEST_VECTOR_HANDLERS=1 - # TOXENV=flake8 - #- python: 3.6 - # env: - # TEST_VECTOR_HANDLERS=1 - # TOXENV=pylint - #- python: 3.6 - # env: - # TEST_VECTOR_HANDLERS=1 - # TOXENV=flake8-tests - #- python: 3.6 - # env: - # TEST_VECTOR_HANDLERS=1 - # TOXENV=pylint-tests - ################## - # Decrypt Oracle # - ################## - # CPython 3.6 - # Because this build as Python 3.6 Lambda, this is the only runtime we are targetting. - - python: 3.6 - env: - DECRYPT_ORACLE=1 - TOXENV=py36-local - stage: Decrypt Oracle Tests - # Linters - - python: 3.6 - env: - DECRYPT_ORACLE=1 - TOXENV=bandit - stage: Decrypt Oracle Formatting Checks - - python: 3.6 - env: - DECRYPT_ORACLE=1 - TOXENV=readme - stage: Decrypt Oracle Formatting Checks - # Pending buildout of docs - #- python: 3.6 - # env: - # DECRYPT_ORACLE=1 - # TOXENV=docs - #- python: 3.6 - # env: - # DECRYPT_ORACLE=1 - # TOXENV=doc8 - - python: 3.6 - env: - DECRYPT_ORACLE=1 - TOXENV=flake8 - stage: Decrypt Oracle Formatting Checks - - python: 3.6 - env: - DECRYPT_ORACLE=1 - TOXENV=pylint - stage: Decrypt Oracle Formatting Checks - - python: 3.6 - env: - DECRYPT_ORACLE=1 - TOXENV=flake8-tests - stage: Decrypt Oracle Formatting Checks - - python: 3.6 + TOXENV=py38-awses_1.3.max + dist: xenial + sudo: true + stage: Test Vector Handler Tests + - python: 3.8 env: - DECRYPT_ORACLE=1 - TOXENV=pylint-tests - stage: Decrypt Oracle Formatting Checks + TEST_VECTOR_HANDLERS=1 + TOXENV=py38-awses_latest + dist: xenial + sudo: true + stage: Test Vector Handler Tests install: pip install tox script: - | diff --git a/ci-requirements.txt b/ci-requirements.txt new file mode 100644 index 000000000..053148f84 --- /dev/null +++ b/ci-requirements.txt @@ -0,0 +1 @@ +tox