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)
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