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/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/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_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/unit_test_utils.py b/test/unit/unit_test_utils.py index 6b0a84bdc..087c8dfa5 100644 --- a/test/unit/unit_test_utils.py +++ b/test/unit/unit_test_utils.py @@ -15,7 +15,150 @@ import io import itertools +from cryptography.hazmat.backends import default_backend + +from aws_encryption_sdk.identifiers import Algorithm, KeyringTraceFlag from aws_encryption_sdk.internal.utils.streams import InsistentReaderBytesIO +from aws_encryption_sdk.keyring.base import EncryptedDataKey, Keyring +from aws_encryption_sdk.materials_managers import DecryptionMaterials, EncryptionMaterials +from aws_encryption_sdk.structures import 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" +) + +_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 + + +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_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_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 all_valid_kwargs(valid_kwargs):