Beispiel #1
0
    def test_prep_message_framed_message(
            self,
            mock_write_header,
            mock_prep_non_framed,
            mock_rostream,
            mock_derive_datakey,
            mock_encryption_materials_request
    ):
        mock_rostream.return_value = sentinel.plaintext_rostream
        test_encryptor = StreamEncryptor(
            source=self.mock_input_stream,
            materials_manager=self.mock_materials_manager,
            frame_length=self.mock_frame_length,
            source_length=5,
            encryption_context=VALUES['encryption_context']
        )
        test_encryptor.content_type = ContentType.FRAMED_DATA
        test_encryption_context = {aws_encryption_sdk.internal.defaults.ENCODED_SIGNER_KEY: sentinel.decoded_bytes}
        self.mock_encryption_materials.encryption_context = test_encryption_context
        self.mock_encryption_materials.encrypted_data_keys = self.mock_encrypted_data_keys

        test_encryptor._prep_message()

        mock_encryption_materials_request.assert_called_once_with(
            algorithm=test_encryptor.config.algorithm,
            encryption_context=VALUES['encryption_context'],
            plaintext_rostream=sentinel.plaintext_rostream,
            frame_length=test_encryptor.config.frame_length,
            plaintext_length=5
        )
        self.mock_materials_manager.get_encryption_materials.assert_called_once_with(
            request=mock_encryption_materials_request.return_value
        )
        self.mock_validate_frame_length.assert_called_once_with(
            frame_length=self.mock_frame_length,
            algorithm=self.mock_encryption_materials.algorithm
        )

        mock_derive_datakey.assert_called_once_with(
            source_key=self.mock_encryption_materials.data_encryption_key.data_key,
            algorithm=self.mock_encryption_materials.algorithm,
            message_id=VALUES['message_id']
        )
        assert test_encryptor._derived_data_key is mock_derive_datakey.return_value
        assert test_encryptor._header == MessageHeader(
            version=aws_encryption_sdk.internal.defaults.VERSION,
            type=aws_encryption_sdk.internal.defaults.TYPE,
            algorithm=self.mock_encryption_materials.algorithm,
            message_id=VALUES['message_id'],
            encryption_context=test_encryption_context,
            encrypted_data_keys=self.mock_encrypted_data_keys,
            content_type=test_encryptor.content_type,
            content_aad_length=0,
            header_iv_length=self.mock_encryption_materials.algorithm.iv_len,
            frame_length=self.mock_frame_length
        )
        mock_write_header.assert_called_once_with()
        assert not mock_prep_non_framed.called
        assert test_encryptor._message_prepped
Beispiel #2
0
def test_message_header_attributes_succeeds():
    MessageHeader(version=MagicMock(__class__=SerializationVersion),
                  type=MagicMock(__class__=ObjectType),
                  algorithm=MagicMock(__class__=Algorithm),
                  message_id=b'',
                  encryption_context={},
                  encrypted_data_keys=set([]),
                  content_type=MagicMock(__class__=ContentType),
                  content_aad_length=5,
                  header_iv_length=5,
                  frame_length=5)
def _deserialize_header_v1(header, tee_stream, max_encrypted_data_keys):
    # type: (IO, Union[int, None]) -> MessageHeader
    """Deserializes the header from a source stream in SerializationVersion.V1.

    :param header: A dictionary in which to store deserialized values
    :type header: dict
    :param tee_stream: The stream from which to read bytes
    :type tee_stream: aws_encryption_sdk.internal.utils.streams.TeeStream
    :param max_encrypted_data_keys: Maximum number of encrypted keys to deserialize
    :type max_encrypted_data_keys: None or positive int
    :returns: Deserialized MessageHeader object
    :rtype: :class:`aws_encryption_sdk.structures.MessageHeader`
    :raises NotSupportedError: if unsupported data types are found
    :raises UnknownIdentityError: if unknown data types are found
    :raises SerializationError: if IV length does not match algorithm
    """
    _LOGGER.debug("Deserializing header in version V1")

    (message_type_id, ) = unpack_values(">B", tee_stream)
    header["type"] = _verified_message_type_from_id(message_type_id)

    algorithm_id, message_id, ser_encryption_context_length = unpack_values(
        ">H16sH", tee_stream)

    header["algorithm"] = _verified_algorithm_from_id(algorithm_id)
    header["message_id"] = message_id

    header["encryption_context"] = deserialize_encryption_context(
        tee_stream.read(ser_encryption_context_length))

    header["encrypted_data_keys"] = deserialize_encrypted_data_keys(
        tee_stream, max_encrypted_data_keys)

    (content_type_id, ) = unpack_values(">B", tee_stream)
    header["content_type"] = _verified_content_type_from_id(content_type_id)

    (content_aad_length, ) = unpack_values(">I", tee_stream)
    header["content_aad_length"] = _verified_content_aad_length(
        content_aad_length)

    (iv_length, ) = unpack_values(">B", tee_stream)
    header["header_iv_length"] = _verified_iv_length(iv_length,
                                                     header["algorithm"])

    (frame_length, ) = unpack_values(">I", tee_stream)
    header["frame_length"] = _verified_frame_length(frame_length,
                                                    header["content_type"])

    return MessageHeader(**header)
def deserialize_header(stream):
    # type: (IO) -> MessageHeader
    """Deserializes the header from a source stream

    :param stream: Source data stream
    :type stream: io.BytesIO
    :returns: Deserialized MessageHeader object
    :rtype: :class:`aws_encryption_sdk.structures.MessageHeader` and bytes
    :raises NotSupportedError: if unsupported data types are found
    :raises UnknownIdentityError: if unknown data types are found
    :raises SerializationError: if IV length does not match algorithm
    """
    _LOGGER.debug("Starting header deserialization")
    tee = io.BytesIO()
    tee_stream = TeeStream(stream, tee)
    version_id, message_type_id = unpack_values(">BB", tee_stream)
    header = dict()
    header["version"] = _verified_version_from_id(version_id)
    header["type"] = _verified_message_type_from_id(message_type_id)

    algorithm_id, message_id, ser_encryption_context_length = unpack_values(
        ">H16sH", tee_stream)

    header["algorithm"] = _verified_algorithm_from_id(algorithm_id)
    header["message_id"] = message_id

    header["encryption_context"] = deserialize_encryption_context(
        tee_stream.read(ser_encryption_context_length))

    header["encrypted_data_keys"] = _deserialize_encrypted_data_keys(
        tee_stream)

    (content_type_id, ) = unpack_values(">B", tee_stream)
    header["content_type"] = _verified_content_type_from_id(content_type_id)

    (content_aad_length, ) = unpack_values(">I", tee_stream)
    header["content_aad_length"] = _verified_content_aad_length(
        content_aad_length)

    (iv_length, ) = unpack_values(">B", tee_stream)
    header["header_iv_length"] = _verified_iv_length(iv_length,
                                                     header["algorithm"])

    (frame_length, ) = unpack_values(">I", tee_stream)
    header["frame_length"] = _verified_frame_length(frame_length,
                                                    header["content_type"])

    return MessageHeader(**header), tee.getvalue()
Beispiel #5
0
def test_message_header_attributes_fails(version, message_type, algorithm,
                                         message_id, encryption_context,
                                         encrypted_data_keys, content_type,
                                         content_aad_length, header_iv_length,
                                         frame_length):
    with pytest.raises(TypeError):
        MessageHeader(version=version,
                      type=message_type,
                      algorithm=algorithm,
                      message_id=message_id,
                      encryption_context=encryption_context,
                      encrypted_data_keys=encrypted_data_keys,
                      content_type=content_type,
                      content_aad_length=content_aad_length,
                      header_iv_length=header_iv_length,
                      frame_length=frame_length)
def _deserialize_header_v2(header, tee_stream):
    # type: (IO) -> MessageHeader
    """Deserializes the header from a source stream in SerializationVersion.V2.

    :param header: A dictionary in which to store deserialized values
    :type header: dict
    :param tee_stream: The stream from which to read bytes
    :type tee_stream: aws_encryption_sdk.internal.utils.streams.TeeStream
    :returns: Deserialized MessageHeader object
    :rtype: :class:`aws_encryption_sdk.structures.MessageHeader`
    :raises NotSupportedError: if unsupported data types are found
    :raises UnknownIdentityError: if unknown data types are found
    :raises SerializationError: if IV length does not match algorithm
    """
    _LOGGER.debug("Deserializing header in version V2")

    algorithm_id, message_id, ser_encryption_context_length = unpack_values(
        ">H32sH", tee_stream)

    header["algorithm"] = _verified_algorithm_from_id(algorithm_id)
    header["message_id"] = message_id

    header["encryption_context"] = deserialize_encryption_context(
        tee_stream.read(ser_encryption_context_length))

    header["encrypted_data_keys"] = _deserialize_encrypted_data_keys(
        tee_stream)

    (content_type_id, ) = unpack_values(">B", tee_stream)
    header["content_type"] = _verified_content_type_from_id(content_type_id)

    (frame_length, ) = unpack_values(">I", tee_stream)
    header["frame_length"] = _verified_frame_length(frame_length,
                                                    header["content_type"])

    algorithm_suite_data_length = header[
        "algorithm"].algorithm_suite_data_length()
    (algorithm_suite_data, ) = unpack_values(
        ">{}s".format(algorithm_suite_data_length), tee_stream)
    header["commitment_key"] = algorithm_suite_data

    return MessageHeader(**header)
Beispiel #7
0
    def _prep_message(self):
        """Performs initial message setup."""
        encryption_context = self.config.encryption_context.copy()

        message_id = aws_encryption_sdk.internal.utils.message_id()

        if self.config.algorithm.signing_algorithm_info is None:
            self.signer = None
        else:
            self.signer = aws_encryption_sdk.internal.crypto.Signer(
                self.config.algorithm)
            encryption_context[aws_encryption_sdk.internal.defaults.
                               ENCODED_SIGNER_KEY] = codecs.decode(
                                   self.signer.encoded_public_key())

        self.encryption_data_key, encrypted_data_keys = aws_encryption_sdk.internal.utils.prepare_data_keys(
            key_provider=self.config.key_provider,
            algorithm=self.config.algorithm,
            encryption_context=encryption_context,
            plaintext_rostream=aws_encryption_sdk.internal.utils.ROStream(
                self.source_stream),
            plaintext_length=self.config.source_length,
            data_key=self.config.data_key)
        self._header = MessageHeader(
            version=aws_encryption_sdk.internal.defaults.VERSION,
            type=aws_encryption_sdk.internal.defaults.TYPE,
            algorithm=self.config.algorithm,
            message_id=message_id,
            encryption_context=encryption_context,
            encrypted_data_keys=encrypted_data_keys,
            content_type=self.content_type,
            content_aad_length=0,
            header_iv_length=self.config.algorithm.iv_len,
            frame_length=self.config.frame_length)
        self._write_header()
        if self.content_type == ContentType.NO_FRAMING:
            self._prep_non_framed()
        self._message_prepped = True
 def test_prep_message_framed_message(self, mock_write_header, mock_prep_non_framed, mock_rostream):
     mock_rostream.return_value = sentinel.plaintext_rostream
     test_encryptor = StreamEncryptor(
         source=self.mock_input_stream,
         key_provider=self.mock_key_provider,
         frame_length=self.mock_frame_length,
         source_length=5
     )
     test_encryptor.content_type = ContentType.FRAMED_DATA
     test_encryptor._prep_message()
     self.mock_signer.assert_called_once_with(test_encryptor.config.algorithm)
     self.mock_signer_instance.encoded_public_key.assert_called_once_with()
     self.mock_codecs.decode.assert_called_once_with(sentinel.encoded_public_key)
     test_encryption_context = {aws_encryption_sdk.internal.defaults.ENCODED_SIGNER_KEY: sentinel.decoded_bytes}
     mock_rostream.assert_called_once_with(self.mock_input_stream)
     self.mock_prepare_data_keys.assert_called_once_with(
         key_provider=self.mock_key_provider,
         algorithm=test_encryptor.config.algorithm,
         encryption_context=test_encryption_context,
         plaintext_rostream=sentinel.plaintext_rostream,
         plaintext_length=5,
         data_key=test_encryptor.config.data_key
     )
     assert test_encryptor._header == MessageHeader(
         version=aws_encryption_sdk.internal.defaults.VERSION,
         type=aws_encryption_sdk.internal.defaults.TYPE,
         algorithm=test_encryptor.config.algorithm,
         message_id=VALUES['message_id'],
         encryption_context=test_encryption_context,
         encrypted_data_keys=self.mock_encrypted_data_keys,
         content_type=test_encryptor.content_type,
         content_aad_length=0,
         header_iv_length=test_encryptor.config.algorithm.iv_len,
         frame_length=self.mock_frame_length
     )
     mock_write_header.assert_called_once_with()
     assert not mock_prep_non_framed.called
     assert test_encryptor._message_prepped
Beispiel #9
0
    def generate_header(self, message_id):
        """Generates the header object.

        :param message_id: The randomly generated id for the message
        :type message_id: bytes
        """
        version = VERSION
        if self._encryption_materials.algorithm.message_format_version == 0x02:
            version = SerializationVersion.V2

        kwargs = dict(
            version=version,
            algorithm=self._encryption_materials.algorithm,
            message_id=message_id,
            encryption_context=self._encryption_materials.encryption_context,
            encrypted_data_keys=self._encryption_materials.encrypted_data_keys,
            content_type=self.content_type,
            frame_length=self.config.frame_length,
        )

        if self._encryption_materials.algorithm.is_committing():
            commitment_key = calculate_commitment_key(
                source_key=self._encryption_materials.data_encryption_key.
                data_key,
                algorithm=self._encryption_materials.algorithm,
                message_id=message_id,
            )
            kwargs["commitment_key"] = commitment_key

        if version == SerializationVersion.V1:
            kwargs["type"] = TYPE
            kwargs["content_aad_length"] = 0
            kwargs[
                "header_iv_length"] = self._encryption_materials.algorithm.iv_len

        return MessageHeader(**kwargs)
    VALUES["body_final_frame"],
    VALUES["footer"],
])
VALUES["message_truncated_frames"] = b"".join([
    VALUES["header"], VALUES["header_auth"], VALUES["body_frame"],
    VALUES["body_frame"], VALUES["footer"]
])
VALUES["deserialized_header_block_no_signature"] = MessageHeader(
    version=SerializationVersion.V1,
    type=ObjectType.CUSTOMER_AE_DATA,
    algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
    message_id=VALUES["message_id"],
    encryption_context=VALUES["encryption_context"],
    encrypted_data_keys=set([
        EncryptedDataKey(
            key_provider=VALUES["data_keys"][0].key_provider,
            encrypted_data_key=VALUES["data_keys"][0].encrypted_data_key,
        )
    ]),
    content_type=ContentType.NO_FRAMING,
    content_aad_length=0,
    header_iv_length=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384.
    iv_len,
    frame_length=0,
)
VALUES["deserialized_header_block"] = MessageHeader(
    version=SerializationVersion.V1,
    type=ObjectType.CUSTOMER_AE_DATA,
    algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
    message_id=VALUES["message_id"],
    encryption_context=VALUES["updated_encryption_context"],
    encrypted_data_keys=set([
Beispiel #11
0
    def _prep_message(self):
        """Performs initial message setup.

        :raises MasterKeyProviderError: if primary master key is not a member of supplied MasterKeyProvider
        :raises MasterKeyProviderError: if no Master Keys are returned from key_provider
        """
        message_id = aws_encryption_sdk.internal.utils.message_id()

        try:
            plaintext_length = self.stream_length
        except NotSupportedError:
            plaintext_length = None
        encryption_materials_request = EncryptionMaterialsRequest(
            algorithm=self.config.algorithm,
            encryption_context=self.config.encryption_context.copy(),
            frame_length=self.config.frame_length,
            plaintext_rostream=aws_encryption_sdk.internal.utils.ROStream(
                self.source_stream),
            plaintext_length=plaintext_length)
        self._encryption_materials = self.config.materials_manager.get_encryption_materials(
            request=encryption_materials_request)

        if self.config.algorithm is not None and self._encryption_materials.algorithm != self.config.algorithm:
            raise ActionNotAllowedError(
                ('Cryptographic materials manager provided algorithm suite'
                 ' differs from algorithm suite in request.\n'
                 'Required: {requested}\n'
                 'Provided: {provided}').format(
                     requested=self.config.algorithm,
                     provided=self._encryption_materials.algorithm))

        if self._encryption_materials.signing_key is None:
            self.signer = None
        else:
            self.signer = Signer.from_key_bytes(
                algorithm=self._encryption_materials.algorithm,
                key_bytes=self._encryption_materials.signing_key)
        aws_encryption_sdk.internal.utils.validate_frame_length(
            frame_length=self.config.frame_length,
            algorithm=self._encryption_materials.algorithm)

        self._derived_data_key = derive_data_encryption_key(
            source_key=self._encryption_materials.data_encryption_key.data_key,
            algorithm=self._encryption_materials.algorithm,
            message_id=message_id)

        self._header = MessageHeader(
            version=VERSION,
            type=TYPE,
            algorithm=self._encryption_materials.algorithm,
            message_id=message_id,
            encryption_context=self._encryption_materials.encryption_context,
            encrypted_data_keys=self._encryption_materials.encrypted_data_keys,
            content_type=self.content_type,
            content_aad_length=0,
            header_iv_length=self._encryption_materials.algorithm.iv_len,
            frame_length=self.config.frame_length)
        self._write_header()
        if self.content_type == ContentType.NO_FRAMING:
            self._prep_non_framed()
        self._message_prepped = True
Beispiel #12
0
def deserialize_header(stream):
    """Deserializes the header from a source stream

    :param stream: Source data stream
    :type stream: io.BytesIO
    :returns: Deserialized MessageHeader object
    :rtype: :class:`aws_encryption_sdk.structures.MessageHeader` and bytes
    :raises NotSupportedError: if unsupported data types are found
    :raises UnknownIdentityError: if unknown data types are found
    :raises SerializationError: if IV length does not match algorithm
    """
    _LOGGER.debug('Starting header deserialization')
    tee = io.BytesIO()
    tee_stream = TeeStream(stream, tee)
    version_id, message_type_id = unpack_values('>BB', tee_stream)
    try:
        message_type = ObjectType(message_type_id)
    except ValueError as error:
        raise NotSupportedError(
            'Unsupported type {} discovered in data stream'.format(
                message_type_id), error)
    try:
        version = SerializationVersion(version_id)
    except ValueError as error:
        raise NotSupportedError('Unsupported version {}'.format(version_id),
                                error)
    header = {'version': version, 'type': message_type}

    algorithm_id, message_id, ser_encryption_context_length = unpack_values(
        '>H16sH', tee_stream)

    try:
        alg = Algorithm.get_by_id(algorithm_id)
    except KeyError as error:
        raise UnknownIdentityError('Unknown algorithm {}'.format(algorithm_id),
                                   error)
    if not alg.allowed:
        raise NotSupportedError('Unsupported algorithm: {}'.format(alg))
    header['algorithm'] = alg
    header['message_id'] = message_id

    header['encryption_context'] = deserialize_encryption_context(
        tee_stream.read(ser_encryption_context_length))
    (encrypted_data_key_count, ) = unpack_values('>H', tee_stream)

    encrypted_data_keys = set([])
    for _ in range(encrypted_data_key_count):
        (key_provider_length, ) = unpack_values('>H', tee_stream)
        (key_provider_identifier, ) = unpack_values(
            '>{}s'.format(key_provider_length), tee_stream)
        (key_provider_information_length, ) = unpack_values('>H', tee_stream)
        (key_provider_information, ) = unpack_values(
            '>{}s'.format(key_provider_information_length), tee_stream)
        (encrypted_data_key_length, ) = unpack_values('>H', tee_stream)
        encrypted_data_key = tee_stream.read(encrypted_data_key_length)
        encrypted_data_keys.add(
            EncryptedDataKey(key_provider=MasterKeyInfo(
                provider_id=to_str(key_provider_identifier),
                key_info=key_provider_information),
                             encrypted_data_key=encrypted_data_key))
    header['encrypted_data_keys'] = encrypted_data_keys

    (content_type_id, ) = unpack_values('>B', tee_stream)
    try:
        content_type = ContentType(content_type_id)
    except ValueError as error:
        raise UnknownIdentityError(
            'Unknown content type {}'.format(content_type_id), error)
    header['content_type'] = content_type

    (content_aad_length, ) = unpack_values('>I', tee_stream)
    if content_aad_length != 0:
        raise SerializationError(
            'Content AAD length field is currently unused, its value must be always 0'
        )
    header['content_aad_length'] = 0

    (iv_length, ) = unpack_values('>B', tee_stream)
    if iv_length != alg.iv_len:
        raise SerializationError(
            'Specified IV length ({length}) does not match algorithm IV length ({alg})'
            .format(length=iv_length, alg=alg))
    header['header_iv_length'] = iv_length

    (frame_length, ) = unpack_values('>I', tee_stream)
    if content_type == ContentType.FRAMED_DATA and frame_length > MAX_FRAME_SIZE:
        raise SerializationError(
            'Specified frame length larger than allowed maximum: {found} > {max}'
            .format(found=frame_length, max=MAX_FRAME_SIZE))
    elif content_type == ContentType.NO_FRAMING and frame_length != 0:
        raise SerializationError(
            'Non-zero frame length found for non-framed message')
    header['frame_length'] = frame_length

    return MessageHeader(**header), tee.getvalue()
])
VALUES['message_truncated_frames'] = b''.join([
    VALUES['header'],
    VALUES['header_auth'],
    VALUES['body_frame'],
    VALUES['body_frame'],
    VALUES['footer']
])
VALUES['deserialized_header_block_no_signature'] = MessageHeader(
    version=SerializationVersion.V1,
    type=ObjectType.CUSTOMER_AE_DATA,
    algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
    message_id=VALUES['message_id'],
    encryption_context=VALUES['encryption_context'],
    encrypted_data_keys=set([EncryptedDataKey(
        key_provider=VALUES['data_keys'][0].key_provider,
        encrypted_data_key=VALUES['data_keys'][0].encrypted_data_key
    )]),
    content_type=ContentType.NO_FRAMING,
    content_aad_length=0,
    header_iv_length=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384.iv_len,
    frame_length=0
)
VALUES['deserialized_header_block'] = MessageHeader(
    version=SerializationVersion.V1,
    type=ObjectType.CUSTOMER_AE_DATA,
    algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
    message_id=VALUES['message_id'],
    encryption_context=VALUES['updated_encryption_context'],
    encrypted_data_keys=set([EncryptedDataKey(
        key_provider=VALUES['data_keys'][0].key_provider,
Beispiel #14
0
def test_json_ready_message_header():
    # pylint: disable=too-many-locals
    message_id = b"a message ID"
    encryption_context = {"a": "b", "c": "d"}
    content_aad_length = 8
    iv_length = 17
    frame_length = 99
    master_key_provider_id_1 = b"provider 1"
    master_key_provider_info_1 = b"master key 1"
    encrypted_data_key_1 = b"an encrypted data key1"
    master_key_provider_id_2 = b"provider 1"
    master_key_provider_info_2 = b"master key 2"
    encrypted_data_key_2 = b"an encrypted data key2"
    master_key_provider_id_3 = b"another provider"
    master_key_provider_info_3 = b"master key 3"
    encrypted_data_key_3 = b"an encrypted data key3"
    raw_header = MessageHeader(
        version=SerializationVersion.V1,
        type=ObjectType.CUSTOMER_AE_DATA,
        algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384,
        message_id=message_id,
        encryption_context=encryption_context,
        encrypted_data_keys=set([
            EncryptedDataKey(
                key_provider=MasterKeyInfo(
                    provider_id=master_key_provider_id_1,
                    key_info=master_key_provider_info_1),
                encrypted_data_key=encrypted_data_key_1,
            ),
            EncryptedDataKey(
                key_provider=MasterKeyInfo(
                    provider_id=master_key_provider_id_2,
                    key_info=master_key_provider_info_2),
                encrypted_data_key=encrypted_data_key_2,
            ),
            EncryptedDataKey(
                key_provider=MasterKeyInfo(
                    provider_id=master_key_provider_id_3,
                    key_info=master_key_provider_info_3),
                encrypted_data_key=encrypted_data_key_3,
            ),
        ]),
        content_type=ContentType.FRAMED_DATA,
        content_aad_length=content_aad_length,
        header_iv_length=iv_length,
        frame_length=frame_length,
    )
    expected_header_dict = {
        "version":
        "1.0",
        "type":
        ObjectType.CUSTOMER_AE_DATA.value,
        "commitment_key":
        None,
        "algorithm":
        Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384.name,
        "message_id":
        metadata.unicode_b64_encode(message_id),
        "encryption_context":
        encryption_context,
        "encrypted_data_keys": [
            {
                "key_provider": {
                    "provider_id":
                    metadata.unicode_b64_encode(master_key_provider_id_3),
                    "key_info":
                    metadata.unicode_b64_encode(master_key_provider_info_3),
                },
                "encrypted_data_key":
                metadata.unicode_b64_encode(encrypted_data_key_3),
            },
            {
                "key_provider": {
                    "provider_id":
                    metadata.unicode_b64_encode(master_key_provider_id_1),
                    "key_info":
                    metadata.unicode_b64_encode(master_key_provider_info_1),
                },
                "encrypted_data_key":
                metadata.unicode_b64_encode(encrypted_data_key_1),
            },
            {
                "key_provider": {
                    "provider_id":
                    metadata.unicode_b64_encode(master_key_provider_id_2),
                    "key_info":
                    metadata.unicode_b64_encode(master_key_provider_info_2),
                },
                "encrypted_data_key":
                metadata.unicode_b64_encode(encrypted_data_key_2),
            },
        ],
        "content_type":
        ContentType.FRAMED_DATA.value,
        "header_iv_length":
        iv_length,
        "frame_length":
        frame_length,
    }

    test = metadata.json_ready_header(raw_header)

    assert test == expected_header_dict
    # verify that the dict is actually JSON-encodable
    json.dumps(test)