test: py: vboot: add test for global image signature

Adds test units for the pre-load header signature.

Signed-off-by: Philippe Reynes <philippe.reynes@softathome.com>
This commit is contained in:
Philippe Reynes 2022-03-28 22:57:06 +02:00 committed by Tom Rini
parent 29451432c7
commit 776db4fa96
6 changed files with 272 additions and 16 deletions

View file

@ -21,6 +21,14 @@ For configuration verification:
- Corrupt the signature - Corrupt the signature
- Check that image verification no-longer works - Check that image verification no-longer works
For pre-load header verification:
- Create FIT image with a pre-load header
- Check that signature verification succeeds
- Corrupt the FIT image
- Check that signature verification fails
- Launch an FIT image without a pre-load header
- Check that image verification fails
Tests run with both SHA1 and SHA256 hashing. Tests run with both SHA1 and SHA256 hashing.
""" """
@ -35,19 +43,21 @@ import vboot_evil
# Only run the full suite on a few combinations, since it doesn't add any more # Only run the full suite on a few combinations, since it doesn't add any more
# test coverage. # test coverage.
TESTDATA = [ TESTDATA = [
['sha1-basic', 'sha1', '', None, False, True, False], ['sha1-basic', 'sha1', '', None, False, True, False, False],
['sha1-pad', 'sha1', '', '-E -p 0x10000', False, False, False], ['sha1-pad', 'sha1', '', '-E -p 0x10000', False, False, False, False],
['sha1-pss', 'sha1', '-pss', None, False, False, False], ['sha1-pss', 'sha1', '-pss', None, False, False, False, False],
['sha1-pss-pad', 'sha1', '-pss', '-E -p 0x10000', False, False, False], ['sha1-pss-pad', 'sha1', '-pss', '-E -p 0x10000', False, False, False, False],
['sha256-basic', 'sha256', '', None, False, False, False], ['sha256-basic', 'sha256', '', None, False, False, False, False],
['sha256-pad', 'sha256', '', '-E -p 0x10000', False, False, False], ['sha256-pad', 'sha256', '', '-E -p 0x10000', False, False, False, False],
['sha256-pss', 'sha256', '-pss', None, False, False, False], ['sha256-pss', 'sha256', '-pss', None, False, False, False, False],
['sha256-pss-pad', 'sha256', '-pss', '-E -p 0x10000', False, False, False], ['sha256-pss-pad', 'sha256', '-pss', '-E -p 0x10000', False, False, False, False],
['sha256-pss-required', 'sha256', '-pss', None, True, False, False], ['sha256-pss-required', 'sha256', '-pss', None, True, False, False, False],
['sha256-pss-pad-required', 'sha256', '-pss', '-E -p 0x10000', True, True, False], ['sha256-pss-pad-required', 'sha256', '-pss', '-E -p 0x10000', True, True, False, False],
['sha384-basic', 'sha384', '', None, False, False, False], ['sha384-basic', 'sha384', '', None, False, False, False, False],
['sha384-pad', 'sha384', '', '-E -p 0x10000', False, False, False], ['sha384-pad', 'sha384', '', '-E -p 0x10000', False, False, False, False],
['algo-arg', 'algo-arg', '', '-o sha256,rsa2048', False, False, True], ['algo-arg', 'algo-arg', '', '-o sha256,rsa2048', False, False, True, False],
['sha256-global-sign', 'sha256', '', '', False, False, False, True],
['sha256-global-sign-pss', 'sha256', '-pss', '', False, False, False, True],
] ]
@pytest.mark.boardspec('sandbox') @pytest.mark.boardspec('sandbox')
@ -56,10 +66,10 @@ TESTDATA = [
@pytest.mark.requiredtool('fdtget') @pytest.mark.requiredtool('fdtget')
@pytest.mark.requiredtool('fdtput') @pytest.mark.requiredtool('fdtput')
@pytest.mark.requiredtool('openssl') @pytest.mark.requiredtool('openssl')
@pytest.mark.parametrize("name,sha_algo,padding,sign_options,required,full_test,algo_arg", @pytest.mark.parametrize("name,sha_algo,padding,sign_options,required,full_test,algo_arg,global_sign",
TESTDATA) TESTDATA)
def test_vboot(u_boot_console, name, sha_algo, padding, sign_options, required, def test_vboot(u_boot_console, name, sha_algo, padding, sign_options, required,
full_test, algo_arg): full_test, algo_arg, global_sign):
"""Test verified boot signing with mkimage and verification with 'bootm'. """Test verified boot signing with mkimage and verification with 'bootm'.
This works using sandbox only as it needs to update the device tree used This works using sandbox only as it needs to update the device tree used
@ -81,6 +91,33 @@ def test_vboot(u_boot_console, name, sha_algo, padding, sign_options, required,
util.run_and_log(cons, 'dtc %s %s%s -O dtb ' util.run_and_log(cons, 'dtc %s %s%s -O dtb '
'-o %s%s' % (dtc_args, datadir, dts, tmpdir, dtb)) '-o %s%s' % (dtc_args, datadir, dts, tmpdir, dtb))
def dtc_options(dts, options):
"""Run the device tree compiler to compile a .dts file
The output file will be the same as the input file but with a .dtb
extension.
Args:
dts: Device tree file to compile.
options: Options provided to the compiler.
"""
dtb = dts.replace('.dts', '.dtb')
util.run_and_log(cons, 'dtc %s %s%s -O dtb '
'-o %s%s %s' % (dtc_args, datadir, dts, tmpdir, dtb, options))
def run_binman(dtb):
"""Run binman to build an image
Args:
dtb: Device tree file used as input file.
"""
pythonpath = os.environ.get('PYTHONPATH', '')
os.environ['PYTHONPATH'] = pythonpath + ':' + '%s/../scripts/dtc/pylibfdt' % tmpdir
util.run_and_log(cons, [binman, 'build', '-d', "%s/%s" % (tmpdir,dtb),
'-a', "pre-load-key-path=%s" % tmpdir, '-O',
tmpdir, '-I', tmpdir])
os.environ['PYTHONPATH'] = pythonpath
def run_bootm(sha_algo, test_type, expect_string, boots, fit=None): def run_bootm(sha_algo, test_type, expect_string, boots, fit=None):
"""Run a 'bootm' command U-Boot. """Run a 'bootm' command U-Boot.
@ -139,6 +176,23 @@ def test_vboot(u_boot_console, name, sha_algo, padding, sign_options, required,
cons.log.action('%s: Sign images' % sha_algo) cons.log.action('%s: Sign images' % sha_algo)
util.run_and_log(cons, args) util.run_and_log(cons, args)
def sign_fit_dtb(sha_algo, options, dtb):
"""Sign the FIT
Signs the FIT and writes the signature into it. It also writes the
public key into the dtb.
Args:
sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
use.
options: Options to provide to mkimage.
"""
args = [mkimage, '-F', '-k', tmpdir, '-K', dtb, '-r', fit]
if options:
args += options.split(' ')
cons.log.action('%s: Sign images' % sha_algo)
util.run_and_log(cons, args)
def sign_fit_norequire(sha_algo, options): def sign_fit_norequire(sha_algo, options):
"""Sign the FIT """Sign the FIT
@ -176,6 +230,20 @@ def test_vboot(u_boot_console, name, sha_algo, padding, sign_options, required,
handle.write(struct.pack(">I", size)) handle.write(struct.pack(">I", size))
return struct.unpack(">I", total_size)[0] return struct.unpack(">I", total_size)[0]
def corrupt_file(fit, offset, value):
"""Corrupt a file
To corrupt a file, a value is written at the specified offset
Args:
fit: The file to corrupt
offset: Offset to write
value: Value written
"""
with open(fit, 'r+b') as handle:
handle.seek(offset)
handle.write(struct.pack(">I", value))
def create_rsa_pair(name): def create_rsa_pair(name):
"""Generate a new RSA key paid and certificate """Generate a new RSA key paid and certificate
@ -374,6 +442,51 @@ def test_vboot(u_boot_console, name, sha_algo, padding, sign_options, required,
(dtb)) (dtb))
run_bootm(sha_algo, 'multi required key', '', False) run_bootm(sha_algo, 'multi required key', '', False)
def test_global_sign(sha_algo, padding, sign_options):
"""Test global image signature with the given hash algorithm and padding.
Args:
sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use
padding: Either '' or '-pss', to select the padding to use for the
rsa signature algorithm.
"""
dtb = '%ssandbox-u-boot-global%s.dtb' % (tmpdir, padding)
cons.config.dtb = dtb
# Compile our device tree files for kernel and U-Boot. These are
# regenerated here since mkimage will modify them (by adding a
# public key) below.
dtc('sandbox-kernel.dts')
dtc_options('sandbox-u-boot-global%s.dts' % padding, '-p 1024')
# Build the FIT with dev key (keys NOT required). This adds the
# signature into sandbox-u-boot.dtb, NOT marked 'required'.
make_fit('simple-images.its')
sign_fit_dtb(sha_algo, '', dtb)
# Build the dtb for binman that define the pre-load header
# with the global sigature.
dtc('sandbox-binman%s.dts' % padding)
# Run binman to create the final image with the not signed fit
# and the pre-load header that contains the global signature.
run_binman('sandbox-binman%s.dtb' % padding)
# Check that the signature is correctly verified by u-boot
run_bootm(sha_algo, 'global image signature',
'signature check has succeed', True, "%ssandbox.img" % tmpdir)
# Corrupt the image (just one byte after the pre-load header)
corrupt_file("%ssandbox.img" % tmpdir, 4096, 255);
# Check that the signature verification fails
run_bootm(sha_algo, 'global image signature',
'signature check has failed', False, "%ssandbox.img" % tmpdir)
# Check that the boot fails if the global signature is not provided
run_bootm(sha_algo, 'global image signature', 'signature is mandatory', False)
cons = u_boot_console cons = u_boot_console
tmpdir = os.path.join(cons.config.result_dir, name) + '/' tmpdir = os.path.join(cons.config.result_dir, name) + '/'
if not os.path.exists(tmpdir): if not os.path.exists(tmpdir):
@ -381,6 +494,7 @@ def test_vboot(u_boot_console, name, sha_algo, padding, sign_options, required,
datadir = cons.config.source_dir + '/test/py/tests/vboot/' datadir = cons.config.source_dir + '/test/py/tests/vboot/'
fit = '%stest.fit' % tmpdir fit = '%stest.fit' % tmpdir
mkimage = cons.config.build_dir + '/tools/mkimage' mkimage = cons.config.build_dir + '/tools/mkimage'
binman = cons.config.source_dir + '/tools/binman/binman'
fit_check_sign = cons.config.build_dir + '/tools/fit_check_sign' fit_check_sign = cons.config.build_dir + '/tools/fit_check_sign'
dtc_args = '-I dts -O dtb -i %s' % tmpdir dtc_args = '-I dts -O dtb -i %s' % tmpdir
dtb = '%ssandbox-u-boot.dtb' % tmpdir dtb = '%ssandbox-u-boot.dtb' % tmpdir
@ -403,7 +517,9 @@ def test_vboot(u_boot_console, name, sha_algo, padding, sign_options, required,
# afterwards. # afterwards.
old_dtb = cons.config.dtb old_dtb = cons.config.dtb
cons.config.dtb = dtb cons.config.dtb = dtb
if required: if global_sign:
test_global_sign(sha_algo, padding, sign_options)
elif required:
test_required_key(sha_algo, padding, sign_options) test_required_key(sha_algo, padding, sign_options)
else: else:
test_with_algo(sha_algo, padding, sign_options) test_with_algo(sha_algo, padding, sign_options)

View file

@ -0,0 +1,25 @@
// SPDX-License-Identifier: GPL-2.0+
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
binman {
filename = "sandbox.img";
pre-load {
content = <&image>;
algo-name = "sha256,rsa2048";
padding-name = "pss";
key-name = "dev.key";
header-size = <4096>;
version = <1>;
};
image: blob-ext {
filename = "test.fit";
};
};
};

View file

@ -0,0 +1,24 @@
// SPDX-License-Identifier: GPL-2.0+
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
binman {
filename = "sandbox.img";
pre-load {
content = <&image>;
algo-name = "sha256,rsa2048";
key-name = "dev.key";
header-size = <4096>;
version = <1>;
};
image: blob-ext {
filename = "test.fit";
};
};
};

View file

@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-2.0+
/dts-v1/;
/ {
model = "Sandbox Verified Boot Test";
compatible = "sandbox";
binman {
};
reset@0 {
compatible = "sandbox,reset";
};
image {
pre-load {
sig {
algo-name = "sha256,rsa2048";
padding-name = "pss";
signature-size = <256>;
mandatory = "yes";
key-name = "dev";
};
};
};
};

View file

@ -0,0 +1,27 @@
// SPDX-License-Identifier: GPL-2.0+
/dts-v1/;
/ {
model = "Sandbox Verified Boot Test";
compatible = "sandbox";
binman {
};
reset@0 {
compatible = "sandbox,reset";
};
image {
pre-load {
sig {
algo-name = "sha256,rsa2048";
signature-size = <256>;
mandatory = "yes";
key-name = "dev";
};
};
};
};

View file

@ -0,0 +1,36 @@
// SPDX-License-Identifier: GPL-2.0+
/dts-v1/;
/ {
description = "Chrome OS kernel image with one or more FDT blobs";
#address-cells = <1>;
images {
kernel {
data = /incbin/("test-kernel.bin");
type = "kernel_noload";
arch = "sandbox";
os = "linux";
compression = "none";
load = <0x4>;
entry = <0x8>;
kernel-version = <1>;
};
fdt-1 {
description = "snow";
data = /incbin/("sandbox-kernel.dtb");
type = "flat_dt";
arch = "sandbox";
compression = "none";
fdt-version = <1>;
};
};
configurations {
default = "conf-1";
conf-1 {
kernel = "kernel";
fdt = "fdt-1";
};
};
};