From 24d0d3c30db0bba6579ae55e1d6202e229c23a0e Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 17 Jul 2018 13:25:47 -0600 Subject: [PATCH] binman: Add an entry for a Chromium vblock This adds support for a Chromium verified boot block, used to sign a read-write section of the image. Signed-off-by: Simon Glass --- tools/binman/README.entries | 17 +++++ tools/binman/bsection.py | 24 +++++++ tools/binman/entry.py | 2 +- tools/binman/etype/vblock.py | 74 +++++++++++++++++++++ tools/binman/ftest.py | 41 ++++++++++++ tools/binman/test/74_vblock.dts | 28 ++++++++ tools/binman/test/75_vblock_no_content.dts | 23 +++++++ tools/binman/test/76_vblock_bad_phandle.dts | 24 +++++++ tools/binman/test/77_vblock_bad_entry.dts | 27 ++++++++ 9 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 tools/binman/etype/vblock.py create mode 100644 tools/binman/test/74_vblock.dts create mode 100644 tools/binman/test/75_vblock_no_content.dts create mode 100644 tools/binman/test/76_vblock_bad_phandle.dts create mode 100644 tools/binman/test/77_vblock_bad_entry.dts diff --git a/tools/binman/README.entries b/tools/binman/README.entries index 41b70192c0..1b75ca0052 100644 --- a/tools/binman/README.entries +++ b/tools/binman/README.entries @@ -496,6 +496,23 @@ complicated. Otherwise it is the same as the u_boot entry. +Entry: vblock: An entry which contains a Chromium OS verified boot block +------------------------------------------------------------------------ + +Properties / Entry arguments: + - keydir: Directory containing the public keys to use + - keyblock: Name of the key file to use (inside keydir) + - signprivate: Name of provide key file to use (inside keydir) + - version: Version number of the vblock (typically 1) + - kernelkey: Name of the kernel key to use (inside keydir) + - preamble-flags: Value of the vboot preamble flags (typically 0) + +Chromium OS signs the read-write firmware and kernel, writing the signature +in this block. This allows U-Boot to verify that the next firmware stage +and kernel are genuine. + + + Entry: x86-start16: x86 16-bit start-up code for U-Boot ------------------------------------------------------- diff --git a/tools/binman/bsection.py b/tools/binman/bsection.py index 08c6f0cda8..70a6ec1776 100644 --- a/tools/binman/bsection.py +++ b/tools/binman/bsection.py @@ -381,3 +381,27 @@ class Section(object): Entry.WriteMapLine(fd, indent, self._name, self._offset, self._size) for entry in self._entries.values(): entry.WriteMap(fd, indent + 1) + + def GetContentsByPhandle(self, phandle, source_entry): + """Get the data contents of an entry specified by a phandle + + This uses a phandle to look up a node and and find the entry + associated with it. Then it returnst he contents of that entry. + + Args: + phandle: Phandle to look up (integer) + source_entry: Entry containing that phandle (used for error + reporting) + + Returns: + data from associated entry (as a string), or None if not found + """ + node = self._node.GetFdt().LookupPhandle(phandle) + if not node: + source_entry.Raise("Cannot find node for phandle %d" % phandle) + for entry in self._entries.values(): + if entry._node == node: + if entry.data is None: + return None + return entry.data + source_entry.Raise("Cannot find entry for node '%s'" % node.name) diff --git a/tools/binman/entry.py b/tools/binman/entry.py index 8b910feff6..996f03e3a6 100644 --- a/tools/binman/entry.py +++ b/tools/binman/entry.py @@ -65,7 +65,7 @@ class Entry(object): self.name = node and (name_prefix + node.name) or 'none' self.offset = None self.size = None - self.data = '' + self.data = None self.contents_size = 0 self.align = None self.align_size = None diff --git a/tools/binman/etype/vblock.py b/tools/binman/etype/vblock.py new file mode 100644 index 0000000000..595af5456d --- /dev/null +++ b/tools/binman/etype/vblock.py @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (c) 2018 Google, Inc +# Written by Simon Glass +# + +# Support for a Chromium OS verified boot block, used to sign a read-write +# section of the image. + +from collections import OrderedDict +import os + +from entry import Entry, EntryArg + +import fdt_util +import tools + +class Entry_vblock(Entry): + """An entry which contains a Chromium OS verified boot block + + Properties / Entry arguments: + - keydir: Directory containing the public keys to use + - keyblock: Name of the key file to use (inside keydir) + - signprivate: Name of provide key file to use (inside keydir) + - version: Version number of the vblock (typically 1) + - kernelkey: Name of the kernel key to use (inside keydir) + - preamble-flags: Value of the vboot preamble flags (typically 0) + + Chromium OS signs the read-write firmware and kernel, writing the signature + in this block. This allows U-Boot to verify that the next firmware stage + and kernel are genuine. + """ + def __init__(self, section, etype, node): + Entry.__init__(self, section, etype, node) + self.content = fdt_util.GetPhandleList(self._node, 'content') + if not self.content: + self.Raise("Vblock must have a 'content' property") + (self.keydir, self.keyblock, self.signprivate, self.version, + self.kernelkey, self.preamble_flags) = self.GetEntryArgsOrProps([ + EntryArg('keydir', str), + EntryArg('keyblock', str), + EntryArg('signprivate', str), + EntryArg('version', int), + EntryArg('kernelkey', str), + EntryArg('preamble-flags', int)]) + + def ObtainContents(self): + # Join up the data files to be signed + input_data = '' + for entry_phandle in self.content: + data = self.section.GetContentsByPhandle(entry_phandle, self) + if data is None: + # Data not available yet + return False + input_data += data + + output_fname = tools.GetOutputFilename('vblock.%s' % self.name) + input_fname = tools.GetOutputFilename('input.%s' % self.name) + tools.WriteFile(input_fname, input_data) + prefix = self.keydir + '/' + args = [ + 'vbutil_firmware', + '--vblock', output_fname, + '--keyblock', prefix + self.keyblock, + '--signprivate', prefix + self.signprivate, + '--version', '%d' % self.version, + '--fv', input_fname, + '--kernelkey', prefix + self.kernelkey, + '--flags', '%d' % self.preamble_flags, + ] + #out.Notice("Sign '%s' into %s" % (', '.join(self.value), self.label)) + stdout = tools.Run('futility', *args) + #out.Debug(stdout) + self.SetContents(tools.ReadFile(output_fname)) + return True diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index f15b215c60..a6de4cb93b 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -49,6 +49,7 @@ TEXT_DATA3 = 'text3' CROS_EC_RW_DATA = 'ecrw' GBB_DATA = 'gbbd' BMPBLK_DATA = 'bmp' +VBLOCK_DATA = 'vblk' class TestFunctional(unittest.TestCase): @@ -1304,6 +1305,46 @@ class TestFunctional(unittest.TestCase): self.assertIn("Node '/binman/gbb': GBB must have a fixed size", str(e.exception)) + def _HandleVblockCommand(self, pipe_list): + """Fake calls to the futility utility""" + if pipe_list[0][0] == 'futility': + fname = pipe_list[0][3] + with open(fname, 'w') as fd: + fd.write(VBLOCK_DATA) + return command.CommandResult() + + def testVblock(self): + """Test for the Chromium OS Verified Boot Block""" + command.test_result = self._HandleVblockCommand + entry_args = { + 'keydir': 'devkeys', + } + data, _, _, _ = self._DoReadFileDtb('74_vblock.dts', + entry_args=entry_args) + expected = U_BOOT_DATA + VBLOCK_DATA + U_BOOT_DTB_DATA + self.assertEqual(expected, data) + + def testVblockNoContent(self): + """Test we detect a vblock which has no content to sign""" + with self.assertRaises(ValueError) as e: + self._DoReadFile('75_vblock_no_content.dts') + self.assertIn("Node '/binman/vblock': Vblock must have a 'content' " + 'property', str(e.exception)) + + def testVblockBadPhandle(self): + """Test that we detect a vblock with an invalid phandle in contents""" + with self.assertRaises(ValueError) as e: + self._DoReadFile('76_vblock_bad_phandle.dts') + self.assertIn("Node '/binman/vblock': Cannot find node for phandle " + '1000', str(e.exception)) + + def testVblockBadEntry(self): + """Test that we detect an entry that points to a non-entry""" + with self.assertRaises(ValueError) as e: + self._DoReadFile('77_vblock_bad_entry.dts') + self.assertIn("Node '/binman/vblock': Cannot find entry for node " + "'other'", str(e.exception)) + if __name__ == "__main__": unittest.main() diff --git a/tools/binman/test/74_vblock.dts b/tools/binman/test/74_vblock.dts new file mode 100644 index 0000000000..f0c21bfe9f --- /dev/null +++ b/tools/binman/test/74_vblock.dts @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0+ +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + u_boot: u-boot { + }; + + vblock { + content = <&u_boot &dtb>; + keyblock = "firmware.keyblock"; + signprivate = "firmware_data_key.vbprivk"; + version = <1>; + kernelkey = "kernel_subkey.vbpubk"; + preamble-flags = <1>; + }; + + /* + * Put this after the vblock so that its contents are not + * available when the vblock first tries to obtain its contents + */ + dtb: u-boot-dtb { + }; + }; +}; diff --git a/tools/binman/test/75_vblock_no_content.dts b/tools/binman/test/75_vblock_no_content.dts new file mode 100644 index 0000000000..676d9474b3 --- /dev/null +++ b/tools/binman/test/75_vblock_no_content.dts @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0+ +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + u_boot: u-boot { + }; + + vblock { + keyblock = "firmware.keyblock"; + signprivate = "firmware_data_key.vbprivk"; + version = <1>; + kernelkey = "kernel_subkey.vbpubk"; + preamble-flags = <1>; + }; + + dtb: u-boot-dtb { + }; + }; +}; diff --git a/tools/binman/test/76_vblock_bad_phandle.dts b/tools/binman/test/76_vblock_bad_phandle.dts new file mode 100644 index 0000000000..ffbd0c335c --- /dev/null +++ b/tools/binman/test/76_vblock_bad_phandle.dts @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0+ +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + u_boot: u-boot { + }; + + vblock { + content = <1000>; + keyblock = "firmware.keyblock"; + signprivate = "firmware_data_key.vbprivk"; + version = <1>; + kernelkey = "kernel_subkey.vbpubk"; + preamble-flags = <1>; + }; + + dtb: u-boot-dtb { + }; + }; +}; diff --git a/tools/binman/test/77_vblock_bad_entry.dts b/tools/binman/test/77_vblock_bad_entry.dts new file mode 100644 index 0000000000..764c42a56e --- /dev/null +++ b/tools/binman/test/77_vblock_bad_entry.dts @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0+ +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + u_boot: u-boot { + }; + + vblock { + content = <&u_boot &other>; + keyblock = "firmware.keyblock"; + signprivate = "firmware_data_key.vbprivk"; + version = <1>; + kernelkey = "kernel_subkey.vbpubk"; + preamble-flags = <1>; + }; + + dtb: u-boot-dtb { + }; + }; + + other: other { + }; +};