
test_efi_secboot/test_signed.py: provide test cases of image authentication for signed images test_efi_secboot/test_unsigned.py: provide test cases of image authentication for unsigned images
This test relies on efitools and user should compile it on his own. (Currently some local change has been applied.)
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org --- test/py/tests/test_efi_secboot/conftest.py | 168 ++++++++++++++++++ test/py/tests/test_efi_secboot/defs.py | 7 + test/py/tests/test_efi_secboot/test_signed.py | 97 ++++++++++ .../tests/test_efi_secboot/test_unsigned.py | 126 +++++++++++++ 4 files changed, 398 insertions(+) create mode 100644 test/py/tests/test_efi_secboot/conftest.py create mode 100644 test/py/tests/test_efi_secboot/defs.py create mode 100644 test/py/tests/test_efi_secboot/test_signed.py create mode 100644 test/py/tests/test_efi_secboot/test_unsigned.py
diff --git a/test/py/tests/test_efi_secboot/conftest.py b/test/py/tests/test_efi_secboot/conftest.py new file mode 100644 index 000000000000..3806f305a138 --- /dev/null +++ b/test/py/tests/test_efi_secboot/conftest.py @@ -0,0 +1,168 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (c) 2019, Linaro Limited +# Author: AKASHI Takahiro takahiro.akashi@linaro.org + +import os +import os.path +import pytest +import re +from subprocess import call, check_call, check_output, CalledProcessError +from defs import * + +# +# Helper functions +# +def mk_fs(config, fs_type, size, id): + """Create a file system volume. + + Args: + fs_type: File system type. + size: Size of file system in MiB. + id: Prefix string of volume's file name. + + Return: + Nothing. + """ + fs_img = '%s.%s.img' % (id, fs_type) + fs_img = config.persistent_data_dir + '/' + fs_img + + if fs_type == 'fat16': + mkfs_opt = '-F 16' + elif fs_type == 'fat32': + mkfs_opt = '-F 32' + elif fs_type == 'ext4': + mkfs_opt = '-O ^metadata_csum' + else: + mkfs_opt = '' + + if re.match('fat', fs_type): + fs_lnxtype = 'vfat' + else: + fs_lnxtype = fs_type + + count = (size + 1048576 - 1) / 1048576 + + try: + check_call('rm -f %s' % fs_img, shell=True) + check_call('dd if=/dev/zero of=%s bs=1M count=%d' + % (fs_img, count), shell=True) + check_call('mkfs.%s %s %s' + % (fs_lnxtype, mkfs_opt, fs_img), shell=True) + return fs_img + except CalledProcessError: + call('rm -f %s' % fs_img, shell=True) + raise + +# from test/py/conftest.py +def tool_is_in_path(tool): + """Check whether a given command is available on host. + + Args: + tool: Command name. + + Return: + True if available, False if not. + """ + for path in os.environ['PATH'].split(os.pathsep): + fn = os.path.join(path, tool) + if os.path.isfile(fn) and os.access(fn, os.X_OK): + return True + return False + +fuse_mounted = False + +def mount_fs(fs_type, device, mount_point): + """Mount a volume. + + Args: + fs_type: File system type. + device: Volume's file name. + mount_point: Mount point. + + Return: + Nothing. + """ + global fuse_mounted + + fuse_mounted = False + try: + if tool_is_in_path('guestmount'): + fuse_mounted = True + check_call('guestmount -a %s -m /dev/sda %s' + % (device, mount_point), shell=True) + else: + mount_opt = 'loop,rw' + if re.match('fat', fs_type): + mount_opt += ',umask=0000' + + check_call('sudo mount -o %s %s %s' + % (mount_opt, device, mount_point), shell=True) + + # may not be effective for some file systems + check_call('sudo chmod a+rw %s' % mount_point, shell=True) + except CalledProcessError: + raise + +def umount_fs(mount_point): + """Unmount a volume. + + Args: + mount_point: Mount point. + + Return: + Nothing. + """ + if fuse_mounted: + call('sync') + call('guestunmount %s' % mount_point, shell=True) + else: + call('sudo umount %s' % mount_point, shell=True) + +# +# Fixture for TestEfiSignedImage test +# +# NOTE: yield_fixture was deprecated since pytest-3.0 +@pytest.yield_fixture() +def efi_boot_env(request, u_boot_config): + """Set up a file system to be used in basic fs test. + + Args: + request: Pytest request object. + u_boot_config: U-boot configuration. + + Return: + A fixture for basic fs test, i.e. a triplet of file system type, + volume file name and a list of MD5 hashes. + """ + fs_type = 'fat32' + fs_img = '' + + mount_dir = u_boot_config.persistent_data_dir + '/mnt' + + try: + + # test volume + fs_img = mk_fs(u_boot_config, 'fat32', TEST_VOL_SIZE, 'test') + + # Mount the image so we can populate it. + check_output('mkdir -p %s' % mount_dir, shell=True) + mount_fs(fs_type, fs_img, mount_dir) + + # Copy files + check_output('cp %s/*.efi %s' % (EFITOOLS_PATH, mount_dir), shell=True) + check_output('cp %s/PK* %s' % (EFITOOLS_PATH, mount_dir), shell=True) + check_output('cp %s/noPK* %s' % (EFITOOLS_PATH, mount_dir), shell=True) + check_output('cp %s/KEK* %s' % (EFITOOLS_PATH, mount_dir), shell=True) + check_output('cp %s/DB* %s' % (EFITOOLS_PATH, mount_dir), shell=True) + + umount_fs(mount_dir) + except CalledProcessError as e: + pytest.skip('Setup failed: %s' % e.cmd) + return + else: + yield fs_img + finally: + umount_fs(mount_dir) + call('rmdir %s' % mount_dir, shell=True) + if fs_img: + call('rm -f %s' % fs_img, shell=True) diff --git a/test/py/tests/test_efi_secboot/defs.py b/test/py/tests/test_efi_secboot/defs.py new file mode 100644 index 000000000000..cbe99ef3a6ba --- /dev/null +++ b/test/py/tests/test_efi_secboot/defs.py @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0+ + +# 64MiB +TEST_VOL_SIZE=0x4000000 + +# efitools build directory +EFITOOLS_PATH='/home/akashi/x86/efitools' diff --git a/test/py/tests/test_efi_secboot/test_signed.py b/test/py/tests/test_efi_secboot/test_signed.py new file mode 100644 index 000000000000..05b193a5266b --- /dev/null +++ b/test/py/tests/test_efi_secboot/test_signed.py @@ -0,0 +1,97 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (c) 2019, Linaro Limited +# Author: AKASHI Takahiro takahiro.akashi@linaro.org +# +# U-Boot UEFI: Signed Image Authentication Test + +""" +This test verifies image authentication for signed images. +""" + +import pytest +import re +from defs import * + +@pytest.mark.boardspec('sandbox') +@pytest.mark.buildconfigspec('efi_secure_boot') +@pytest.mark.slow +class TestEfiSignedImage(object): + def test_efi_signed_image_auth1(self, u_boot_console, efi_boot_env): + """ + Test Case 1 - authenticated by DB + """ + disk_img = efi_boot_env + with u_boot_console.log.section('Test Case 1a'): + # Test Case 1a, run signed image if no DB/DBX + output = u_boot_console.run_command_list([ + 'host bind 0 %s' % disk_img, + 'efidebug boot add 1 HELLO1 host 0:0 /HelloWorld_simple-signed.efi ""', + 'efidebug boot next 1', + 'bootefi bootmgr']) + assert(re.search('Hello World!', ''.join(output))) + + with u_boot_console.log.section('Test Case 1b'): + # Test Case 1b, run unsigned image if no DB/DBX + output = u_boot_console.run_command_list([ + 'efidebug boot add 2 HELLO2 host 0:0 /HelloWorld_simple.efi ""', + 'efidebug boot next 2', + 'bootefi bootmgr']) + assert(re.search('Hello World!', ''.join(output))) + + with u_boot_console.log.section('Test Case 1c'): + # Test Case 1c, not authenticated by DB + output = u_boot_console.run_command_list([ + 'efidebug boot add 3 UPDV host 0:0 /UpdateVars.efi ""', + 'setenv bootargs "cmd db DB.auth"', + 'efidebug boot next 3', + 'bootefi bootmgr', + 'setenv bootargs "cmd KEK KEK.auth"', + 'efidebug boot next 3', + 'bootefi bootmgr', + 'setenv bootargs "cmd PK PK.auth"', + 'efidebug boot next 3', + 'bootefi bootmgr', + 'efidebug boot next 2', + 'bootefi bootmgr']) + assert(not re.search('Hello World!', ''.join(output))) + + with u_boot_console.log.section('Test Case 1d'): + # Test Case 1d, authenticated by DB + output = u_boot_console.run_command_list([ + 'efidebug boot next 1', + 'bootefi bootmgr']) + assert(re.search('Hello World!', ''.join(output))) + + def test_efi_signed_image_auth2(self, u_boot_console, efi_boot_env): + """ + Test Case 2 - rejected by DBX + """ + disk_img = efi_boot_env + with u_boot_console.log.section('Test Case 2a'): + # Test Case 2a, rejected by DBX + output = u_boot_console.run_command_list([ + 'host bind 0 %s' % disk_img, + 'efidebug boot add 1 UPDV host 0:0 /UpdateVars-signed.efi ""', + 'setenv bootargs "cmd dbx DB.auth"', + 'efidebug boot next 1', + 'bootefi bootmgr', + 'setenv bootargs "cmd KEK KEK.auth"', + 'efidebug boot next 1', + 'bootefi bootmgr', + 'setenv bootargs "cmd PK PK.auth"', + 'efidebug boot next 1', + 'bootefi bootmgr', + 'efidebug boot add 2 HELLO1 host 0:0 /HelloWorld_simple-signed.efi ""', + 'efidebug boot next 2', + 'bootefi bootmgr']) + assert(not re.search('Hello World!', ''.join(output))) + + with u_boot_console.log.section('Test Case 2b'): + # Test Case 2b, rejected by DBX even if DB allows + output = u_boot_console.run_command_list([ + 'setenv bootargs "cmd db DB.auth"', + 'efidebug boot next 1', + 'bootefi bootmgr', + 'efidebug boot next 2', + 'bootefi bootmgr']) + assert(not re.search('Hello World!', ''.join(output))) diff --git a/test/py/tests/test_efi_secboot/test_unsigned.py b/test/py/tests/test_efi_secboot/test_unsigned.py new file mode 100644 index 000000000000..232793e431b3 --- /dev/null +++ b/test/py/tests/test_efi_secboot/test_unsigned.py @@ -0,0 +1,126 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (c) 2019, Linaro Limited +# Author: AKASHI Takahiro takahiro.akashi@linaro.org +# +# U-Boot UEFI: Signed Image Authentication Test + +""" +This test verifies image authentication for unsigned images. +""" + +import pytest +import re +from defs import * + +@pytest.mark.boardspec('sandbox') +@pytest.mark.buildconfigspec('efi_secure_boot') +@pytest.mark.slow +class TestEfiUnsignedImage(object): + def test_efi_unsigned_image_auth1(self, u_boot_console, efi_boot_env): + """ + Test Case 1 - rejected when not hash in DB or DBX + """ + disk_img = efi_boot_env + with u_boot_console.log.section('Test Case 1'): + # Test Case 1 + output = u_boot_console.run_command_list([ + 'host bind 0 %s' % disk_img, + 'efidebug boot add 1 UPDV host 0:0 /UpdateVars.efi ""', + 'setenv bootargs "cmd KEK KEK.auth"', + 'efidebug boot next 1', + 'bootefi bootmgr', + 'setenv bootargs "cmd PK PK.auth"', + 'efidebug boot next 1', + 'bootefi bootmgr']) + assert(not re.search(''UPDV' failed', ''.join(output))) + + output = u_boot_console.run_command_list([ + 'efidebug boot add 2 HELLO1 host 0:0 /HelloWorld_simple.efi ""', + 'efidebug boot next 2', + 'bootefi bootmgr']) + assert(re.search(''HELLO1' failed', ''.join(output))) + + def test_efi_unsigned_image_auth2(self, u_boot_console, efi_boot_env): + """ + Test Case 2 - authenticated by DB with hash + """ + disk_img = efi_boot_env + with u_boot_console.log.section('Test Case 2'): + # Test Case 2 + output = u_boot_console.run_command_list([ + 'host bind 0 %s' % disk_img, + 'efidebug boot add 1 UPDV host 0:0 /UpdateVars.efi ""', + 'setenv bootargs "cmd db DB_HWs_hash.auth"', + 'efidebug boot next 1', + 'bootefi bootmgr', + 'setenv bootargs "cmd KEK KEK.auth"', + 'efidebug boot next 1', + 'bootefi bootmgr', + 'setenv bootargs "cmd PK PK.auth"', + 'efidebug boot next 1', + 'bootefi bootmgr']) + assert(not re.search(''UPDV' failed', ''.join(output))) + + output = u_boot_console.run_command_list([ + 'efidebug boot add 2 HELLO1 host 0:0 /HelloWorld_simple.efi ""', + 'efidebug boot next 2', + 'bootefi bootmgr']) + assert(re.search('Hello World!', ''.join(output))) + + def test_efi_unsigned_image_auth3(self, u_boot_console, efi_boot_env): + """ + Test Case 3 - rejected by DBX with hash + """ + disk_img = efi_boot_env + with u_boot_console.log.section('Test Case 3a'): + # Test Case 3a, rejected by DBX + output = u_boot_console.run_command_list([ + 'host bind 0 %s' % disk_img, + 'efidebug boot add 1 UPDV host 0:0 /UpdateVars-signed.efi ""', + 'setenv bootargs "cmd dbx DBX_HWs_hash.auth"', + 'efidebug boot next 1', + 'bootefi bootmgr', + 'setenv bootargs "cmd KEK KEK.auth"', + 'efidebug boot next 1', + 'bootefi bootmgr', + 'setenv bootargs "cmd PK PK.auth"', + 'efidebug boot next 1', + 'bootefi bootmgr']) + assert(not re.search(''UPDV' failed', ''.join(output))) + + output = u_boot_console.run_command_list([ + 'efidebug boot add 2 HELLO1 host 0:0 /HelloWorld_simple.efi ""', + 'efidebug boot next 2', + 'bootefi bootmgr']) + assert(re.search(''HELLO1' failed', ''.join(output))) + + # TODO: can be merged with auth3 + def test_efi_unsigned_image_auth4(self, u_boot_console, efi_boot_env): + """ + Test Case 4 - rejected by DBX with hash + """ + disk_img = efi_boot_env + with u_boot_console.log.section('Test Case 4a'): + # Test Case 4a, rejected by DBX even if DB allows + output = u_boot_console.run_command_list([ + 'host bind 0 %s' % disk_img, + 'efidebug boot add 1 UPDV host 0:0 /UpdateVars-signed.efi ""', + 'setenv bootargs "cmd db DB_HWs_hash.auth"', + 'efidebug boot next 1', + 'bootefi bootmgr', + 'setenv bootargs "cmd dbx DBX_HWs_hash.auth"', + 'efidebug boot next 1', + 'bootefi bootmgr', + 'setenv bootargs "cmd KEK KEK.auth"', + 'efidebug boot next 1', + 'bootefi bootmgr', + 'setenv bootargs "cmd PK PK.auth"', + 'efidebug boot next 1', + 'bootefi bootmgr']) + assert(not re.search(''UPDV' failed', ''.join(output))) + + output = u_boot_console.run_command_list([ + 'efidebug boot add 2 HELLO1 host 0:0 /HelloWorld_simple.efi ""', + 'efidebug boot next 2', + 'bootefi bootmgr']) + assert(re.search(''HELLO1' failed', ''.join(output)))