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>
This commit is contained in:
Simon Glass 2018-07-17 13:25:47 -06:00
parent 94a7c603b4
commit 24d0d3c30d
9 changed files with 259 additions and 1 deletions

View file

@ -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
-------------------------------------------------------

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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()

View file

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

View file

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

View file

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

View file

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