Ejemplo n.º 1
0
    def export(self, padding8: Optional[bytes] = None, dbg_info: DebugInfo = DebugInfo.disabled()) -> bytes:
        """Serialization to binary form.

        :param padding8: 8 padding bytes used for in the header, None to use random bytes
                This value shall be used only for regression testing to generate same results
        :param dbg_info: class allowing to debug output from the export
        :return: Serialize object into bytes
        """
        major_version, minor_version = [int(v) for v in self.version.split('.')]
        product_version_words = [swap16(n) for n in self.product_version.nums]
        component_version_words = [swap16(n) for n in self.component_version.nums]
        signature2 = crypto_backend().random_bytes(4)
        padding = padding8 if padding8 else crypto_backend().random_bytes(8)

        if (major_version > 1) or ((major_version == 1) and (minor_version >= 2)):
            signature2 = self._SIGNATURE2

        dbg_info.append_section('SB-file-Header')

        result = pack(
            self._FORMAT,
            self.digest,
            self._SIGNATURE1,
            # header version
            major_version, minor_version,
            self.flags,
            self.image_blocks,
            self.first_boot_tag_block,
            self.first_boot_section_id,
            self.key_count,
            self.key_dictionary_block,
            self.header_blocks,
            self.section_count,
            self.section_header_size,
            padding[0:2],
            signature2,
            pack_timestamp(self.timestamp),
            # product version
            product_version_words[0], 0,
            product_version_words[1], 0,
            product_version_words[2], 0,
            # component version
            component_version_words[0], 0,
            component_version_words[1], 0,
            component_version_words[2], 0,
            self.drive_tag,
            padding[2:]
        )

        result = result[len(self.digest):]
        self.digest = crypto_backend().hash(result, 'sha1')

        dbg_info.append_binary_section('digest', self.digest)
        dbg_info.append_binary_section('attrs', result)

        return self.digest + result
Ejemplo n.º 2
0
    def parse(
        cls,
        data: bytes,
        offset: int = 0,
        dek: bytes = b"",
        mac: bytes = b"",
        counter: Optional[Counter] = None,
    ) -> "CertSectionV2":
        """Parse Certificate Section from bytes array.

        :param data: Raw data of parsed image
        :param offset: The offset of input data
        :param dek: The DEK value in bytes (required)
        :param mac: The MAC value in bytes (required)
        :param counter: The counter object (required)
        :return: parsed cert section v2 object
        :raises SPSDKError: Raised when dek, mac, counter are not valid
        :raises SPSDKError: Raised when there is invalid header HMAC, TAG, FLAGS, Mark
        :raises SPSDKError: Raised when there is invalid certificate block HMAC
        """
        if not isinstance(dek, bytes):
            raise SPSDKError("DEK value has invalid format")
        if not isinstance(mac, bytes):
            raise SPSDKError("MAC value has invalid format")
        if not isinstance(counter, Counter):
            raise SPSDKError("Counter value has invalid format")
        index = offset
        header_encrypted = data[index:index + CmdHeader.SIZE]
        index += CmdHeader.SIZE
        header_hmac = data[index:index + cls.HMAC_SIZE]
        index += cls.HMAC_SIZE
        cert_block_hmac = data[index:index + cls.HMAC_SIZE]
        index += cls.HMAC_SIZE
        if header_hmac != crypto_backend().hmac(mac, header_encrypted):
            raise SPSDKError("Invalid Header HMAC")
        header_encrypted = crypto_backend().aes_ctr_decrypt(
            dek, header_encrypted, counter.value)
        header = CmdHeader.parse(header_encrypted)
        if header.tag != EnumCmdTag.TAG:
            raise SPSDKError(f"Invalid Header TAG: 0x{header.tag:02X}")
        if header.flags != (EnumSectionFlag.CLEARTEXT
                            | EnumSectionFlag.LAST_SECT):
            raise SPSDKError(f"Invalid Header FLAGS: 0x{header.flags:02X}")
        if header.address != cls.SECT_MARK:
            raise SPSDKError(f"Invalid Section Mark: 0x{header.address:08X}")
        # Parse Certificate Block
        cert_block = CertBlockV2.parse(data, index)
        if cert_block_hmac != crypto_backend().hmac(
                mac, data[index:index + cert_block.raw_size]):
            raise SPSDKError("Invalid Certificate Block HMAC")
        index += cert_block.raw_size
        cert_section_obj = cls(cert_block)
        counter.increment(calc_cypher_block_count(index - offset))
        return cert_section_obj
Ejemplo n.º 3
0
    def export(self, padding: bytes = None) -> bytes:
        """Serialize object into bytes.

        :param padding: header padding 8 bytes (for testing purposes); None to use random value
        :return: binary representation
        :raise AttributeError: raised when format is incorrect
        """
        if not isinstance(self.nonce, bytes) or len(self.nonce) != 16:
            raise AttributeError()
        major_version, minor_version = [
            int(v) for v in self.version.split('.')
        ]
        product_version_words = [swap16(v) for v in self.product_version.nums]
        component_version_words = [
            swap16(v) for v in self.product_version.nums
        ]
        if padding is None:
            padding = crypto_backend().random_bytes(8)
        else:
            assert len(padding) == 8

        result = pack(
            self.FORMAT,
            self.nonce,
            # padding 8 bytes
            padding,
            self.SIGNATURE1,
            # header version
            major_version,
            minor_version,
            self.flags,
            self.image_blocks,
            self.first_boot_tag_block,
            self.first_boot_section_id,
            self.offset_to_certificate_block,
            self.header_blocks,
            self.key_blob_block,
            self.key_blob_block_count,
            self.max_section_mac_count,
            self.SIGNATURE2,
            pack_timestamp(self.timestamp),
            # product version
            product_version_words[0],
            0,
            product_version_words[1],
            0,
            product_version_words[2],
            0,
            # component version
            component_version_words[0],
            0,
            component_version_words[1],
            0,
            component_version_words[2],
            0,
            self.build_number,
            # padding[4]
            padding[4:])
        assert len(result) == self.SIZE
        return result
Ejemplo n.º 4
0
def align_block(data: Union[bytes, bytearray],
                alignment: int = 4,
                padding: int = 0) -> bytes:
    """Align binary data block length to specified boundary by adding padding bytes to the end.

    :param data: to be aligned
    :param alignment: boundary alignment (typically 2, 4, 16, 64 or 256 boundary)
    :param padding: byte to be added, use -1 to fill with random data
    :return: aligned block
    :raises SPSDKError: When there is wrong alignment
    """
    assert isinstance(data, (bytes, bytearray))

    if alignment < 0:
        raise SPSDKError("Wrong alignment")
    if padding < -1 or padding > 255:
        raise SPSDKError("Wrong padding")
    current_size = len(data)
    num_padding = align(current_size, alignment) - current_size
    if not num_padding:
        return bytes(data)
    if padding == -1:
        # pylint: disable=import-outside-toplevel
        from spsdk.utils.crypto.common import crypto_backend

        return bytes(data + crypto_backend().random_bytes(num_padding))
    return bytes(data + bytes([padding]) * num_padding)
Ejemplo n.º 5
0
def get_oem_share_input(binary: BinaryIO) -> bytes:
    """Get binary from text or binary file.

    :param binary: Path to binary file.
    :return: Binary array loaded from file.
    :raises SPSDKValueError: When invalid input value is recognized.
    """
    if binary:
        oem_share_input = binary.read()
    else:
        oem_share_input = crypto_backend().random_bytes(16)

    if len(oem_share_input) != 16:
        raise SPSDKValueError(
            f"Invalid length of OEM SHARE INPUT ({len(oem_share_input)} not equal to 16)."
        )

    return oem_share_input
Ejemplo n.º 6
0
def align_block(data: bytes, alignment: int = 4, padding: int = 0) -> bytes:
    """Align binary data block length to specified boundary by adding padding bytes to the end.

    :param data: to be aligned
    :param alignment: boundary alignment (typically 2, 4, 16, 64 or 256 boundary)
    :param padding: byte to be added, use -1 to fill with random data
    :return: aligned block
    """
    assert isinstance(data, bytes)
    assert alignment > 0
    assert -1 <= padding <= 255
    curr_size = len(data)
    num_padding = align(curr_size, alignment) - curr_size
    if not num_padding:
        return data
    if padding == -1:
        # pylint: disable=import-outside-toplevel
        from spsdk.utils.crypto.common import crypto_backend
        return data + crypto_backend().random_bytes(num_padding)
    return data + bytes([padding]) * num_padding
Ejemplo n.º 7
0
    def parse(
        cls,
        data: bytes,
        offset: int = 0,
        plain_sect: bool = False,
        dek: bytes = b"",
        mac: bytes = b"",
        counter: Optional[Counter] = None,
    ) -> "BootSectionV2":
        """Parse Boot Section from bytes.

        :param data: Raw data of parsed image
        :param offset: The offset of input data
        :param plain_sect: If the sections are not encrypted; It is used for debugging only, not supported by ROM code
        :param dek: The DEK value in bytes (required)
        :param mac: The MAC value in bytes (required)
        :param counter: The counter object (required)
        :return: exported bytes
        :raise SPSDKError: raised when dek, mac, counter have invalid format
        """
        if not isinstance(dek, bytes):
            raise SPSDKError("Invalid type of dek, should be bytes")
        if not isinstance(mac, bytes):
            raise SPSDKError("Invalid type of mac, should be bytes")
        if not isinstance(counter, Counter):
            raise SPSDKError("Invalid type of counter")
        # Get Header specific data
        header_encrypted = data[offset : offset + CmdHeader.SIZE]
        header_hmac_data = data[offset + CmdHeader.SIZE : offset + CmdHeader.SIZE + cls.HMAC_SIZE]
        offset += CmdHeader.SIZE + cls.HMAC_SIZE
        # Check header HMAC
        if header_hmac_data != crypto_backend().hmac(mac, header_encrypted):
            raise SPSDKError()
        # Decrypt header
        header_decrypted = crypto_backend().aes_ctr_decrypt(dek, header_encrypted, counter.value)
        counter.increment()
        # Parse header
        header = CmdHeader.parse(header_decrypted)
        counter.increment((header.data + 1) * 2)
        # Get HMAC data
        hmac_data = data[offset : offset + (cls.HMAC_SIZE * header.data)]
        offset += cls.HMAC_SIZE * header.data
        encrypted_commands = data[offset : offset + (header.count * 16)]
        # Check HMAC
        hmac_index = 0
        hmac_count = header.data
        block_size = (header.count // hmac_count) * 16
        section_size = header.count * 16
        while hmac_count > 0:
            if hmac_count == 1:
                block_size = section_size
            hmac_block = crypto_backend().hmac(mac, data[offset : offset + block_size])
            if hmac_block != hmac_data[hmac_index : hmac_index + cls.HMAC_SIZE]:
                raise SPSDKError()
            hmac_count -= 1
            hmac_index += cls.HMAC_SIZE
            section_size -= block_size
            offset += block_size
        # Decrypt commands
        decrypted_commands = b""
        for hmac_index in range(0, len(encrypted_commands), 16):
            encr_block = encrypted_commands[hmac_index : hmac_index + 16]
            decrypted_block = (
                encr_block
                if plain_sect
                else crypto_backend().aes_ctr_decrypt(dek, encr_block, counter.value)
            )
            decrypted_commands += decrypted_block
            counter.increment()
        # ...
        cmd_offset = 0
        obj = cls(header.address, hmac_count=header.data)
        while cmd_offset < len(decrypted_commands):
            cmd_obj = parse_command(decrypted_commands, cmd_offset)
            cmd_offset += cmd_obj.raw_size
            obj.append(cmd_obj)
        return obj
Ejemplo n.º 8
0
    def export(
        self,
        dek: bytes = b"",
        mac: bytes = b"",
        counter: Optional[Counter] = None,
        dbg_info: Optional[List[str]] = None,
    ) -> bytes:
        """Serialize Boot Section object.

        :param dek: The DEK value in bytes (required)
        :param mac: The MAC value in bytes (required)
        :param counter: The counter object (required)
        :param dbg_info: Optional[List[str]] optional list to export debug information about content in text format
        :return: exported bytes
        :raise SPSDKError: raised when dek, mac, counter have invalid format
        """
        cmd_dbg_info: Optional[List[str]] = None
        if dbg_info is not None:
            dbg_info.append("[bootable_section]")
            cmd_dbg_info = []
            cmd_dbg_info.append("[commands]")
        if not isinstance(dek, bytes):
            raise SPSDKError("Invalid type of dek, should be bytes")
        if not isinstance(mac, bytes):
            raise SPSDKError("Invalid type of mac, should be bytes")
        if not isinstance(counter, Counter):
            raise SPSDKError("Invalid type of counter")
        # Export commands
        commands_data = b""
        for cmd in self._commands:
            cmd_data = cmd.export()
            commands_data += cmd_data
            if cmd_dbg_info is not None:
                cmd_dbg_info.append(f"[command:{type(cmd).__name__}]")
                cmd_dbg_info.append(cmd_data.hex())
        if len(commands_data) % 16:
            commands_data += b"\x00" * (16 - (len(commands_data) % 16))
        # Encrypt header
        self._header.data = self.hmac_count
        self._header.count = len(commands_data) // 16
        encrypted_header = crypto_backend().aes_ctr_encrypt(
            dek, self._header.export(), counter.value
        )
        hmac_data = crypto_backend().hmac(mac, encrypted_header)
        counter.increment(1 + (self.hmac_count + 1) * 2)
        if dbg_info:
            dbg_info.append("[header]")
            dbg_info.append(self._header.export().hex())
            dbg_info.append("[encrypted_header]")
            dbg_info.append(encrypted_header.hex())
            dbg_info.append("[hmac]")
            dbg_info.append(hmac_data.hex())
            if cmd_dbg_info:
                dbg_info.extend(cmd_dbg_info)
        # Encrypt commands
        encrypted_commands = b""
        for index in range(0, len(commands_data), 16):
            encrypted_block = crypto_backend().aes_ctr_encrypt(
                dek, commands_data[index : index + 16], counter.value
            )
            encrypted_commands += encrypted_block
            counter.increment()
        # Calculate HMAC of commands
        index = 0
        hmac_count = self._header.data
        block_size = (self._header.count // hmac_count) * 16
        while hmac_count > 0:
            enc_block = (
                encrypted_commands[index:]
                if hmac_count == 1
                else encrypted_commands[index : index + block_size]
            )
            hmac_data += crypto_backend().hmac(mac, enc_block)
            hmac_count -= 1
            index += len(enc_block)
        return encrypted_header + hmac_data + encrypted_commands