xref: /openbmc/u-boot/test/py/tests/test_vboot.py (revision 9450ab2b)
18729d582SSimon Glass# SPDX-License-Identifier:	GPL-2.0+
283d290c5STom Rini# Copyright (c) 2016, Google Inc.
38729d582SSimon Glass#
48729d582SSimon Glass# U-Boot Verified Boot Test
58729d582SSimon Glass
68729d582SSimon Glass"""
78729d582SSimon GlassThis tests verified boot in the following ways:
88729d582SSimon Glass
98729d582SSimon GlassFor image verification:
108729d582SSimon Glass- Create FIT (unsigned) with mkimage
118729d582SSimon Glass- Check that verification shows that no keys are verified
128729d582SSimon Glass- Sign image
138729d582SSimon Glass- Check that verification shows that a key is now verified
148729d582SSimon Glass
158729d582SSimon GlassFor configuration verification:
168729d582SSimon Glass- Corrupt signature and check for failure
178729d582SSimon Glass- Create FIT (with unsigned configuration) with mkimage
1872f52268SSimon Glass- Check that image verification works
198729d582SSimon Glass- Sign the FIT and mark the key as 'required' for verification
208729d582SSimon Glass- Check that image verification works
218729d582SSimon Glass- Corrupt the signature
228729d582SSimon Glass- Check that image verification no-longer works
238729d582SSimon Glass
248729d582SSimon GlassTests run with both SHA1 and SHA256 hashing.
258729d582SSimon Glass"""
268729d582SSimon Glass
278729d582SSimon Glassimport pytest
288729d582SSimon Glassimport sys
2972239fc8STeddy Reedimport struct
308729d582SSimon Glassimport u_boot_utils as util
318729d582SSimon Glass
3204a4786cSMichal Simek@pytest.mark.boardspec('sandbox')
338729d582SSimon Glass@pytest.mark.buildconfigspec('fit_signature')
342d26bf6cSStephen Warren@pytest.mark.requiredtool('dtc')
352d26bf6cSStephen Warren@pytest.mark.requiredtool('fdtget')
362d26bf6cSStephen Warren@pytest.mark.requiredtool('fdtput')
372d26bf6cSStephen Warren@pytest.mark.requiredtool('openssl')
388729d582SSimon Glassdef test_vboot(u_boot_console):
398729d582SSimon Glass    """Test verified boot signing with mkimage and verification with 'bootm'.
408729d582SSimon Glass
418729d582SSimon Glass    This works using sandbox only as it needs to update the device tree used
428729d582SSimon Glass    by U-Boot to hold public keys from the signing process.
438729d582SSimon Glass
448729d582SSimon Glass    The SHA1 and SHA256 tests are combined into a single test since the
458729d582SSimon Glass    key-generation process is quite slow and we want to avoid doing it twice.
468729d582SSimon Glass    """
478729d582SSimon Glass    def dtc(dts):
4872f52268SSimon Glass        """Run the device tree compiler to compile a .dts file
498729d582SSimon Glass
508729d582SSimon Glass        The output file will be the same as the input file but with a .dtb
518729d582SSimon Glass        extension.
528729d582SSimon Glass
538729d582SSimon Glass        Args:
548729d582SSimon Glass            dts: Device tree file to compile.
558729d582SSimon Glass        """
568729d582SSimon Glass        dtb = dts.replace('.dts', '.dtb')
57ec70f8a9SSimon Glass        util.run_and_log(cons, 'dtc %s %s%s -O dtb '
588729d582SSimon Glass                         '-o %s%s' % (dtc_args, datadir, dts, tmpdir, dtb))
598729d582SSimon Glass
60de4be9ecSTom Rini    def run_bootm(sha_algo, test_type, expect_string, boots):
618729d582SSimon Glass        """Run a 'bootm' command U-Boot.
628729d582SSimon Glass
638729d582SSimon Glass        This always starts a fresh U-Boot instance since the device tree may
648729d582SSimon Glass        contain a new public key.
658729d582SSimon Glass
668729d582SSimon Glass        Args:
67ac9a23cfSSimon Glass            test_type: A string identifying the test type.
68ac9a23cfSSimon Glass            expect_string: A string which is expected in the output.
69ac9a23cfSSimon Glass            sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
70ac9a23cfSSimon Glass                    use.
71de4be9ecSTom Rini            boots: A boolean that is True if Linux should boot and False if
72de4be9ecSTom Rini                    we are expected to not boot
738729d582SSimon Glass        """
7427c087d5SSimon Glass        cons.restart_uboot()
75851271a7SSimon Glass        with cons.log.section('Verified boot %s %s' % (sha_algo, test_type)):
768729d582SSimon Glass            output = cons.run_command_list(
776d07d63dSSimon Glass                ['host load hostfs - 100 %stest.fit' % tmpdir,
788729d582SSimon Glass                'fdt addr 100',
798729d582SSimon Glass                'bootm 100'])
80f6d34651SSimon Glass        assert(expect_string in ''.join(output))
81de4be9ecSTom Rini        if boots:
82de4be9ecSTom Rini            assert('sandbox: continuing, as we cannot run' in ''.join(output))
838729d582SSimon Glass
848729d582SSimon Glass    def make_fit(its):
8572f52268SSimon Glass        """Make a new FIT from the .its source file.
868729d582SSimon Glass
878729d582SSimon Glass        This runs 'mkimage -f' to create a new FIT.
888729d582SSimon Glass
898729d582SSimon Glass        Args:
9072f52268SSimon Glass            its: Filename containing .its source.
918729d582SSimon Glass        """
928729d582SSimon Glass        util.run_and_log(cons, [mkimage, '-D', dtc_args, '-f',
938729d582SSimon Glass                                '%s%s' % (datadir, its), fit])
948729d582SSimon Glass
95ac9a23cfSSimon Glass    def sign_fit(sha_algo):
968729d582SSimon Glass        """Sign the FIT
978729d582SSimon Glass
988729d582SSimon Glass        Signs the FIT and writes the signature into it. It also writes the
998729d582SSimon Glass        public key into the dtb.
100ac9a23cfSSimon Glass
101ac9a23cfSSimon Glass        Args:
102ac9a23cfSSimon Glass            sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
103ac9a23cfSSimon Glass                    use.
1048729d582SSimon Glass        """
105ac9a23cfSSimon Glass        cons.log.action('%s: Sign images' % sha_algo)
1068729d582SSimon Glass        util.run_and_log(cons, [mkimage, '-F', '-k', tmpdir, '-K', dtb,
1078729d582SSimon Glass                                '-r', fit])
1088729d582SSimon Glass
10972239fc8STeddy Reed    def replace_fit_totalsize(size):
11072239fc8STeddy Reed        """Replace FIT header's totalsize with something greater.
11172239fc8STeddy Reed
11272239fc8STeddy Reed        The totalsize must be less than or equal to FIT_SIGNATURE_MAX_SIZE.
11372239fc8STeddy Reed        If the size is greater, the signature verification should return false.
11472239fc8STeddy Reed
11572239fc8STeddy Reed        Args:
11672239fc8STeddy Reed            size: The new totalsize of the header
11772239fc8STeddy Reed
11872239fc8STeddy Reed        Returns:
11972239fc8STeddy Reed            prev_size: The previous totalsize read from the header
12072239fc8STeddy Reed        """
12172239fc8STeddy Reed        total_size = 0
12272239fc8STeddy Reed        with open(fit, 'r+b') as handle:
12372239fc8STeddy Reed            handle.seek(4)
12472239fc8STeddy Reed            total_size = handle.read(4)
12572239fc8STeddy Reed            handle.seek(4)
12672239fc8STeddy Reed            handle.write(struct.pack(">I", size))
12772239fc8STeddy Reed        return struct.unpack(">I", total_size)[0]
12872239fc8STeddy Reed
129e246b728SPhilippe Reynes    def test_with_algo(sha_algo, padding):
13072f52268SSimon Glass        """Test verified boot with the given hash algorithm.
1318729d582SSimon Glass
1328729d582SSimon Glass        This is the main part of the test code. The same procedure is followed
1338729d582SSimon Glass        for both hashing algorithms.
1348729d582SSimon Glass
1358729d582SSimon Glass        Args:
136ac9a23cfSSimon Glass            sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
137ac9a23cfSSimon Glass                    use.
1388729d582SSimon Glass        """
139bcbd0c8fSSimon Glass        # Compile our device tree files for kernel and U-Boot. These are
140bcbd0c8fSSimon Glass        # regenerated here since mkimage will modify them (by adding a
141bcbd0c8fSSimon Glass        # public key) below.
1428729d582SSimon Glass        dtc('sandbox-kernel.dts')
1438729d582SSimon Glass        dtc('sandbox-u-boot.dts')
1448729d582SSimon Glass
1458729d582SSimon Glass        # Build the FIT, but don't sign anything yet
146ac9a23cfSSimon Glass        cons.log.action('%s: Test FIT with signed images' % sha_algo)
147e246b728SPhilippe Reynes        make_fit('sign-images-%s%s.its' % (sha_algo , padding))
148de4be9ecSTom Rini        run_bootm(sha_algo, 'unsigned images', 'dev-', True)
1498729d582SSimon Glass
1508729d582SSimon Glass        # Sign images with our dev keys
151ac9a23cfSSimon Glass        sign_fit(sha_algo)
152de4be9ecSTom Rini        run_bootm(sha_algo, 'signed images', 'dev+', True)
1538729d582SSimon Glass
1548729d582SSimon Glass        # Create a fresh .dtb without the public keys
1558729d582SSimon Glass        dtc('sandbox-u-boot.dts')
1568729d582SSimon Glass
157ac9a23cfSSimon Glass        cons.log.action('%s: Test FIT with signed configuration' % sha_algo)
158e246b728SPhilippe Reynes        make_fit('sign-configs-%s%s.its' % (sha_algo , padding))
159de4be9ecSTom Rini        run_bootm(sha_algo, 'unsigned config', '%s+ OK' % sha_algo, True)
1608729d582SSimon Glass
1618729d582SSimon Glass        # Sign images with our dev keys
162ac9a23cfSSimon Glass        sign_fit(sha_algo)
163de4be9ecSTom Rini        run_bootm(sha_algo, 'signed config', 'dev+', True)
1648729d582SSimon Glass
165ac9a23cfSSimon Glass        cons.log.action('%s: Check signed config on the host' % sha_algo)
1668729d582SSimon Glass
1678729d582SSimon Glass        util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', tmpdir,
1688729d582SSimon Glass                                '-k', dtb])
1698729d582SSimon Glass
17072239fc8STeddy Reed        # Replace header bytes
17172239fc8STeddy Reed        bcfg = u_boot_console.config.buildconfig
17272239fc8STeddy Reed        max_size = int(bcfg.get('config_fit_signature_max_size', 0x10000000), 0)
17372239fc8STeddy Reed        existing_size = replace_fit_totalsize(max_size + 1)
17472239fc8STeddy Reed        run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash', False)
17572239fc8STeddy Reed        cons.log.action('%s: Check overflowed FIT header totalsize' % sha_algo)
17672239fc8STeddy Reed
17772239fc8STeddy Reed        # Replace with existing header bytes
17872239fc8STeddy Reed        replace_fit_totalsize(existing_size)
17972239fc8STeddy Reed        run_bootm(sha_algo, 'signed config', 'dev+', True)
18072239fc8STeddy Reed        cons.log.action('%s: Check default FIT header totalsize' % sha_algo)
18172239fc8STeddy Reed
1828729d582SSimon Glass        # Increment the first byte of the signature, which should cause failure
183ec70f8a9SSimon Glass        sig = util.run_and_log(cons, 'fdtget -t bx %s %s value' %
184ec70f8a9SSimon Glass                               (fit, sig_node))
1858729d582SSimon Glass        byte_list = sig.split()
1868729d582SSimon Glass        byte = int(byte_list[0], 16)
187bcbd0c8fSSimon Glass        byte_list[0] = '%x' % (byte + 1)
1888729d582SSimon Glass        sig = ' '.join(byte_list)
189ec70f8a9SSimon Glass        util.run_and_log(cons, 'fdtput -t bx %s %s value %s' %
190ec70f8a9SSimon Glass                         (fit, sig_node, sig))
1918729d582SSimon Glass
192de4be9ecSTom Rini        run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash', False)
1938729d582SSimon Glass
194ac9a23cfSSimon Glass        cons.log.action('%s: Check bad config on the host' % sha_algo)
1958729d582SSimon Glass        util.run_and_log_expect_exception(cons, [fit_check_sign, '-f', fit,
1968729d582SSimon Glass                '-k', dtb], 1, 'Failed to verify required signature')
1978729d582SSimon Glass
1988729d582SSimon Glass    cons = u_boot_console
1998729d582SSimon Glass    tmpdir = cons.config.result_dir + '/'
2008729d582SSimon Glass    tmp = tmpdir + 'vboot.tmp'
201c9ba60c4SStephen Warren    datadir = cons.config.source_dir + '/test/py/tests/vboot/'
2028729d582SSimon Glass    fit = '%stest.fit' % tmpdir
2038729d582SSimon Glass    mkimage = cons.config.build_dir + '/tools/mkimage'
2048729d582SSimon Glass    fit_check_sign = cons.config.build_dir + '/tools/fit_check_sign'
2058729d582SSimon Glass    dtc_args = '-I dts -O dtb -i %s' % tmpdir
2068729d582SSimon Glass    dtb = '%ssandbox-u-boot.dtb' % tmpdir
207*ed47097aSPhilippe Reynes    sig_node = '/configurations/conf-1/signature'
2088729d582SSimon Glass
2098729d582SSimon Glass    # Create an RSA key pair
2108729d582SSimon Glass    public_exponent = 65537
211ec70f8a9SSimon Glass    util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %sdev.key '
2128729d582SSimon Glass                     '-pkeyopt rsa_keygen_bits:2048 '
2138793631eSPaul Burton                     '-pkeyopt rsa_keygen_pubexp:%d' %
2148793631eSPaul Burton                     (tmpdir, public_exponent))
2158729d582SSimon Glass
2168729d582SSimon Glass    # Create a certificate containing the public key
217ec70f8a9SSimon Glass    util.run_and_log(cons, 'openssl req -batch -new -x509 -key %sdev.key -out '
2188729d582SSimon Glass                     '%sdev.crt' % (tmpdir, tmpdir))
2198729d582SSimon Glass
2208729d582SSimon Glass    # Create a number kernel image with zeroes
2218729d582SSimon Glass    with open('%stest-kernel.bin' % tmpdir, 'w') as fd:
2228729d582SSimon Glass        fd.write(5000 * chr(0))
2238729d582SSimon Glass
2248729d582SSimon Glass    try:
2258729d582SSimon Glass        # We need to use our own device tree file. Remember to restore it
2268729d582SSimon Glass        # afterwards.
2278729d582SSimon Glass        old_dtb = cons.config.dtb
2288729d582SSimon Glass        cons.config.dtb = dtb
229e246b728SPhilippe Reynes        test_with_algo('sha1','')
230e246b728SPhilippe Reynes        test_with_algo('sha1','-pss')
231e246b728SPhilippe Reynes        test_with_algo('sha256','')
232e246b728SPhilippe Reynes        test_with_algo('sha256','-pss')
2338729d582SSimon Glass    finally:
23427c087d5SSimon Glass        # Go back to the original U-Boot with the correct dtb.
2358729d582SSimon Glass        cons.config.dtb = old_dtb
23627c087d5SSimon Glass        cons.restart_uboot()
237