def on_encrypt(self, encryption_materials):
        # type: (EncryptionMaterials) -> EncryptionMaterials
        """Generate a data key using generator keyring
        and encrypt it using any available wrapping key in any child keyring.

        :param EncryptionMaterials encryption_materials: Encryption materials for 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:
            new_materials = _generate_data_key(
                encryption_materials=new_materials,
                key_provider=self._key_provider)

        if self._public_wrapping_key is None:
            # This should be impossible, but just in case, give a useful error message.
            raise EncryptKeyError(
                "Raw RSA keyring unable to encrypt data key: no public key available"
            )

        try:
            # Encrypt data key
            encrypted_wrapped_key = EncryptedData(
                iv=None,
                ciphertext=self._public_wrapping_key.encrypt(
                    plaintext=new_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)
            raise EncryptKeyError(error_message)

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

        # Add encrypted data key to encryption_materials
        return new_materials.with_encrypted_data_key(
            encrypted_data_key=encrypted_data_key, keyring_trace=keyring_trace)
    def _encrypt_data_key(self, data_key, algorithm, encryption_context=None):
        """Encrypts a data key and returns the ciphertext.

        :param data_key: Unencrypted data key
        :type data_key: :class:`aws_encryption_sdk.structures.RawDataKey`
            or :class:`aws_encryption_sdk.structures.DataKey`
        :param algorithm: Placeholder to maintain API compatibility with parent
        :param dict encryption_context: Encryption context to pass to KMS
        :returns: Data key containing encrypted data key
        :rtype: aws_encryption_sdk.structures.EncryptedDataKey
        :raises EncryptKeyError: if Master Key is unable to encrypt data key
        """
        kms_params = {"KeyId": self._key_id, "Plaintext": data_key.data_key}
        if encryption_context:
            kms_params["EncryptionContext"] = encryption_context
        if self.config.grant_tokens:
            kms_params["GrantTokens"] = self.config.grant_tokens
        # Catch any boto3 errors and normalize to expected EncryptKeyError
        try:
            response = self.config.client.encrypt(**kms_params)
            ciphertext = response["CiphertextBlob"]
            key_id = response["KeyId"]
        except (ClientError, KeyError):
            error_message = "Master Key {key_id} unable to encrypt data key".format(
                key_id=self._key_id)
            _LOGGER.exception(error_message)
            raise EncryptKeyError(error_message)
        return EncryptedDataKey(key_provider=MasterKeyInfo(
            provider_id=self.provider_id, key_info=key_id),
                                encrypted_data_key=ciphertext)
    def on_encrypt(self, encryption_materials):
        # type: (EncryptionMaterials) -> EncryptionMaterials
        """Generate a data key using generator keyring
        and encrypt it using any available wrapping key in any child keyring.

        :param EncryptionMaterials encryption_materials: Encryption materials for keyring to modify.
        :returns: Optionally modified encryption materials.
        :rtype: EncryptionMaterials
        :raises EncryptKeyError: if unable to encrypt data key.
        """
        # Check if generator keyring is not provided and data key is not generated
        if self.generator is None and encryption_materials.data_encryption_key is None:
            raise EncryptKeyError(
                "Generator keyring not provided "
                "and encryption materials do not already contain a plaintext data key."
            )

        new_materials = encryption_materials

        # Call on_encrypt on the generator keyring if it is provided
        if self.generator is not None:
            new_materials = self.generator.on_encrypt(
                encryption_materials=new_materials)

        # Check if data key is generated
        if new_materials.data_encryption_key is None:
            raise GenerateKeyError("Unable to generate data encryption key.")

        # Call on_encrypt on all other keyrings
        for keyring in self.children:
            new_materials = keyring.on_encrypt(
                encryption_materials=new_materials)

        return new_materials
    def on_encrypt(self, encryption_materials):
        # type: (EncryptionMaterials) -> EncryptionMaterials
        trace_info = MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=self._key_id)
        new_materials = encryption_materials
        try:
            if new_materials.data_encryption_key is None:
                plaintext_key, encrypted_key = _do_aws_kms_generate_data_key(
                    client_supplier=self._client_supplier,
                    key_name=self._key_id,
                    encryption_context=new_materials.encryption_context,
                    algorithm=new_materials.algorithm,
                    grant_tokens=self._grant_tokens,
                )
                new_materials = new_materials.with_data_encryption_key(
                    data_encryption_key=plaintext_key,
                    keyring_trace=KeyringTrace(wrapping_key=trace_info, flags=_GENERATE_FLAGS),
                )
            else:
                encrypted_key = _do_aws_kms_encrypt(
                    client_supplier=self._client_supplier,
                    key_name=self._key_id,
                    plaintext_data_key=new_materials.data_encryption_key,
                    encryption_context=new_materials.encryption_context,
                    grant_tokens=self._grant_tokens,
                )
        except Exception:  # pylint: disable=broad-except
            # We intentionally WANT to catch all exceptions here
            message = "Unable to generate or encrypt data key using {}".format(trace_info)
            _LOGGER.exception(message)
            raise EncryptKeyError(message)

        return new_materials.with_encrypted_data_key(
            encrypted_data_key=encrypted_key, keyring_trace=KeyringTrace(wrapping_key=trace_info, flags=_ENCRYPT_FLAGS)
        )
    def _encrypt_data_key(self, data_key, algorithm, encryption_context=None):
        """Encrypts a data key and returns the ciphertext.

        :param data_key: Unencrypted data key
        :type data_key: :class:`aws_encryption_sdk.structures.RawDataKey`
            or :class:`aws_encryption_sdk.structures.DataKey`
        :param algorithm: Placeholder to maintain API compatibility with parent
        :param dict encryption_context: Encryption context to pass to KMS
        :returns: Data key containing encrypted data key
        :rtype: aws_encryption_sdk.structures.EncryptedDataKey
        :raises EncryptKeyError: if Master Key is unable to encrypt data key
        """
        kms_params = self._build_encrypt_request(data_key, encryption_context)
        # Catch any boto3 errors and normalize to expected EncryptKeyError
        try:
            response = self.config.client.encrypt(**kms_params)
            # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key.txt#2.11
            # //# The response's cipher text blob MUST be used as the "ciphertext" for the
            # //# encrypted data key.
            ciphertext = response["CiphertextBlob"]
            key_id = response["KeyId"]
        except (ClientError, KeyError):
            error_message = "Master Key {key_id} unable to encrypt data key".format(
                key_id=self._key_id)
            _LOGGER.exception(error_message)
            raise EncryptKeyError(error_message)

        # //= compliance/framework/aws-kms/aws-kms-mrk-aware-master-key.txt#2.11
        # //# The AWS KMS Encrypt response MUST contain a valid "KeyId".
        # arn_from_str will error if given an invalid key ARN
        try:
            key_id_str = to_str(key_id)
            arn_from_str(key_id_str)
        except MalformedArnError:
            error_message = "Retrieved an unexpected KeyID in response from KMS: {key_id}".format(
                key_id=key_id)
            _LOGGER.exception(error_message)
            raise EncryptKeyError(error_message)

        return EncryptedDataKey(key_provider=MasterKeyInfo(
            provider_id=self.provider_id, key_info=key_id),
                                encrypted_data_key=ciphertext)
    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)