mirror of
https://github.com/u-boot/u-boot.git
synced 2025-04-19 03:15:00 +00:00
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:
parent
29451432c7
commit
776db4fa96
6 changed files with 272 additions and 16 deletions
|
@ -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)
|
||||||
|
|
25
test/py/tests/vboot/sandbox-binman-pss.dts
Normal file
25
test/py/tests/vboot/sandbox-binman-pss.dts
Normal 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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
24
test/py/tests/vboot/sandbox-binman.dts
Normal file
24
test/py/tests/vboot/sandbox-binman.dts
Normal 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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
28
test/py/tests/vboot/sandbox-u-boot-global-pss.dts
Normal file
28
test/py/tests/vboot/sandbox-u-boot-global-pss.dts
Normal 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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
27
test/py/tests/vboot/sandbox-u-boot-global.dts
Normal file
27
test/py/tests/vboot/sandbox-u-boot-global.dts
Normal 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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
36
test/py/tests/vboot/simple-images.its
Normal file
36
test/py/tests/vboot/simple-images.its
Normal 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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
Loading…
Add table
Reference in a new issue