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 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)
Пример #3
0
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