def test_elftosb_mbi_signed(data_dir, tmpdir, config_file, device, sign_digest): runner = CliRunner() with use_working_directory(data_dir): config_file = f"{data_dir}/workspace/cfgs/{device}/{config_file}" ref_binary, new_binary, new_config = process_config_file( config_file, tmpdir) cmd = f"--image-conf {new_config}" result = runner.invoke(elftosb.main, cmd.split()) assert os.path.isfile(new_binary) # validate file lengths with open(ref_binary, "rb") as f: ref_data = f.read() with open(new_binary, "rb") as f: new_data = f.read() assert len(ref_data) == len(new_data) # validate signatures signing_key = get_signing_key(config_file=config_file) signature_length = 2 * signing_key.pointQ.size_in_bytes() if sign_digest: sign_offset = 32 if sign_digest and sign_digest == "sha256" else 48 assert internal_backend.ecc_verify( signing_key, new_data[-(signature_length + sign_offset):-sign_offset], new_data[:-(signature_length + sign_offset)], ) assert internal_backend.ecc_verify( signing_key, ref_data[-(signature_length + sign_offset):-sign_offset], ref_data[:-(signature_length + sign_offset)], ) # validate data before signature assert (ref_data[:-(signature_length + sign_offset)] == new_data[:-(signature_length + sign_offset)]) # validate signature digest assert (internal_backend.hash( new_data[:-sign_offset], sign_digest) == new_data[-sign_offset:]) assert (internal_backend.hash( ref_data[:-sign_offset], sign_digest) == ref_data[-sign_offset:]) else: assert internal_backend.ecc_verify(signing_key, new_data[-signature_length:], new_data[:-signature_length]) assert internal_backend.ecc_verify(signing_key, ref_data[-signature_length:], ref_data[:-signature_length]) # validate data before signature assert ref_data[:-signature_length] == new_data[:-signature_length]
def create_ctrk_table(rot_pub_keys: List[str]) -> bytes: """Creates ctrk table.""" if len(rot_pub_keys) == 1: return bytes() ctrk_table = bytes() for pub_key_path in rot_pub_keys: pub_key = crypto.load_public_key(pub_key_path) assert isinstance(pub_key, crypto.EllipticCurvePublicKey) key_length = pub_key.key_size data = ecc_key_to_bytes(key=pub_key, length=key_length // 8) ctrk_hash = internal_backend.hash(data=data, algorithm=f'sha{key_length}') ctrk_table += ctrk_hash return ctrk_table
def _process_block(self, block_number: int, block_data: bytes) -> bytes: """Process single block.""" if self.is_encrypted: assert self.key_derivator block_key = self.key_derivator.get_block_key(block_number) encrypted_block = internal_backend.aes_cbc_encrypt( block_key, block_data) else: encrypted_block = block_data full_block = pack(f"<L{len(self.final_hash)}s{len(encrypted_block)}s", block_number, self.final_hash, encrypted_block) block_hash = internal_backend.hash(full_block, self.hash_type) self.final_hash = block_hash return full_block
def _get_rot_meta(used_root_cert: int, rot_pub_keys: List[str]) -> bytes: """Creates the RoT meta-data required by the device to corroborate. The meta-data is created by getting the public numbers (modulus and exponent) from each of the RoT public keys, hashing them and combing together. :return: binary representing the rot-meta data """ rot_meta = bytearray(128) for index, rot_key in enumerate(rot_pub_keys): rot = crypto.load_public_key(file_path=rot_key) assert isinstance(rot, crypto.RSAPublicKey) data = rsa_key_to_bytes(key=rot, exp_length=3, modulus_length=None) result = internal_backend.hash(data) rot_meta[index * 32:(index + 1) * 32] = result return bytes(rot_meta)
def info(self) -> str: """String representation of DebugCredential. :return: binary representation of the debug credential """ msg = f"Version : {self.VERSION}\n" msg += f"SOCC : {self.socc}\n" msg += f"UUID : {self.uuid.hex().upper()}\n" msg += f"CC_SOCC : {hex(self.cc_socu)}\n" msg += f"CC_VU : {hex(self.cc_vu)}\n" msg += f"BEACON : {self.cc_beacon}\n" ctrk_records_num = self.rot_meta[0] >> 4 if ctrk_records_num == 1: msg += "CRTK table not present \n" else: msg += f"CRTK table has {ctrk_records_num} entries\n" # Compute and show RKTH HASH key_length = 256 if (len(self.rot_meta) - 4) == 32 else 384 ctrk_hash = internal_backend.hash(data=self.rot_meta[4:], algorithm=f"sha{key_length}") msg += f"CRTK Hash: {ctrk_hash.hex()}" return msg
def parse( cls, data: bytes, offset: int = 0, kek: bytes = bytes(), plain_sections: bool = False, ) -> "BootImageV21": """Parse image from bytes. :param data: Raw data of parsed image :param offset: The offset of input data :param kek: The Key for unwrapping DEK and MAC keys (required) :param plain_sections: Sections are not encrypted; this is used only for debugging, not supported by ROM code :return: BootImageV21 parsed object :raises Exception: raised when header is in incorrect format :raises Exception: raised when signature is incorrect :raises SPSDKError: Raised when kek is empty :raises Exception: raised when header's nonce not present" """ if not kek: raise SPSDKError("kek cannot be empty") index = offset header_raw_data = data[index:index + ImageHeaderV2.SIZE] index += ImageHeaderV2.SIZE # TODO not used right now: hmac_data = data[index: index + cls.HEADER_MAC_SIZE] index += cls.HEADER_MAC_SIZE key_blob = data[index:index + cls.KEY_BLOB_SIZE] index += cls.KEY_BLOB_SIZE key_blob_unwrap = crypto_backend().aes_key_unwrap(kek, key_blob[:-8]) dek = key_blob_unwrap[:32] mac = key_blob_unwrap[32:] # Parse Header header = ImageHeaderV2.parse(header_raw_data) if header.offset_to_certificate_block != (index - offset): raise Exception() # Parse Certificate Block cert_block = CertBlockV2.parse(data, index) index += cert_block.raw_size # Verify Signature signature_index = index # The image may containt SHA, in such a case the signature is placed # after SHA. Thus we must shift the index by SHA size. if header.flags & BootImageV21.FLAGS_SHA_PRESENT_BIT: signature_index += BootImageV21.SHA_256_SIZE result = cert_block.verify_data( data[signature_index:signature_index + cert_block.signature_size], data[offset:signature_index], ) if not result: raise Exception() # Check flags, if 0x8000 bit is set, the SB file contains SHA-256 between # certificate and signature. if header.flags & BootImageV21.FLAGS_SHA_PRESENT_BIT: bootable_section_sha256 = data[index:index + BootImageV21.SHA_256_SIZE] index += BootImageV21.SHA_256_SIZE index += cert_block.signature_size # Check first Boot Section HMAC # TODO: not implemented yet # hmac_data_calc = crypto_backend().hmac(mac, data[index + CmdHeader.SIZE: index + CmdHeader.SIZE + ((2) * 32)]) # if hmac_data != hmac_data_calc: # raise Exception() if not header.nonce: raise SPSDKError("Header's nonce not present") counter = Counter(header.nonce) counter.increment(calc_cypher_block_count(index - offset)) boot_section = BootSectionV2.parse(data, index, dek=dek, mac=mac, counter=counter, plain_sect=plain_sections) if header.flags & BootImageV21.FLAGS_SHA_PRESENT_BIT: computed_bootable_section_sha256 = internal_backend.hash( data[index:], algorithm="sha256") if bootable_section_sha256 != computed_bootable_section_sha256: raise SPSDKError(desc=( "Error: invalid Bootable section SHA." f"Expected {bootable_section_sha256.decode('utf-8')}," f"got {computed_bootable_section_sha256.decode('utf-8')}")) adv_params = SBV2xAdvancedParams(dek=dek, mac=mac, nonce=header.nonce, timestamp=header.timestamp) obj = cls( kek=kek, product_version=str(header.product_version), component_version=str(header.component_version), build_number=header.build_number, advanced_params=adv_params, ) obj.cert_block = cert_block obj.add_boot_section(boot_section) return obj
def export(self, padding: Optional[bytes] = None, dbg_info: Optional[List[str]] = None) -> bytes: """Serialize image object. :param padding: header padding (8 bytes) for testing purpose; None to use random values (recommended) :param dbg_info: optional list, where debug info is exported in text form :return: exported bytes :raises SPSDKError: Raised when there is no boot section to be added :raises SPSDKError: Raised when certificate is not assigned :raises SPSDKError: Raised when private key is not assigned :raises SPSDKError: Raised when private header's nonce is invalid :raises SPSDKError: Raised when private key does not match certificate :raises SPSDKError: Raised when there is no debug info """ # validate params if not self._boot_sections: raise SPSDKError("At least one Boot Section must be added") if self.cert_block is None: raise SPSDKError("Certificate is not assigned") if self.private_key_pem_data is None: raise SPSDKError("Private key not assigned, cannot sign the image") # Update internals if dbg_info is not None: dbg_info.append("[sb_file]") bs_dbg_info: Optional[List[str]] = list() if dbg_info else None self.update() # Export Boot Sections bs_data = bytes() # TODO: implement helper method for get key size in bytes. Now is working only with internal backend bs_offset = (ImageHeaderV2.SIZE + self.HEADER_MAC_SIZE + self.KEY_BLOB_SIZE + self.cert_block.raw_size + self.cert_block.signature_size) if self.header.flags & self.FLAGS_SHA_PRESENT_BIT: bs_offset += self.SHA_256_SIZE if not self._header.nonce: raise SPSDKError("Invalid header's nonce") counter = Counter(self._header.nonce, calc_cypher_block_count(bs_offset)) for sect in self._boot_sections: bs_data += sect.export(dek=self.dek, mac=self.mac, counter=counter, dbg_info=bs_dbg_info) # Export Header signed_data = self._header.export(padding=padding) if dbg_info: dbg_info.append("[header]") dbg_info.append(signed_data.hex()) # Add HMAC data first_bs_hmac_count = self._boot_sections[0].hmac_count hmac_data = bs_data[CmdHeader.SIZE:CmdHeader.SIZE + (first_bs_hmac_count * 32) + 32] hmac = crypto_backend().hmac(self.mac, hmac_data) signed_data += hmac if dbg_info: dbg_info.append("[hmac]") dbg_info.append(hmac.hex()) # Add KeyBlob data key_blob = crypto_backend().aes_key_wrap(self.kek, self.dek + self.mac) key_blob += b"\00" * (self.KEY_BLOB_SIZE - len(key_blob)) signed_data += key_blob if dbg_info: dbg_info.append("[key_blob]") dbg_info.append(key_blob.hex()) # Add Certificates data signed_data += self.cert_block.export() if dbg_info: dbg_info.append("[cert_block]") dbg_info.append(self.cert_block.export().hex()) # Add SHA-256 of Bootable sections if requested if self.header.flags & self.FLAGS_SHA_PRESENT_BIT: signed_data += internal_backend.hash(bs_data) # Add Signature data if not self.cert_block.verify_private_key(self.private_key_pem_data): raise SPSDKError("Private key does not match certificate") signature = crypto_backend().rsa_sign(self.private_key_pem_data, signed_data) if dbg_info: dbg_info.append("[signature]") dbg_info.append(signature.hex()) dbg_info.append("[boot_sections]") if not bs_dbg_info: raise SPSDKError("No debug information") dbg_info.extend(bs_dbg_info) return signed_data + signature + bs_data
def test_hash(): plain_text = b'testestestestestestestestestestestestestestestestestestestest' text_sha256 = unhexlify( "41116FE4EFB90A050AABB83419E19BF2196A0E76AB8E3034C8D674042EE23621") calc_sha256 = internal_backend.hash(plain_text, 'sha256') assert calc_sha256 == text_sha256
def test_hash_invalid(): with pytest.raises(SPSDKError): internal_backend.hash(data=b"n", algorithm="")