test_vboot.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. # SPDX-License-Identifier: GPL-2.0+
  2. # Copyright (c) 2016, Google Inc.
  3. #
  4. # U-Boot Verified Boot Test
  5. """
  6. This tests verified boot in the following ways:
  7. For image verification:
  8. - Create FIT (unsigned) with mkimage
  9. - Check that verification shows that no keys are verified
  10. - Sign image
  11. - Check that verification shows that a key is now verified
  12. For configuration verification:
  13. - Corrupt signature and check for failure
  14. - Create FIT (with unsigned configuration) with mkimage
  15. - Check that image verification works
  16. - Sign the FIT and mark the key as 'required' for verification
  17. - Check that image verification works
  18. - Corrupt the signature
  19. - Check that image verification no-longer works
  20. Tests run with both SHA1 and SHA256 hashing.
  21. """
  22. import shutil
  23. import struct
  24. import pytest
  25. import u_boot_utils as util
  26. import vboot_forge
  27. import vboot_evil
  28. # Only run the full suite on a few combinations, since it doesn't add any more
  29. # test coverage.
  30. TESTDATA = [
  31. ['sha1', '', None, False, True],
  32. ['sha1', '', '-E -p 0x10000', False, False],
  33. ['sha1', '-pss', None, False, False],
  34. ['sha1', '-pss', '-E -p 0x10000', False, False],
  35. ['sha256', '', None, False, False],
  36. ['sha256', '', '-E -p 0x10000', False, False],
  37. ['sha256', '-pss', None, False, False],
  38. ['sha256', '-pss', '-E -p 0x10000', False, False],
  39. ['sha256', '-pss', None, True, False],
  40. ['sha256', '-pss', '-E -p 0x10000', True, True],
  41. ]
  42. @pytest.mark.boardspec('sandbox')
  43. @pytest.mark.buildconfigspec('fit_signature')
  44. @pytest.mark.requiredtool('dtc')
  45. @pytest.mark.requiredtool('fdtget')
  46. @pytest.mark.requiredtool('fdtput')
  47. @pytest.mark.requiredtool('openssl')
  48. @pytest.mark.parametrize("sha_algo,padding,sign_options,required,full_test",
  49. TESTDATA)
  50. def test_vboot(u_boot_console, sha_algo, padding, sign_options, required,
  51. full_test):
  52. """Test verified boot signing with mkimage and verification with 'bootm'.
  53. This works using sandbox only as it needs to update the device tree used
  54. by U-Boot to hold public keys from the signing process.
  55. The SHA1 and SHA256 tests are combined into a single test since the
  56. key-generation process is quite slow and we want to avoid doing it twice.
  57. """
  58. def dtc(dts):
  59. """Run the device tree compiler to compile a .dts file
  60. The output file will be the same as the input file but with a .dtb
  61. extension.
  62. Args:
  63. dts: Device tree file to compile.
  64. """
  65. dtb = dts.replace('.dts', '.dtb')
  66. util.run_and_log(cons, 'dtc %s %s%s -O dtb '
  67. '-o %s%s' % (dtc_args, datadir, dts, tmpdir, dtb))
  68. def run_bootm(sha_algo, test_type, expect_string, boots, fit=None):
  69. """Run a 'bootm' command U-Boot.
  70. This always starts a fresh U-Boot instance since the device tree may
  71. contain a new public key.
  72. Args:
  73. test_type: A string identifying the test type.
  74. expect_string: A string which is expected in the output.
  75. sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
  76. use.
  77. boots: A boolean that is True if Linux should boot and False if
  78. we are expected to not boot
  79. fit: FIT filename to load and verify
  80. """
  81. if not fit:
  82. fit = '%stest.fit' % tmpdir
  83. cons.restart_uboot()
  84. with cons.log.section('Verified boot %s %s' % (sha_algo, test_type)):
  85. output = cons.run_command_list(
  86. ['host load hostfs - 100 %s' % fit,
  87. 'fdt addr 100',
  88. 'bootm 100'])
  89. assert expect_string in ''.join(output)
  90. if boots:
  91. assert 'sandbox: continuing, as we cannot run' in ''.join(output)
  92. else:
  93. assert('sandbox: continuing, as we cannot run'
  94. not in ''.join(output))
  95. def make_fit(its):
  96. """Make a new FIT from the .its source file.
  97. This runs 'mkimage -f' to create a new FIT.
  98. Args:
  99. its: Filename containing .its source.
  100. """
  101. util.run_and_log(cons, [mkimage, '-D', dtc_args, '-f',
  102. '%s%s' % (datadir, its), fit])
  103. def sign_fit(sha_algo, options):
  104. """Sign the FIT
  105. Signs the FIT and writes the signature into it. It also writes the
  106. public key into the dtb.
  107. Args:
  108. sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
  109. use.
  110. options: Options to provide to mkimage.
  111. """
  112. args = [mkimage, '-F', '-k', tmpdir, '-K', dtb, '-r', fit]
  113. if options:
  114. args += options.split(' ')
  115. cons.log.action('%s: Sign images' % sha_algo)
  116. util.run_and_log(cons, args)
  117. def sign_fit_norequire(sha_algo, options):
  118. """Sign the FIT
  119. Signs the FIT and writes the signature into it. It also writes the
  120. public key into the dtb. It does not mark key as 'required' in dtb.
  121. Args:
  122. sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
  123. use.
  124. options: Options to provide to mkimage.
  125. """
  126. args = [mkimage, '-F', '-k', tmpdir, '-K', dtb, fit]
  127. if options:
  128. args += options.split(' ')
  129. cons.log.action('%s: Sign images' % sha_algo)
  130. util.run_and_log(cons, args)
  131. def replace_fit_totalsize(size):
  132. """Replace FIT header's totalsize with something greater.
  133. The totalsize must be less than or equal to FIT_SIGNATURE_MAX_SIZE.
  134. If the size is greater, the signature verification should return false.
  135. Args:
  136. size: The new totalsize of the header
  137. Returns:
  138. prev_size: The previous totalsize read from the header
  139. """
  140. total_size = 0
  141. with open(fit, 'r+b') as handle:
  142. handle.seek(4)
  143. total_size = handle.read(4)
  144. handle.seek(4)
  145. handle.write(struct.pack(">I", size))
  146. return struct.unpack(">I", total_size)[0]
  147. def create_rsa_pair(name):
  148. """Generate a new RSA key paid and certificate
  149. Args:
  150. name: Name of of the key (e.g. 'dev')
  151. """
  152. public_exponent = 65537
  153. util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %s%s.key '
  154. '-pkeyopt rsa_keygen_bits:2048 '
  155. '-pkeyopt rsa_keygen_pubexp:%d' %
  156. (tmpdir, name, public_exponent))
  157. # Create a certificate containing the public key
  158. util.run_and_log(cons, 'openssl req -batch -new -x509 -key %s%s.key '
  159. '-out %s%s.crt' % (tmpdir, name, tmpdir, name))
  160. def test_with_algo(sha_algo, padding, sign_options):
  161. """Test verified boot with the given hash algorithm.
  162. This is the main part of the test code. The same procedure is followed
  163. for both hashing algorithms.
  164. Args:
  165. sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
  166. use.
  167. padding: Either '' or '-pss', to select the padding to use for the
  168. rsa signature algorithm.
  169. sign_options: Options to mkimage when signing a fit image.
  170. """
  171. # Compile our device tree files for kernel and U-Boot. These are
  172. # regenerated here since mkimage will modify them (by adding a
  173. # public key) below.
  174. dtc('sandbox-kernel.dts')
  175. dtc('sandbox-u-boot.dts')
  176. # Build the FIT, but don't sign anything yet
  177. cons.log.action('%s: Test FIT with signed images' % sha_algo)
  178. make_fit('sign-images-%s%s.its' % (sha_algo, padding))
  179. run_bootm(sha_algo, 'unsigned images', 'dev-', True)
  180. # Sign images with our dev keys
  181. sign_fit(sha_algo, sign_options)
  182. run_bootm(sha_algo, 'signed images', 'dev+', True)
  183. # Create a fresh .dtb without the public keys
  184. dtc('sandbox-u-boot.dts')
  185. cons.log.action('%s: Test FIT with signed configuration' % sha_algo)
  186. make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
  187. run_bootm(sha_algo, 'unsigned config', '%s+ OK' % sha_algo, True)
  188. # Sign images with our dev keys
  189. sign_fit(sha_algo, sign_options)
  190. run_bootm(sha_algo, 'signed config', 'dev+', True)
  191. cons.log.action('%s: Check signed config on the host' % sha_algo)
  192. util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', dtb])
  193. if full_test:
  194. # Make sure that U-Boot checks that the config is in the list of hashed
  195. # nodes. If it isn't, a security bypass is possible.
  196. ffit = '%stest.forged.fit' % tmpdir
  197. shutil.copyfile(fit, ffit)
  198. with open(ffit, 'rb') as fd:
  199. root, strblock = vboot_forge.read_fdt(fd)
  200. root, strblock = vboot_forge.manipulate(root, strblock)
  201. with open(ffit, 'w+b') as fd:
  202. vboot_forge.write_fdt(root, strblock, fd)
  203. util.run_and_log_expect_exception(
  204. cons, [fit_check_sign, '-f', ffit, '-k', dtb],
  205. 1, 'Failed to verify required signature')
  206. run_bootm(sha_algo, 'forged config', 'Bad Data Hash', False, ffit)
  207. # Try adding an evil root node. This should be detected.
  208. efit = '%stest.evilf.fit' % tmpdir
  209. shutil.copyfile(fit, efit)
  210. vboot_evil.add_evil_node(fit, efit, evil_kernel, 'fakeroot')
  211. util.run_and_log_expect_exception(
  212. cons, [fit_check_sign, '-f', efit, '-k', dtb],
  213. 1, 'Failed to verify required signature')
  214. run_bootm(sha_algo, 'evil fakeroot', 'Bad Data Hash', False, efit)
  215. # Try adding an @ to the kernel node name. This should be detected.
  216. efit = '%stest.evilk.fit' % tmpdir
  217. shutil.copyfile(fit, efit)
  218. vboot_evil.add_evil_node(fit, efit, evil_kernel, 'kernel@')
  219. util.run_and_log_expect_exception(
  220. cons, [fit_check_sign, '-f', efit, '-k', dtb],
  221. 1, 'Node name contains @')
  222. run_bootm(sha_algo, 'evil kernel@', 'Bad Data Hash', False, efit)
  223. # Create a new properly signed fit and replace header bytes
  224. make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
  225. sign_fit(sha_algo, sign_options)
  226. bcfg = u_boot_console.config.buildconfig
  227. max_size = int(bcfg.get('config_fit_signature_max_size', 0x10000000), 0)
  228. existing_size = replace_fit_totalsize(max_size + 1)
  229. run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash',
  230. False)
  231. cons.log.action('%s: Check overflowed FIT header totalsize' % sha_algo)
  232. # Replace with existing header bytes
  233. replace_fit_totalsize(existing_size)
  234. run_bootm(sha_algo, 'signed config', 'dev+', True)
  235. cons.log.action('%s: Check default FIT header totalsize' % sha_algo)
  236. # Increment the first byte of the signature, which should cause failure
  237. sig = util.run_and_log(cons, 'fdtget -t bx %s %s value' %
  238. (fit, sig_node))
  239. byte_list = sig.split()
  240. byte = int(byte_list[0], 16)
  241. byte_list[0] = '%x' % (byte + 1)
  242. sig = ' '.join(byte_list)
  243. util.run_and_log(cons, 'fdtput -t bx %s %s value %s' %
  244. (fit, sig_node, sig))
  245. run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash',
  246. False)
  247. cons.log.action('%s: Check bad config on the host' % sha_algo)
  248. util.run_and_log_expect_exception(
  249. cons, [fit_check_sign, '-f', fit, '-k', dtb],
  250. 1, 'Failed to verify required signature')
  251. def test_required_key(sha_algo, padding, sign_options):
  252. """Test verified boot with the given hash algorithm.
  253. This function tests if U-Boot rejects an image when a required key isn't
  254. used to sign a FIT.
  255. Args:
  256. sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use
  257. padding: Either '' or '-pss', to select the padding to use for the
  258. rsa signature algorithm.
  259. sign_options: Options to mkimage when signing a fit image.
  260. """
  261. # Compile our device tree files for kernel and U-Boot. These are
  262. # regenerated here since mkimage will modify them (by adding a
  263. # public key) below.
  264. dtc('sandbox-kernel.dts')
  265. dtc('sandbox-u-boot.dts')
  266. cons.log.action('%s: Test FIT with configs images' % sha_algo)
  267. # Build the FIT with prod key (keys required) and sign it. This puts the
  268. # signature into sandbox-u-boot.dtb, marked 'required'
  269. make_fit('sign-configs-%s%s-prod.its' % (sha_algo, padding))
  270. sign_fit(sha_algo, sign_options)
  271. # Build the FIT with dev key (keys NOT required). This adds the
  272. # signature into sandbox-u-boot.dtb, NOT marked 'required'.
  273. make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
  274. sign_fit_norequire(sha_algo, sign_options)
  275. # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys.
  276. # Only the prod key is set as 'required'. But FIT we just built has
  277. # a dev signature only (sign_fit_norequire() overwrites the FIT).
  278. # Try to boot the FIT with dev key. This FIT should not be accepted by
  279. # U-Boot because the prod key is required.
  280. run_bootm(sha_algo, 'required key', '', False)
  281. # Build the FIT with dev key (keys required) and sign it. This puts the
  282. # signature into sandbox-u-boot.dtb, marked 'required'.
  283. make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
  284. sign_fit(sha_algo, sign_options)
  285. # Set the required-mode policy to "any".
  286. # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys.
  287. # Both the dev and prod key are set as 'required'. But FIT we just built has
  288. # a dev signature only (sign_fit() overwrites the FIT).
  289. # Try to boot the FIT with dev key. This FIT should be accepted by
  290. # U-Boot because the dev key is required and policy is "any" required key.
  291. util.run_and_log(cons, 'fdtput -t s %s /signature required-mode any' %
  292. (dtb))
  293. run_bootm(sha_algo, 'multi required key', 'dev+', True)
  294. # Set the required-mode policy to "all".
  295. # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys.
  296. # Both the dev and prod key are set as 'required'. But FIT we just built has
  297. # a dev signature only (sign_fit() overwrites the FIT).
  298. # Try to boot the FIT with dev key. This FIT should not be accepted by
  299. # U-Boot because the prod key is required and policy is "all" required key
  300. util.run_and_log(cons, 'fdtput -t s %s /signature required-mode all' %
  301. (dtb))
  302. run_bootm(sha_algo, 'multi required key', '', False)
  303. cons = u_boot_console
  304. tmpdir = cons.config.result_dir + '/'
  305. datadir = cons.config.source_dir + '/test/py/tests/vboot/'
  306. fit = '%stest.fit' % tmpdir
  307. mkimage = cons.config.build_dir + '/tools/mkimage'
  308. fit_check_sign = cons.config.build_dir + '/tools/fit_check_sign'
  309. dtc_args = '-I dts -O dtb -i %s' % tmpdir
  310. dtb = '%ssandbox-u-boot.dtb' % tmpdir
  311. sig_node = '/configurations/conf-1/signature'
  312. create_rsa_pair('dev')
  313. create_rsa_pair('prod')
  314. # Create a number kernel image with zeroes
  315. with open('%stest-kernel.bin' % tmpdir, 'wb') as fd:
  316. fd.write(500 * b'\0')
  317. # Create a second kernel image with ones
  318. evil_kernel = '%stest-kernel1.bin' % tmpdir
  319. with open(evil_kernel, 'wb') as fd:
  320. fd.write(500 * b'\x01')
  321. try:
  322. # We need to use our own device tree file. Remember to restore it
  323. # afterwards.
  324. old_dtb = cons.config.dtb
  325. cons.config.dtb = dtb
  326. if required:
  327. test_required_key(sha_algo, padding, sign_options)
  328. else:
  329. test_with_algo(sha_algo, padding, sign_options)
  330. finally:
  331. # Go back to the original U-Boot with the correct dtb.
  332. cons.config.dtb = old_dtb
  333. cons.restart_uboot()