def test_otfad(data_dir): """Test OTFAD generator""" otfad = Otfad() key = bytes.fromhex("B1A0C56AF31E98CD6936A79D9E6F829D") counter = bytes.fromhex("5689fab8b4bfb264") key_blob = KeyBlob(start_addr=0x08001000, end_addr=0x0800F3FF, key=key, counter_iv=counter) otfad.add_key_blob(key_blob) assert otfad[0] == key_blob with open(os.path.join(data_dir, "boot_image.bin"), "rb") as f: image = f.read() # invalid address with pytest.raises(SPSDKError): key_blob.encrypt_image(0x0, image, True) encr_image = otfad.encrypt_image(image, 0x08001000, True) otfad.encrypt_image(image, 0x08001000, False) # TODO finish the test with open(os.path.join(data_dir, "otfad_image.bin"), "rb") as f: otfad_image = f.read() assert encr_image == otfad_image otfad.info()
def _encrypt(cmd_args: dict) -> CmdLoad: """Returns a CmdLoad object initialized based on cmd_args. Encrypt holds an ID, which is a reference to keyblob to be used for encryption. So the encrypt command requires a list of keyblobs, the keyblob ID and load command. e.g. encrypt (0){ load myImage > 0x0810000; } :param cmd_args: dictionary holding list of keyblobs, keyblob ID and load dict :raises SPSDKError: If keyblob to be used is not in the list or is invalid :return: CmdLoad object """ keyblob_id = cmd_args["keyblob_id"] keyblobs = cmd_args.get("keyblobs", []) load_dict = cmd_args.get("load", {}) address = load_dict["address"] if load_dict.get("file"): data = load_binary(load_dict["file"]) if load_dict.get("values"): values = [int(s, 16) for s in load_dict["values"].split(",")] data = struct.pack(f"<{len(values)}L", *values) try: valid_keyblob = _validate_keyblob(keyblobs, keyblob_id) except SPSDKError as exc: raise SPSDKError(f"Invalid key blob {str(exc)}") from exc if valid_keyblob is None: raise SPSDKError(f"Missing keyblob {keyblob_id} for encryption.") start_addr = valid_keyblob["keyblob_content"][0]["start"] end_addr = valid_keyblob["keyblob_content"][0]["end"] key = bytes.fromhex(valid_keyblob["keyblob_content"][0]["key"]) counter = bytes.fromhex(valid_keyblob["keyblob_content"][0]["counter"]) byte_swap = valid_keyblob["keyblob_content"][0].get("byte_swap", False) keyblob = KeyBlob(start_addr=start_addr, end_addr=end_addr, key=key, counter_iv=counter) encoded_data = keyblob.encrypt_image(base_address=address, data=data, byte_swap=byte_swap) return CmdLoad(address, encoded_data)
def gen_boot_section_otfad() -> BootSectionV2: """Generate a Boot Section with content encrypted by OTFAD.""" with open(f'{DATA_DIR}/boot_image.bin', 'rb') as boot_image_file: boot_data = boot_image_file.read() otfad = Otfad() key = bytes.fromhex('B1A0C56AF31E98CD6936A79D9E6F829D') counter = bytes.fromhex("5689fab8b4bfb264") otfad.add_key_blob( KeyBlob(0x08001000, 0x0800F3FF, key, counter, zero_fill=bytes(4), crc=bytes( 4))) # zero_fill and crc should be used only for testing ! enc_image = otfad.encrypt_image(boot_data, 0x08001000, True) key_blobs = otfad.encrypt_key_blobs( kek=bytes.fromhex('50F66BB4F23B855DCD8FEFC0DA59E963')) assert len(key_blobs) == 256 boot_section = BootSectionV2(0, CmdErase(address=0x08001000, length=0x0800F000 - 0x08001000), CmdLoad(address=0x08001000, data=enc_image), CmdLoad(address=0x08000000, data=key_blobs), CmdReset(), hmac_count=10) return boot_section
def _keywrap(cmd_args: dict) -> CmdLoad: """Returns a CmdLoad object initialized based on cmd_args. Keywrap holds keyblob ID to be encoded by a value stored in load command and stored to address defined in the load command. e.g. keywrap (0) { load {{ 00000000 }} > 0x08000000; } :param cmd_args: dictionary holding list of keyblobs, keyblob ID and load dict :raises SPSDKError: If keyblob to be used is not in the list or is invalid :return: CmdLoad object """ # iterate over keyblobs keyblobs = cmd_args.get("keyblobs", None) keyblob_id = cmd_args.get("keyblob_id", None) load_info = cmd_args.get("load", None) address = load_info.get("address") otfad_key = load_info.get("values") try: valid_keyblob = _validate_keyblob(keyblobs, keyblob_id) except SPSDKError as exc: raise SPSDKError(f" Key blob validation failed: {str(exc)}") from exc if valid_keyblob is None: raise SPSDKError(f"Missing keyblob {keyblob_id} for given keywrap") start_addr = valid_keyblob["keyblob_content"][0]["start"] end_addr = valid_keyblob["keyblob_content"][0]["end"] key = bytes.fromhex(valid_keyblob["keyblob_content"][0]["key"]) counter = bytes.fromhex(valid_keyblob["keyblob_content"][0]["counter"]) blob = KeyBlob(start_addr=start_addr, end_addr=end_addr, key=key, counter_iv=counter) encoded_keyblob = blob.export(kek=otfad_key) print("creating wrapped keyblob") return CmdLoad(address=address, data=encoded_keyblob)
def test_oftad_invalid(data_dir): """Test OTFAD - image address range does not match to key blob""" otfad = Otfad() key = bytes.fromhex("B1A0C56AF31E98CD6936A79D9E6F829D") counter = bytes.fromhex("5689fab8b4bfb264") key_blob = KeyBlob(start_addr=0x08001000, end_addr=0x0800F3FF, key=key, counter_iv=counter) otfad.add_key_blob(key_blob) assert otfad[0] == key_blob with open(os.path.join(data_dir, "boot_image.bin"), "rb") as f: image = f.read() with pytest.raises(SPSDKError): otfad.encrypt_image(image, 0x0800FFF, True)
def gen_boot_section_otfad() -> BootSectionV2: """Generate a Boot Section with content encrypted by OTFAD. :raises SPSDKError: When length of key blobs is not 256 """ with open(f"{DATA_DIR}/boot_image.bin", "rb") as boot_image_file: boot_data = boot_image_file.read() otfad = Otfad() key = bytes.fromhex("B1A0C56AF31E98CD6936A79D9E6F829D") counter = bytes.fromhex("5689fab8b4bfb264") otfad.add_key_blob( KeyBlob( 0x08001000, 0x0800F3FF, key, counter, zero_fill=bytes(4), crc=bytes(4), )) # zero_fill and crc should be used only for testing ! enc_image = otfad.encrypt_image(boot_data, 0x08001000, True) key_blobs = otfad.encrypt_key_blobs( kek=bytes.fromhex("50F66BB4F23B855DCD8FEFC0DA59E963")) if len(key_blobs) != 256: raise SPSDKError("Length of key blobs is not 256") boot_section = BootSectionV2( 0, CmdErase(address=0x08001000, length=0x0800F000 - 0x08001000), CmdLoad(address=0x08001000, data=enc_image), CmdLoad(address=0x08000000, data=key_blobs), CmdReset(), hmac_count=10, ) return boot_section
def test_otfad_keyblob(data_dir): """Test generation of key blob for OTFAD""" # generate key blob using random keys key_blob = KeyBlob(start_addr=0x08001000, end_addr=0x0800F3FF) gen_blob = key_blob.export( kek=bytes.fromhex("50F66BB4F23B855DCD8FEFC0DA59E963")) assert gen_blob is not None # generate key blob using fixed keys key = bytes.fromhex("B1A0C56AF31E98CD6936A79D9E6F829D") counter = bytes.fromhex("5689fab8b4bfb264") zeros = bytes( 4 ) # zero_fill and crc are '0' just for this test; in reality should be random key_blob = KeyBlob( start_addr=0x08001000, end_addr=0x0800F3FF, key=key, counter_iv=counter, zero_fill=zeros, crc=zeros, ) gen_blob = key_blob.export( kek=bytes.fromhex("50F66BB4F23B855DCD8FEFC0DA59E963")) with open(os.path.join(data_dir, "otfad_keyblob.bin"), "rb") as f: keyblob_bin = f.read() assert gen_blob == keyblob_bin # check that info produces non-empty text assert key_blob.info() # test image encryption with open(os.path.join(data_dir, "boot_image.bin"), "rb") as f: plain_image = f.read() encr_image = key_blob.encrypt_image(0x08001000, plain_image, True) with open(os.path.join(data_dir, "otfad_image.bin"), "rb") as f: otfad_image = f.read() assert encr_image == otfad_image # check key blob is created with random bytes for zero_fill and crc key_blob = KeyBlob(start_addr=0x08001000, end_addr=0x0800F3FF, key=key, counter_iv=counter) gen_blob = key_blob.export( kek=bytes.fromhex("50F66BB4F23B855DCD8FEFC0DA59E963")) assert gen_blob != keyblob_bin # start address not aligned with pytest.raises(SPSDKError): KeyBlob(start_addr=0x08001001, end_addr=0x0800F3FF, key=key, counter_iv=counter) # end address not aligned with pytest.raises(SPSDKError): KeyBlob(start_addr=0x08001000, end_addr=0x0800F000, key=key, counter_iv=counter) # address of the image is not within key blob key_blob = KeyBlob(start_addr=0x08001000, end_addr=0x0800F3FF, key=key, counter_iv=counter) with pytest.raises(SPSDKError): key_blob.encrypt_image(0x8000000, plain_image, True) with pytest.raises(SPSDKError): key_blob.encrypt_image(0x800F000, plain_image, True) with pytest.raises(SPSDKError, match="Invalid start address"): key_blob.encrypt_image(0x800F001, plain_image, True)
def test_keyblob_invalid(): with pytest.raises(SPSDKError, match="Invalid start/end address"): KeyBlob(start_addr=0x08001000, end_addr=0x08000000) key = bytes.fromhex("B1") counter_iv = bytes.fromhex("53") with pytest.raises(SPSDKError, match="Invalid key"): KeyBlob(key=key, counter_iv=counter_iv, start_addr=0x08001000, end_addr=0x0800F3FF) with pytest.raises(SPSDKError, match="key_flags exceeds mask "): KeyBlob(start_addr=0x08000000, end_addr=0x080003FF, key_flags=0x8) counter = bytes.fromhex("5689fab8b4bfb264") key = bytes.fromhex("B1A0C56AF31E98CD6936A79D9E6F829D") key_blob = KeyBlob(start_addr=0x08001000, end_addr=0x0800F3FF, key=key, counter_iv=counter) with pytest.raises(SPSDKError, match="Invalid length of kek"): key_blob.export(kek=bytes(15)) with pytest.raises(SPSDKError, match="Invalid value crc"): key_blob = KeyBlob(start_addr=0x08001000, end_addr=0x080013FF, crc=bytes(5)) key_blob.export(kek=bytes(16)) with pytest.raises(SPSDKError, match="Invalid value"): key_blob = KeyBlob(start_addr=0x08001000, end_addr=0x080013FF, zero_fill=bytes(5)) key_blob.export(kek=bytes(16)) with pytest.raises(SPSDKError, match="Invalid length of initialization vector"): key_blob = KeyBlob(start_addr=0x08001000, end_addr=0x080013FF) key_blob.export(kek=bytes(16), iv=bytes(32)) with pytest.raises(SPSDKError, match="Invalid length of data to be encrypted"): key_blob = KeyBlob(start_addr=0x08001000, end_addr=0x080013FF) key_blob._EXPORT_NBLOCKS_5 = 90 key_blob.export(kek=bytes(16)) key_blob = KeyBlob(start_addr=0x08001000, end_addr=0x080013FF, counter_iv=bytes(8)) key_blob.ctr_init_vector = bytes(99) with pytest.raises(SPSDKError, match="Invalid length of counter init"): key_blob._get_ctr_nonce()
def test_sb_otfad_keystore(data_dir: str, subdir: str, image_name: str, secure: bool) -> None: """Test creation of SB file for RT5xx with OTFAD encrypted image. SBKEK Key for SB file is stored in KEYSTORE. :param data_dir: absolute path of the directory with data files for the test :param image_name: file name of the signed image WITHOUT file extension :param secure: whether security should be enabled """ if not TEST_IMG_CONTENT: secure_boot_en = 0x900000 if secure else 0 # BOOT_CFG[0]: SECURE_BOOT_EN=? write_shadow_regis( data_dir, [ (0x40130194, 0x00000080), # BOOT_CFG[5]: USE_PUF = 1 (0x401301A8, 0x00001000), # OTFAD CFG (0x40130180, 0x00000010 + secure_boot_en), # BOOT_CFG[0]: DEFAULT_ISP = 1(USB) ], ) with open(os.path.join(data_dir, KEYSTORE_SUBDIR, "SBkek_PUF.txt"), "r") as f: sbkek_str = f.readline() key_store = get_keystore(data_dir) adv_params = SBV2xAdvancedParams( dek=b"\xA0" * 32, mac=b"\x0B" * 32, nonce=bytes(16), timestamp=datetime(2020, month=1, day=31, hour=0, tzinfo=timezone.utc), ) # create SB file boot image boot_image = BootImageV21( kek=bytes.fromhex(sbkek_str), product_version="1.0.0", component_version="1.0.0", build_number=1, # parameters fixed for test only (to have always same output), do not use in production advanced_params=adv_params, flags=0x0008, ) # certificate + private key cert_block, priv_key_pem_data = create_cert_block(data_dir) boot_image.cert_block = cert_block boot_image.private_key_pem_data = priv_key_pem_data fcb_data = load_binary(data_dir, FCB_FILE_NAME) plain_image_data = load_binary(data_dir, subdir, image_name + ".bin") # images are aligned for test purposes only, otherwise export will align with random data fcb_data = align_block(fcb_data, 16) plain_image_data = align_block(plain_image_data, 16) otfad = Otfad() # keys used to encrypt image, for RT5xx always define 4 key blobs!! key = bytes.fromhex("B1A0C56AF31E98CD6936A79D9E6F829D") counter = bytes.fromhex("5689fab8b4bfb264") otfad.add_key_blob( KeyBlob(0x8001000, 0x80FFFFF, key, counter, zero_fill=bytes(4), crc=bytes( 4))) # zero_fill and crc should be used only for testing ! # to use random keys: otfad.add_key_blob(KeyBlob(0x8001000, 0x80FFFFF)) otfad.add_key_blob( KeyBlob(0x8FFD000, 0x8FFDFFF, key, counter, zero_fill=bytes(4), crc=bytes( 4))) # zero_fill and crc should be used only for testing ! otfad.add_key_blob( KeyBlob(0x8FFE000, 0x8FFEFFF, key, counter, zero_fill=bytes(4), crc=bytes( 4))) # zero_fill and crc should be used only for testing ! otfad.add_key_blob( KeyBlob(0x8FFF000, 0x8FFFFFF, key, counter, zero_fill=bytes(4), crc=bytes( 4))) # zero_fill and crc should be used only for testing ! encr_image_data = otfad.encrypt_image(plain_image_data, 0x8001000, False) with open(os.path.join(data_dir, KEYSTORE_SUBDIR, "OTFADKek_PUF.txt"), "r") as f: otfad_kek = f.readline() # create boot section 0 boot_section = BootSectionV2( 0, # configure external FLASH CmdFill(address=0x10C000, pattern=int("063040C0", 16)), CmdMemEnable(0x10C000, 4, ExtMemId.FLEX_SPI_NOR), # erase the FLASH CmdErase(address=0x8000000, length=0x10000), # load key blobs allowing th decrypt the image CmdLoad(address=0x8000000, data=otfad.encrypt_key_blobs(kek=otfad_kek)), # load FCB data CmdLoad(address=0x8000400, data=fcb_data), # load key-store CmdLoad(address=0x8000800, data=key_store.export()), # load encrypted image CmdLoad(address=0x8001000, data=encr_image_data), ) boot_image.add_boot_section(boot_section) dbg_info = list() # debug log for analysis of the binary output content sb_file = boot_image.export( padding=bytes(8), dbg_info=dbg_info) # padding for unit test only, to avoid random data write_dbg_log(data_dir, image_name + "_otfad_keystore.sb", dbg_info, TEST_IMG_CONTENT) write_sb(data_dir, image_name + "_otfad_keystore.sb", sb_file, key_store)
def test_sb_otfad_otp(data_dir: str, subdir: str, image_name: str, secure: bool) -> None: """Test creation of SB file for RT5xx with OTFAD encrypted image. SBKEK Key for SB file is derived from master key in OTP. :param data_dir: absolute path of the directory with data files for the test :param image_name: file name of the signed image WITHOUT file extension :param secure: whether security should be enabled """ if not TEST_IMG_CONTENT: secure_boot_en = 0x900000 if secure else 0 # BOOT_CFG[0]: SECURE_BOOT_EN=? write_shadow_regis( data_dir, [ (0x401301A8, 0x00001000), # OTFAD CFG # OTFAD KEY INPUT - 12aaaaaabb34bbbbcccc56ccdddddd78 (0x401301B0, 0xAAAAAA12), (0x401301B4, 0xBBBB34BB), (0x401301B8, 0xCC56CCCC), (0x401301BC, 0x78DDDDDD), ], ) write_shadow_regis( data_dir, [ # MASTER KEY - 000102030405060708090a0b0c0d0e0f00112233445566778899aabbccddeeff (0x401301C0, 0xCCDDEEFF), (0x401301C4, 0x8899AABB), (0x401301C8, 0x44556677), (0x401301CC, 0x00112233), (0x401301D0, 0x0C0D0E0F), (0x401301D4, 0x08090A0B), (0x401301D8, 0x04050607), (0x401301DC, 0x00010203), # BOOT_CFG[0]: DEFAULT_ISP = 1(USB) (0x40130180, 0x00000010 + secure_boot_en), ], ) sbkek = KeyStore.derive_sb_kek_key(bytes.fromhex(MASTER_KEY)) otfad_kek = KeyStore.derive_otfad_kek_key( bytes.fromhex(MASTER_KEY), bytes.fromhex("12aaaaaabb34bbbbcccc56ccdddddd78")) key_store = get_keystore(data_dir) adv_params = SBV2xAdvancedParams( dek=b"\xA0" * 32, mac=b"\x0B" * 32, nonce=bytes(16), timestamp=datetime(2020, month=1, day=31, hour=0, tzinfo=timezone.utc), ) # create SB file boot image boot_image = BootImageV21( kek=sbkek, product_version="1.0.0", component_version="1.0.0", build_number=1, # parameters fixed for test only (to have always same output), do not use in production advanced_params=adv_params, flags=0x0008, ) # certificate + private key cert_block, priv_key_pem_data = create_cert_block(data_dir) boot_image.cert_block = cert_block boot_image.private_key_pem_data = priv_key_pem_data fcb_data = load_binary(data_dir, FCB_FILE_NAME) plain_image_data = load_binary(data_dir, subdir, image_name + ".bin") # images are aligned for test purposes only, otherwise export will align with random data fcb_data = align_block(fcb_data, 16) plain_image_data = align_block(plain_image_data, 16) otfad = Otfad() # keys used to encrypt image, for RT5xx always define 4 key blobs!! key = bytes.fromhex("B1A0C56AF31E98CD6936A79D9E6F829D") counter = bytes.fromhex("5689fab8b4bfb264") otfad.add_key_blob( KeyBlob(0x8001000, 0x80FFFFF, key, counter, zero_fill=bytes(4), crc=bytes( 4))) # zero_fill and crc should be used only for testing ! # to use random keys: otfad.add_key_blob(KeyBlob(0x8001000, 0x80FFFFF)) otfad.add_key_blob( KeyBlob(0x8FFD000, 0x8FFDFFF, key, counter, zero_fill=bytes(4), crc=bytes( 4))) # zero_fill and crc should be used only for testing ! otfad.add_key_blob( KeyBlob(0x8FFE000, 0x8FFEFFF, key, counter, zero_fill=bytes(4), crc=bytes( 4))) # zero_fill and crc should be used only for testing ! otfad.add_key_blob( KeyBlob(0x8FFF000, 0x8FFFFFF, key, counter, zero_fill=bytes(4), crc=bytes( 4))) # zero_fill and crc should be used only for testing ! encr_image_data = otfad.encrypt_image(plain_image_data, 0x8001000, False) # create boot section 0 boot_section = BootSectionV2( 0, # configure external FLASH CmdFill(address=0x10C000, pattern=int("063040C0", 16)), CmdMemEnable(0x10C000, 4, ExtMemId.FLEX_SPI_NOR), # erase the FLASH CmdErase(address=0x8000000, length=0x10000), # load key blobs allowing th decrypt the image CmdLoad(address=0x8000000, data=otfad.encrypt_key_blobs(kek=otfad_kek)), # load FCB data CmdLoad(address=0x8000400, data=fcb_data), # load key-store CmdLoad(address=0x8000800, data=key_store.export()), # load encrypted image CmdLoad(address=0x8001000, data=encr_image_data), ) boot_image.add_boot_section(boot_section) dbg_info = list() # debug log for analysis of the binary output content sb_file = boot_image.export( padding=bytes(8), dbg_info=dbg_info) # padding for unit test only, to avoid random data write_dbg_log(data_dir, image_name + "_otfad_otp.sb", dbg_info, TEST_IMG_CONTENT) write_sb(data_dir, image_name + "_otfad_otp.sb", sb_file, key_store)
def get_boot_sections(data_dir: str, otfad: bool, sect_cont: SectionsContent, load_addr: int) -> List[BootSectionV2]: """Create list of boot sections for SB 2.x file :param data_dir: absolute path to load boot image :param otfad: True to encrypt section with OTFAD; False otherwise :param sect_cont: sections content to test :param load_addr: address where to load the image (for simple section) :return: """ result = list() # load input image (binary) with open(os.path.join(data_dir, 'sb2_x', 'boot_image.bin'), 'rb') as f: plain_image = f.read() # OTFAD key_blobs_data = list() if otfad: otfad = Otfad() # key blob 0 key = bytes.fromhex('B1A0C56AF31E98CD6936A79D9E6F829D') counter = bytes.fromhex("5689fab8b4bfb264") key_blob = KeyBlob(0x08001000, 0x0800F3FF, key, counter, zero_fill=bytes(4), crc=bytes(4)) # zero_fill and crc should be used only for testing ! otfad.add_key_blob(key_blob) key_blobs_data = list() key_blobs_data.append(key_blob.export(kek=bytes.fromhex('50F66BB4F23B855DCD8FEFC0DA59E963'))) # verify `otfad.encrypt_key_blobs` returns the same assert key_blobs_data[0] == otfad.encrypt_key_blobs(kek=bytes.fromhex('50F66BB4F23B855DCD8FEFC0DA59E963'))[:64] # key blob 1 if sect_cont == SectionsContent.ADVANCED: key = bytes.fromhex('12345678901234567890123456789012') counter = bytes.fromhex("0011223344556677") key_blob1 = KeyBlob(0x08010000, 0x0801F3FF, key, counter, zero_fill=bytes(4), crc=bytes(4)) # zero_fill and crc should be used only for testing ! otfad.add_key_blob(key_blob1) key_blobs_data.append(key_blob1.export(kek=bytes.fromhex('0123456789ABCDEF0123456789ABCDEF'))) # encrypted image encr_image = otfad.encrypt_image(plain_image, load_addr, True) else: encr_image = plain_image if sect_cont == SectionsContent.ADVANCED: # add boot sections 1 - advanced boot_section2 = BootSectionV2( 1, CmdErase(address=0, length=0x2800), CmdLoad(address=0x10000000, data=plain_image), CmdLoad(address=0x20000000, data=plain_image), CmdCall(0xffff0000), CmdJump(0x12345678), CmdReset(), hmac_count=5) assert boot_section2.uid == 1 result.append(boot_section2) # create boot section 0 if sect_cont == SectionsContent.NEW_CMDS: boot_section0 = BootSectionV2( 0, CmdVersionCheck(VersionCheckType.SECURE_VERSION, 0x16), CmdVersionCheck(VersionCheckType.NON_SECURE_VERSION, 15263), CmdErase(address=0, length=0x2800), CmdLoad(address=load_addr, data=encr_image), CmdKeyStoreBackup(0x12345678, 3), CmdKeyStoreRestore(0x12345678, 3), hmac_count=1) else: boot_section0 = BootSectionV2( 0, CmdErase(address=0, length=0x2800), CmdLoad(address=load_addr, data=encr_image), hmac_count=10) for index, key_blob_data in enumerate(key_blobs_data): key_blob_aligned = align_block(key_blob_data, 256) # it seems key-blob from elf-to-sb is aligned to 256 boot_section0.append(CmdLoad(address=0x8000000 + 0x100 * index, data=key_blob_aligned)) boot_section0.append(CmdReset()) result.append(boot_section0) return result