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