diff --git a/decrypt_oracle/tox.ini b/decrypt_oracle/tox.ini index f0a7804e5..02d808cc5 100644 --- a/decrypt_oracle/tox.ini +++ b/decrypt_oracle/tox.ini @@ -156,7 +156,11 @@ basepython = python3 deps = flake8 flake8-docstrings +<<<<<<< HEAD pydocstyle<4.0.0 +======= + pydocstyle < 4.0.0 +>>>>>>> 2e85bfd3d42965b9972506c39371e971132196e0 # https://github.com/JBKahn/flake8-print/pull/30 flake8-print>=3.1.0 commands = diff --git a/requirements.txt b/requirements.txt index f04114485..8378d126e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ +six boto3>=1.4.4 cryptography>=1.8.1 -attrs>=17.4.0 +attrs>=19.1.0 wrapt>=1.10.11 \ No newline at end of file diff --git a/src/aws_encryption_sdk/__init__.py b/src/aws_encryption_sdk/__init__.py index 3f6d86e2e..3aae2c9c7 100644 --- a/src/aws_encryption_sdk/__init__.py +++ b/src/aws_encryption_sdk/__init__.py @@ -14,7 +14,7 @@ # 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 -from aws_encryption_sdk.identifiers import Algorithm, __version__ # noqa +from aws_encryption_sdk.identifiers import AlgorithmSuite, __version__ # noqa from aws_encryption_sdk.key_providers.kms import KMSMasterKeyProvider, KMSMasterKeyProviderConfig # noqa from aws_encryption_sdk.materials_managers.caching import CachingCryptoMaterialsManager # noqa from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager # noqa @@ -69,8 +69,8 @@ def encrypt(**kwargs): this is not enforced if a `key_provider` is provided. :param dict encryption_context: Dictionary defining encryption context - :param algorithm: Algorithm to use for encryption - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite to use for encryption + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param int frame_length: Frame length in bytes :returns: Tuple containing the encrypted ciphertext and the message header object :rtype: tuple of bytes and :class:`aws_encryption_sdk.structures.MessageHeader` diff --git a/src/aws_encryption_sdk/exceptions.py b/src/aws_encryption_sdk/exceptions.py index a71d414c0..cd60ab6bd 100644 --- a/src/aws_encryption_sdk/exceptions.py +++ b/src/aws_encryption_sdk/exceptions.py @@ -53,6 +53,13 @@ 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.""" @@ -73,6 +80,13 @@ 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 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 1bd9bb1f1..049422a08 100644 --- a/src/aws_encryption_sdk/identifiers.py +++ b/src/aws_encryption_sdk/identifiers.py @@ -240,6 +240,8 @@ def safe_to_cache(self): return self.kdf is not KDFSuite.NONE +# algorithm is just an alias for AlgorithmSuite ... but Sphinx does not recognize this fact +# so we need to go through and fix the references Algorithm = AlgorithmSuite @@ -271,9 +273,27 @@ class WrappingAlgorithm(Enum): :type padding_mgf: """ - AES_128_GCM_IV12_TAG16_NO_PADDING = (EncryptionType.SYMMETRIC, Algorithm.AES_128_GCM_IV12_TAG16, None, None, None) - AES_192_GCM_IV12_TAG16_NO_PADDING = (EncryptionType.SYMMETRIC, Algorithm.AES_192_GCM_IV12_TAG16, None, None, None) - AES_256_GCM_IV12_TAG16_NO_PADDING = (EncryptionType.SYMMETRIC, Algorithm.AES_256_GCM_IV12_TAG16, None, None, None) + AES_128_GCM_IV12_TAG16_NO_PADDING = ( + EncryptionType.SYMMETRIC, + AlgorithmSuite.AES_128_GCM_IV12_TAG16, + None, + None, + None, + ) + AES_192_GCM_IV12_TAG16_NO_PADDING = ( + EncryptionType.SYMMETRIC, + AlgorithmSuite.AES_192_GCM_IV12_TAG16, + None, + None, + None, + ) + AES_256_GCM_IV12_TAG16_NO_PADDING = ( + EncryptionType.SYMMETRIC, + AlgorithmSuite.AES_256_GCM_IV12_TAG16, + None, + None, + None, + ) RSA_PKCS1 = (EncryptionType.ASYMMETRIC, rsa, padding.PKCS1v15, None, None) RSA_OAEP_SHA1_MGF1 = (EncryptionType.ASYMMETRIC, rsa, padding.OAEP, hashes.SHA1, padding.MGF1) RSA_OAEP_SHA256_MGF1 = (EncryptionType.ASYMMETRIC, rsa, padding.OAEP, hashes.SHA256, padding.MGF1) @@ -328,3 +348,13 @@ 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.""" + + WRAPPING_KEY_GENERATED_DATA_KEY = 1 + WRAPPING_KEY_ENCRYPTED_DATA_KEY = 1 << 1 + WRAPPING_KEY_DECRYPTED_DATA_KEY = 1 << 2 + WRAPPING_KEY_SIGNED_ENC_CTX = 1 << 3 + WRAPPING_KEY_VERIFIED_ENC_CTX = 1 << 4 diff --git a/src/aws_encryption_sdk/internal/crypto/authentication.py b/src/aws_encryption_sdk/internal/crypto/authentication.py index 560fdb2a2..dc9929bf7 100644 --- a/src/aws_encryption_sdk/internal/crypto/authentication.py +++ b/src/aws_encryption_sdk/internal/crypto/authentication.py @@ -33,8 +33,8 @@ class _PrehashingAuthenticator(object): """Parent class for Signer/Verifier. Provides common behavior and interface. - :param algorithm: Algorithm on which to base authenticator - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite on which to base authenticator + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param key: Key with which to build authenticator """ @@ -46,7 +46,7 @@ def __init__(self, algorithm, key): self._hasher = self._build_hasher() def _set_signature_type(self): - """Ensures that the algorithm signature type is a known type and sets a reference value.""" + """Ensures that the algorithm (suite) signature type is a known type and sets a reference value.""" try: verify_interface(ec.EllipticCurve, self.algorithm.signing_algorithm_info) return ec.EllipticCurve @@ -64,8 +64,8 @@ def _build_hasher(self): class Signer(_PrehashingAuthenticator): """Abstract signing handler. - :param algorithm: Algorithm on which to base signer - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite on which to base signer + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param key: Private key from which a signer can be generated :type key: currently only Elliptic Curve Private Keys are supported """ @@ -74,8 +74,8 @@ class Signer(_PrehashingAuthenticator): def from_key_bytes(cls, algorithm, key_bytes): """Builds a `Signer` from an algorithm suite and a raw signing key. - :param algorithm: Algorithm on which to base signer - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite on which to base signer + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param bytes key_bytes: Raw signing key :rtype: aws_encryption_sdk.internal.crypto.Signer """ @@ -127,18 +127,18 @@ class Verifier(_PrehashingAuthenticator): .. note:: For ECC curves, the signature must be DER encoded as specified in RFC 3279. - :param algorithm: Algorithm on which to base verifier - :type algorithm: aws_encryption_sdk.identifiers.Algorithm - :param public_key: Appropriate public key object for algorithm + :param algorithm: Algorithm suite on which to base verifier + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite + :param public_key: Appropriate public key object for algorithm suite :type public_key: may vary """ @classmethod def from_encoded_point(cls, algorithm, encoded_point): - """Creates a Verifier object based on the supplied algorithm and encoded compressed ECC curve point. + """Creates a Verifier object based on the supplied algorithm suite and encoded compressed ECC curve point. - :param algorithm: Algorithm on which to base verifier - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite on which to base verifier + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param bytes encoded_point: ECC public point compressed and encoded with _ecc_encode_compressed_point :returns: Instance of Verifier generated from encoded point :rtype: aws_encryption_sdk.internal.crypto.Verifier @@ -152,10 +152,10 @@ def from_encoded_point(cls, algorithm, encoded_point): @classmethod def from_key_bytes(cls, algorithm, key_bytes): - """Creates a `Verifier` object based on the supplied algorithm and raw verification key. + """Creates a `Verifier` object based on the supplied algorithm suite and raw verification key. - :param algorithm: Algorithm on which to base verifier - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite on which to base verifier + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param bytes encoded_point: Raw verification key :returns: Instance of Verifier generated from encoded point :rtype: aws_encryption_sdk.internal.crypto.Verifier diff --git a/src/aws_encryption_sdk/internal/crypto/data_keys.py b/src/aws_encryption_sdk/internal/crypto/data_keys.py index f16873106..4d773f9ec 100644 --- a/src/aws_encryption_sdk/internal/crypto/data_keys.py +++ b/src/aws_encryption_sdk/internal/crypto/data_keys.py @@ -20,11 +20,11 @@ def derive_data_encryption_key(source_key, algorithm, message_id): - """Derives the data encryption key using the defined algorithm. + """Derives the data encryption key using the defined algorithm suite. :param bytes source_key: Raw source key - :param algorithm: Algorithm used to encrypt this body - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite used to encrypt this body + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param bytes message_id: Message ID :returns: Derived data encryption key :rtype: bytes diff --git a/src/aws_encryption_sdk/internal/crypto/elliptic_curve.py b/src/aws_encryption_sdk/internal/crypto/elliptic_curve.py index 47af50b8c..56f49976e 100644 --- a/src/aws_encryption_sdk/internal/crypto/elliptic_curve.py +++ b/src/aws_encryption_sdk/internal/crypto/elliptic_curve.py @@ -57,8 +57,8 @@ def _ecc_static_length_signature(key, algorithm, digest): :param key: Elliptic curve private key :type key: cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey - :param algorithm: Master algorithm to use - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Master algorithm suite to use + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param bytes digest: Pre-calculated hash digest :returns: Signature with required length :rtype: bytes @@ -177,10 +177,10 @@ def _ecc_public_numbers_from_compressed_point(curve, compressed_point): def generate_ecc_signing_key(algorithm): """Returns an ECC signing key. - :param algorithm: Algorithm object which determines what signature to generate - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite object which determines what signature to generate + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :returns: Generated signing key - :raises NotSupportedError: if signing algorithm is not supported on this platform + :raises NotSupportedError: if signing algorithm suite is not supported on this platform """ try: verify_interface(ec.EllipticCurve, algorithm.signing_algorithm_info) diff --git a/src/aws_encryption_sdk/internal/crypto/encryption.py b/src/aws_encryption_sdk/internal/crypto/encryption.py index 1e5523826..b6251e3ca 100644 --- a/src/aws_encryption_sdk/internal/crypto/encryption.py +++ b/src/aws_encryption_sdk/internal/crypto/encryption.py @@ -24,8 +24,8 @@ class Encryptor(object): """Abstract encryption handler. - :param algorithm: Algorithm used to encrypt this body - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite used to encrypt this body + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param bytes key: Encryption key :param bytes associated_data: Associated Data to send to encryption subsystem :param bytes iv: IV to use when encrypting message @@ -76,8 +76,8 @@ def tag(self): def encrypt(algorithm, key, plaintext, associated_data, iv): """Encrypts a frame body. - :param algorithm: Algorithm used to encrypt this body - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite used to encrypt this body + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param bytes key: Encryption key :param bytes plaintext: Body plaintext :param bytes associated_data: Body AAD Data @@ -93,8 +93,8 @@ def encrypt(algorithm, key, plaintext, associated_data, iv): class Decryptor(object): """Abstract decryption handler. - :param algorithm: Algorithm used to encrypt this body - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite used to encrypt this body + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param bytes key: Raw source key :param bytes associated_data: Associated Data to send to decryption subsystem :param bytes iv: IV value with which to initialize decryption subsystem @@ -135,8 +135,8 @@ def finalize(self): def decrypt(algorithm, key, encrypted_data, associated_data): """Decrypts a frame body. - :param algorithm: Algorithm used to encrypt this body - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite used to encrypt this body + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param bytes key: Plaintext data key :param encrypted_data: EncryptedData containing body data :type encrypted_data: :class:`aws_encryption_sdk.internal.structures.EncryptedData`, diff --git a/src/aws_encryption_sdk/internal/crypto/iv.py b/src/aws_encryption_sdk/internal/crypto/iv.py index e5424057b..d6515df7c 100644 --- a/src/aws_encryption_sdk/internal/crypto/iv.py +++ b/src/aws_encryption_sdk/internal/crypto/iv.py @@ -46,8 +46,8 @@ def frame_iv(algorithm, sequence_number): """Builds the deterministic IV for a body frame. - :param algorithm: Algorithm for which to build IV - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite for which to build IV + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param int sequence_number: Frame sequence number :returns: Generated IV :rtype: bytes @@ -67,8 +67,8 @@ def frame_iv(algorithm, sequence_number): def non_framed_body_iv(algorithm): """Builds the deterministic IV for a non-framed body. - :param algorithm: Algorithm for which to build IV - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite for which to build IV + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :returns: Generated IV :rtype: bytes """ @@ -78,8 +78,8 @@ def non_framed_body_iv(algorithm): def header_auth_iv(algorithm): """Builds the deterministic IV for header authentication. - :param algorithm: Algorithm for which to build IV - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite for which to build IV + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :returns: Generated IV :rtype: bytes """ diff --git a/src/aws_encryption_sdk/internal/defaults.py b/src/aws_encryption_sdk/internal/defaults.py index f63a42d61..2dc4ae0c4 100644 --- a/src/aws_encryption_sdk/internal/defaults.py +++ b/src/aws_encryption_sdk/internal/defaults.py @@ -29,7 +29,7 @@ #: Default message structure Type as defined in specification TYPE = aws_encryption_sdk.identifiers.ObjectType.CUSTOMER_AE_DATA #: Default algorithm as defined in specification -ALGORITHM = aws_encryption_sdk.identifiers.Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384 +ALGORITHM = aws_encryption_sdk.identifiers.AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384 #: Key to add encoded signing key to encryption context dictionary as defined in specification ENCODED_SIGNER_KEY = "aws-crypto-public-key" diff --git a/src/aws_encryption_sdk/internal/formatting/serialize.py b/src/aws_encryption_sdk/internal/formatting/serialize.py index e7c86a0cb..ee6e388c1 100644 --- a/src/aws_encryption_sdk/internal/formatting/serialize.py +++ b/src/aws_encryption_sdk/internal/formatting/serialize.py @@ -121,8 +121,8 @@ def serialize_header(header, signer=None): def serialize_header_auth(algorithm, header, data_encryption_key, signer=None): """Creates serialized header authentication data. - :param algorithm: Algorithm to use for encryption - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite to use for encryption + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param bytes header: Serialized message header :param bytes data_encryption_key: Data key with which to encrypt message :param signer: Cryptographic signer object (optional) @@ -150,8 +150,8 @@ def serialize_header_auth(algorithm, header, data_encryption_key, signer=None): def serialize_non_framed_open(algorithm, iv, plaintext_length, signer=None): """Serializes the opening block for a non-framed message body. - :param algorithm: Algorithm to use for encryption - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite to use for encryption + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param bytes iv: IV value used to encrypt body :param int plaintext_length: Length of plaintext (and thus ciphertext) in body :param signer: Cryptographic signer object (optional) @@ -187,8 +187,8 @@ def serialize_frame( """Receives a message plaintext, breaks off a frame, encrypts and serializes the frame, and returns the encrypted frame and the remaining plaintext. - :param algorithm: Algorithm to use for encryption - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite to use for encryption + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param bytes plaintext: Source plaintext to encrypt and serialize :param bytes message_id: Message ID :param bytes data_encryption_key: Data key with which to encrypt message diff --git a/src/aws_encryption_sdk/internal/utils/__init__.py b/src/aws_encryption_sdk/internal/utils/__init__.py index 1e7400c3a..2288b1ebc 100644 --- a/src/aws_encryption_sdk/internal/utils/__init__.py +++ b/src/aws_encryption_sdk/internal/utils/__init__.py @@ -45,8 +45,8 @@ def validate_frame_length(frame_length, algorithm): """Validates that frame length is within the defined limits and is compatible with the selected algorithm. :param int frame_length: Frame size in bytes - :param algorithm: Algorithm to use for encryption - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite to use for encryption + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :raises SerializationError: if frame size is negative or not a multiple of the algorithm block size :raises SerializationError: if frame size is larger than the maximum allowed frame size """ @@ -103,8 +103,8 @@ def prepare_data_keys(primary_master_key, master_keys, algorithm, encryption_con :type primary_master_key: aws_encryption_sdk.key_providers.base.MasterKey :param master_keys: All master keys with which to encrypt data keys :type master_keys: list of :class:`aws_encryption_sdk.key_providers.base.MasterKey` - :param algorithm: Algorithm to use for encryption - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite to use for encryption + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param dict encryption_context: Encryption context to use when generating data key :rtype: tuple containing :class:`aws_encryption_sdk.structures.DataKey` and set of :class:`aws_encryption_sdk.structures.EncryptedDataKey` @@ -151,8 +151,8 @@ def source_data_key_length_check(source_data_key, algorithm): :param source_data_key: Source data key object received from MasterKey decrypt or generate data_key methods :type source_data_key: :class:`aws_encryption_sdk.structures.RawDataKey` or :class:`aws_encryption_sdk.structures.DataKey` - :param algorithm: Algorithm object which directs how this data key will be used - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite object which directs how this data key will be used + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :raises InvalidDataKeyError: if data key length does not match required kdf input length """ if len(source_data_key.data_key) != algorithm.kdf_input_len: diff --git a/src/aws_encryption_sdk/key_providers/base.py b/src/aws_encryption_sdk/key_providers/base.py index 3112cba6d..9554eb44d 100644 --- a/src/aws_encryption_sdk/key_providers/base.py +++ b/src/aws_encryption_sdk/key_providers/base.py @@ -218,8 +218,8 @@ def decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context): :param encrypted_data_key: Encrypted data key to decrypt :type encrypted_data_key: aws_encryption_sdk.structures.EncryptedDataKey - :param algorithm: Algorithm object which directs how this Master Key will encrypt the data key - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite object which directs how this Master Key will encrypt the data key + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param dict encryption_context: Encryption context to use in encryption :returns: Decrypted data key :rtype: aws_encryption_sdk.structures.DataKey diff --git a/src/aws_encryption_sdk/key_providers/kms.py b/src/aws_encryption_sdk/key_providers/kms.py index df089e3b1..8f29cbd0f 100644 --- a/src/aws_encryption_sdk/key_providers/kms.py +++ b/src/aws_encryption_sdk/key_providers/kms.py @@ -115,7 +115,6 @@ def __init__(self, **kwargs): # pylint: disable=unused-argument def _process_config(self): """Traverses the config and adds master keys and regional clients as needed.""" self._user_agent_adding_config = botocore.config.Config(user_agent_extra=USER_AGENT_SUFFIX) - if self.config.region_names: self.add_regional_clients_from_list(self.config.region_names) self.default_region = self.config.region_names[0] @@ -123,7 +122,6 @@ def _process_config(self): self.default_region = self.config.botocore_session.get_config_variable("region") if self.default_region is not None: self.add_regional_client(self.default_region) - if self.config.key_ids: self.add_master_keys_from_list(self.config.key_ids) @@ -244,8 +242,8 @@ def __init__(self, **kwargs): # pylint: disable=unused-argument def _generate_data_key(self, algorithm, encryption_context=None): """Generates data key and returns plaintext and ciphertext of key. - :param algorithm: Algorithm on which to base data key - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite on which to base data key + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param dict encryption_context: Encryption context to pass to KMS :returns: Generated data key :rtype: aws_encryption_sdk.structures.DataKey @@ -306,7 +304,7 @@ def _decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context=No :param data_key: Encrypted data key :type data_key: aws_encryption_sdk.structures.EncryptedDataKey - :type algorithm: `aws_encryption_sdk.identifiers.Algorithm` (not used for KMS) + :type algorithm: `aws_encryption_sdk.identifiers.AlgorithmSuite` (not used for KMS) :param dict encryption_context: Encryption context to use in decryption :returns: Decrypted data key :rtype: aws_encryption_sdk.structures.DataKey diff --git a/src/aws_encryption_sdk/key_providers/raw.py b/src/aws_encryption_sdk/key_providers/raw.py index 57a1d5edf..ca6c690d6 100644 --- a/src/aws_encryption_sdk/key_providers/raw.py +++ b/src/aws_encryption_sdk/key_providers/raw.py @@ -115,8 +115,8 @@ def owns_data_key(self, data_key): def _generate_data_key(self, algorithm, encryption_context): """Generates data key and returns :class:`aws_encryption_sdk.structures.DataKey`. - :param algorithm: Algorithm on which to base data key - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite on which to base data key + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param dict encryption_context: Encryption context to use in encryption :returns: Generated data key :rtype: aws_encryption_sdk.structures.DataKey @@ -139,8 +139,8 @@ def _encrypt_data_key(self, data_key, algorithm, encryption_context): :param data_key: Unencrypted data key :type data_key: :class:`aws_encryption_sdk.structures.RawDataKey` or :class:`aws_encryption_sdk.structures.DataKey` - :param algorithm: Algorithm object which directs how this Master Key will encrypt the data key - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite object which directs how this Master Key will encrypt the data key + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param dict encryption_context: Encryption context to use in encryption :returns: Decrypted data key :rtype: aws_encryption_sdk.structures.EncryptedDataKey @@ -163,8 +163,8 @@ def _decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context): :param data_key: Encrypted data key :type data_key: aws_encryption_sdk.structures.EncryptedDataKey - :param algorithm: Algorithm object which directs how this Master Key will encrypt the data key - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite object which directs how this Master Key will encrypt the data key + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param dict encryption_context: Encryption context to use in decryption :returns: Data key containing decrypted data key :rtype: aws_encryption_sdk.structures.DataKey diff --git a/src/aws_encryption_sdk/keyring/__init__.py b/src/aws_encryption_sdk/keyring/__init__.py new file mode 100644 index 000000000..ada03b4d7 --- /dev/null +++ b/src/aws_encryption_sdk/keyring/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""All provided Keyrings.""" diff --git a/src/aws_encryption_sdk/keyring/base.py b/src/aws_encryption_sdk/keyring/base.py new file mode 100644 index 000000000..770b53c0b --- /dev/null +++ b/src/aws_encryption_sdk/keyring/base.py @@ -0,0 +1,54 @@ +# 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. +"""Base class interface for Keyrings.""" +from aws_encryption_sdk.materials_managers import DecryptionMaterials, EncryptionMaterials +from aws_encryption_sdk.structures import EncryptedDataKey + +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 + + +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 encryption_materials: Encryption materials for the keyring to modify. + :type encryption_materials: aws_encryption_sdk.materials_managers.EncryptionMaterials + :returns: Optionally modified encryption materials. + :rtype: aws_encryption_sdk.materials_managers.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 decryption_materials: Decryption materials for the keyring to modify. + :type decryption_materials: aws_encryption_sdk.materials_managers.DecryptionMaterials + :param encrypted_data_keys: List of encrypted data keys. + :type: Iterable of :class:`aws_encryption_sdk.structures.EncryptedDataKey` + :returns: Optionally modified decryption materials. + :rtype: aws_encryption_sdk.materials_managers.DecryptionMaterials + :raises NotImplementedError: if method is not implemented + """ + raise NotImplementedError("Keyring does not implement on_decrypt function") diff --git a/src/aws_encryption_sdk/keyring/multi_keyring.py b/src/aws_encryption_sdk/keyring/multi_keyring.py new file mode 100644 index 000000000..f43820be5 --- /dev/null +++ b/src/aws_encryption_sdk/keyring/multi_keyring.py @@ -0,0 +1,105 @@ +# 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. +"""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.keyring.base import DecryptionMaterials, EncryptedDataKey, EncryptionMaterials, Keyring + +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 + + +@attr.s +class MultiKeyring(Keyring): + """Public class for Multi Keyring. + + :param generator: Generator keyring used to generate data encryption key (optional) + :type generator: Keyring + :param list children: List of keyrings used to encrypt the data encryption key (optional) + :raises EncryptKeyError: if encryption of data key fails for any reason + """ + + children = attr.ib( + default=attr.Factory(tuple), validator=optional(deep_iterable(member_validator=instance_of(Keyring))) + ) + generator = attr.ib(default=None, validator=optional(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 encryption_materials: Encryption materials for keyring to modify. + :type encryption_materials: aws_encryption_sdk.materials_managers.EncryptionMaterials + :returns: Optionally modified encryption materials. + :rtype: aws_encryption_sdk.materials_managers.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." + ) + + # Call on_encrypt on the generator keyring if it is provided + if self.generator is not None: + + encryption_materials = self.generator.on_encrypt(encryption_materials=encryption_materials) + + # Check if data key is generated + if encryption_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: + encryption_materials = keyring.on_encrypt(encryption_materials=encryption_materials) + + return encryption_materials + + def on_decrypt(self, decryption_materials, encrypted_data_keys): + # type: (DecryptionMaterials, Iterable[EncryptedDataKey]) -> DecryptionMaterials + """Attempt to decrypt the encrypted data keys. + + :param decryption_materials: Decryption materials for keyring to modify. + :type decryption_materials: aws_encryption_sdk.materials_managers.DecryptionMaterials + :param encrypted_data_keys: List of encrypted data keys. + :type: List of `aws_encryption_sdk.structures.EncryptedDataKey` + :returns: Optionally modified decryption materials. + :rtype: aws_encryption_sdk.materials_managers.DecryptionMaterials + """ + # Call on_decrypt on all keyrings till decryption is successful + for keyring in self._decryption_keyrings: + if decryption_materials.data_encryption_key is not None: + return decryption_materials + decryption_materials = keyring.on_decrypt( + decryption_materials=decryption_materials, encrypted_data_keys=encrypted_data_keys + ) + return decryption_materials diff --git a/src/aws_encryption_sdk/keyring/raw_keyring.py b/src/aws_encryption_sdk/keyring/raw_keyring.py new file mode 100644 index 000000000..513119246 --- /dev/null +++ b/src/aws_encryption_sdk/keyring/raw_keyring.py @@ -0,0 +1,436 @@ +# 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. +"""Resources required for Raw Keyrings.""" + +import logging +import os + +import attr +import six +from attr.validators import instance_of, optional +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 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.keyring.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 + +_LOGGER = logging.getLogger(__name__) + + +def _generate_data_key( + encryption_materials, # type: EncryptionMaterials + key_provider, # type: MasterKeyInfo +): + # type: (...) -> bytes + """Generates plaintext data key for the keyring. + + :param encryption_materials: Encryption materials for the keyring to modify. + :type encryption_materials: aws_encryption_sdk.materials_managers.EncryptionMaterials + :param key_provider: Information about the key in the keyring. + :type key_provider: MasterKeyInfo + :return bytes: Plaintext data 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.WRAPPING_KEY_GENERATED_DATA_KEY}) + + # plaintext_data_key to RawDataKey + data_encryption_key = RawDataKey(key_provider=key_provider, data_key=plaintext_data_key) + + # Add generated data key to encryption_materials + encryption_materials.add_data_encryption_key(data_encryption_key, keyring_trace) + + return plaintext_data_key + + +@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 + + :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. + :param wrapping_algorithm: Wrapping Algorithm with which to wrap plaintext data key. + :type wrapping_algorithm: WrappingAlgorithm + + .. 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)) + _wrapping_algorithm = attr.ib(repr=False, validator=instance_of(WrappingAlgorithm)) + + 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) + + 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 wrapping_key: Encryption key with which to wrap plaintext data key. + :type wrapping_key: WrappingKey + :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 encryption_materials: Encryption materials for the keyring to modify + :type encryption_materials: aws_encryption_sdk.materials_managers.EncryptionMaterials + :returns: Optionally modified encryption materials + :rtype: aws_encryption_sdk.materials_managers.EncryptionMaterials + """ + if encryption_materials.data_encryption_key is None: + _generate_data_key(encryption_materials=encryption_materials, key_provider=self._key_provider) + + try: + # Encrypt data key + encrypted_wrapped_key = self._wrapping_key_structure.encrypt( + plaintext_data_key=encryption_materials.data_encryption_key.data_key, + encryption_context=encryption_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) + return encryption_materials + + # Update Keyring Trace + keyring_trace = KeyringTrace( + wrapping_key=encrypted_data_key.key_provider, flags={KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY} + ) + + # Add encrypted data key to encryption_materials + encryption_materials.add_encrypted_data_key(encrypted_data_key=encrypted_data_key, keyring_trace=keyring_trace) + return encryption_materials + + def on_decrypt(self, decryption_materials, encrypted_data_keys): + # type: (DecryptionMaterials, Iterable[EncryptedDataKey]) -> DecryptionMaterials + """Attempt to decrypt the encrypted data keys. + + :param decryption_materials: Decryption materials for the keyring to modify + :type decryption_materials: aws_encryption_sdk.materials_managers.DecryptionMaterials + :param encrypted_data_keys: List of encrypted data keys + :type: List of `aws_encryption_sdk.structures.EncryptedDataKey` + :returns: Optionally modified decryption materials + :rtype: aws_encryption_sdk.materials_managers.DecryptionMaterials + """ + if decryption_materials.data_encryption_key is not None: + return decryption_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 decryption_materials.data_encryption_key is not None: + return decryption_materials + + 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=decryption_materials.encryption_context, + ) + + except Exception: # pylint: disable=broad-except + error_message = "Raw AES Keyring unable to decrypt data key" + _LOGGER.exception(error_message) + return decryption_materials + + # Create a keyring trace + keyring_trace = KeyringTrace( + wrapping_key=self._key_provider, flags={KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY} + ) + + # Update decryption materials + data_encryption_key = RawDataKey(key_provider=self._key_provider, data_key=plaintext_data_key) + decryption_materials.add_data_encryption_key(data_encryption_key, keyring_trace) + + return decryption_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 + + :param str key_namespace: String defining the keyring ID + :param bytes key_name: Key ID + :param private_wrapping_key: Private encryption key with which to wrap plaintext data key (optional) + :type private_wrapping_key: RSAPrivateKey + :param public_wrapping_key: Public encryption key with which to wrap plaintext data key (optional) + :type public_wrapping_key: RSAPublicKey + :param wrapping_algorithm: Wrapping Algorithm with which to wrap plaintext data key + :type wrapping_algorithm: WrappingAlgorithm + :param key_provider: Complete information about the key in the keyring + :type key_provider: MasterKeyInfo + + .. 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=instance_of(WrappingAlgorithm)) + _private_wrapping_key = attr.ib(default=None, repr=False, validator=optional(instance_of(rsa.RSAPrivateKey))) + _public_wrapping_key = attr.ib(default=None, repr=False, validator=optional(instance_of(rsa.RSAPublicKey))) + + def __attrs_post_init__(self): + # type: () -> None + """Prepares initial values not handled by attrs.""" + self._key_provider = MasterKeyInfo(provider_id=self.key_namespace, key_info=self.key_name) + + if self._public_wrapping_key is None and self._private_wrapping_key is None: + raise TypeError("At least one of public key or private key must be provided.") + + if self._private_wrapping_key is not None and self._public_wrapping_key is None: + self._public_wrapping_key = self._private_wrapping_key.public_key() + + @classmethod + def from_pem_encoding( + cls, + key_namespace, # type: str + key_name, # type: bytes + wrapping_algorithm, # type: WrappingAlgorithm + public_encoded_key=None, # type: bytes + private_encoded_key=None, # type: bytes + password=None, # type: bytes + ): + # type: (...) -> RawRSAKeyring + """Generate a Raw RSA keyring using PEM Encoded public and private keys + + :param str key_namespace: String defining the keyring ID + :param bytes key_name: Key ID + :param wrapping_algorithm: Wrapping Algorithm with which to wrap plaintext data key + :type wrapping_algorithm: WrappingAlgorithm + :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: Calls RawRSAKeyring class with 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 + ): + """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 wrapping_algorithm: Wrapping Algorithm with which to wrap plaintext data key + :type wrapping_algorithm: WrappingAlgorithm + :param bytes public_encoded_key: DER encoded public key (optional) + :param bytes private_encoded_key: DER encoded private key (optional) + :param password: Password to load private key (optional) + :return: Calls RawRSAKeyring class with 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 if not present and encrypt it using any available wrapping key. + + :param encryption_materials: Encryption materials for the keyring to modify. + :type encryption_materials: aws_encryption_sdk.materials_managers.EncryptionMaterials + :returns: Optionally modified encryption materials. + :rtype: aws_encryption_sdk.materials_managers.EncryptionMaterials + """ + if encryption_materials.data_encryption_key is None: + _generate_data_key(encryption_materials=encryption_materials, key_provider=self._key_provider) + + if self._public_wrapping_key is None: + return encryption_materials + + try: + # Encrypt data key + encrypted_wrapped_key = EncryptedData( + iv=None, + ciphertext=self._public_wrapping_key.encrypt( + plaintext=encryption_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) + return encryption_materials + + # Update Keyring Trace + keyring_trace = KeyringTrace( + wrapping_key=encrypted_data_key.key_provider, flags={KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY} + ) + + # Add encrypted data key to encryption_materials + encryption_materials.add_encrypted_data_key(encrypted_data_key=encrypted_data_key, keyring_trace=keyring_trace) + + return encryption_materials + + def on_decrypt(self, decryption_materials, encrypted_data_keys): + # type: (DecryptionMaterials, Iterable[EncryptedDataKey]) -> DecryptionMaterials + """Attempt to decrypt the encrypted data keys. + + :param decryption_materials: Decryption materials for the keyring to modify. + :type decryption_materials: aws_encryption_sdk.materials_managers.DecryptionMaterials + :param encrypted_data_keys: List of encrypted data keys. + :type: List of `aws_encryption_sdk.structures.EncryptedDataKey` + :returns: Optionally modified decryption materials. + :rtype: aws_encryption_sdk.materials_managers.DecryptionMaterials + """ + if self._private_wrapping_key is None: + return decryption_materials + + # Decrypt data key + for key in encrypted_data_keys: + if decryption_materials.data_encryption_key is not None: + return decryption_materials + 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) + continue + + # Create a keyring trace + keyring_trace = KeyringTrace( + wrapping_key=self._key_provider, flags={KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY} + ) + + # Update decryption materials + data_encryption_key = RawDataKey(key_provider=self._key_provider, data_key=plaintext_data_key) + decryption_materials.add_data_encryption_key(data_encryption_key, keyring_trace) + + return decryption_materials diff --git a/src/aws_encryption_sdk/materials_managers/__init__.py b/src/aws_encryption_sdk/materials_managers/__init__.py index bc5230c51..8a617fcc2 100644 --- a/src/aws_encryption_sdk/materials_managers/__init__.py +++ b/src/aws_encryption_sdk/materials_managers/__init__.py @@ -16,10 +16,25 @@ """ import attr import six +from attr.validators import deep_iterable, deep_mapping, instance_of, optional -from ..identifiers import Algorithm +<<<<<<< HEAD +from ..identifiers import AlgorithmSuite from ..internal.utils.streams import ROStream from ..structures import DataKey +======= +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, FrozenSet, 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 +>>>>>>> 2e85bfd3d42965b9972506c39371e971132196e0 @attr.s(hash=False) @@ -35,43 +50,313 @@ class EncryptionMaterialsRequest(object): :param int frame_length: Frame length to be used while encrypting stream :param plaintext_rostream: Source plaintext read-only stream (optional) :type plaintext_rostream: aws_encryption_sdk.internal.utils.streams.ROStream - :param algorithm: Algorithm passed to underlying master key provider and master keys (optional) - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite passed to underlying master key provider and master keys (optional) + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param int plaintext_length: Length of source plaintext (optional) """ - 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)) + 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)) + ) ) - algorithm = attr.ib(default=None, validator=attr.validators.optional(attr.validators.instance_of(Algorithm))) +<<<<<<< HEAD + algorithm = attr.ib(default=None, validator=attr.validators.optional(attr.validators.instance_of(AlgorithmSuite))) plaintext_length = attr.ib( default=None, validator=attr.validators.optional(attr.validators.instance_of(six.integer_types)) +======= + data_encryption_key = attr.ib( + default=None, validator=optional(instance_of(RawDataKey)), converter=_data_key_to_raw_data_key +>>>>>>> 2e85bfd3d42965b9972506c39371e971132196e0 + ) + _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 -@attr.s(hash=False) -class EncryptionMaterials(object): + 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. + + :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 _add_data_encryption_key(self, data_encryption_key, keyring_trace, required_flags): + # type: (Union[DataKey, RawDataKey], KeyringTrace, Iterable[KeyringTraceFlag]) -> None + """Add a plaintext data encryption key. + + :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 + ) + + data_key = _data_key_to_raw_data_key(data_key=data_encryption_key) + + super(CryptographicMaterials, self).__setattr__("data_encryption_key", data_key) + self._keyring_trace.append(keyring_trace) + + @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): """Encryption materials returned by a crypto material manager's `get_encryption_materials` method. .. versionadded:: 1.3.0 - :param algorithm: Algorithm to use for encrypting message - :type algorithm: aws_encryption_sdk.identifiers.Algorithm +<<<<<<< HEAD + :param algorithm: Algorithm suite to use for encrypting message + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :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` +======= + .. 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` +>>>>>>> 2e85bfd3d42965b9972506c39371e971132196e0 :param dict encryption_context: Encryption context tied to `encrypted_data_keys` - :param bytes signing_key: Encoded signing key + :param bytes signing_key: Encoded signing key (optional) + :param keyring_trace: Any KeyRing trace entries (optional) + :type keyring_trace: list of :class:`KeyringTrace` """ - algorithm = attr.ib(validator=attr.validators.instance_of(Algorithm)) +<<<<<<< HEAD + algorithm = attr.ib(validator=attr.validators.instance_of(AlgorithmSuite)) 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))) +======= + _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 is not None: + 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) + + @property + def encrypted_data_keys(self): + # type: () -> FrozenSet[EncryptedDataKey] + """Return a read-only version of the encrypted data keys. + + :rtype: frozenset + """ + return tuple(self._encrypted_data_keys) + + @property + def is_complete(self): + # type: () -> bool + """Determine whether these materials are sufficiently complete for use as decryption 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 add_data_encryption_key(self, data_encryption_key, keyring_trace): + # type: (Union[DataKey, RawDataKey], KeyringTrace) -> None + """Add a plaintext 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 + :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 + """ + self._add_data_encryption_key( + data_encryption_key=data_encryption_key, + keyring_trace=keyring_trace, + required_flags={KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY}, + ) + + def add_encrypted_data_key(self, encrypted_data_key, keyring_trace): + # type: (EncryptedDataKey, KeyringTrace) -> None + """Add an 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 + :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.WRAPPING_KEY_ENCRYPTED_DATA_KEY not in keyring_trace.flags: + raise InvalidKeyringTraceError("Keyring flags do not match action.") + + if keyring_trace.wrapping_key != encrypted_data_key.key_provider: + raise InvalidKeyringTraceError("Keyring trace does not match data key encryptor.") + + self._encrypted_data_keys.append(encrypted_data_key) + self._keyring_trace.append(keyring_trace) + + def add_signing_key(self, signing_key): + # type: (bytes) -> None + """Add a signing key. + + .. versionadded:: 1.5.0 + + :param bytes signing_key: Signing key + :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.") + + # Verify that the signing key matches the algorithm + Signer.from_key_bytes(algorithm=self.algorithm, key_bytes=signing_key) + + self._setattr("signing_key", signing_key) +>>>>>>> 2e85bfd3d42965b9972506c39371e971132196e0 @attr.s(hash=False) @@ -80,28 +365,139 @@ class DecryptionMaterialsRequest(object): .. versionadded:: 1.3.0 - :param algorithm: Algorithm to provide to master keys for underlying decrypt requests - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite to provide to master keys for underlying decrypt requests + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param encrypted_data_keys: Set of encrypted data keys :type encrypted_data_keys: set of `aws_encryption_sdk.structures.EncryptedDataKey` :param dict encryption_context: Encryption context to provide to master keys for underlying decrypt requests """ - algorithm = attr.ib(validator=attr.validators.instance_of(Algorithm)) +<<<<<<< HEAD + algorithm = attr.ib(validator=attr.validators.instance_of(AlgorithmSuite)) encrypted_data_keys = attr.ib(validator=attr.validators.instance_of(set)) encryption_context = attr.ib(validator=attr.validators.instance_of(dict)) +======= + 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) + ) + ) +>>>>>>> 2e85bfd3d42965b9972506c39371e971132196e0 -@attr.s(hash=False) -class DecryptionMaterials(object): +_DEFAULT_SENTINEL = object() + + +@attr.s(hash=False, init=False) +class DecryptionMaterials(CryptographicMaterials): """Decryption materials returned by a crypto material manager's `decrypt_materials` method. .. versionadded:: 1.3.0 - :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 + .. 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` """ - 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))) + 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) + + @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 add_data_encryption_key(self, data_encryption_key, keyring_trace): + # type: (Union[DataKey, RawDataKey], KeyringTrace) -> None + """Add a plaintext 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 + :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") + + self._add_data_encryption_key( + data_encryption_key=data_encryption_key, + keyring_trace=keyring_trace, + required_flags={KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY}, + ) + + def add_verification_key(self, verification_key): + # type: (bytes) -> None + """Add a verification key. + + .. versionadded:: 1.5.0 + + :param bytes verification_key: Verification key + """ + 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.") + + # Verify that the verification key matches the algorithm + Verifier.from_key_bytes(algorithm=self.algorithm, key_bytes=verification_key) + + self._setattr("verification_key", verification_key) diff --git a/src/aws_encryption_sdk/materials_managers/default.py b/src/aws_encryption_sdk/materials_managers/default.py index 6d10465a9..402a853ce 100644 --- a/src/aws_encryption_sdk/materials_managers/default.py +++ b/src/aws_encryption_sdk/materials_managers/default.py @@ -44,8 +44,8 @@ class DefaultCryptoMaterialsManager(CryptoMaterialsManager): def _generate_signing_key_and_update_encryption_context(self, algorithm, encryption_context): """Generates a signing key based on the provided algorithm. - :param algorithm: Algorithm for which to generate signing key - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite for which to generate signing key + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param dict encryption_context: Encryption context from request :returns: Signing key bytes :rtype: bytes or None @@ -104,8 +104,8 @@ def get_encryption_materials(self, 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. - :param algorithm: Algorithm for which to generate signing key - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite for which to generate signing key + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param dict encryption_context: Encryption context from request :returns: Raw verification key :rtype: bytes diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 90dc9d25c..b04750852 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -29,7 +29,7 @@ NotSupportedError, SerializationError, ) -from aws_encryption_sdk.identifiers import Algorithm, ContentType +from aws_encryption_sdk.identifiers import AlgorithmSuite, ContentType from aws_encryption_sdk.internal.crypto.authentication import Signer, Verifier from aws_encryption_sdk.internal.crypto.data_keys import derive_data_encryption_key from aws_encryption_sdk.internal.crypto.encryption import Decryptor, Encryptor, decrypt @@ -333,8 +333,8 @@ class EncryptorConfig(_ClientConfig): this is not enforced if a `key_provider` is provided. :param dict encryption_context: Dictionary defining encryption context - :param algorithm: Algorithm to use for encryption (optional) - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite to use for encryption (optional) + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param int frame_length: Frame length in bytes (optional) """ @@ -344,7 +344,7 @@ class EncryptorConfig(_ClientConfig): validator=attr.validators.instance_of(dict), ) algorithm = attr.ib( - hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(Algorithm)) + hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(AlgorithmSuite)) ) frame_length = attr.ib(hash=True, default=FRAME_LENGTH, validator=attr.validators.instance_of(six.integer_types)) @@ -384,8 +384,8 @@ class StreamEncryptor(_EncryptionStream): # pylint: disable=too-many-instance-a this is not enforced if a `key_provider` is provided. :param dict encryption_context: Dictionary defining encryption context - :param algorithm: Algorithm to use for encryption - :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param algorithm: Algorithm suite to use for encryption + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param int frame_length: Frame length in bytes """ diff --git a/src/aws_encryption_sdk/structures.py b/src/aws_encryption_sdk/structures.py index 8229d65fb..52086eb68 100644 --- a/src/aws_encryption_sdk/structures.py +++ b/src/aws_encryption_sdk/structures.py @@ -11,14 +11,18 @@ # 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 -import aws_encryption_sdk.identifiers +from aws_encryption_sdk.identifiers import Algorithm, ContentType, KeyringTraceFlag, ObjectType, SerializationVersion from aws_encryption_sdk.internal.str_ops import to_bytes, to_str @attr.s(hash=True) +<<<<<<< HEAD class MessageHeader(object): """Deserialized message header object. @@ -26,8 +30,8 @@ class MessageHeader(object): :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 algorithm: Algorithm suite to use for encryption + :type algorithm: aws_encryption_sdk.identifiers.AlgorithmSuite :param bytes message_id: Message ID :param dict encryption_context: Dictionary defining encryption context :param encrypted_data_keys: Encrypted data keys @@ -43,7 +47,7 @@ class MessageHeader(object): 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)) + algorithm = attr.ib(hash=True, validator=attr.validators.instance_of(aws_encryption_sdk.identifiers.AlgorithmSuite)) 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)) @@ -54,6 +58,8 @@ class MessageHeader(object): @attr.s(hash=True) +======= +>>>>>>> 2e85bfd3d42965b9972506c39371e971132196e0 class MasterKeyInfo(object): """Contains information necessary to identify a Master Key. @@ -61,8 +67,8 @@ class MasterKeyInfo(object): :param bytes key_info: MasterKey key_info value """ - 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) + 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) @attr.s(hash=True) @@ -74,8 +80,20 @@ class RawDataKey(object): :param bytes data_key: Plaintext 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)) + 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)) @attr.s(hash=True) @@ -88,9 +106,9 @@ class DataKey(object): :param bytes encrypted_data_key: Encrypted 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)) - encrypted_data_key = attr.ib(hash=True, validator=attr.validators.instance_of(bytes)) + 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)) @attr.s(hash=True) @@ -102,5 +120,72 @@ class EncryptedDataKey(object): :param bytes encrypted_data_key: Encrypted data 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)) + 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 flags: Actions performed + :type flags: set of :class:`KeyringTraceFlag` + """ + + 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): + """Deserialized message header object. + + :param version: Message format version, per spec + :type version: SerializationVersion + :param type: Message content type, per spec + :type type: ObjectType + :param algorithm: Algorithm to use for encryption + :type algorithm: 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: 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 + """ + + 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)) diff --git a/test/functional/test_f_aws_encryption_sdk_client.py b/test/functional/test_f_aws_encryption_sdk_client.py index fb19e868a..6e05ec8ee 100644 --- a/test/functional/test_f_aws_encryption_sdk_client.py +++ b/test/functional/test_f_aws_encryption_sdk_client.py @@ -30,7 +30,7 @@ from aws_encryption_sdk import KMSMasterKeyProvider from aws_encryption_sdk.caches import build_decryption_materials_cache_key, build_encryption_materials_cache_key from aws_encryption_sdk.exceptions import CustomMaximumValueExceeded -from aws_encryption_sdk.identifiers import Algorithm, EncryptionKeyType, WrappingAlgorithm +from aws_encryption_sdk.identifiers import AlgorithmSuite, EncryptionKeyType, WrappingAlgorithm 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 @@ -255,7 +255,7 @@ def test_no_infinite_encryption_cycle_on_empty_source(): def test_encrypt_load_header(): """Test that StreamEncryptor can extract header without reading plaintext.""" # Using a non-signed algorithm to simplify header size calculation - algorithm = aws_encryption_sdk.Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA256 + algorithm = aws_encryption_sdk.AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA256 key_provider = fake_kms_key_provider(algorithm.kdf_input_len) header_length = len(serialize_encryption_context(VALUES["encryption_context"])) header_length += 34 @@ -296,7 +296,7 @@ def test_encrypt_decrypt_header_only(): [ [frame_length, algorithm_suite, encryption_context] for frame_length in VALUES["frame_lengths"] - for algorithm_suite in Algorithm + for algorithm_suite in AlgorithmSuite for encryption_context in [{}, VALUES["encryption_context"]] ], ) @@ -374,7 +374,7 @@ def test_encryption_cycle_raw_mkp_openssl_102_plus(wrapping_algorithm, encryptio [ [frame_length, algorithm_suite, encryption_context] for frame_length in VALUES["frame_lengths"] - for algorithm_suite in Algorithm + for algorithm_suite in AlgorithmSuite for encryption_context in [{}, VALUES["encryption_context"]] ], ) @@ -399,7 +399,7 @@ def test_encryption_cycle_oneshot_kms(frame_length, algorithm, encryption_contex [ [frame_length, algorithm_suite, encryption_context] for frame_length in VALUES["frame_lengths"] - for algorithm_suite in Algorithm + for algorithm_suite in AlgorithmSuite for encryption_context in [{}, VALUES["encryption_context"]] ], ) @@ -446,7 +446,7 @@ def test_decrypt_legacy_provided_message(): def test_encryption_cycle_with_caching(): - algorithm = Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384 + algorithm = AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384 frame_length = 1024 key_provider = fake_kms_key_provider(algorithm.kdf_input_len) cache = aws_encryption_sdk.LocalCryptoMaterialsCache(capacity=10) diff --git a/test/functional/test_f_crypto.py b/test/functional/test_f_crypto.py index 9242deedd..9d71c040a 100644 --- a/test/functional/test_f_crypto.py +++ b/test/functional/test_f_crypto.py @@ -26,8 +26,8 @@ # Run several of each type to make get a high probability of forcing signature length correction @pytest.mark.parametrize( "algorithm", - [aws_encryption_sdk.Algorithm.AES_128_GCM_IV12_TAG16_HKDF_SHA256_ECDSA_P256 for i in range(10)] - + [aws_encryption_sdk.Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384 for i in range(10)], + [aws_encryption_sdk.AlgorithmSuite.AES_128_GCM_IV12_TAG16_HKDF_SHA256_ECDSA_P256 for i in range(10)] + + [aws_encryption_sdk.AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384 for i in range(10)], ) def test_ecc_static_length_signature(algorithm): private_key = ec.generate_private_key(curve=algorithm.signing_algorithm_info(), backend=default_backend()) @@ -44,9 +44,9 @@ def test_ecc_static_length_signature(algorithm): def test_signer_key_bytes_cycle(): key = ec.generate_private_key(curve=ec.SECP384R1, backend=default_backend()) - signer = Signer(algorithm=aws_encryption_sdk.Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, key=key) + signer = Signer(algorithm=aws_encryption_sdk.AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, key=key) key_bytes = signer.key_bytes() new_signer = Signer.from_key_bytes( - algorithm=aws_encryption_sdk.Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, key_bytes=key_bytes + algorithm=aws_encryption_sdk.AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, key_bytes=key_bytes ) assert new_signer.key.private_numbers().private_value == signer.key.private_numbers().private_value diff --git a/test/functional/test_f_keyring_raw_aes.py b/test/functional/test_f_keyring_raw_aes.py new file mode 100644 index 000000000..aa08b07ff --- /dev/null +++ b/test/functional/test_f_keyring_raw_aes.py @@ -0,0 +1,202 @@ +# 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.keyring.raw_keyring 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.WRAPPING_KEY_GENERATED_DATA_KEY}, + ) + ], + ), + ] + + +@pytest.mark.parametrize("encryption_materials_samples", sample_encryption_materials()) +@pytest.mark.parametrize("wrapping_algorithm_samples", _WRAPPING_ALGORITHM) +def test_raw_aes_encryption_decryption(encryption_materials_samples, wrapping_algorithm_samples): + + # Initializing attributes + key_namespace = _PROVIDER_ID + key_name = _KEY_ID + _wrapping_algorithm = wrapping_algorithm_samples + + # 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, + wrapping_algorithm=_wrapping_algorithm, + ) + + # 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()) +@pytest.mark.parametrize("wrapping_algorithm_samples", _WRAPPING_ALGORITHM) +def test_raw_master_key_decrypts_what_raw_keyring_encrypts(encryption_materials_samples, wrapping_algorithm_samples): + + # Initializing attributes + key_namespace = _PROVIDER_ID + key_name = _KEY_ID + _wrapping_algorithm = wrapping_algorithm_samples + + # 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, + wrapping_algorithm=_wrapping_algorithm, + ) + + # 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()) +@pytest.mark.parametrize("wrapping_algorithm_samples", _WRAPPING_ALGORITHM) +def test_raw_keyring_decrypts_what_raw_master_key_encrypts(encryption_materials_samples, wrapping_algorithm_samples): + + # Initializing attributes + key_namespace = _PROVIDER_ID + key_name = _KEY_ID + _wrapping_algorithm = wrapping_algorithm_samples + + # 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, + wrapping_algorithm=_wrapping_algorithm, + ) + + # 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/test_f_keyring_raw_rsa.py b/test/functional/test_f_keyring_raw_rsa.py new file mode 100644 index 000000000..bdf5bf25c --- /dev/null +++ b/test/functional/test_f_keyring_raw_rsa.py @@ -0,0 +1,297 @@ +# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Functional tests for Raw AES keyring encryption decryption path.""" + +import pytest +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa + +from aws_encryption_sdk.identifiers import ( + Algorithm, + EncryptionKeyType, + EncryptionType, + KeyringTraceFlag, + WrappingAlgorithm, +) +from aws_encryption_sdk.internal.crypto import WrappingKey +from aws_encryption_sdk.key_providers.raw import RawMasterKey +from aws_encryption_sdk.keyring.raw_keyring import RawRSAKeyring +from aws_encryption_sdk.materials_managers import DecryptionMaterials, EncryptionMaterials +from aws_encryption_sdk.structures import KeyringTrace, MasterKeyInfo, RawDataKey + +pytestmark = [pytest.mark.functional, pytest.mark.local] + +_ENCRYPTION_CONTEXT = {"encryption": "context", "values": "here"} +_PROVIDER_ID = "Random Raw Keys" +_KEY_ID = b"5325b043-5843-4629-869c-64794af77ada" +_WRAPPING_ALGORITHM = WrappingAlgorithm.RSA_OAEP_SHA256_MGF1 + +_PUBLIC_EXPONENT = 65537 +_KEY_SIZE = 2048 +_BACKEND = default_backend() + +_PRIVATE_WRAPPING_KEY = rsa.generate_private_key(public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND) + +_PRIVATE_WRAPPING_KEY_PEM = ( + b"-----BEGIN RSA PRIVATE KEY-----\n" + b"MIIEowIBAAKCAQEAo8uCyhiO4JUGZV+rtNq5DBA9Lm4xkw5kTA3v6EPybs8bVXL2\n" + b"ZE6jkbo+xT4Jg/bKzUpnp1fE+T1ruGPtsPdoEmhY/P64LDNIs3sRq5U4QV9IETU1\n" + b"vIcbNNkgGhRjV8J87YNY0tV0H7tuWuZRpqnS+gjV6V9lUMkbvjMCc5IBqQc3heut\n" + b"/+fH4JwpGlGxOVXI8QAapnSy1XpCr3+PT29kydVJnIMuAoFrurojRpOQbOuVvhtA\n" + b"gARhst1Ji4nfROGYkj6eZhvkz2Bkud4/+3lGvVU5LO1vD8oY7WoGtpin3h50VcWe\n" + b"aBT4kejx4s9/G9C4R24lTH09J9HO2UUsuCqZYQIDAQABAoIBAQCfC90bCk+qaWqF\n" + b"gymC+qOWwCn4bM28gswHQb1D5r6AtKBRD8mKywVvWs7azguFVV3Fi8sspkBA2FBC\n" + b"At5p6ULoJOTL/TauzLl6djVJTCMM701WUDm2r+ZOIctXJ5bzP4n5Q4I7b0NMEL7u\n" + b"ixib4elYGr5D1vrVQAKtZHCr8gmkqyx8Mz7wkJepzBP9EeVzETCHsmiQDd5WYlO1\n" + b"C2IQYgw6MJzgM4entJ0V/GPytkodblGY95ORVK7ZhyNtda+r5BZ6/jeMW+hA3VoK\n" + b"tHSWjHt06ueVCCieZIATmYzBNt+zEz5UA2l7ksg3eWfVORJQS7a6Ef4VvbJLM9Ca\n" + b"m1kdsjelAoGBANKgvRf39i3bSuvm5VoyJuqinSb/23IH3Zo7XOZ5G164vh49E9Cq\n" + b"dOXXVxox74ppj/kbGUoOk+AvaB48zzfzNvac0a7lRHExykPH2kVrI/NwH/1OcT/x\n" + b"2e2DnFYocXcb4gbdZQ+m6X3zkxOYcONRzPVW1uMrFTWHcJveMUm4PGx7AoGBAMcU\n" + b"IRvrT6ye5se0s27gHnPweV+3xjsNtXZcK82N7duXyHmNjxrwOAv0SOhUmTkRXArM\n" + b"6aN5D8vyZBSWma2TgUKwpQYFTI+4Sp7sdkkyojGAEixJ+c5TZJNxZFrUe0FwAoic\n" + b"c2kb7ntaiEj5G+qHvykJJro5hy6uLnjiMVbAiJDTAoGAKb67241EmHAXGEwp9sdr\n" + b"2SMjnIAnQSF39UKAthkYqJxa6elXDQtLoeYdGE7/V+J2K3wIdhoPiuY6b4vD0iX9\n" + b"JcGM+WntN7YTjX2FsC588JmvbWfnoDHR7HYiPR1E58N597xXdFOzgUgORVr4PMWQ\n" + b"pqtwaZO3X2WZlvrhr+e46hMCgYBfdIdrm6jYXFjL6RkgUNZJQUTxYGzsY+ZemlNm\n" + b"fGdQo7a8kePMRuKY2MkcnXPaqTg49YgRmjq4z8CtHokRcWjJUWnPOTs8rmEZUshk\n" + b"0KJ0mbQdCFt/Uv0mtXgpFTkEZ3DPkDTGcV4oR4CRfOCl0/EU/A5VvL/U4i/mRo7h\n" + b"ye+xgQKBgD58b+9z+PR5LAJm1tZHIwb4tnyczP28PzwknxFd2qylR4ZNgvAUqGtU\n" + b"xvpUDpzMioz6zUH9YV43YNtt+5Xnzkqj+u9Mr27/H2v9XPwORGfwQ5XPwRJz/2oC\n" + b"EnPmP1SZoY9lXKUpQXHXSpDZ2rE2Klt3RHMUMHt8Zpy36E8Vwx8o\n" + b"-----END RSA PRIVATE KEY-----\n" +) + +_RAW_RSA_PRIVATE_KEY_PEM_ENCODED_WITHOUT_PASSWORD = rsa.generate_private_key( + public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND +).private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), +) + +_RAW_RSA_PRIVATE_KEY_PEM_ENCODED_WITH_PASSWORD = rsa.generate_private_key( + public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND +).private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.BestAvailableEncryption(b"mypassword"), +) + +_RAW_RSA_PUBLIC_KEY_PEM_ENCODED = ( + rsa.generate_private_key(public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND) + .public_key() + .public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo) +) + +_RAW_RSA_PRIVATE_KEY_DER_ENCODED_WITHOUT_PASSWORD = rsa.generate_private_key( + public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND +).private_bytes( + encoding=serialization.Encoding.DER, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), +) + +_RAW_RSA_PRIVATE_KEY_DER_ENCODED_WITH_PASSWORD = rsa.generate_private_key( + public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND +).private_bytes( + encoding=serialization.Encoding.DER, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.BestAvailableEncryption(b"mypassword"), +) + +_RAW_RSA_PUBLIC_KEY_DER_ENCODED = ( + rsa.generate_private_key(public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND) + .public_key() + .public_bytes(encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo) +) + + +def sample_encryption_materials(): + return [ + EncryptionMaterials( + algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, encryption_context=_ENCRYPTION_CONTEXT + ), + EncryptionMaterials( + algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, + data_encryption_key=RawDataKey( + key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_KEY_ID), + data_key=b'*!\xa1"^-(\xf3\x105\x05i@B\xc2\xa2\xb7\xdd\xd5\xd5\xa9\xddm\xfae\xa8\\$\xf9d\x1e(', + ), + encryption_context=_ENCRYPTION_CONTEXT, + keyring_trace=[ + KeyringTrace( + wrapping_key=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_KEY_ID), + flags={KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY}, + ) + ], + ), + ] + + +def sample_raw_rsa_keyring_using_different_wrapping_algorithm(): + for alg in WrappingAlgorithm: + if alg.encryption_type is EncryptionType.ASYMMETRIC: + yield RawRSAKeyring( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=alg, + private_wrapping_key=_PRIVATE_WRAPPING_KEY, + ) + pem_and_der_encoded_raw_rsa_keyring = [ + RawRSAKeyring.from_pem_encoding( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + private_encoded_key=_RAW_RSA_PRIVATE_KEY_PEM_ENCODED_WITHOUT_PASSWORD, + wrapping_algorithm=_WRAPPING_ALGORITHM, + ), + RawRSAKeyring.from_pem_encoding( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + private_encoded_key=_RAW_RSA_PRIVATE_KEY_PEM_ENCODED_WITH_PASSWORD, + password=b"mypassword", + wrapping_algorithm=_WRAPPING_ALGORITHM, + ), + RawRSAKeyring.from_pem_encoding( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + public_encoded_key=_RAW_RSA_PUBLIC_KEY_PEM_ENCODED, + wrapping_algorithm=_WRAPPING_ALGORITHM, + ), + RawRSAKeyring.from_der_encoding( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + private_encoded_key=_RAW_RSA_PRIVATE_KEY_DER_ENCODED_WITHOUT_PASSWORD, + wrapping_algorithm=_WRAPPING_ALGORITHM, + ), + RawRSAKeyring.from_der_encoding( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + private_encoded_key=_RAW_RSA_PRIVATE_KEY_DER_ENCODED_WITH_PASSWORD, + password=b"mypassword", + wrapping_algorithm=_WRAPPING_ALGORITHM, + ), + RawRSAKeyring.from_der_encoding( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + public_encoded_key=_RAW_RSA_PUBLIC_KEY_DER_ENCODED, + password=b"mypassword", + wrapping_algorithm=_WRAPPING_ALGORITHM, + ), + ] + for keyring in pem_and_der_encoded_raw_rsa_keyring: + yield keyring + + +@pytest.mark.parametrize("encryption_materials_samples", sample_encryption_materials()) +@pytest.mark.parametrize("test_raw_rsa_keyring", sample_raw_rsa_keyring_using_different_wrapping_algorithm()) +def test_raw_rsa_encryption_decryption(encryption_materials_samples, test_raw_rsa_keyring): + + # Call on_encrypt function for the keyring + encryption_materials = test_raw_rsa_keyring.on_encrypt(encryption_materials=encryption_materials_samples) + + assert encryption_materials.encrypted_data_keys is not None + + # Generate decryption materials + decryption_materials = DecryptionMaterials( + algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, + verification_key=b"ex_verification_key", + encryption_context=_ENCRYPTION_CONTEXT, + ) + + # Call on_decrypt function for the keyring + decryption_materials = test_raw_rsa_keyring.on_decrypt( + decryption_materials=decryption_materials, encrypted_data_keys=encryption_materials.encrypted_data_keys + ) + + if test_raw_rsa_keyring._private_wrapping_key is not None: + # Check if the data keys match + assert encryption_materials.data_encryption_key.data_key == decryption_materials.data_encryption_key.data_key + + +@pytest.mark.parametrize("encryption_materials_samples", sample_encryption_materials()) +def test_raw_master_key_decrypts_what_raw_keyring_encrypts(encryption_materials_samples): + test_raw_rsa_keyring = RawRSAKeyring.from_pem_encoding( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=_WRAPPING_ALGORITHM, + private_encoded_key=_PRIVATE_WRAPPING_KEY_PEM, + ) + + # Creating an instance of a raw master key + test_raw_master_key = RawMasterKey( + key_id=_KEY_ID, + provider_id=_PROVIDER_ID, + wrapping_key=WrappingKey( + wrapping_algorithm=_WRAPPING_ALGORITHM, + wrapping_key=_PRIVATE_WRAPPING_KEY_PEM, + wrapping_key_type=EncryptionKeyType.PRIVATE, + ), + ) + + # Call on_encrypt function for the keyring + encryption_materials = test_raw_rsa_keyring.on_encrypt(encryption_materials=encryption_materials_samples) + + # Check if plaintext data key encrypted by raw keyring is decrypted by raw master key + raw_mkp_decrypted_data_key = test_raw_master_key.decrypt_data_key_from_list( + encrypted_data_keys=encryption_materials._encrypted_data_keys, + algorithm=encryption_materials.algorithm, + encryption_context=encryption_materials.encryption_context, + ).data_key + + assert encryption_materials.data_encryption_key.data_key == raw_mkp_decrypted_data_key + + +@pytest.mark.parametrize("encryption_materials_samples", sample_encryption_materials()) +def test_raw_keyring_decrypts_what_raw_master_key_encrypts(encryption_materials_samples): + + # Create instance of raw master key + test_raw_master_key = RawMasterKey( + key_id=_KEY_ID, + provider_id=_PROVIDER_ID, + wrapping_key=WrappingKey( + wrapping_algorithm=_WRAPPING_ALGORITHM, + wrapping_key=_PRIVATE_WRAPPING_KEY_PEM, + wrapping_key_type=EncryptionKeyType.PRIVATE, + ), + ) + + test_raw_rsa_keyring = RawRSAKeyring.from_pem_encoding( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=_WRAPPING_ALGORITHM, + private_encoded_key=_PRIVATE_WRAPPING_KEY_PEM, + ) + + raw_mkp_generated_data_key = test_raw_master_key.generate_data_key( + algorithm=encryption_materials_samples.algorithm, + encryption_context=encryption_materials_samples.encryption_context, + ) + + raw_mkp_encrypted_data_key = test_raw_master_key.encrypt_data_key( + data_key=raw_mkp_generated_data_key, + algorithm=encryption_materials_samples.algorithm, + encryption_context=encryption_materials_samples.encryption_context, + ) + + decryption_materials = test_raw_rsa_keyring.on_decrypt( + decryption_materials=DecryptionMaterials( + algorithm=encryption_materials_samples.algorithm, + encryption_context=encryption_materials_samples.encryption_context, + verification_key=b"ex_verification_key", + ), + encrypted_data_keys=[raw_mkp_encrypted_data_key], + ) + + assert raw_mkp_generated_data_key.data_key == decryption_materials.data_encryption_key.data_key diff --git a/test/functional/test_f_multi_keyring.py b/test/functional/test_f_multi_keyring.py new file mode 100644 index 000000000..54d519f0b --- /dev/null +++ b/test/functional/test_f_multi_keyring.py @@ -0,0 +1,132 @@ +# 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.keyring.multi_keyring import MultiKeyring +from aws_encryption_sdk.keyring.raw_keyring 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.WRAPPING_KEY_GENERATED_DATA_KEY}, + ) + ], +) + +_MULTI_KEYRING_WITH_GENERATOR_AND_CHILDREN = MultiKeyring( + generator=RawAESKeyring( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING, + wrapping_key=_WRAPPING_KEY_AES, + ), + children=[ + RawRSAKeyring( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, + private_wrapping_key=rsa.generate_private_key( + public_exponent=65537, key_size=2048, backend=default_backend() + ), + ), + RawRSAKeyring( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, + private_wrapping_key=rsa.generate_private_key( + public_exponent=65537, key_size=2048, backend=default_backend() + ), + ), + ], +) + +_MULTI_KEYRING_WITHOUT_CHILDREN = MultiKeyring( + generator=RawRSAKeyring( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, + private_wrapping_key=rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()), + ) +) + +_MULTI_KEYRING_WITHOUT_GENERATOR = MultiKeyring( + children=[ + RawRSAKeyring( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, + private_wrapping_key=rsa.generate_private_key( + public_exponent=65537, key_size=2048, backend=default_backend() + ), + ), + RawAESKeyring( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=WrappingAlgorithm.AES_128_GCM_IV12_TAG16_NO_PADDING, + 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_f_xcompat.py b/test/functional/test_f_xcompat.py index e87082503..d97835b4c 100644 --- a/test/functional/test_f_xcompat.py +++ b/test/functional/test_f_xcompat.py @@ -147,7 +147,7 @@ def _generate_test_cases(): # noqa=C901 # Collect test cases from ciphertext manifest for test_case in ciphertext_manifest["test_cases"]: key_ids = [] - algorithm = aws_encryption_sdk.Algorithm.get_by_id(int(test_case["algorithm"], 16)) + algorithm = aws_encryption_sdk.AlgorithmSuite.get_by_id(int(test_case["algorithm"], 16)) for key in test_case["master_keys"]: sys.stderr.write("XC:: " + json.dumps(key) + "\n") if key["provider_id"] == StaticStoredMasterKeyProvider.provider_id: diff --git a/test/integration/test_i_aws_encrytion_sdk_client.py b/test/integration/test_i_aws_encrytion_sdk_client.py index 56b0536fd..2f880c00e 100644 --- a/test/integration/test_i_aws_encrytion_sdk_client.py +++ b/test/integration/test_i_aws_encrytion_sdk_client.py @@ -18,7 +18,7 @@ from botocore.exceptions import BotoCoreError import aws_encryption_sdk -from aws_encryption_sdk.identifiers import USER_AGENT_SUFFIX, Algorithm +from aws_encryption_sdk.identifiers import USER_AGENT_SUFFIX, AlgorithmSuite from aws_encryption_sdk.key_providers.kms import KMSMasterKey, KMSMasterKeyProvider from .integration_test_utils import get_cmk_arn, setup_kms_master_key_provider @@ -44,7 +44,7 @@ def test_encrypt_verify_user_agent_kms_master_key_provider(caplog): 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={}) + mk.generate_data_key(algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, encryption_context={}) assert USER_AGENT_SUFFIX in caplog.text @@ -53,7 +53,7 @@ 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={}) + mk.generate_data_key(algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, encryption_context={}) assert USER_AGENT_SUFFIX in caplog.text @@ -61,11 +61,18 @@ def test_encrypt_verify_user_agent_kms_master_key(caplog): 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 + # I believe that because KMSMasterKeyProvider() sets a default regional client + # we want to test that the fake key was properly removed, instead of the dict (of regional clients) + # being empty. That is to say, after the first line of this test function + # the dict is NOT EMPTY, and this default first value will stay with us, so + # if we test for emptiness of the dict then we will get a non-passing test, when really + # it might be passing. The old line is commented out in case it matters later. + + # assert not test._regional_clients + assert "us-fakey-12" not in test._regional_clients class TestKMSThickClientIntegration(object): @@ -178,7 +185,7 @@ def test_encryption_cycle_aes_128_gcm_iv12_tag16_single_frame(self): key_provider=self.kms_master_key_provider, encryption_context=VALUES["encryption_context"], frame_length=1024, - algorithm=Algorithm.AES_128_GCM_IV12_TAG16, + algorithm=AlgorithmSuite.AES_128_GCM_IV12_TAG16, ) plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) assert plaintext == VALUES["plaintext_128"] @@ -193,7 +200,7 @@ def test_encryption_cycle_aes_128_gcm_iv12_tag16_non_framed(self): key_provider=self.kms_master_key_provider, encryption_context=VALUES["encryption_context"], frame_length=0, - algorithm=Algorithm.AES_128_GCM_IV12_TAG16, + algorithm=AlgorithmSuite.AES_128_GCM_IV12_TAG16, ) plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) assert plaintext == VALUES["plaintext_128"] @@ -208,7 +215,7 @@ def test_encryption_cycle_aes_192_gcm_iv12_tag16_single_frame(self): key_provider=self.kms_master_key_provider, encryption_context=VALUES["encryption_context"], frame_length=1024, - algorithm=Algorithm.AES_192_GCM_IV12_TAG16, + algorithm=AlgorithmSuite.AES_192_GCM_IV12_TAG16, ) plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) assert plaintext == VALUES["plaintext_128"] @@ -223,7 +230,7 @@ def test_encryption_cycle_aes_192_gcm_iv12_tag16_non_framed(self): key_provider=self.kms_master_key_provider, encryption_context=VALUES["encryption_context"], frame_length=0, - algorithm=Algorithm.AES_192_GCM_IV12_TAG16, + algorithm=AlgorithmSuite.AES_192_GCM_IV12_TAG16, ) plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) assert plaintext == VALUES["plaintext_128"] @@ -238,7 +245,7 @@ def test_encryption_cycle_aes_256_gcm_iv12_tag16_single_frame(self): key_provider=self.kms_master_key_provider, encryption_context=VALUES["encryption_context"], frame_length=1024, - algorithm=Algorithm.AES_256_GCM_IV12_TAG16, + algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16, ) plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) assert plaintext == VALUES["plaintext_128"] @@ -253,7 +260,7 @@ def test_encryption_cycle_aes_256_gcm_iv12_tag16_non_framed(self): key_provider=self.kms_master_key_provider, encryption_context=VALUES["encryption_context"], frame_length=0, - algorithm=Algorithm.AES_256_GCM_IV12_TAG16, + algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16, ) plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) assert plaintext == VALUES["plaintext_128"] @@ -268,7 +275,7 @@ def test_encryption_cycle_aes_128_gcm_iv12_tag16_hkdf_sha256_single_frame(self): key_provider=self.kms_master_key_provider, encryption_context=VALUES["encryption_context"], frame_length=1024, - algorithm=Algorithm.AES_128_GCM_IV12_TAG16_HKDF_SHA256, + algorithm=AlgorithmSuite.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"] @@ -283,7 +290,7 @@ def test_encryption_cycle_aes_128_gcm_iv12_tag16_hkdf_sha256_non_framed(self): key_provider=self.kms_master_key_provider, encryption_context=VALUES["encryption_context"], frame_length=0, - algorithm=Algorithm.AES_128_GCM_IV12_TAG16_HKDF_SHA256, + algorithm=AlgorithmSuite.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"] @@ -298,7 +305,7 @@ def test_encryption_cycle_aes_192_gcm_iv12_tag16_hkdf_sha256_single_frame(self): key_provider=self.kms_master_key_provider, encryption_context=VALUES["encryption_context"], frame_length=1024, - algorithm=Algorithm.AES_192_GCM_IV12_TAG16_HKDF_SHA256, + algorithm=AlgorithmSuite.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"] @@ -313,7 +320,7 @@ def test_encryption_cycle_aes_192_gcm_iv12_tag16_hkdf_sha256_non_framed(self): key_provider=self.kms_master_key_provider, encryption_context=VALUES["encryption_context"], frame_length=0, - algorithm=Algorithm.AES_192_GCM_IV12_TAG16_HKDF_SHA256, + algorithm=AlgorithmSuite.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"] @@ -328,7 +335,7 @@ def test_encryption_cycle_aes_256_gcm_iv12_tag16_hkdf_sha256_single_frame(self): key_provider=self.kms_master_key_provider, encryption_context=VALUES["encryption_context"], frame_length=1024, - algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA256, + algorithm=AlgorithmSuite.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"] @@ -343,7 +350,7 @@ def test_encryption_cycle_aes_256_gcm_iv12_tag16_hkdf_sha256_non_framed(self): key_provider=self.kms_master_key_provider, encryption_context=VALUES["encryption_context"], frame_length=0, - algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA256, + algorithm=AlgorithmSuite.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"] @@ -358,7 +365,7 @@ def test_encryption_cycle_aes_128_gcm_iv12_tag16_hkdf_sha256_ecdsa_p256_single_f 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, + algorithm=AlgorithmSuite.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"] @@ -373,7 +380,7 @@ def test_encryption_cycle_aes_128_gcm_iv12_tag16_hkdf_sha256_ecdsa_p256_non_fram 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, + algorithm=AlgorithmSuite.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"] @@ -388,7 +395,7 @@ def test_encryption_cycle_aes_192_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384_single_f 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, + algorithm=AlgorithmSuite.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"] @@ -403,7 +410,7 @@ def test_encryption_cycle_aes_192_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384_non_fram 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, + algorithm=AlgorithmSuite.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"] @@ -418,7 +425,7 @@ def test_encryption_cycle_aes_256_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384_single_f 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, + algorithm=AlgorithmSuite.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"] @@ -433,7 +440,7 @@ def test_encryption_cycle_aes_256_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384_non_fram 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, + algorithm=AlgorithmSuite.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/unit/test_caches.py b/test/unit/test_caches.py index 250ad6d5b..58c1b4944 100644 --- a/test/unit/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 DataKey, MasterKeyInfo +from aws_encryption_sdk.structures import EncryptedDataKey, MasterKeyInfo pytestmark = [pytest.mark.unit, pytest.mark.local] @@ -47,19 +47,17 @@ }, "encrypted_data_keys": [ { - "key": DataKey( + "key": EncryptedDataKey( 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": DataKey( + "key": EncryptedDataKey( 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/test_keyring_base.py b/test/unit/test_keyring_base.py new file mode 100644 index 000000000..70863de53 --- /dev/null +++ b/test/unit/test_keyring_base.py @@ -0,0 +1,45 @@ +# 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.keyring.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/test_keyring_multi.py b/test/unit/test_keyring_multi.py new file mode 100644 index 000000000..6b66a490f --- /dev/null +++ b/test/unit/test_keyring_multi.py @@ -0,0 +1,247 @@ +# 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.keyring.base import Keyring +from aws_encryption_sdk.keyring.multi_keyring import MultiKeyring +from aws_encryption_sdk.keyring.raw_keyring 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, + wrapping_algorithm=WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING, + ) + 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, + wrapping_algorithm=WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING, + ) + ] + 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_keyrings(): + 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]) + test_multi_keyring.on_encrypt(encryption_materials=get_encryption_materials_with_data_key()) + 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]) + test_multi_keyring.on_decrypt(decryption_materials=get_decryption_materials_with_data_key(), encrypted_data_keys=[]) + 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]) + test_multi_keyring.on_decrypt( + decryption_materials=get_decryption_materials_without_data_key(), encrypted_data_keys=[] + ) + 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/test_keyring_raw_aes.py b/test/unit/test_keyring_raw_aes.py new file mode 100644 index 000000000..b98279d04 --- /dev/null +++ b/test/unit/test_keyring_raw_aes.py @@ -0,0 +1,282 @@ +# 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 +from pytest_mock import mocker # noqa pylint: disable=unused-import + +import aws_encryption_sdk.key_providers.raw +import aws_encryption_sdk.keyring.raw_keyring +from aws_encryption_sdk.identifiers import Algorithm, KeyringTraceFlag, WrappingAlgorithm +from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey +from aws_encryption_sdk.keyring.base import Keyring +from aws_encryption_sdk.keyring.raw_keyring 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_algorithm=WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING, + wrapping_key=_WRAPPING_KEY, + ) + + +@pytest.fixture +def patch_generate_data_key(mocker): + mocker.patch.object(aws_encryption_sdk.keyring.raw_keyring, "_generate_data_key") + return aws_encryption_sdk.keyring.raw_keyring._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(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_algorithm=wrapping_algorithm, + wrapping_key=wrapping_key, + ) + + +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()) + + for keyring_trace in test.keyring_trace: + if keyring_trace.wrapping_key.key_info == _KEY_ID: + # Check keyring trace does not contain KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY + assert KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY not in keyring_trace.flags + + +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 + + generated_flag_count = 0 + encrypted_flag_count = 0 + + for keyring_trace in test.keyring_trace: + if ( + keyring_trace.wrapping_key.key_info == _KEY_ID + and KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY in keyring_trace.flags + ): + # Check keyring trace contains KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY + generated_flag_count += 1 + if KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY in keyring_trace.flags: + encrypted_flag_count += 1 + + assert generated_flag_count == 1 + + assert len(test.encrypted_data_keys) == original_number_of_encrypted_data_keys + 1 + + assert encrypted_flag_count == 1 + + +@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_keyring_trace_on_decrypt_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], + ) + for keyring_trace in test.keyring_trace: + if keyring_trace.wrapping_key.key_info == _KEY_ID: + # Check keyring trace does not contain KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY + assert KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY not in keyring_trace.flags + + +@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 + + for keyring_trace in test.keyring_trace: + if keyring_trace.wrapping_key.key_info == _KEY_ID: + assert KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY not in keyring_trace.flags + + 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_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], + ) + decrypted_flag_count = 0 + + for keyring_trace in test.keyring_trace: + if KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY in keyring_trace.flags: + decrypted_flag_count += 1 + + assert decrypted_flag_count == 1 + + +def test_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, + ) + _generate_data_key( + encryption_materials=encryption_materials_without_data_key, + key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_KEY_ID), + ) + + assert encryption_materials_without_data_key.data_encryption_key.key_provider.provider_id == _PROVIDER_ID + assert encryption_materials_without_data_key.data_encryption_key.key_provider.key_info == _KEY_ID + + generate_flag_count = 0 + + for keyring_trace in encryption_materials_without_data_key.keyring_trace: + if KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY in keyring_trace.flags: + generate_flag_count += 1 + assert generate_flag_count == 1 diff --git a/test/unit/test_keyring_raw_rsa.py b/test/unit/test_keyring_raw_rsa.py new file mode 100644 index 000000000..1fcf23da5 --- /dev/null +++ b/test/unit/test_keyring_raw_rsa.py @@ -0,0 +1,249 @@ +# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Unit tests for Raw AES keyring.""" + +import pytest +from cryptography.hazmat.primitives.asymmetric import rsa +from pytest_mock import mocker # noqa pylint: disable=unused-import + +import aws_encryption_sdk.key_providers.raw +import aws_encryption_sdk.keyring.raw_keyring +from aws_encryption_sdk.identifiers import KeyringTraceFlag, WrappingAlgorithm +from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey +from aws_encryption_sdk.keyring.base import Keyring +from aws_encryption_sdk.keyring.raw_keyring import RawRSAKeyring + +from .test_values import VALUES +from .unit_test_utils import ( + _BACKEND, + _DATA_KEY, + _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, +) + +pytestmark = [pytest.mark.unit, pytest.mark.local] + + +@pytest.fixture +def raw_rsa_keyring(): + return RawRSAKeyring.from_pem_encoding( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, + private_encoded_key=VALUES["private_rsa_key_bytes"][1], + ) + + +def raw_rsa_private_key(): + return rsa.generate_private_key(public_exponent=_PUBLIC_EXPONENT, key_size=_KEY_SIZE, backend=_BACKEND) + + +@pytest.fixture +def patch_generate_data_key(mocker): + mocker.patch.object(aws_encryption_sdk.keyring.raw_keyring, "_generate_data_key") + return aws_encryption_sdk.keyring.raw_keyring._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, + ) + + +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_keyring_trace_on_encrypt_when_data_encryption_key_given(raw_rsa_keyring): + test_raw_rsa_keyring = raw_rsa_keyring + + test = test_raw_rsa_keyring.on_encrypt(encryption_materials=get_encryption_materials_with_data_encryption_key()) + + for keyring_trace in test.keyring_trace: + if keyring_trace.wrapping_key.key_info == _KEY_ID: + # Check keyring trace does not contain KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY + assert KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY not in keyring_trace.flags + + +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()) + + # Check if data key is generated + assert test.data_encryption_key is not None + + generated_flag_count = 0 + encrypted_flag_count = 0 + + for keyring_trace in test.keyring_trace: + if ( + keyring_trace.wrapping_key.key_info == _KEY_ID + and KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY in keyring_trace.flags + ): + # Check keyring trace contains KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY + generated_flag_count += 1 + if KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY in keyring_trace.flags: + encrypted_flag_count += 1 + + assert generated_flag_count == 1 + + assert len(test.encrypted_data_keys) == original_number_of_encrypted_data_keys + 1 + + assert encrypted_flag_count == 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_keyring_trace_on_decrypt_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], + ) + for keyring_trace in test.keyring_trace: + if keyring_trace.wrapping_key.key_info == _KEY_ID: + # Check keyring trace does not contain KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY + assert KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY not in keyring_trace.flags + + +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 + + for keyring_trace in test.keyring_trace: + assert KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY not in keyring_trace.flags + + 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 + + for keyring_trace in test.keyring_trace: + if keyring_trace.wrapping_key.key_info == _KEY_ID: + assert KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY not in keyring_trace.flags + + 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_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, + ) + decrypted_flag_count = 0 + + for keyring_trace in test.keyring_trace: + if KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY in keyring_trace.flags: + decrypted_flag_count += 1 + + assert decrypted_flag_count == 1 + assert test.data_encryption_key is not None diff --git a/test/unit/test_material_managers.py b/test/unit/test_material_managers.py index fcd4977f5..975e9ffda 100644 --- a/test/unit/test_material_managers.py +++ b/test/unit/test_material_managers.py @@ -11,71 +11,129 @@ # 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 pytest_mock import mocker # noqa pylint: disable=unused-import -from aws_encryption_sdk.identifiers import Algorithm +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 +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.WRAPPING_KEY_GENERATED_DATA_KEY}, + ) + ], + ), "EncryptionMaterialsRequest": dict( encryption_context={}, plaintext_rostream=MagicMock(__class__=ROStream), frame_length=5, - algorithm=MagicMock(__class__=Algorithm), + algorithm=ALGORITHM, plaintext_length=5, ), "EncryptionMaterials": dict( - algorithm=MagicMock(__class__=Algorithm), - data_encryption_key=MagicMock(__class__=DataKey), - encrypted_data_keys=set([]), + algorithm=ALGORITHM, + data_encryption_key=_DATA_KEY, + encrypted_data_keys=[], encryption_context={}, - signing_key=b"", + signing_key=_SIGNING_KEY.key_bytes(), ), - "DecryptionMaterialsRequest": dict( - algorithm=MagicMock(__class__=Algorithm), encrypted_data_keys=set([]), encryption_context={} + "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={} ), - "DecryptionMaterials": dict(data_key=MagicMock(__class__=DataKey), verification_key=b"ex_verification_key"), } +_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(data_encryption_key=None)), - (EncryptionMaterials, dict(encrypted_data_keys=None)), (EncryptionMaterials, dict(encryption_context=None)), (EncryptionMaterials, dict(signing_key=u"not bytes or None")), + (EncryptionMaterials, dict(data_encryption_key=_REMOVE)), (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)), + (DecryptionMaterials, dict(data_key=_DATA_KEY, data_encryption_key=_DATA_KEY)), ), ) def test_attributes_fails(attr_class, invalid_kwargs): - kwargs = _VALID_KWARGS[attr_class.__name__].copy() - kwargs.update(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, {}), + (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 @@ -85,14 +143,337 @@ def test_encryption_materials_request_attributes_defaults(): def test_encryption_materials_defaults(): test = EncryptionMaterials( - algorithm=MagicMock(__class__=Algorithm), - data_encryption_key=MagicMock(__class__=DataKey), - encrypted_data_keys=set([]), - encryption_context={}, + 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=MagicMock(__class__=DataKey)) + 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.WRAPPING_KEY_GENERATED_DATA_KEY), + (DecryptionMaterials, KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY), + ), +) +def test_add_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) + + materials.add_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}), + ) + + +def _add_data_encryption_key_test_cases(): + for material_class, required_flags in ( + (EncryptionMaterials, KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY), + (DecryptionMaterials, KeyringTraceFlag.WRAPPING_KEY_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_add_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.add_data_encryption_key(data_encryption_key=data_encryption_key, keyring_trace=keyring_trace) + + excinfo.match(exception_message) + + +def test_add_encrypted_data_key_success(): + kwargs = _copy_and_update_kwargs("EncryptionMaterials", {}) + materials = EncryptionMaterials(**kwargs) + + materials.add_encrypted_data_key( + _ENCRYPTED_DATA_KEY, + keyring_trace=KeyringTrace( + wrapping_key=_ENCRYPTED_DATA_KEY.key_provider, flags={KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY} + ), + ) + + +@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.WRAPPING_KEY_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.WRAPPING_KEY_ENCRYPTED_DATA_KEY} + ), + AttributeError, + "Data encryption key is not set.", + ), + ), +) +def test_add_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.add_encrypted_data_key(encrypted_data_key=encrypted_data_key, keyring_trace=keyring_trace) + + excinfo.match(exception_message) + + +def test_add_signing_key_success(): + kwargs = _copy_and_update_kwargs("EncryptionMaterials", dict(signing_key=_REMOVE)) + materials = EncryptionMaterials(**kwargs) + + materials.add_signing_key(signing_key=_SIGNING_KEY.key_bytes()) + + +@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_add_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.add_signing_key(signing_key=signing_key) + + excinfo.match(exception_message) + + +def test_add_verification_key_success(): + kwargs = _copy_and_update_kwargs("DecryptionMaterials", dict(verification_key=_REMOVE)) + materials = DecryptionMaterials(**kwargs) + + materials.add_verification_key(verification_key=_VERIFICATION_KEY.key_bytes()) + + +@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_add_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.add_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", {})) + + assert materials.is_complete + + +@pytest.mark.parametrize( + "mod_kwargs", + ( + dict(data_encryption_key=_REMOVE, encrypted_data_keys=_REMOVE), + 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/test_material_managers_default.py b/test/unit/test_material_managers_default.py index 9d6bd949f..32fdc953a 100644 --- a/test/unit/test_material_managers_default.py +++ b/test/unit/test_material_managers_default.py @@ -22,10 +22,17 @@ from aws_encryption_sdk.key_providers.base import MasterKeyProvider from aws_encryption_sdk.materials_managers import EncryptionMaterials from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager -from aws_encryption_sdk.structures import DataKey +from aws_encryption_sdk.structures import DataKey, EncryptedDataKey, 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", +) +_ENCRYPTED_DATA_KEY = EncryptedDataKey.from_data_key(_DATA_KEY) + @pytest.fixture def patch_for_dcmm_encrypt(mocker): @@ -33,8 +40,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 = MagicMock(__class__=DataKey) - mock_encrypted_data_keys = set([mock_data_encryption_key]) + mock_data_encryption_key = _DATA_KEY + mock_encrypted_data_keys = (_ENCRYPTED_DATA_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 @@ -50,7 +57,7 @@ def patch_for_dcmm_decrypt(mocker): def build_cmm(): mock_mkp = MagicMock(__class__=MasterKeyProvider) - mock_mkp.decrypt_data_key_from_list.return_value = MagicMock(__class__=DataKey) + mock_mkp.decrypt_data_key_from_list.return_value = _DATA_KEY mock_mkp.master_keys_for_encryption.return_value = ( sentinel.primary_mk, set([sentinel.primary_mk, sentinel.mk_a, sentinel.mk_b]), @@ -127,8 +134,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 is patch_for_dcmm_encrypt[0][0] - assert test.encrypted_data_keys is patch_for_dcmm_encrypt[0][1] + 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.encryption_context == encryption_context assert test.signing_key == patch_for_dcmm_encrypt[1] @@ -158,7 +165,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, - set([sentinel.mk_a, sentinel.mk_b]), + {sentinel.mk_a, sentinel.mk_b}, ) with pytest.raises(MasterKeyProviderError) as excinfo: @@ -232,5 +239,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 is cmm.master_key_provider.decrypt_data_key_from_list.return_value + assert test.data_key == RawDataKey.from_data_key(cmm.master_key_provider.decrypt_data_key_from_list.return_value) assert test.verification_key == patch_for_dcmm_decrypt diff --git a/test/unit/test_streaming_client_stream_encryptor.py b/test/unit/test_streaming_client_stream_encryptor.py index 501214e9f..5cb2b8e37 100644 --- a/test/unit/test_streaming_client_stream_encryptor.py +++ b/test/unit/test_streaming_client_stream_encryptor.py @@ -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: sentinel.decoded_bytes} + test_encryption_context = {aws_encryption_sdk.internal.defaults.ENCODED_SIGNER_KEY: "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 1a9caa01d..e1070c574 100644 --- a/test/unit/test_structures.py +++ b/test/unit/test_structures.py @@ -107,3 +107,30 @@ 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 *") diff --git a/test/unit/test_utils.py b/test/unit/test_utils.py index b1374a09d..0dee36b41 100644 --- a/test/unit/test_utils.py +++ b/test/unit/test_utils.py @@ -21,7 +21,8 @@ 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.structures import DataKey, EncryptedDataKey, MasterKeyInfo, RawDataKey +from aws_encryption_sdk.keyring.base import EncryptedDataKey +from aws_encryption_sdk.structures import DataKey, MasterKeyInfo, RawDataKey from .test_values import VALUES from .unit_test_utils import assert_prepped_stream_identity diff --git a/test/unit/test_values.py b/test/unit/test_values.py index 26eff1341..b9aba0c02 100644 --- a/test/unit/test_values.py +++ b/test/unit/test_values.py @@ -187,6 +187,55 @@ 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 6b0a84bdc..1e5d073e8 100644 --- a/test/unit/unit_test_utils.py +++ b/test/unit/unit_test_utils.py @@ -14,8 +14,316 @@ import copy import io import itertools +import os +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.asymmetric import rsa + +from aws_encryption_sdk.identifiers import Algorithm, KeyringTraceFlag, WrappingAlgorithm from aws_encryption_sdk.internal.utils.streams import InsistentReaderBytesIO +from aws_encryption_sdk.keyring.base import Keyring +from aws_encryption_sdk.keyring.multi_keyring import MultiKeyring +from aws_encryption_sdk.keyring.raw_keyring 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 Iterable # 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" +_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.add_data_encryption_key( + data_encryption_key=data_encryption_key, + keyring_trace=KeyringTrace( + wrapping_key=key_provider, flags={KeyringTraceFlag.WRAPPING_KEY_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=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.WRAPPING_KEY_GENERATED_DATA_KEY}, + ) + ], + ) + + +def get_encryption_materials_with_data_encryption_key(): + return 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=b"5430b043-5843-4629-869c-64794af77ada"), + 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=b"5430b043-5843-4629-869c-64794af77ada"), + flags={KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY}, + ) + ], + ) + + +def get_encryption_materials_without_data_key(): + return EncryptionMaterials( + algorithm=Algorithm.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=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(', + ), + encrypted_data_keys=[ + EncryptedDataKey( + key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_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=_KEY_ID), + flags={ + KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY, + KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY, + }, + ) + ], + ) + + +def get_encryption_materials_with_encrypted_data_key_aes(): + return 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(', + ), + 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=_KEY_ID), + flags={ + KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY, + KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY, + }, + ) + ], + ) + + +def get_encryption_materials_without_data_encryption_key(): + return EncryptionMaterials( + algorithm=Algorithm.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=Algorithm.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=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, + verification_key=b"ex_verification_key", + keyring_trace=[ + KeyringTrace( + wrapping_key=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=_KEY_ID), + flags={KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY}, + ) + ], + ) + + +def get_decryption_materials_with_data_encryption_key(): + return DecryptionMaterials( + algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, + data_encryption_key=RawDataKey( + key_provider=MasterKeyInfo(provider_id=_PROVIDER_ID, key_info=b"5430b043-5843-4629-869c-64794af77ada"), + 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=b"5430b043-5843-4629-869c-64794af77ada"), + flags={KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY}, + ) + ], + ) + + +def get_decryption_materials_without_data_key(): + return DecryptionMaterials(encryption_context=_ENCRYPTION_CONTEXT, verification_key=b"ex_verification_key") + + +def get_multi_keyring_with_generator_and_children(): + return MultiKeyring( + generator=RawAESKeyring( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING, + wrapping_key=_WRAPPING_KEY_AES, + ), + children=[ + RawRSAKeyring( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, + private_wrapping_key=rsa.generate_private_key( + public_exponent=65537, key_size=2048, backend=default_backend() + ), + ), + RawRSAKeyring( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, + private_wrapping_key=rsa.generate_private_key( + public_exponent=65537, key_size=2048, backend=default_backend() + ), + ), + ], + ) + + +def get_multi_keyring_with_no_children(): + return MultiKeyring( + generator=RawRSAKeyring( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, + private_wrapping_key=rsa.generate_private_key( + public_exponent=65537, key_size=2048, backend=default_backend() + ), + ) + ) + + +def get_multi_keyring_with_no_generator(): + return MultiKeyring( + children=[ + RawRSAKeyring( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, + private_wrapping_key=rsa.generate_private_key( + public_exponent=65537, key_size=2048, backend=default_backend() + ), + ), + RawAESKeyring( + key_namespace=_PROVIDER_ID, + key_name=_KEY_ID, + wrapping_algorithm=WrappingAlgorithm.AES_128_GCM_IV12_TAG16_NO_PADDING, + wrapping_key=_WRAPPING_KEY_AES, + ), + ] + ) def all_valid_kwargs(valid_kwargs): diff --git a/tox.ini b/tox.ini index 06564ef6a..fd1e6a6d7 100644 --- a/tox.ini +++ b/tox.ini @@ -117,7 +117,11 @@ basepython = python3 deps = flake8 flake8-docstrings +<<<<<<< HEAD pydocstyle<4.0.0 +======= + pydocstyle < 4.0.0 +>>>>>>> 2e85bfd3d42965b9972506c39371e971132196e0 # https://github.com/JBKahn/flake8-print/pull/30 flake8-print>=3.1.0 flake8-bugbear @@ -252,7 +256,7 @@ commands = python setup.py check -r -s [testenv:bandit] basepython = python3 -deps = +deps = bandit>=1.5.1 commands = bandit -r src/aws_encryption_sdk/