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 <sjg@chromium.org>
diff --git a/tools/binman/README.entries b/tools/binman/README.entries
index 41b7019..1b75ca0 100644
--- a/tools/binman/README.entries
+++ b/tools/binman/README.entries
@@ -496,6 +496,23 @@
+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 08c6f0c..70a6ec1 100644
--- a/tools/binman/bsection.py
+++ b/tools/binman/bsection.py
@@ -381,3 +381,27 @@
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 8b910fe..996f03e 100644
--- a/tools/binman/entry.py
+++ b/tools/binman/entry.py
@@ -65,7 +65,7 @@
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 0000000..595af54
--- /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 <sjg@chromium.org>
+#
+
+# 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 f15b215..a6de4cb 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -49,6 +49,7 @@
CROS_EC_RW_DATA = 'ecrw'
GBB_DATA = 'gbbd'
BMPBLK_DATA = 'bmp'
+VBLOCK_DATA = 'vblk'
class TestFunctional(unittest.TestCase):
@@ -1304,6 +1305,46 @@
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 0000000..f0c21bf
--- /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 0000000..676d947
--- /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 0000000..ffbd0c3
--- /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 0000000..764c42a
--- /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 {
+ };
+};