diff options
Diffstat (limited to 'tools/binman/ftest.py')
-rw-r--r-- | tools/binman/ftest.py | 306 |
1 files changed, 289 insertions, 17 deletions
diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index d74aa90a620..43b4f850a69 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -34,10 +34,10 @@ from dtoc import fdt_util from binman.etype import fdtmap from binman.etype import image_header from binman.image import Image -from patman import command -from patman import test_util -from patman import tools -from patman import tout +from u_boot_pylib import command +from u_boot_pylib import test_util +from u_boot_pylib import tools +from u_boot_pylib import tout # Contents of test files, corresponding to different entry types U_BOOT_DATA = b'1234' @@ -709,6 +709,14 @@ class TestFunctional(unittest.TestCase): AddNode(dtb.GetRoot(), '') return tree + def _CheckSign(self, fit, key): + try: + tools.run('fit_check_sign', '-k', key, '-f', fit) + except: + self.fail('Expected signed FIT container') + return False + return True + def testRun(self): """Test a basic run with valid args""" result = self._RunBinman('-h') @@ -1704,7 +1712,7 @@ class TestFunctional(unittest.TestCase): self.assertEqual(b'SECTION0', fentry.name) self.assertEqual(0, fentry.offset) self.assertEqual(16, fentry.size) - self.assertEqual(0, fentry.flags) + self.assertEqual(fmap_util.FMAP_AREA_PRESERVE, fentry.flags) fentry = next(fiter) self.assertEqual(b'RO_U_BOOT', fentry.name) @@ -1752,7 +1760,7 @@ class TestFunctional(unittest.TestCase): def _HandleGbbCommand(self, pipe_list): """Fake calls to the futility utility""" - if pipe_list[0][0] == 'futility': + if 'futility' in pipe_list[0][0]: fname = pipe_list[0][-1] # Append our GBB data to the file, which will happen every time the # futility command is called. @@ -1814,7 +1822,7 @@ class TestFunctional(unittest.TestCase): self._hash_data is False, it writes VBLOCK_DATA, else it writes a hash of the input data (here, 'input.vblock'). """ - if pipe_list[0][0] == 'futility': + if 'futility' in pipe_list[0][0]: fname = pipe_list[0][3] with open(fname, 'wb') as fd: if self._hash_data: @@ -4001,9 +4009,17 @@ class TestFunctional(unittest.TestCase): self.assertEqual(expected, data[image_pos:image_pos+size]) def testFitMissing(self): + """Test that binman complains if mkimage is missing""" + with self.assertRaises(ValueError) as e: + self._DoTestFile('162_fit_external.dts', + force_missing_bintools='mkimage') + self.assertIn("Node '/binman/fit': Missing tool: 'mkimage'", + str(e.exception)) + + def testFitMissingOK(self): """Test that binman still produces a FIT image if mkimage is missing""" with test_util.capture_sys_output() as (_, stderr): - self._DoTestFile('162_fit_external.dts', + self._DoTestFile('162_fit_external.dts', allow_missing=True, force_missing_bintools='mkimage') err = stderr.getvalue() self.assertRegex(err, "Image 'image'.*missing bintools.*: mkimage") @@ -5813,13 +5829,61 @@ fdt fdtmap Extract the devicetree blob from the fdtmap self.assertEqual(expected_fdtmap, fdtmap) def testReplaceSectionSimple(self): - """Test replacing a simple section with arbitrary data""" + """Test replacing a simple section with same-sized data""" new_data = b'w' * len(COMPRESS_DATA + U_BOOT_DATA) - with self.assertRaises(ValueError) as exc: - self._RunReplaceCmd('section', new_data, - dts='241_replace_section_simple.dts') + data, expected_fdtmap, image = self._RunReplaceCmd('section', + new_data, dts='241_replace_section_simple.dts') + self.assertEqual(new_data, data) + + entries = image.GetEntries() + self.assertIn('section', entries) + entry = entries['section'] + self.assertEqual(len(new_data), entry.size) + + def testReplaceSectionLarger(self): + """Test replacing a simple section with larger data""" + new_data = b'w' * (len(COMPRESS_DATA + U_BOOT_DATA) + 1) + data, expected_fdtmap, image = self._RunReplaceCmd('section', + new_data, dts='241_replace_section_simple.dts') + self.assertEqual(new_data, data) + + entries = image.GetEntries() + self.assertIn('section', entries) + entry = entries['section'] + self.assertEqual(len(new_data), entry.size) + fentry = entries['fdtmap'] + self.assertEqual(entry.offset + entry.size, fentry.offset) + + def testReplaceSectionSmaller(self): + """Test replacing a simple section with smaller data""" + new_data = b'w' * (len(COMPRESS_DATA + U_BOOT_DATA) - 1) + b'\0' + data, expected_fdtmap, image = self._RunReplaceCmd('section', + new_data, dts='241_replace_section_simple.dts') + self.assertEqual(new_data, data) + + # The new size is the same as the old, just with a pad byte at the end + entries = image.GetEntries() + self.assertIn('section', entries) + entry = entries['section'] + self.assertEqual(len(new_data), entry.size) + + def testReplaceSectionSmallerAllow(self): + """Test failing to replace a simple section with smaller data""" + new_data = b'w' * (len(COMPRESS_DATA + U_BOOT_DATA) - 1) + try: + state.SetAllowEntryContraction(True) + with self.assertRaises(ValueError) as exc: + self._RunReplaceCmd('section', new_data, + dts='241_replace_section_simple.dts') + finally: + state.SetAllowEntryContraction(False) + + # Since we have no information about the position of things within the + # section, we cannot adjust the position of /section-u-boot so it ends + # up outside the section self.assertIn( - "Node '/section': Replacing sections is not implemented yet", + "Node '/section/u-boot': Offset 0x24 (36) size 0x4 (4) is outside " + "the section '/section' starting at 0x0 (0) of size 0x27 (39)", str(exc.exception)) def testMkimageImagename(self): @@ -6355,10 +6419,11 @@ fdt fdtmap Extract the devicetree blob from the fdtmap 'tee-os-path': 'missing.bin', } test_subdir = os.path.join(self._indir, TEST_FDT_SUBDIR) - data = self._DoReadFileDtb( - '276_fit_firmware_loadables.dts', - entry_args=entry_args, - extra_indirs=[test_subdir])[0] + with test_util.capture_sys_output() as (stdout, stderr): + data = self._DoReadFileDtb( + '276_fit_firmware_loadables.dts', + entry_args=entry_args, + extra_indirs=[test_subdir])[0] dtb = fdt.Fdt.FromData(data) dtb.Scan() @@ -6388,6 +6453,128 @@ fdt fdtmap Extract the devicetree blob from the fdtmap self.assertEqual(['u-boot', 'atf-2'], fdt_util.GetStringList(node, 'loadables')) + def testTooldir(self): + """Test that we can specify the tooldir""" + with test_util.capture_sys_output() as (stdout, stderr): + self.assertEqual(0, self._DoBinman('--tooldir', 'fred', + 'tool', '-l')) + self.assertEqual('fred', bintool.Bintool.tooldir) + + # Check that the toolpath is updated correctly + self.assertEqual(['fred'], tools.tool_search_paths) + + # Try with a few toolpaths; the tooldir should be at the end + with test_util.capture_sys_output() as (stdout, stderr): + self.assertEqual(0, self._DoBinman( + '--toolpath', 'mary', '--toolpath', 'anna', '--tooldir', 'fred', + 'tool', '-l')) + self.assertEqual(['mary', 'anna', 'fred'], tools.tool_search_paths) + + def testReplaceSectionEntry(self): + """Test replacing an entry in a section""" + expect_data = b'w' * len(U_BOOT_DATA + COMPRESS_DATA) + entry_data, expected_fdtmap, image = self._RunReplaceCmd('section/blob', + expect_data, dts='241_replace_section_simple.dts') + self.assertEqual(expect_data, entry_data) + + entries = image.GetEntries() + self.assertIn('section', entries) + section = entries['section'] + + sect_entries = section.GetEntries() + self.assertIn('blob', sect_entries) + entry = sect_entries['blob'] + self.assertEqual(len(expect_data), entry.size) + + fname = tools.get_output_filename('image-updated.bin') + data = tools.read_file(fname) + + new_blob_data = data[entry.image_pos:entry.image_pos + len(expect_data)] + self.assertEqual(expect_data, new_blob_data) + + self.assertEqual(U_BOOT_DATA, + data[entry.image_pos + len(expect_data):] + [:len(U_BOOT_DATA)]) + + def testReplaceSectionDeep(self): + """Test replacing an entry in two levels of sections""" + expect_data = b'w' * len(U_BOOT_DATA + COMPRESS_DATA) + entry_data, expected_fdtmap, image = self._RunReplaceCmd( + 'section/section/blob', expect_data, + dts='278_replace_section_deep.dts') + self.assertEqual(expect_data, entry_data) + + entries = image.GetEntries() + self.assertIn('section', entries) + section = entries['section'] + + subentries = section.GetEntries() + self.assertIn('section', subentries) + section = subentries['section'] + + sect_entries = section.GetEntries() + self.assertIn('blob', sect_entries) + entry = sect_entries['blob'] + self.assertEqual(len(expect_data), entry.size) + + fname = tools.get_output_filename('image-updated.bin') + data = tools.read_file(fname) + + new_blob_data = data[entry.image_pos:entry.image_pos + len(expect_data)] + self.assertEqual(expect_data, new_blob_data) + + self.assertEqual(U_BOOT_DATA, + data[entry.image_pos + len(expect_data):] + [:len(U_BOOT_DATA)]) + + def testReplaceFitSibling(self): + """Test an image with a FIT inside where we replace its sibling""" + fname = TestFunctional._MakeInputFile('once', b'available once') + self._DoReadFileRealDtb('277_replace_fit_sibling.dts') + os.remove(fname) + + try: + tmpdir, updated_fname = self._SetupImageInTmpdir() + + fname = os.path.join(tmpdir, 'update-blob') + expected = b'w' * (len(COMPRESS_DATA + U_BOOT_DATA) + 1) + tools.write_file(fname, expected) + + self._DoBinman('replace', '-i', updated_fname, 'blob', '-f', fname) + data = tools.read_file(updated_fname) + start = len(U_BOOT_DTB_DATA) + self.assertEqual(expected, data[start:start + len(expected)]) + map_fname = os.path.join(tmpdir, 'image-updated.map') + self.assertFalse(os.path.exists(map_fname)) + finally: + shutil.rmtree(tmpdir) + + def testX509Cert(self): + """Test creating an X509 certificate""" + keyfile = self.TestFile('key.key') + entry_args = { + 'keyfile': keyfile, + } + data = self._DoReadFileDtb('279_x509_cert.dts', + entry_args=entry_args)[0] + cert = data[:-4] + self.assertEqual(U_BOOT_DATA, data[-4:]) + + # TODO: verify the signature + + def testX509CertMissing(self): + """Test that binman still produces an image if openssl is missing""" + keyfile = self.TestFile('key.key') + entry_args = { + 'keyfile': 'keyfile', + } + with test_util.capture_sys_output() as (_, stderr): + self._DoTestFile('279_x509_cert.dts', + force_missing_bintools='openssl', + entry_args=entry_args) + err = stderr.getvalue() + self.assertRegex(err, "Image 'image'.*missing bintools.*: openssl") + def testPackRockchipTpl(self): """Test that an image with a Rockchip TPL binary can be created""" data = self._DoReadFile('277_rockchip_tpl.dts') @@ -6404,6 +6591,91 @@ fdt fdtmap Extract the devicetree blob from the fdtmap self._DoTestFile('278_mkimage_missing_multiple.dts', allow_missing=False) self.assertIn("not found in input path", str(e.exception)) + def _PrepareSignEnv(self, dts='280_fit_sign.dts'): + """Prepare sign environment + + Create private and public keys, add pubkey into dtb. + + Returns: + Tuple: + FIT container + Image name + Private key + DTB + """ + + data = self._DoReadFileRealDtb(dts) + updated_fname = tools.get_output_filename('image-updated.bin') + tools.write_file(updated_fname, data) + dtb = tools.get_output_filename('source.dtb') + private_key = tools.get_output_filename('test_key.key') + public_key = tools.get_output_filename('test_key.crt') + fit = tools.get_output_filename('fit.fit') + key_dir = tools.get_output_dir() + + tools.run('openssl', 'req', '-batch' , '-newkey', 'rsa:4096', + '-sha256', '-new', '-nodes', '-x509', '-keyout', + private_key, '-out', public_key) + tools.run('fdt_add_pubkey', '-a', 'sha256,rsa4096', '-k', key_dir, + '-n', 'test_key', '-r', 'conf', dtb) + + return fit, updated_fname, private_key, dtb + + def testSignSimple(self): + """Test that a FIT container can be signed in image""" + is_signed = False + fit, fname, private_key, dtb = self._PrepareSignEnv() + + # do sign with private key + control.SignEntries(fname, None, private_key, 'sha256,rsa4096', + ['fit']) + is_signed = self._CheckSign(fit, dtb) + + self.assertEqual(is_signed, True) + + def testSignExactFIT(self): + """Test that a FIT container can be signed and replaced in image""" + is_signed = False + fit, fname, private_key, dtb = self._PrepareSignEnv() + + # Make sure we propagate the toolpath, since mkimage may not be on PATH + args = [] + if self.toolpath: + for path in self.toolpath: + args += ['--toolpath', path] + + # do sign with private key + self._DoBinman(*args, 'sign', '-i', fname, '-k', private_key, '-a', + 'sha256,rsa4096', '-f', fit, 'fit') + is_signed = self._CheckSign(fit, dtb) + + self.assertEqual(is_signed, True) + + def testSignNonFit(self): + """Test a non-FIT entry cannot be signed""" + is_signed = False + fit, fname, private_key, _ = self._PrepareSignEnv( + '281_sign_non_fit.dts') + + # do sign with private key + with self.assertRaises(ValueError) as e: + self._DoBinman('sign', '-i', fname, '-k', private_key, '-a', + 'sha256,rsa4096', '-f', fit, 'u-boot') + self.assertIn( + "Node '/u-boot': Updating signatures is not supported with this entry type", + str(e.exception)) + + def testSignMissingMkimage(self): + """Test that FIT signing handles a missing mkimage tool""" + fit, fname, private_key, _ = self._PrepareSignEnv() + + # try to sign with a missing mkimage tool + bintool.Bintool.set_missing_list(['mkimage']) + with self.assertRaises(ValueError) as e: + control.SignEntries(fname, None, private_key, 'sha256,rsa4096', + ['fit']) + self.assertIn("Node '/fit': Missing tool: 'mkimage'", str(e.exception)) + if __name__ == "__main__": unittest.main() |