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 {
+	};
+};