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 5c39e3593..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,14 +251,14 @@ 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/ .. _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 +.. _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/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 a5c9a93de..000000000 --- a/examples/README.md +++ /dev/null @@ -1,144 +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) - * 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) - * [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) - * 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 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/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/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 5ebf57b00..000000000 --- a/examples/src/keyring/aws_kms/discovery_decrypt.py +++ /dev/null @@ -1,82 +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. -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. - -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 e3e32a354..000000000 --- a/examples/src/keyring/aws_kms/discovery_decrypt_in_region_only.py +++ /dev/null @@ -1,95 +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. -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. - -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 50eafb7d2..000000000 --- a/examples/src/keyring/aws_kms/discovery_decrypt_with_preferred_regions.py +++ /dev/null @@ -1,116 +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. -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. - -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/keypair.py b/examples/src/keyring/raw_rsa/keypair.py deleted file mode 100644 index f053b75a3..000000000 --- a/examples/src/keyring/raw_rsa/keypair.py +++ /dev/null @@ -1,90 +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 keypair. - -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, - 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. - # - # 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/keypair_from_pem.py b/examples/src/keyring/raw_rsa/keypair_from_pem.py deleted file mode 100644 index 2ec7a6ba1..000000000 --- a/examples/src/keyring/raw_rsa/keypair_from_pem.py +++ /dev/null @@ -1,106 +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 keypair. - -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 keypair 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. - # - # 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, - public_encoded_key=public_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/keyrings/raw/__init__.py b/examples/test/test_i_one_kms_cmk.py similarity index 52% rename from test/functional/keyrings/raw/__init__.py rename to examples/test/test_i_one_kms_cmk.py index ad0e71d6c..71ce74d3d 100644 --- a/test/functional/keyrings/raw/__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/internal/crypto/__init__.py b/examples/test/test_i_one_kms_cmk_unsigned.py similarity index 50% rename from test/functional/internal/crypto/__init__.py rename to examples/test/test_i_one_kms_cmk_unsigned.py index ad0e71d6c..8a2758c96 100644 --- a/test/functional/internal/crypto/__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 88da93d25..3f6d86e2e 100644 --- a/src/aws_encryption_sdk/__init__.py +++ b/src/aws_encryption_sdk/__init__.py @@ -1,8 +1,16 @@ -# 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.""" -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 @@ -16,9 +24,6 @@ StreamDecryptor, StreamEncryptor, ) -from aws_encryption_sdk.structures import CryptoResult - -__all__ = ("encrypt", "decrypt", "stream") def encrypt(**kwargs): @@ -28,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:: @@ -80,16 +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() - - 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 ciphertext, encryptor.header def decrypt(**kwargs): @@ -99,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:: @@ -142,16 +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() - - 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 plaintext, decryptor.header def stream(**kwargs): @@ -174,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) @@ -194,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) @@ -212,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 917819fdb..c0a2dc46e 100644 --- a/src/aws_encryption_sdk/key_providers/kms.py +++ b/src/aws_encryption_sdk/key_providers/kms.py @@ -78,62 +78,22 @@ 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. - - 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: + .. note:: + If no botocore_session is provided, the default botocore session will be used. - .. _AWS KMS: https://docs.aws.amazon.com/kms/latest/developerguide/overview.html + .. note:: + If multiple AWS Identities are needed, one of two options are available: - 1. Provide your AWS credentials in one of the standard `AWS credential discovery locations`_ - and the :class:`KMSMasterKeyProvider` instance automatically discovers those credentials. + * Additional KMSMasterKeyProvider instances may be added to the primary MasterKeyProvider. - .. _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() - - 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. - - >>> 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) - - 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 @@ -266,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 ddb07fb03..000000000 --- a/src/aws_encryption_sdk/keyrings/raw.py +++ /dev/null @@ -1,464 +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._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.") - - @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 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) - - 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 index e69de29bb..53a960891 100644 --- a/test/__init__.py +++ 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. 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/__init__.py b/test/functional/internal/__init__.py deleted file mode 100644 index ad0e71d6c..000000000 --- a/test/functional/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/functional/keyrings/__init__.py b/test/functional/keyrings/__init__.py deleted file mode 100644 index ad0e71d6c..000000000 --- a/test/functional/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/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/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 f72ffee51..000000000 --- a/test/functional/keyrings/raw/test_raw_rsa.py +++ /dev/null @@ -1,373 +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.exceptions import EncryptKeyError -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) -_PUBLIC_WRAPPING_KEY = _PRIVATE_WRAPPING_KEY.public_key() - -_PRIVATE_WRAPPING_KEY_PEM = _PRIVATE_WRAPPING_KEY.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( - 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_PRIVATE_KEY_DER_ENCODED_WITHOUT_PASSWORD = _PRIVATE_WRAPPING_KEY.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( - 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 -) - - -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, - 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, - ), - 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, - 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, - ), - RawRSAKeyring.from_der_encoding( - key_namespace=_PROVIDER_ID, - key_name=_KEY_ID, - public_encoded_key=_RAW_RSA_PUBLIC_KEY_DER_ENCODED, - 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, - public_encoded_key=_PUBLIC_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, - public_encoded_key=_PUBLIC_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 - - -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 deleted file mode 100644 index 43833a41a..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}, - ) - ], -) - -_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=[ - RawRSAKeyring( - 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(), - ), - 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(), - ), - ], -) - -_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_private_key_a, - public_wrapping_key=_rsa_private_key_a.public_key(), - ) -) - -_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_private_key_a, - public_wrapping_key=_rsa_private_key_a.public_key(), - ), - 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 55b91de92..000000000 --- a/test/unit/keyrings/raw/test_raw_rsa.py +++ /dev/null @@ -1,325 +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.backends import default_backend -from cryptography.hazmat.primitives import serialization -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(): - private_key = serialization.load_pem_private_key( - data=VALUES["private_rsa_key_bytes"][1], password=None, backend=default_backend() - ) - return 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(), - ) - - -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): - 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() - - with pytest.raises(EncryptKeyError) as excinfo: - test_keyring.on_encrypt(encryption_materials=initial_materials) - - excinfo.match("A public key is required to encrypt") - - -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_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 87% rename from test/unit/test_client.py rename to test/unit/test_aws_encryption_sdk.py index bd40eb03a..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,16 +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 == _HEADER - assert test_header is not _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 == _HEADER - assert test_header is not _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 bd6a9a82f..6b0a84bdc 100644 --- a/test/unit/unit_test_utils.py +++ b/test/unit/unit_test_utils.py @@ -1,309 +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(): - 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=[ - 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(), - ), - 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(), - ), - ], - ) - - -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(), - ) - ) - - -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(), - ), - RawAESKeyring(key_namespace=_PROVIDER_ID, key_name=_KEY_ID, wrapping_key=_WRAPPING_KEY_AES,), - ] - ) def all_valid_kwargs(valid_kwargs): @@ -381,253 +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 - private_key = rsa.generate_private_key(public_exponent=65537, key_size=size, backend=default_backend()) - return RawRSAKeyring( - 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(), - ) - - -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}