def test_wrapping_key_encrypt_symmetric(
    patch_default_backend,
    patch_serialization,
    patch_serialize_encryption_context,
    patch_derive_data_encryption_key,
    patch_encrypt,
    patch_urandom,
):
    wrapping_algorithm = MagicMock()
    wrapping_key = MagicMock()
    test_wrapping_key = WrappingKey(
        wrapping_algorithm=wrapping_algorithm,
        wrapping_key=wrapping_key,
        wrapping_key_type=EncryptionKeyType.SYMMETRIC)

    test = test_wrapping_key.encrypt(
        plaintext_data_key=sentinel.plaintext_data_key,
        encryption_context=sentinel.encryption_context)

    assert not patch_serialization.load_pem_private_key.called
    assert not patch_serialization.load_pem_public_key.called
    patch_serialize_encryption_context.assert_called_once_with(
        encryption_context=sentinel.encryption_context)
    patch_urandom.assert_called_once_with(wrapping_algorithm.algorithm.iv_len)
    patch_encrypt.assert_called_once_with(
        algorithm=wrapping_algorithm.algorithm,
        key=patch_derive_data_encryption_key.return_value,
        plaintext=sentinel.plaintext_data_key,
        associated_data=patch_serialize_encryption_context.return_value,
        iv=patch_urandom.return_value,
    )
    assert test is patch_encrypt.return_value
    def __attrs_post_init__(self):
        # type: () -> None
        """Prepares initial values not handled by attrs."""
        key_size_to_wrapping_algorithm = {
            wrapper.algorithm.kdf_input_len: wrapper
            for wrapper in (
                WrappingAlgorithm.AES_128_GCM_IV12_TAG16_NO_PADDING,
                WrappingAlgorithm.AES_192_GCM_IV12_TAG16_NO_PADDING,
                WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING,
            )
        }

        try:
            self._wrapping_algorithm = key_size_to_wrapping_algorithm[len(
                self._wrapping_key)]
        except KeyError:
            raise ValueError(
                "Invalid wrapping key length. Must be one of {} bytes.".format(
                    sorted(key_size_to_wrapping_algorithm.keys())))

        self._key_provider = MasterKeyInfo(provider_id=self.key_namespace,
                                           key_info=self.key_name)

        self._wrapping_key_structure = WrappingKey(
            wrapping_algorithm=self._wrapping_algorithm,
            wrapping_key=self._wrapping_key,
            wrapping_key_type=EncryptionKeyType.SYMMETRIC,
        )

        self._key_info_prefix = self._get_key_info_prefix(
            key_namespace=self.key_namespace,
            key_name=self.key_name,
            wrapping_key=self._wrapping_key_structure)
def test_wrapping_key_encrypt_public(patch_default_backend,
                                     patch_serialization,
                                     patch_serialize_encryption_context,
                                     patch_encrypt):
    _, public_key = mock_wrapping_rsa_keys()
    patch_serialization.load_pem_public_key.return_value = public_key
    public_key.encrypt.return_value = VALUES["ciphertext"]
    mock_wrapping_algorithm = MagicMock(
        encryption_type=EncryptionType.ASYMMETRIC)
    test_wrapping_key = WrappingKey(
        wrapping_algorithm=mock_wrapping_algorithm,
        wrapping_key=sentinel.wrapping_key,
        wrapping_key_type=EncryptionKeyType.PUBLIC,
    )
    test = test_wrapping_key.encrypt(
        plaintext_data_key=sentinel.plaintext_data_key,
        encryption_context=sentinel.encryption_context)
    public_key.encrypt.assert_called_once_with(
        plaintext=sentinel.plaintext_data_key,
        padding=mock_wrapping_algorithm.padding)
    assert not patch_serialize_encryption_context.called
    assert not patch_encrypt.called
    assert test == EncryptedData(iv=None,
                                 ciphertext=VALUES["ciphertext"],
                                 tag=None)
def test_wrapping_key_decrypt_symmetric(
    patch_default_backend,
    patch_serialization,
    patch_serialize_encryption_context,
    patch_derive_data_encryption_key,
    patch_decrypt,
):
    mock_wrapping_algorithm = MagicMock()
    test_wrapping_key = WrappingKey(
        wrapping_algorithm=mock_wrapping_algorithm,
        wrapping_key=sentinel.wrapping_key,
        wrapping_key_type=EncryptionKeyType.SYMMETRIC,
    )
    test = test_wrapping_key.decrypt(
        encrypted_wrapped_data_key=VALUES["ciphertext"],
        encryption_context=sentinel.encryption_context)
    patch_serialize_encryption_context.assert_called_once_with(
        encryption_context=sentinel.encryption_context)
    patch_decrypt.assert_called_once_with(
        algorithm=mock_wrapping_algorithm.algorithm,
        key=patch_derive_data_encryption_key.return_value,
        encrypted_data=VALUES["ciphertext"],
        associated_data=patch_serialize_encryption_context.return_value,
    )
    assert test is patch_decrypt.return_value
def raw_rsa_mkps_from_keyring(keyring):
    # type: (RawRSAKeyring) -> (MasterKeyProvider, MasterKeyProvider)
    """Constructs a private and public raw RSA MKP using the private key in the raw RSA keyring."""
    private_key = keyring._private_wrapping_key
    private_pem = private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption(),
    )
    public_pem = private_key.public_key().public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo)
    private_key_mkp = RawMasterKey(
        provider_id=keyring.key_namespace,
        key_id=keyring.key_name,
        wrapping_key=WrappingKey(
            wrapping_algorithm=keyring._wrapping_algorithm,
            wrapping_key=private_pem,
            wrapping_key_type=EncryptionKeyType.PRIVATE,
        ),
    )
    public_key_mkp = RawMasterKey(
        provider_id=keyring.key_namespace,
        key_id=keyring.key_name,
        wrapping_key=WrappingKey(
            wrapping_algorithm=keyring._wrapping_algorithm,
            wrapping_key=public_pem,
            wrapping_key_type=EncryptionKeyType.PUBLIC,
        ),
    )
    return private_key_mkp, public_key_mkp
Example #6
0
def test_wrapping_key_decrypt_private(
        patch_default_backend,
        patch_serialization,
        patch_serialize_encryption_context,
        patch_decrypt
):
    private_key, _ = mock_wrapping_rsa_keys()
    patch_serialization.load_pem_private_key.return_value = private_key
    private_key.decrypt.return_value = sentinel.plaintext_data
    mock_wrapping_algorithm = MagicMock(encryption_type=EncryptionType.ASYMMETRIC)
    test_wrapping_key = WrappingKey(
        wrapping_algorithm=mock_wrapping_algorithm,
        wrapping_key=sentinel.wrapping_key,
        wrapping_key_type=EncryptionKeyType.PRIVATE
    )
    test = test_wrapping_key.decrypt(
        encrypted_wrapped_data_key=mock_encrypted_data(),
        encryption_context=sentinel.encryption_context
    )
    private_key.decrypt.assert_called_once_with(
        ciphertext=VALUES['ciphertext'],
        padding=mock_wrapping_algorithm.padding
    )
    assert not patch_serialize_encryption_context.called
    assert not patch_decrypt.called
    assert test is sentinel.plaintext_data
Example #7
0
 def _get_raw_key(self, key_id) -> WrappingKey:
     return WrappingKey(
         wrapping_algorithm=WrappingAlgorithm.
         AES_256_GCM_IV12_TAG16_NO_PADDING,
         wrapping_key=urandom(32),
         wrapping_key_type=EncryptionKeyType.SYMMETRIC,
     )
def test_wrapping_key_init_invalid_key_type():
    with pytest.raises(InvalidDataKeyError) as excinfo:
        WrappingKey(wrapping_algorithm=MagicMock(),
                    wrapping_key=MagicMock(),
                    wrapping_key_type=sentinel.key_type)

    excinfo.match(r"Invalid wrapping_key_type: *")
Example #9
0
 def _get_raw_key(self, key_id):
     wrapping_key = VALUES['raw'][key_id][self.config.encryption_key_type]
     if key_id == b'sym1':
         wrapping_key = wrapping_key[:self.config.wrapping_algorithm.
                                     algorithm.data_key_len]
     return WrappingKey(wrapping_algorithm=self.config.wrapping_algorithm,
                        wrapping_key=wrapping_key,
                        wrapping_key_type=self.config.encryption_key_type)
def test_wrapping_key_decrypt_public(patch_default_backend,
                                     patch_serialization,
                                     patch_serialize_encryption_context,
                                     patch_decrypt):
    private_key, _ = mock_wrapping_rsa_keys()
    patch_serialization.load_pem_private_key.return_value = private_key
    private_key.decrypt.return_value = sentinel.plaintext_data
    mock_wrapping_algorithm = MagicMock(
        encryption_type=EncryptionType.ASYMMETRIC)
    test_wrapping_key = WrappingKey(wrapping_algorithm=mock_wrapping_algorithm,
                                    wrapping_key=sentinel.wrapping_key,
                                    wrapping_key_type=EncryptionKeyType.PUBLIC)
    with pytest.raises(IncorrectMasterKeyError) as excinfo:
        test_wrapping_key.decrypt(
            encrypted_wrapped_data_key=mock_encrypted_data(),
            encryption_context=sentinel.encryption_context)

    excinfo.match(r'Public key cannot decrypt')
def test_wrapping_key_init_public(patch_default_backend, patch_serialization):
    wrapping_algorithm = MagicMock()
    wrapping_key = MagicMock()
    test_wrapping_key = WrappingKey(wrapping_algorithm=wrapping_algorithm,
                                    wrapping_key=wrapping_key,
                                    wrapping_key_type=EncryptionKeyType.PUBLIC)
    patch_serialization.load_pem_public_key.assert_called_once_with(
        data=wrapping_key, backend=patch_default_backend.return_value)
    assert not patch_serialization.load_pem_private_key.called
    assert test_wrapping_key._wrapping_key is patch_serialization.load_pem_public_key.return_value
def test_wrapping_key_init_private_with_password(patch_default_backend,
                                                 patch_serialization):
    wrapping_algorithm = MagicMock()
    wrapping_key = MagicMock()
    WrappingKey(wrapping_algorithm=wrapping_algorithm,
                wrapping_key=wrapping_key,
                wrapping_key_type=EncryptionKeyType.PRIVATE,
                password=sentinel.password)
    patch_serialization.load_pem_private_key.assert_called_once_with(
        data=wrapping_key,
        password=sentinel.password,
        backend=patch_default_backend.return_value)
def ephemeral_raw_rsa_master_key(size=4096):
    # type: (int) -> RawMasterKey
    key_bytes = _generate_rsa_key_bytes(size)
    return RawMasterKey(
        provider_id="fake",
        key_id="rsa-{}".format(size).encode("utf-8"),
        wrapping_key=WrappingKey(
            wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1,
            wrapping_key=key_bytes,
            wrapping_key_type=EncryptionKeyType.PRIVATE,
        ),
    )
    def _wrapping_key(self, key_spec):
        # type: (KeySpec) -> WrappingKey
        """Build the  correct wrapping key if this is a raw master key.

        :param KeySpec key_spec: Key specification to use with this master key
        :return: Wrapping key to use
        :rtype: WrappingKey
        :raises TypeError: if this is not a raw master key specification
        """
        if not self.type_name == "raw":
            raise TypeError("This is not a raw master key")

        algorithm = self._wrapping_algorithm(key_spec.bits)
        material = key_spec.raw_material
        key_type = _RAW_ENCRYPTION_KEY_TYPE[key_spec.type_name]
        return WrappingKey(wrapping_algorithm=algorithm, wrapping_key=material, wrapping_key_type=key_type)
Example #15
0
    def _get_raw_key(self, key_id):
        """Returns a static, randomly-generated symmetric key for the specified key ID.

        :param str key_id: Key ID
        :returns: Wrapping key that contains the specified static key
        :rtype: :class:`aws_encryption_sdk.internal.crypto.WrappingKey`
        """
        try:
            static_key = self._static_keys[key_id]
        except KeyError:
            static_key = os.urandom(32)
            self._static_keys[key_id] = static_key
        return WrappingKey(wrapping_algorithm=WrappingAlgorithm.
                           AES_256_GCM_IV12_TAG16_NO_PADDING,
                           wrapping_key=static_key,
                           wrapping_key_type=EncryptionKeyType.SYMMETRIC)
Example #16
0
 def _get_raw_key(self, key_id):
     """Finds a loaded raw key."""
     try:
         algorithm, key_bits, padding_algorithm, padding_hash = key_id.upper(
         ).split(b".", 3)
         key_bits = int(key_bits)
         key_type = _KEY_TYPES_MAP[algorithm]
         wrapping_algorithm = _WRAPPING_ALGORITHM_MAP[algorithm][key_bits][
             padding_algorithm][padding_hash]
         static_key = _STATIC_KEYS[algorithm][key_bits]
         return WrappingKey(wrapping_algorithm=wrapping_algorithm,
                            wrapping_key=static_key,
                            wrapping_key_type=key_type)
     except KeyError:
         _LOGGER.exception("Unknown Key ID: %s", key_id)
         raise InvalidKeyIdError("Unknown Key ID: {}".format(key_id))
def ephemeral_raw_aes_master_key(
        wrapping_algorithm=WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING,
        key=None):
    # type: (WrappingAlgorithm, Optional[bytes]) -> RawMasterKey
    key_length = wrapping_algorithm.algorithm.data_key_len
    if key is None:
        key = os.urandom(key_length)
    return RawMasterKey(
        provider_id="fake",
        key_id="aes-{}".format(key_length * 8).encode("utf-8"),
        wrapping_key=WrappingKey(
            wrapping_algorithm=wrapping_algorithm,
            wrapping_key=key,
            wrapping_key_type=EncryptionKeyType.SYMMETRIC,
        ),
    )
def test_wrapping_key_init_symmetric(patch_default_backend,
                                     patch_serialization,
                                     patch_derive_data_encryption_key):
    wrapping_algorithm = MagicMock()
    wrapping_key = MagicMock()
    test_wrapping_key = WrappingKey(
        wrapping_algorithm=wrapping_algorithm,
        wrapping_key=wrapping_key,
        wrapping_key_type=EncryptionKeyType.SYMMETRIC)
    assert not patch_serialization.load_pem_private_key.called
    assert not patch_serialization.load_pem_public_key.called
    assert test_wrapping_key._wrapping_key is wrapping_key
    patch_derive_data_encryption_key.assert_called_once_with(
        source_key=wrapping_key,
        algorithm=wrapping_algorithm.algorithm,
        message_id=None)
    assert test_wrapping_key._derived_wrapping_key is patch_derive_data_encryption_key.return_value
Example #19
0
    def _get_raw_key(self, key_id):
        """Retrieves a static, randomly generated, RSA key for the specified key id.

        :param str key_id: User-defined ID for the static key
        :returns: Wrapping key that contains the specified static key
        :rtype: :class:`aws_encryption_sdk.internal.crypto.WrappingKey`
        """
        try:
            static_key = self._static_keys[key_id]
        except KeyError:
            private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096, backend=default_backend())
            static_key = private_key.private_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PrivateFormat.PKCS8,
                encryption_algorithm=serialization.NoEncryption(),
            )
            self._static_keys[key_id] = static_key
        return WrappingKey(
            wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA1_MGF1,
            wrapping_key=static_key,
            wrapping_key_type=EncryptionKeyType.PRIVATE,
        )
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
"""Unit test suite to validate aws_encryption_sdk.key_providers.raw.RawMasterKeyConfig"""
import pytest
import six

from aws_encryption_sdk.identifiers import EncryptionKeyType, WrappingAlgorithm
from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey
from aws_encryption_sdk.key_providers.base import MasterKeyConfig
from aws_encryption_sdk.key_providers.raw import RawMasterKeyConfig
from .unit_test_utils import all_invalid_kwargs, all_valid_kwargs

pytestmark = [pytest.mark.unit, pytest.mark.local]

STATIC_WRAPPING_KEY = WrappingKey(
    wrapping_algorithm=WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING,
    wrapping_key=b'_________a symmetric key________',
    wrapping_key_type=EncryptionKeyType.SYMMETRIC)
VALID_KWARGS = {
    RawMasterKeyConfig: [
        dict(key_id=b'a raw key',
             provider_id='a provider',
             wrapping_key=STATIC_WRAPPING_KEY),
        dict(key_id=b'a raw key',
             provider_id=b'a provider',
             wrapping_key=STATIC_WRAPPING_KEY)
    ]
}


@pytest.mark.parametrize('cls, kwargs', all_valid_kwargs(VALID_KWARGS))
def test_attributes_valid_kwargs(cls, kwargs):
class RawAESKeyring(Keyring):
    """Generate an instance of Raw AES Keyring which encrypts using AES-GCM algorithm using wrapping key provided as a
    byte array

    .. versionadded:: 1.5.0

    :param str key_namespace: String defining the keyring.
    :param bytes key_name: Key ID
    :param bytes wrapping_key: Encryption key with which to wrap plaintext data key.

    .. note::

        Only one wrapping key can be specified in a Raw AES Keyring
    """

    key_namespace = attr.ib(validator=instance_of(six.string_types))
    key_name = attr.ib(validator=instance_of(six.binary_type))
    _wrapping_key = attr.ib(repr=False, validator=instance_of(six.binary_type))

    def __attrs_post_init__(self):
        # type: () -> None
        """Prepares initial values not handled by attrs."""
        key_size_to_wrapping_algorithm = {
            wrapper.algorithm.kdf_input_len: wrapper
            for wrapper in (
                WrappingAlgorithm.AES_128_GCM_IV12_TAG16_NO_PADDING,
                WrappingAlgorithm.AES_192_GCM_IV12_TAG16_NO_PADDING,
                WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING,
            )
        }

        try:
            self._wrapping_algorithm = key_size_to_wrapping_algorithm[len(
                self._wrapping_key)]
        except KeyError:
            raise ValueError(
                "Invalid wrapping key length. Must be one of {} bytes.".format(
                    sorted(key_size_to_wrapping_algorithm.keys())))

        self._key_provider = MasterKeyInfo(provider_id=self.key_namespace,
                                           key_info=self.key_name)

        self._wrapping_key_structure = WrappingKey(
            wrapping_algorithm=self._wrapping_algorithm,
            wrapping_key=self._wrapping_key,
            wrapping_key_type=EncryptionKeyType.SYMMETRIC,
        )

        self._key_info_prefix = self._get_key_info_prefix(
            key_namespace=self.key_namespace,
            key_name=self.key_name,
            wrapping_key=self._wrapping_key_structure)

    @staticmethod
    def _get_key_info_prefix(key_namespace, key_name, wrapping_key):
        # type: (str, bytes, WrappingKey) -> six.binary_type
        """Helper function to get key info prefix

        :param str key_namespace: String defining the keyring.
        :param bytes key_name: Key ID
        :param WrappingKey wrapping_key: Encryption key with which to wrap plaintext data key.
        :return: Serialized key_info prefix
        :rtype: bytes
        """
        key_info_prefix = serialize_raw_master_key_prefix(
            RawMasterKey(provider_id=key_namespace,
                         key_id=key_name,
                         wrapping_key=wrapping_key))
        return key_info_prefix

    def on_encrypt(self, encryption_materials):
        # type: (EncryptionMaterials) -> EncryptionMaterials
        """Generate a data key if not present and encrypt it using any available wrapping key

        :param EncryptionMaterials encryption_materials: Encryption materials for the keyring to modify
        :returns: Encryption materials containing data key and encrypted data key
        :rtype: EncryptionMaterials
        """
        new_materials = encryption_materials

        if new_materials.data_encryption_key is None:
            # Get encryption materials with a new data key.
            new_materials = _generate_data_key(
                encryption_materials=new_materials,
                key_provider=self._key_provider)

        try:
            # Encrypt data key
            encrypted_wrapped_key = self._wrapping_key_structure.encrypt(
                plaintext_data_key=new_materials.data_encryption_key.data_key,
                encryption_context=new_materials.encryption_context,
            )

            # EncryptedData to EncryptedDataKey
            encrypted_data_key = serialize_wrapped_key(
                key_provider=self._key_provider,
                wrapping_algorithm=self._wrapping_algorithm,
                wrapping_key_id=self.key_name,
                encrypted_wrapped_key=encrypted_wrapped_key,
            )
        except Exception:  # pylint: disable=broad-except
            error_message = "Raw AES keyring unable to encrypt data key"
            _LOGGER.exception(error_message)
            raise EncryptKeyError(error_message)

        # Update Keyring Trace
        keyring_trace = KeyringTrace(
            wrapping_key=self._key_provider,
            flags={
                KeyringTraceFlag.ENCRYPTED_DATA_KEY,
                KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT
            },
        )

        return new_materials.with_encrypted_data_key(
            encrypted_data_key=encrypted_data_key, keyring_trace=keyring_trace)

    def on_decrypt(self, decryption_materials, encrypted_data_keys):
        # type: (DecryptionMaterials, Iterable[EncryptedDataKey]) -> DecryptionMaterials
        """Attempt to decrypt the encrypted data keys.

        :param DecryptionMaterials decryption_materials: Decryption materials for the keyring to modify
        :param List[EncryptedDataKey] encrypted_data_keys: List of encrypted data keys
        :returns: Decryption materials that MAY include a plaintext data key
        :rtype: DecryptionMaterials
        """
        new_materials = decryption_materials

        if new_materials.data_encryption_key is not None:
            return new_materials

        # Decrypt data key
        expected_key_info_len = len(
            self._key_info_prefix) + self._wrapping_algorithm.algorithm.iv_len
        for key in encrypted_data_keys:

            if (key.key_provider.provider_id != self._key_provider.provider_id
                    or len(key.key_provider.key_info) != expected_key_info_len
                    or not key.key_provider.key_info.startswith(
                        self._key_info_prefix)):
                continue

            # Wrapped EncryptedDataKey to deserialized EncryptedData
            encrypted_wrapped_key = deserialize_wrapped_key(
                wrapping_algorithm=self._wrapping_algorithm,
                wrapping_key_id=self.key_name,
                wrapped_encrypted_key=key)

            # EncryptedData to raw key string
            try:
                plaintext_data_key = self._wrapping_key_structure.decrypt(
                    encrypted_wrapped_data_key=encrypted_wrapped_key,
                    encryption_context=new_materials.encryption_context,
                )

            except Exception:  # pylint: disable=broad-except
                # We intentionally WANT to catch all exceptions here
                error_message = "Raw AES Keyring unable to decrypt data key"
                _LOGGER.exception(error_message)
                # The Raw AES keyring MUST evaluate every encrypted data key
                # until it either succeeds or runs out of encrypted data keys.
                continue

            # Create a keyring trace
            keyring_trace = KeyringTrace(
                wrapping_key=self._key_provider,
                flags={
                    KeyringTraceFlag.DECRYPTED_DATA_KEY,
                    KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT
                },
            )

            # Update decryption materials
            data_encryption_key = RawDataKey(key_provider=self._key_provider,
                                             data_key=plaintext_data_key)

            return new_materials.with_data_encryption_key(
                data_encryption_key=data_encryption_key,
                keyring_trace=keyring_trace)

        return new_materials