diff mbox series

[v2,8/8] test/py: efi_secboot: add test for intermediate certificates

Message ID 20200616052655.4845-9-takahiro.akashi@linaro.org
State Superseded
Delegated to: Heinrich Schuchardt
Headers show
Series efi_loader: secure boot: support intermediate certificates in signature | expand

Commit Message

AKASHI Takahiro June 16, 2020, 5:26 a.m. UTC
In this test case, an image may have a signature with additional
intermediate certificates. A chain of trust will be followed and all
the certificates in the middle of chain must be verified before loading.

Signed-off-by: AKASHI Takahiro <takahiro.akashi@linaro.org>
---
 test/py/tests/test_efi_secboot/conftest.py    | 138 +++++++++++++++++-
 test/py/tests/test_efi_secboot/defs.py        |  11 +-
 test/py/tests/test_efi_secboot/openssl.cnf    |  48 ++++++
 .../test_efi_secboot/test_signed_intca.py     | 134 +++++++++++++++++
 4 files changed, 328 insertions(+), 3 deletions(-)
 create mode 100644 test/py/tests/test_efi_secboot/openssl.cnf
 create mode 100644 test/py/tests/test_efi_secboot/test_signed_intca.py

Comments

Heinrich Schuchardt July 7, 2020, 10:42 a.m. UTC | #1
On 16.06.20 07:26, AKASHI Takahiro wrote:
> In this test case, an image may have a signature with additional
> intermediate certificates. A chain of trust will be followed and all
> the certificates in the middle of chain must be verified before loading.
>
> Signed-off-by: AKASHI Takahiro <takahiro.akashi@linaro.org>
> ---
>  test/py/tests/test_efi_secboot/conftest.py    | 138 +++++++++++++++++-
>  test/py/tests/test_efi_secboot/defs.py        |  11 +-
>  test/py/tests/test_efi_secboot/openssl.cnf    |  48 ++++++
>  .../test_efi_secboot/test_signed_intca.py     | 134 +++++++++++++++++
>  4 files changed, 328 insertions(+), 3 deletions(-)
>  create mode 100644 test/py/tests/test_efi_secboot/openssl.cnf
>  create mode 100644 test/py/tests/test_efi_secboot/test_signed_intca.py
>
> diff --git a/test/py/tests/test_efi_secboot/conftest.py b/test/py/tests/test_efi_secboot/conftest.py
> index 34abcd79ae00..e5ac2a2a21b7 100644
> --- a/test/py/tests/test_efi_secboot/conftest.py
> +++ b/test/py/tests/test_efi_secboot/conftest.py
> @@ -37,7 +37,7 @@ def efi_boot_env(request, u_boot_config):
>      global HELLO_PATH
>
>      image_path = u_boot_config.persistent_data_dir
> -    image_path = image_path + '/' + EFI_SECBOOT_IMAGE_NAME
> +    image_path = image_path + '/' + EFI_SECBOOT_IMAGE_NAME + '.img'
>      image_size = EFI_SECBOOT_IMAGE_SIZE
>      part_size = EFI_SECBOOT_PART_SIZE
>      fs_type = EFI_SECBOOT_FS_TYPE
> @@ -46,7 +46,7 @@ def efi_boot_env(request, u_boot_config):
>          HELLO_PATH = u_boot_config.build_dir + '/lib/efi_loader/helloworld.efi'
>
>      try:
> -        mnt_point = u_boot_config.persistent_data_dir + '/mnt_efisecure'
> +        mnt_point = u_boot_config.persistent_data_dir + MNTPNT
>          check_call('mkdir -p {}'.format(mnt_point), shell=True)
>
>          # create a disk/partition
> @@ -170,3 +170,137 @@ def efi_boot_env(request, u_boot_config):
>          yield image_path
>      finally:
>          call('rm -f %s' % image_path, shell=True)
> +
> +#
> +# Fixture for UEFI secure boot test of intermediate certificates

Thanks for adding a test.


> +#
> +@pytest.fixture(scope='session')
> +def efi_boot_env_intca(request, u_boot_config):
> +    """Set up a file system to be used in UEFI secure boot test
> +    of intermediate certificates.
> +
> +    Args:
> +        request: Pytest request object.
> +	u_boot_config: U-boot configuration.
> +
> +    Return:
> +        A path to disk image to be used for testing
> +    """
> +    global HELLO_PATH
> +
> +    image_path = u_boot_config.persistent_data_dir
> +    image_path = image_path + '/' + EFI_SECBOOT_IMAGE_NAME + '_intca.img'
> +    image_size = EFI_SECBOOT_IMAGE_SIZE
> +    part_size = EFI_SECBOOT_PART_SIZE
> +    fs_type = EFI_SECBOOT_FS_TYPE
> +
> +    if HELLO_PATH == '':
> +        HELLO_PATH = u_boot_config.build_dir + '/lib/efi_loader/helloworld.efi'
> +
> +    try:
> +        mnt_point = u_boot_config.persistent_data_dir + MNTPNT
> +        check_call('mkdir -p {}'.format(mnt_point), shell=True)
> +
> +        # create a disk/partition
> +        check_call('dd if=/dev/zero of=%s bs=1MiB count=%d'
> +                            % (image_path, image_size), shell=True)
> +        check_call('sgdisk %s -n 1:0:+%dMiB'
> +                            % (image_path, part_size), shell=True)
> +        # create a file system
> +        check_call('dd if=/dev/zero of=%s.tmp bs=1MiB count=%d'
> +                            % (image_path, part_size), shell=True)
> +        check_call('mkfs -t %s %s.tmp' % (fs_type, image_path), shell=True)
> +        check_call('dd if=%s.tmp of=%s bs=1MiB seek=1 count=%d conv=notrunc'
> +                            % (image_path, image_path, 1), shell=True)
> +        check_call('rm %s.tmp' % image_path, shell=True)
> +        loop_dev = check_output('sudo losetup -o 1MiB --sizelimit %dMiB --show -f %s | tr -d "\n"'
> +                                % (part_size, image_path), shell=True).decode()
> +        check_output('sudo mount -t %s -o umask=000 %s %s'
> +                                % (fs_type, loop_dev, mnt_point), shell=True)

Can we use virt-make-fs to avoid sudo, please. Package libguestfs-tools
has been added to the Docker image for Gitlab recently.

> +
> +        # Create signature database
> +        ## PK
> +        check_call('cd %s; openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_PK/ -keyout PK.key -out PK.crt -nodes -days 365'
> +                            % mnt_point, shell=True)
> +        check_call('cd %s; %scert-to-efi-sig-list -g %s PK.crt PK.esl; %ssign-efi-sig-list -c PK.crt -k PK.key PK PK.esl PK.auth'
> +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> +                            shell=True)
> +        ## KEK
> +        check_call('cd %s; openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_KEK/ -keyout KEK.key -out KEK.crt -nodes -days 365'
> +                            % mnt_point, shell=True)
> +        check_call('cd %s; %scert-to-efi-sig-list -g %s KEK.crt KEK.esl; %ssign-efi-sig-list -c PK.crt -k PK.key KEK KEK.esl KEK.auth'
> +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> +                            shell=True)
> +
> +        # We will have three-tier hierarchy of certificates:
> +        #   TestRoot: Root CA (self-signed)
> +        #   TestSub: Intermediate CA (signed by Root CA)
> +        #   TestCert: User certificate (signed by Intermediate CA, and used
> +        #             for signing an image)
> +        #
> +        # NOTE:
> +        # I consulted the following EDK2 document for certificate options:
> +        #     BaseTools/Source/Python/Pkcs7Sign/Readme.md
> +        # Please not use them as they are in product system. They are
> +        # for test purpose only.
> +
> +        # TestRoot
> +        check_call('cp %s/test/py/tests/test_efi_secboot/openssl.cnf %s' % (u_boot_config.source_dir, mnt_point), shell=True)
> +        check_call('cd %s; openssl genrsa -out TestRoot.key 2048; openssl req --config openssl.cnf -extensions v3_ca -new -x509 -days 365 -key TestRoot.key -out TestRoot.crt -subj "/CN=TEST_root/"; touch index.txt' % mnt_point, shell=True)

Please, use the .format() function of the string class.

Best regards

Heinrich

> +        # TestSub
> +        check_call('cd %s; openssl genrsa -out TestSub.key 2048; openssl req -new -key TestSub.key -out TestSub.csr -subj "/CN=TEST_sub/"; openssl ca --config openssl.cnf -in TestSub.csr -out TestSub.crt -extensions v3_int_ca -days 365 -batch -rand_serial -cert TestRoot.crt -keyfile TestRoot.key' % mnt_point, shell=True)
> +        # TestCert
> +        check_call('cd %s; openssl genrsa -out TestCert.key 2048; openssl req -new -key TestCert.key -out TestCert.csr -subj "/CN=TEST_cert/"; openssl ca --config openssl.cnf -in TestCert.csr -out TestCert.crt -extensions usr_cert -days 365 -batch -rand_serial -cert TestSub.crt -keyfile TestSub.key' % mnt_point, shell=True)
> +        ## db
> +        #  for TestCert
> +        check_call('cd %s; %scert-to-efi-sig-list -g %s TestCert.crt TestCert.esl; %ssign-efi-sig-list -c KEK.crt -k KEK.key db TestCert.esl db_a.auth'
> +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> +                            shell=True)
> +        #  for TestSub
> +        check_call('cd %s; %scert-to-efi-sig-list -g %s TestSub.crt TestSub.esl; %ssign-efi-sig-list -c KEK.crt -k KEK.key db TestSub.esl db_b.auth'
> +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> +                            shell=True)
> +        #  for TestRoot
> +        check_call('cd %s; %scert-to-efi-sig-list -g %s TestRoot.crt TestRoot.esl; %ssign-efi-sig-list -c KEK.crt -k KEK.key db TestRoot.esl db_c.auth'
> +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> +                            shell=True)
> +        ## dbx (hash of certificate with revocation time)
> +        #  for TestCert
> +        check_call('cd %s; %scert-to-efi-hash-list -g %s -t 0 -s 256 TestCert.crt TestCert.crl; %ssign-efi-sig-list -c KEK.crt -k KEK.key dbx TestCert.crl dbx_a.auth'
> +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> +                            shell=True)
> +        #  for TestSub
> +        check_call('cd %s; %scert-to-efi-hash-list -g %s -t 0 -s 256 TestSub.crt TestSub.crl; %ssign-efi-sig-list -c KEK.crt -k KEK.key dbx TestSub.crl dbx_b.auth'
> +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> +                            shell=True)
> +        #  for TestRoot
> +        check_call('cd %s; %scert-to-efi-hash-list -g %s -t 0 -s 256 TestRoot.crt TestRoot.crl; %ssign-efi-sig-list -c KEK.crt -k KEK.key dbx TestRoot.crl dbx_c.auth'
> +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> +                            shell=True)
> +
> +        # Sign image
> +        # additional intermediate certificates may be included
> +        # in SignedData
> +
> +        check_call('cp %s %s' % (HELLO_PATH, mnt_point), shell=True)
> +        ## signed by TestCert
> +        check_call('cd %s; %ssbsign --key TestCert.key --cert TestCert.crt --out helloworld.efi.signed_a helloworld.efi'
> +                            % (mnt_point, SBSIGN_PATH), shell=True)
> +        ## signed by TestCert with TestSub in signature
> +        check_call('cd %s; %ssbsign --key TestCert.key --cert TestCert.crt --addcert TestSub.crt --out helloworld.efi.signed_ab helloworld.efi'
> +                            % (mnt_point, SBSIGN_PATH), shell=True)
> +        ## signed by TestCert with TestSub and TestRoot in signature
> +        check_call('cd %s; cat TestSub.crt TestRoot.crt > TestSubRoot.crt; %ssbsign --key TestCert.key --cert TestCert.crt --addcert TestSubRoot.crt --out helloworld.efi.signed_abc helloworld.efi'
> +                            % (mnt_point, SBSIGN_PATH), shell=True)
> +
> +        # Clean-up
> +        check_call('sudo umount %s' % loop_dev, shell=True)
> +        check_call('sudo losetup -d %s' % loop_dev, shell=True)
> +
> +    except CalledProcessError as e:
> +        pytest.skip('Setup failed: %s' % e.cmd)
> +        return
> +    else:
> +        yield image_path
> +    finally:
> +        call('rm -f %s' % image_path, shell=True)
> diff --git a/test/py/tests/test_efi_secboot/defs.py b/test/py/tests/test_efi_secboot/defs.py
> index 099f453979ff..c61f69a316f8 100644
> --- a/test/py/tests/test_efi_secboot/defs.py
> +++ b/test/py/tests/test_efi_secboot/defs.py
> @@ -1,7 +1,7 @@
>  # SPDX-License-Identifier:      GPL-2.0+
>
>  # Disk image name
> -EFI_SECBOOT_IMAGE_NAME = 'test_efi_secboot.img'
> +EFI_SECBOOT_IMAGE_NAME = 'test_efi_secboot'
>
>  # Size in MiB
>  EFI_SECBOOT_IMAGE_SIZE = 16
> @@ -10,12 +10,21 @@ EFI_SECBOOT_PART_SIZE = 8
>  # Partition file system type
>  EFI_SECBOOT_FS_TYPE = 'vfat'
>
> +# Mount point
> +MNTPNT= 'mnt'
> +
>  # Owner guid
>  GUID = '11111111-2222-3333-4444-123456789abc'
>
>  # v1.5.1 or earlier of efitools has a bug in sha256 calculation, and
>  # you need build a newer version on your own.
> +# The path must terminate with '/'.
>  EFITOOLS_PATH = ''
>
> +# "--addcert" option of sbsign must be available, otherwise
> +# you need build a newer version on your own.
> +# The path must terminate with '/'.
> +SBSIGN_PATH= '/home/akashi/arm/misc/sbsigntools/src/'
> +
>  # Hello World application for sandbox
>  HELLO_PATH = ''
> diff --git a/test/py/tests/test_efi_secboot/openssl.cnf b/test/py/tests/test_efi_secboot/openssl.cnf
> new file mode 100644
> index 000000000000..f684f1df7e69
> --- /dev/null
> +++ b/test/py/tests/test_efi_secboot/openssl.cnf
> @@ -0,0 +1,48 @@
> +[ ca ]
> +default_ca = CA_default
> +
> +[ CA_default ]
> +new_certs_dir = .
> +database = ./index.txt
> +serial = ./serial
> +default_md = sha256
> +policy = policy_min
> +
> +[ req ]
> +distinguished_name = def_distinguished_name
> +
> +[def_distinguished_name]
> +
> +# Extensions
> +#   -addext " ... = ..."
> +#
> +[ v3_ca ]
> +   # Extensions for a typical Root CA.
> +   basicConstraints = critical,CA:TRUE
> +   keyUsage = critical, digitalSignature, cRLSign, keyCertSign
> +   subjectKeyIdentifier = hash
> +   authorityKeyIdentifier = keyid:always,issuer
> +
> +[ v3_int_ca ]
> +   # Extensions for a typical intermediate CA.
> +   basicConstraints = critical, CA:TRUE
> +   keyUsage = critical, digitalSignature, cRLSign, keyCertSign
> +   subjectKeyIdentifier = hash
> +   authorityKeyIdentifier = keyid:always,issuer
> +
> +[ usr_cert ]
> +   # Extensions for user end certificates.
> +   basicConstraints = CA:FALSE
> +   keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
> +   extendedKeyUsage = clientAuth, emailProtection
> +   subjectKeyIdentifier = hash
> +   authorityKeyIdentifier = keyid,issuer
> +
> +[ policy_min ]
> +   countryName		= optional
> +   stateOrProvinceName	= optional
> +   localityName		= optional
> +   organizationName	= optional
> +   organizationalUnitName = optional
> +   commonName		= supplied
> +   emailAddress		= optional
> diff --git a/test/py/tests/test_efi_secboot/test_signed_intca.py b/test/py/tests/test_efi_secboot/test_signed_intca.py
> new file mode 100644
> index 000000000000..80c1917a2cd3
> --- /dev/null
> +++ b/test/py/tests/test_efi_secboot/test_signed_intca.py
> @@ -0,0 +1,134 @@
> +# SPDX-License-Identifier:      GPL-2.0+
> +# Copyright (c) 2020, Linaro Limited
> +# Author: AKASHI Takahiro <takahiro.akashi@linaro.org>
> +#
> +# U-Boot UEFI: Image Authentication Test (signature with certificates chain)
> +
> +"""
> +This test verifies image authentication for a signed image which is signed
> +by user certificate and contains additional intermediate certificates in its
> +signature.
> +"""
> +
> +import pytest
> +
> +@pytest.mark.boardspec('sandbox')
> +@pytest.mark.buildconfigspec('efi_secure_boot')
> +@pytest.mark.buildconfigspec('cmd_efidebug')
> +@pytest.mark.buildconfigspec('cmd_fat')
> +@pytest.mark.buildconfigspec('cmd_nvedit_efi')
> +@pytest.mark.slow
> +class TestEfiSignedImageExt(object):
> +    def test_efi_signed_image_ext1(self, u_boot_console, efi_boot_env_intca):
> +        """
> +        Test Case 1 - authenticated by root CA in db
> +        """
> +        u_boot_console.restart_uboot()
> +        disk_img = efi_boot_env_intca
> +        with u_boot_console.log.section('Test Case 1a'):
> +            # Test Case 1a, with no Int CA and not authenticated by root CA
> +            output = u_boot_console.run_command_list([
> +                'host bind 0 %s' % disk_img,
> +                'fatload host 0:1 4000000 db_c.auth',
> +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize db',
> +                'fatload host 0:1 4000000 KEK.auth',
> +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize KEK',
> +                'fatload host 0:1 4000000 PK.auth',
> +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize PK'])
> +            assert 'Failed to set EFI variable' not in ''.join(output)
> +
> +            output = u_boot_console.run_command_list([
> +                'efidebug boot add 1 HELLO_a host 0:1 /helloworld.efi.signed_a ""',
> +                'efidebug boot next 1',
> +                'efidebug test bootmgr'])
> +            assert '\'HELLO_a\' failed' in ''.join(output)
> +            assert 'efi_start_image() returned: 26' in ''.join(output)
> +
> +        with u_boot_console.log.section('Test Case 1b'):
> +            # Test Case 1b, signed and authenticated by root CA
> +            output = u_boot_console.run_command_list([
> +                'efidebug boot add 2 HELLO_ab host 0:1 /helloworld.efi.signed_ab ""',
> +                'efidebug boot next 2',
> +                'bootefi bootmgr'])
> +            assert 'Hello, world!' in ''.join(output)
> +
> +    def test_efi_signed_image_ext2(self, u_boot_console, efi_boot_env_intca):
> +        """
> +        Test Case 2 - authenticated by root CA in db
> +        """
> +        u_boot_console.restart_uboot()
> +        disk_img = efi_boot_env_intca
> +        with u_boot_console.log.section('Test Case 2a'):
> +            # Test Case 2a, unsigned and not authenticated by root CA
> +            output = u_boot_console.run_command_list([
> +                'host bind 0 %s' % disk_img,
> +                'fatload host 0:1 4000000 KEK.auth',
> +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize KEK',
> +                'fatload host 0:1 4000000 PK.auth',
> +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize PK'])
> +            assert 'Failed to set EFI variable' not in ''.join(output)
> +
> +            output = u_boot_console.run_command_list([
> +                'efidebug boot add 1 HELLO_abc host 0:1 /helloworld.efi.signed_abc ""',
> +                'efidebug boot next 1',
> +                'efidebug test bootmgr'])
> +            assert '\'HELLO_abc\' failed' in ''.join(output)
> +            assert 'efi_start_image() returned: 26' in ''.join(output)
> +
> +        with u_boot_console.log.section('Test Case 2b'):
> +            # Test Case 2b, signed and authenticated by root CA
> +            output = u_boot_console.run_command_list([
> +                'fatload host 0:1 4000000 db_b.auth',
> +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize db',
> +                'efidebug boot next 1',
> +                'efidebug test bootmgr'])
> +            assert '\'HELLO_abc\' failed' in ''.join(output)
> +            assert 'efi_start_image() returned: 26' in ''.join(output)
> +
> +        with u_boot_console.log.section('Test Case 2c'):
> +            # Test Case 2c, signed and authenticated by root CA
> +            output = u_boot_console.run_command_list([
> +                'fatload host 0:1 4000000 db_c.auth',
> +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize db',
> +                'efidebug boot next 1',
> +                'efidebug test bootmgr'])
> +            assert 'Hello, world!' in ''.join(output)
> +
> +    def test_efi_signed_image_ext3(self, u_boot_console, efi_boot_env_intca):
> +        """
> +        Test Case 3 - revoked by dbx
> +        """
> +        u_boot_console.restart_uboot()
> +        disk_img = efi_boot_env_intca
> +        with u_boot_console.log.section('Test Case 3a'):
> +            # Test Case 3a, revoked by root CA in dbx
> +            output = u_boot_console.run_command_list([
> +                'host bind 0 %s' % disk_img,
> +                'fatload host 0:1 4000000 dbx_c.auth',
> +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize dbx',
> +                'fatload host 0:1 4000000 db_c.auth',
> +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize db',
> +                'fatload host 0:1 4000000 KEK.auth',
> +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize KEK',
> +                'fatload host 0:1 4000000 PK.auth',
> +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize PK'])
> +            assert 'Failed to set EFI variable' not in ''.join(output)
> +
> +            output = u_boot_console.run_command_list([
> +                'efidebug boot add 1 HELLO_abc host 0:1 /helloworld.efi.signed_abc ""',
> +                'efidebug boot next 1',
> +                'efidebug test bootmgr'])
> +            assert '\'HELLO_abc\' failed' in ''.join(output)
> +            assert 'efi_start_image() returned: 26' in ''.join(output)
> +
> +        with u_boot_console.log.section('Test Case 3b'):
> +            # Test Case 3b, revoked by int CA in dbx
> +            output = u_boot_console.run_command_list([
> +                'fatload host 0:1 4000000 dbx_b.auth',
> +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize dbx',
> +                'efidebug boot next 1',
> +                'efidebug test bootmgr'])
> +            assert 'Hello, world!' in ''.join(output)
> +            # Or,
> +            # assert('\'HELLO_abc\' failed' in ''.join(output))
> +            # assert('efi_start_image() returned: 26' in ''.join(output))
>
AKASHI Takahiro July 8, 2020, 6:39 a.m. UTC | #2
On Tue, Jul 07, 2020 at 12:42:35PM +0200, Heinrich Schuchardt wrote:
> On 16.06.20 07:26, AKASHI Takahiro wrote:
> > In this test case, an image may have a signature with additional
> > intermediate certificates. A chain of trust will be followed and all
> > the certificates in the middle of chain must be verified before loading.
> >
> > Signed-off-by: AKASHI Takahiro <takahiro.akashi@linaro.org>
> > ---
> >  test/py/tests/test_efi_secboot/conftest.py    | 138 +++++++++++++++++-
> >  test/py/tests/test_efi_secboot/defs.py        |  11 +-
> >  test/py/tests/test_efi_secboot/openssl.cnf    |  48 ++++++
> >  .../test_efi_secboot/test_signed_intca.py     | 134 +++++++++++++++++
> >  4 files changed, 328 insertions(+), 3 deletions(-)
> >  create mode 100644 test/py/tests/test_efi_secboot/openssl.cnf
> >  create mode 100644 test/py/tests/test_efi_secboot/test_signed_intca.py
> >
> > diff --git a/test/py/tests/test_efi_secboot/conftest.py b/test/py/tests/test_efi_secboot/conftest.py
> > index 34abcd79ae00..e5ac2a2a21b7 100644
> > --- a/test/py/tests/test_efi_secboot/conftest.py
> > +++ b/test/py/tests/test_efi_secboot/conftest.py
> > @@ -37,7 +37,7 @@ def efi_boot_env(request, u_boot_config):
> >      global HELLO_PATH
> >
> >      image_path = u_boot_config.persistent_data_dir
> > -    image_path = image_path + '/' + EFI_SECBOOT_IMAGE_NAME
> > +    image_path = image_path + '/' + EFI_SECBOOT_IMAGE_NAME + '.img'
> >      image_size = EFI_SECBOOT_IMAGE_SIZE
> >      part_size = EFI_SECBOOT_PART_SIZE
> >      fs_type = EFI_SECBOOT_FS_TYPE
> > @@ -46,7 +46,7 @@ def efi_boot_env(request, u_boot_config):
> >          HELLO_PATH = u_boot_config.build_dir + '/lib/efi_loader/helloworld.efi'
> >
> >      try:
> > -        mnt_point = u_boot_config.persistent_data_dir + '/mnt_efisecure'
> > +        mnt_point = u_boot_config.persistent_data_dir + MNTPNT
> >          check_call('mkdir -p {}'.format(mnt_point), shell=True)
> >
> >          # create a disk/partition
> > @@ -170,3 +170,137 @@ def efi_boot_env(request, u_boot_config):
> >          yield image_path
> >      finally:
> >          call('rm -f %s' % image_path, shell=True)
> > +
> > +#
> > +# Fixture for UEFI secure boot test of intermediate certificates
> 
> Thanks for adding a test.
> 
> 
> > +#
> > +@pytest.fixture(scope='session')
> > +def efi_boot_env_intca(request, u_boot_config):
> > +    """Set up a file system to be used in UEFI secure boot test
> > +    of intermediate certificates.
> > +
> > +    Args:
> > +        request: Pytest request object.
> > +	u_boot_config: U-boot configuration.
> > +
> > +    Return:
> > +        A path to disk image to be used for testing
> > +    """
> > +    global HELLO_PATH
> > +
> > +    image_path = u_boot_config.persistent_data_dir
> > +    image_path = image_path + '/' + EFI_SECBOOT_IMAGE_NAME + '_intca.img'
> > +    image_size = EFI_SECBOOT_IMAGE_SIZE
> > +    part_size = EFI_SECBOOT_PART_SIZE
> > +    fs_type = EFI_SECBOOT_FS_TYPE
> > +
> > +    if HELLO_PATH == '':
> > +        HELLO_PATH = u_boot_config.build_dir + '/lib/efi_loader/helloworld.efi'
> > +
> > +    try:
> > +        mnt_point = u_boot_config.persistent_data_dir + MNTPNT
> > +        check_call('mkdir -p {}'.format(mnt_point), shell=True)
> > +
> > +        # create a disk/partition
> > +        check_call('dd if=/dev/zero of=%s bs=1MiB count=%d'
> > +                            % (image_path, image_size), shell=True)
> > +        check_call('sgdisk %s -n 1:0:+%dMiB'
> > +                            % (image_path, part_size), shell=True)
> > +        # create a file system
> > +        check_call('dd if=/dev/zero of=%s.tmp bs=1MiB count=%d'
> > +                            % (image_path, part_size), shell=True)
> > +        check_call('mkfs -t %s %s.tmp' % (fs_type, image_path), shell=True)
> > +        check_call('dd if=%s.tmp of=%s bs=1MiB seek=1 count=%d conv=notrunc'
> > +                            % (image_path, image_path, 1), shell=True)
> > +        check_call('rm %s.tmp' % image_path, shell=True)
> > +        loop_dev = check_output('sudo losetup -o 1MiB --sizelimit %dMiB --show -f %s | tr -d "\n"'
> > +                                % (part_size, image_path), shell=True).decode()
> > +        check_output('sudo mount -t %s -o umask=000 %s %s'
> > +                                % (fs_type, loop_dev, mnt_point), shell=True)
> 
> Can we use virt-make-fs to avoid sudo, please. Package libguestfs-tools
> has been added to the Docker image for Gitlab recently.

I will check.

> > +
> > +        # Create signature database
> > +        ## PK
> > +        check_call('cd %s; openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_PK/ -keyout PK.key -out PK.crt -nodes -days 365'
> > +                            % mnt_point, shell=True)
> > +        check_call('cd %s; %scert-to-efi-sig-list -g %s PK.crt PK.esl; %ssign-efi-sig-list -c PK.crt -k PK.key PK PK.esl PK.auth'
> > +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> > +                            shell=True)
> > +        ## KEK
> > +        check_call('cd %s; openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_KEK/ -keyout KEK.key -out KEK.crt -nodes -days 365'
> > +                            % mnt_point, shell=True)
> > +        check_call('cd %s; %scert-to-efi-sig-list -g %s KEK.crt KEK.esl; %ssign-efi-sig-list -c PK.crt -k PK.key KEK KEK.esl KEK.auth'
> > +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> > +                            shell=True)
> > +
> > +        # We will have three-tier hierarchy of certificates:
> > +        #   TestRoot: Root CA (self-signed)
> > +        #   TestSub: Intermediate CA (signed by Root CA)
> > +        #   TestCert: User certificate (signed by Intermediate CA, and used
> > +        #             for signing an image)
> > +        #
> > +        # NOTE:
> > +        # I consulted the following EDK2 document for certificate options:
> > +        #     BaseTools/Source/Python/Pkcs7Sign/Readme.md
> > +        # Please not use them as they are in product system. They are
> > +        # for test purpose only.
> > +
> > +        # TestRoot
> > +        check_call('cp %s/test/py/tests/test_efi_secboot/openssl.cnf %s' % (u_boot_config.source_dir, mnt_point), shell=True)
> > +        check_call('cd %s; openssl genrsa -out TestRoot.key 2048; openssl req --config openssl.cnf -extensions v3_ca -new -x509 -days 365 -key TestRoot.key -out TestRoot.crt -subj "/CN=TEST_root/"; touch index.txt' % mnt_point, shell=True)
> 
> Please, use the .format() function of the string class.

See my previous comment.

-Takahiro Akashi
> 
> Best regards
> 
> Heinrich
> 
> > +        # TestSub
> > +        check_call('cd %s; openssl genrsa -out TestSub.key 2048; openssl req -new -key TestSub.key -out TestSub.csr -subj "/CN=TEST_sub/"; openssl ca --config openssl.cnf -in TestSub.csr -out TestSub.crt -extensions v3_int_ca -days 365 -batch -rand_serial -cert TestRoot.crt -keyfile TestRoot.key' % mnt_point, shell=True)
> > +        # TestCert
> > +        check_call('cd %s; openssl genrsa -out TestCert.key 2048; openssl req -new -key TestCert.key -out TestCert.csr -subj "/CN=TEST_cert/"; openssl ca --config openssl.cnf -in TestCert.csr -out TestCert.crt -extensions usr_cert -days 365 -batch -rand_serial -cert TestSub.crt -keyfile TestSub.key' % mnt_point, shell=True)
> > +        ## db
> > +        #  for TestCert
> > +        check_call('cd %s; %scert-to-efi-sig-list -g %s TestCert.crt TestCert.esl; %ssign-efi-sig-list -c KEK.crt -k KEK.key db TestCert.esl db_a.auth'
> > +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> > +                            shell=True)
> > +        #  for TestSub
> > +        check_call('cd %s; %scert-to-efi-sig-list -g %s TestSub.crt TestSub.esl; %ssign-efi-sig-list -c KEK.crt -k KEK.key db TestSub.esl db_b.auth'
> > +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> > +                            shell=True)
> > +        #  for TestRoot
> > +        check_call('cd %s; %scert-to-efi-sig-list -g %s TestRoot.crt TestRoot.esl; %ssign-efi-sig-list -c KEK.crt -k KEK.key db TestRoot.esl db_c.auth'
> > +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> > +                            shell=True)
> > +        ## dbx (hash of certificate with revocation time)
> > +        #  for TestCert
> > +        check_call('cd %s; %scert-to-efi-hash-list -g %s -t 0 -s 256 TestCert.crt TestCert.crl; %ssign-efi-sig-list -c KEK.crt -k KEK.key dbx TestCert.crl dbx_a.auth'
> > +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> > +                            shell=True)
> > +        #  for TestSub
> > +        check_call('cd %s; %scert-to-efi-hash-list -g %s -t 0 -s 256 TestSub.crt TestSub.crl; %ssign-efi-sig-list -c KEK.crt -k KEK.key dbx TestSub.crl dbx_b.auth'
> > +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> > +                            shell=True)
> > +        #  for TestRoot
> > +        check_call('cd %s; %scert-to-efi-hash-list -g %s -t 0 -s 256 TestRoot.crt TestRoot.crl; %ssign-efi-sig-list -c KEK.crt -k KEK.key dbx TestRoot.crl dbx_c.auth'
> > +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> > +                            shell=True)
> > +
> > +        # Sign image
> > +        # additional intermediate certificates may be included
> > +        # in SignedData
> > +
> > +        check_call('cp %s %s' % (HELLO_PATH, mnt_point), shell=True)
> > +        ## signed by TestCert
> > +        check_call('cd %s; %ssbsign --key TestCert.key --cert TestCert.crt --out helloworld.efi.signed_a helloworld.efi'
> > +                            % (mnt_point, SBSIGN_PATH), shell=True)
> > +        ## signed by TestCert with TestSub in signature
> > +        check_call('cd %s; %ssbsign --key TestCert.key --cert TestCert.crt --addcert TestSub.crt --out helloworld.efi.signed_ab helloworld.efi'
> > +                            % (mnt_point, SBSIGN_PATH), shell=True)
> > +        ## signed by TestCert with TestSub and TestRoot in signature
> > +        check_call('cd %s; cat TestSub.crt TestRoot.crt > TestSubRoot.crt; %ssbsign --key TestCert.key --cert TestCert.crt --addcert TestSubRoot.crt --out helloworld.efi.signed_abc helloworld.efi'
> > +                            % (mnt_point, SBSIGN_PATH), shell=True)
> > +
> > +        # Clean-up
> > +        check_call('sudo umount %s' % loop_dev, shell=True)
> > +        check_call('sudo losetup -d %s' % loop_dev, shell=True)
> > +
> > +    except CalledProcessError as e:
> > +        pytest.skip('Setup failed: %s' % e.cmd)
> > +        return
> > +    else:
> > +        yield image_path
> > +    finally:
> > +        call('rm -f %s' % image_path, shell=True)
> > diff --git a/test/py/tests/test_efi_secboot/defs.py b/test/py/tests/test_efi_secboot/defs.py
> > index 099f453979ff..c61f69a316f8 100644
> > --- a/test/py/tests/test_efi_secboot/defs.py
> > +++ b/test/py/tests/test_efi_secboot/defs.py
> > @@ -1,7 +1,7 @@
> >  # SPDX-License-Identifier:      GPL-2.0+
> >
> >  # Disk image name
> > -EFI_SECBOOT_IMAGE_NAME = 'test_efi_secboot.img'
> > +EFI_SECBOOT_IMAGE_NAME = 'test_efi_secboot'
> >
> >  # Size in MiB
> >  EFI_SECBOOT_IMAGE_SIZE = 16
> > @@ -10,12 +10,21 @@ EFI_SECBOOT_PART_SIZE = 8
> >  # Partition file system type
> >  EFI_SECBOOT_FS_TYPE = 'vfat'
> >
> > +# Mount point
> > +MNTPNT= 'mnt'
> > +
> >  # Owner guid
> >  GUID = '11111111-2222-3333-4444-123456789abc'
> >
> >  # v1.5.1 or earlier of efitools has a bug in sha256 calculation, and
> >  # you need build a newer version on your own.
> > +# The path must terminate with '/'.
> >  EFITOOLS_PATH = ''
> >
> > +# "--addcert" option of sbsign must be available, otherwise
> > +# you need build a newer version on your own.
> > +# The path must terminate with '/'.
> > +SBSIGN_PATH= '/home/akashi/arm/misc/sbsigntools/src/'
> > +
> >  # Hello World application for sandbox
> >  HELLO_PATH = ''
> > diff --git a/test/py/tests/test_efi_secboot/openssl.cnf b/test/py/tests/test_efi_secboot/openssl.cnf
> > new file mode 100644
> > index 000000000000..f684f1df7e69
> > --- /dev/null
> > +++ b/test/py/tests/test_efi_secboot/openssl.cnf
> > @@ -0,0 +1,48 @@
> > +[ ca ]
> > +default_ca = CA_default
> > +
> > +[ CA_default ]
> > +new_certs_dir = .
> > +database = ./index.txt
> > +serial = ./serial
> > +default_md = sha256
> > +policy = policy_min
> > +
> > +[ req ]
> > +distinguished_name = def_distinguished_name
> > +
> > +[def_distinguished_name]
> > +
> > +# Extensions
> > +#   -addext " ... = ..."
> > +#
> > +[ v3_ca ]
> > +   # Extensions for a typical Root CA.
> > +   basicConstraints = critical,CA:TRUE
> > +   keyUsage = critical, digitalSignature, cRLSign, keyCertSign
> > +   subjectKeyIdentifier = hash
> > +   authorityKeyIdentifier = keyid:always,issuer
> > +
> > +[ v3_int_ca ]
> > +   # Extensions for a typical intermediate CA.
> > +   basicConstraints = critical, CA:TRUE
> > +   keyUsage = critical, digitalSignature, cRLSign, keyCertSign
> > +   subjectKeyIdentifier = hash
> > +   authorityKeyIdentifier = keyid:always,issuer
> > +
> > +[ usr_cert ]
> > +   # Extensions for user end certificates.
> > +   basicConstraints = CA:FALSE
> > +   keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
> > +   extendedKeyUsage = clientAuth, emailProtection
> > +   subjectKeyIdentifier = hash
> > +   authorityKeyIdentifier = keyid,issuer
> > +
> > +[ policy_min ]
> > +   countryName		= optional
> > +   stateOrProvinceName	= optional
> > +   localityName		= optional
> > +   organizationName	= optional
> > +   organizationalUnitName = optional
> > +   commonName		= supplied
> > +   emailAddress		= optional
> > diff --git a/test/py/tests/test_efi_secboot/test_signed_intca.py b/test/py/tests/test_efi_secboot/test_signed_intca.py
> > new file mode 100644
> > index 000000000000..80c1917a2cd3
> > --- /dev/null
> > +++ b/test/py/tests/test_efi_secboot/test_signed_intca.py
> > @@ -0,0 +1,134 @@
> > +# SPDX-License-Identifier:      GPL-2.0+
> > +# Copyright (c) 2020, Linaro Limited
> > +# Author: AKASHI Takahiro <takahiro.akashi@linaro.org>
> > +#
> > +# U-Boot UEFI: Image Authentication Test (signature with certificates chain)
> > +
> > +"""
> > +This test verifies image authentication for a signed image which is signed
> > +by user certificate and contains additional intermediate certificates in its
> > +signature.
> > +"""
> > +
> > +import pytest
> > +
> > +@pytest.mark.boardspec('sandbox')
> > +@pytest.mark.buildconfigspec('efi_secure_boot')
> > +@pytest.mark.buildconfigspec('cmd_efidebug')
> > +@pytest.mark.buildconfigspec('cmd_fat')
> > +@pytest.mark.buildconfigspec('cmd_nvedit_efi')
> > +@pytest.mark.slow
> > +class TestEfiSignedImageExt(object):
> > +    def test_efi_signed_image_ext1(self, u_boot_console, efi_boot_env_intca):
> > +        """
> > +        Test Case 1 - authenticated by root CA in db
> > +        """
> > +        u_boot_console.restart_uboot()
> > +        disk_img = efi_boot_env_intca
> > +        with u_boot_console.log.section('Test Case 1a'):
> > +            # Test Case 1a, with no Int CA and not authenticated by root CA
> > +            output = u_boot_console.run_command_list([
> > +                'host bind 0 %s' % disk_img,
> > +                'fatload host 0:1 4000000 db_c.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize db',
> > +                'fatload host 0:1 4000000 KEK.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize KEK',
> > +                'fatload host 0:1 4000000 PK.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize PK'])
> > +            assert 'Failed to set EFI variable' not in ''.join(output)
> > +
> > +            output = u_boot_console.run_command_list([
> > +                'efidebug boot add 1 HELLO_a host 0:1 /helloworld.efi.signed_a ""',
> > +                'efidebug boot next 1',
> > +                'efidebug test bootmgr'])
> > +            assert '\'HELLO_a\' failed' in ''.join(output)
> > +            assert 'efi_start_image() returned: 26' in ''.join(output)
> > +
> > +        with u_boot_console.log.section('Test Case 1b'):
> > +            # Test Case 1b, signed and authenticated by root CA
> > +            output = u_boot_console.run_command_list([
> > +                'efidebug boot add 2 HELLO_ab host 0:1 /helloworld.efi.signed_ab ""',
> > +                'efidebug boot next 2',
> > +                'bootefi bootmgr'])
> > +            assert 'Hello, world!' in ''.join(output)
> > +
> > +    def test_efi_signed_image_ext2(self, u_boot_console, efi_boot_env_intca):
> > +        """
> > +        Test Case 2 - authenticated by root CA in db
> > +        """
> > +        u_boot_console.restart_uboot()
> > +        disk_img = efi_boot_env_intca
> > +        with u_boot_console.log.section('Test Case 2a'):
> > +            # Test Case 2a, unsigned and not authenticated by root CA
> > +            output = u_boot_console.run_command_list([
> > +                'host bind 0 %s' % disk_img,
> > +                'fatload host 0:1 4000000 KEK.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize KEK',
> > +                'fatload host 0:1 4000000 PK.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize PK'])
> > +            assert 'Failed to set EFI variable' not in ''.join(output)
> > +
> > +            output = u_boot_console.run_command_list([
> > +                'efidebug boot add 1 HELLO_abc host 0:1 /helloworld.efi.signed_abc ""',
> > +                'efidebug boot next 1',
> > +                'efidebug test bootmgr'])
> > +            assert '\'HELLO_abc\' failed' in ''.join(output)
> > +            assert 'efi_start_image() returned: 26' in ''.join(output)
> > +
> > +        with u_boot_console.log.section('Test Case 2b'):
> > +            # Test Case 2b, signed and authenticated by root CA
> > +            output = u_boot_console.run_command_list([
> > +                'fatload host 0:1 4000000 db_b.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize db',
> > +                'efidebug boot next 1',
> > +                'efidebug test bootmgr'])
> > +            assert '\'HELLO_abc\' failed' in ''.join(output)
> > +            assert 'efi_start_image() returned: 26' in ''.join(output)
> > +
> > +        with u_boot_console.log.section('Test Case 2c'):
> > +            # Test Case 2c, signed and authenticated by root CA
> > +            output = u_boot_console.run_command_list([
> > +                'fatload host 0:1 4000000 db_c.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize db',
> > +                'efidebug boot next 1',
> > +                'efidebug test bootmgr'])
> > +            assert 'Hello, world!' in ''.join(output)
> > +
> > +    def test_efi_signed_image_ext3(self, u_boot_console, efi_boot_env_intca):
> > +        """
> > +        Test Case 3 - revoked by dbx
> > +        """
> > +        u_boot_console.restart_uboot()
> > +        disk_img = efi_boot_env_intca
> > +        with u_boot_console.log.section('Test Case 3a'):
> > +            # Test Case 3a, revoked by root CA in dbx
> > +            output = u_boot_console.run_command_list([
> > +                'host bind 0 %s' % disk_img,
> > +                'fatload host 0:1 4000000 dbx_c.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize dbx',
> > +                'fatload host 0:1 4000000 db_c.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize db',
> > +                'fatload host 0:1 4000000 KEK.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize KEK',
> > +                'fatload host 0:1 4000000 PK.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize PK'])
> > +            assert 'Failed to set EFI variable' not in ''.join(output)
> > +
> > +            output = u_boot_console.run_command_list([
> > +                'efidebug boot add 1 HELLO_abc host 0:1 /helloworld.efi.signed_abc ""',
> > +                'efidebug boot next 1',
> > +                'efidebug test bootmgr'])
> > +            assert '\'HELLO_abc\' failed' in ''.join(output)
> > +            assert 'efi_start_image() returned: 26' in ''.join(output)
> > +
> > +        with u_boot_console.log.section('Test Case 3b'):
> > +            # Test Case 3b, revoked by int CA in dbx
> > +            output = u_boot_console.run_command_list([
> > +                'fatload host 0:1 4000000 dbx_b.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize dbx',
> > +                'efidebug boot next 1',
> > +                'efidebug test bootmgr'])
> > +            assert 'Hello, world!' in ''.join(output)
> > +            # Or,
> > +            # assert('\'HELLO_abc\' failed' in ''.join(output))
> > +            # assert('efi_start_image() returned: 26' in ''.join(output))
> >
>
AKASHI Takahiro July 9, 2020, 12:58 a.m. UTC | #3
Hi Tom,

I'd like to make sure of your policy about usage of "sudo" on CI.
Do you think that we should always avoid using "sudo" in testing?

I remember that you had allowed us to run sudo in (python)
test scripts on Travis CI when I requested this (for FAT filesystem?).

-Takahiro Akashi

On Tue, Jul 07, 2020 at 12:42:35PM +0200, Heinrich Schuchardt wrote:
> On 16.06.20 07:26, AKASHI Takahiro wrote:
> > In this test case, an image may have a signature with additional
> > intermediate certificates. A chain of trust will be followed and all
> > the certificates in the middle of chain must be verified before loading.
> >
> > Signed-off-by: AKASHI Takahiro <takahiro.akashi@linaro.org>
> > ---
> >  test/py/tests/test_efi_secboot/conftest.py    | 138 +++++++++++++++++-
> >  test/py/tests/test_efi_secboot/defs.py        |  11 +-
> >  test/py/tests/test_efi_secboot/openssl.cnf    |  48 ++++++
> >  .../test_efi_secboot/test_signed_intca.py     | 134 +++++++++++++++++
> >  4 files changed, 328 insertions(+), 3 deletions(-)
> >  create mode 100644 test/py/tests/test_efi_secboot/openssl.cnf
> >  create mode 100644 test/py/tests/test_efi_secboot/test_signed_intca.py
> >
> > diff --git a/test/py/tests/test_efi_secboot/conftest.py b/test/py/tests/test_efi_secboot/conftest.py
> > index 34abcd79ae00..e5ac2a2a21b7 100644
> > --- a/test/py/tests/test_efi_secboot/conftest.py
> > +++ b/test/py/tests/test_efi_secboot/conftest.py
> > @@ -37,7 +37,7 @@ def efi_boot_env(request, u_boot_config):
> >      global HELLO_PATH
> >
> >      image_path = u_boot_config.persistent_data_dir
> > -    image_path = image_path + '/' + EFI_SECBOOT_IMAGE_NAME
> > +    image_path = image_path + '/' + EFI_SECBOOT_IMAGE_NAME + '.img'
> >      image_size = EFI_SECBOOT_IMAGE_SIZE
> >      part_size = EFI_SECBOOT_PART_SIZE
> >      fs_type = EFI_SECBOOT_FS_TYPE
> > @@ -46,7 +46,7 @@ def efi_boot_env(request, u_boot_config):
> >          HELLO_PATH = u_boot_config.build_dir + '/lib/efi_loader/helloworld.efi'
> >
> >      try:
> > -        mnt_point = u_boot_config.persistent_data_dir + '/mnt_efisecure'
> > +        mnt_point = u_boot_config.persistent_data_dir + MNTPNT
> >          check_call('mkdir -p {}'.format(mnt_point), shell=True)
> >
> >          # create a disk/partition
> > @@ -170,3 +170,137 @@ def efi_boot_env(request, u_boot_config):
> >          yield image_path
> >      finally:
> >          call('rm -f %s' % image_path, shell=True)
> > +
> > +#
> > +# Fixture for UEFI secure boot test of intermediate certificates
> 
> Thanks for adding a test.
> 
> 
> > +#
> > +@pytest.fixture(scope='session')
> > +def efi_boot_env_intca(request, u_boot_config):
> > +    """Set up a file system to be used in UEFI secure boot test
> > +    of intermediate certificates.
> > +
> > +    Args:
> > +        request: Pytest request object.
> > +	u_boot_config: U-boot configuration.
> > +
> > +    Return:
> > +        A path to disk image to be used for testing
> > +    """
> > +    global HELLO_PATH
> > +
> > +    image_path = u_boot_config.persistent_data_dir
> > +    image_path = image_path + '/' + EFI_SECBOOT_IMAGE_NAME + '_intca.img'
> > +    image_size = EFI_SECBOOT_IMAGE_SIZE
> > +    part_size = EFI_SECBOOT_PART_SIZE
> > +    fs_type = EFI_SECBOOT_FS_TYPE
> > +
> > +    if HELLO_PATH == '':
> > +        HELLO_PATH = u_boot_config.build_dir + '/lib/efi_loader/helloworld.efi'
> > +
> > +    try:
> > +        mnt_point = u_boot_config.persistent_data_dir + MNTPNT
> > +        check_call('mkdir -p {}'.format(mnt_point), shell=True)
> > +
> > +        # create a disk/partition
> > +        check_call('dd if=/dev/zero of=%s bs=1MiB count=%d'
> > +                            % (image_path, image_size), shell=True)
> > +        check_call('sgdisk %s -n 1:0:+%dMiB'
> > +                            % (image_path, part_size), shell=True)
> > +        # create a file system
> > +        check_call('dd if=/dev/zero of=%s.tmp bs=1MiB count=%d'
> > +                            % (image_path, part_size), shell=True)
> > +        check_call('mkfs -t %s %s.tmp' % (fs_type, image_path), shell=True)
> > +        check_call('dd if=%s.tmp of=%s bs=1MiB seek=1 count=%d conv=notrunc'
> > +                            % (image_path, image_path, 1), shell=True)
> > +        check_call('rm %s.tmp' % image_path, shell=True)
> > +        loop_dev = check_output('sudo losetup -o 1MiB --sizelimit %dMiB --show -f %s | tr -d "\n"'
> > +                                % (part_size, image_path), shell=True).decode()
> > +        check_output('sudo mount -t %s -o umask=000 %s %s'
> > +                                % (fs_type, loop_dev, mnt_point), shell=True)
> 
> Can we use virt-make-fs to avoid sudo, please. Package libguestfs-tools
> has been added to the Docker image for Gitlab recently.
> 
> > +
> > +        # Create signature database
> > +        ## PK
> > +        check_call('cd %s; openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_PK/ -keyout PK.key -out PK.crt -nodes -days 365'
> > +                            % mnt_point, shell=True)
> > +        check_call('cd %s; %scert-to-efi-sig-list -g %s PK.crt PK.esl; %ssign-efi-sig-list -c PK.crt -k PK.key PK PK.esl PK.auth'
> > +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> > +                            shell=True)
> > +        ## KEK
> > +        check_call('cd %s; openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_KEK/ -keyout KEK.key -out KEK.crt -nodes -days 365'
> > +                            % mnt_point, shell=True)
> > +        check_call('cd %s; %scert-to-efi-sig-list -g %s KEK.crt KEK.esl; %ssign-efi-sig-list -c PK.crt -k PK.key KEK KEK.esl KEK.auth'
> > +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> > +                            shell=True)
> > +
> > +        # We will have three-tier hierarchy of certificates:
> > +        #   TestRoot: Root CA (self-signed)
> > +        #   TestSub: Intermediate CA (signed by Root CA)
> > +        #   TestCert: User certificate (signed by Intermediate CA, and used
> > +        #             for signing an image)
> > +        #
> > +        # NOTE:
> > +        # I consulted the following EDK2 document for certificate options:
> > +        #     BaseTools/Source/Python/Pkcs7Sign/Readme.md
> > +        # Please not use them as they are in product system. They are
> > +        # for test purpose only.
> > +
> > +        # TestRoot
> > +        check_call('cp %s/test/py/tests/test_efi_secboot/openssl.cnf %s' % (u_boot_config.source_dir, mnt_point), shell=True)
> > +        check_call('cd %s; openssl genrsa -out TestRoot.key 2048; openssl req --config openssl.cnf -extensions v3_ca -new -x509 -days 365 -key TestRoot.key -out TestRoot.crt -subj "/CN=TEST_root/"; touch index.txt' % mnt_point, shell=True)
> 
> Please, use the .format() function of the string class.
> 
> Best regards
> 
> Heinrich
> 
> > +        # TestSub
> > +        check_call('cd %s; openssl genrsa -out TestSub.key 2048; openssl req -new -key TestSub.key -out TestSub.csr -subj "/CN=TEST_sub/"; openssl ca --config openssl.cnf -in TestSub.csr -out TestSub.crt -extensions v3_int_ca -days 365 -batch -rand_serial -cert TestRoot.crt -keyfile TestRoot.key' % mnt_point, shell=True)
> > +        # TestCert
> > +        check_call('cd %s; openssl genrsa -out TestCert.key 2048; openssl req -new -key TestCert.key -out TestCert.csr -subj "/CN=TEST_cert/"; openssl ca --config openssl.cnf -in TestCert.csr -out TestCert.crt -extensions usr_cert -days 365 -batch -rand_serial -cert TestSub.crt -keyfile TestSub.key' % mnt_point, shell=True)
> > +        ## db
> > +        #  for TestCert
> > +        check_call('cd %s; %scert-to-efi-sig-list -g %s TestCert.crt TestCert.esl; %ssign-efi-sig-list -c KEK.crt -k KEK.key db TestCert.esl db_a.auth'
> > +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> > +                            shell=True)
> > +        #  for TestSub
> > +        check_call('cd %s; %scert-to-efi-sig-list -g %s TestSub.crt TestSub.esl; %ssign-efi-sig-list -c KEK.crt -k KEK.key db TestSub.esl db_b.auth'
> > +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> > +                            shell=True)
> > +        #  for TestRoot
> > +        check_call('cd %s; %scert-to-efi-sig-list -g %s TestRoot.crt TestRoot.esl; %ssign-efi-sig-list -c KEK.crt -k KEK.key db TestRoot.esl db_c.auth'
> > +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> > +                            shell=True)
> > +        ## dbx (hash of certificate with revocation time)
> > +        #  for TestCert
> > +        check_call('cd %s; %scert-to-efi-hash-list -g %s -t 0 -s 256 TestCert.crt TestCert.crl; %ssign-efi-sig-list -c KEK.crt -k KEK.key dbx TestCert.crl dbx_a.auth'
> > +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> > +                            shell=True)
> > +        #  for TestSub
> > +        check_call('cd %s; %scert-to-efi-hash-list -g %s -t 0 -s 256 TestSub.crt TestSub.crl; %ssign-efi-sig-list -c KEK.crt -k KEK.key dbx TestSub.crl dbx_b.auth'
> > +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> > +                            shell=True)
> > +        #  for TestRoot
> > +        check_call('cd %s; %scert-to-efi-hash-list -g %s -t 0 -s 256 TestRoot.crt TestRoot.crl; %ssign-efi-sig-list -c KEK.crt -k KEK.key dbx TestRoot.crl dbx_c.auth'
> > +                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
> > +                            shell=True)
> > +
> > +        # Sign image
> > +        # additional intermediate certificates may be included
> > +        # in SignedData
> > +
> > +        check_call('cp %s %s' % (HELLO_PATH, mnt_point), shell=True)
> > +        ## signed by TestCert
> > +        check_call('cd %s; %ssbsign --key TestCert.key --cert TestCert.crt --out helloworld.efi.signed_a helloworld.efi'
> > +                            % (mnt_point, SBSIGN_PATH), shell=True)
> > +        ## signed by TestCert with TestSub in signature
> > +        check_call('cd %s; %ssbsign --key TestCert.key --cert TestCert.crt --addcert TestSub.crt --out helloworld.efi.signed_ab helloworld.efi'
> > +                            % (mnt_point, SBSIGN_PATH), shell=True)
> > +        ## signed by TestCert with TestSub and TestRoot in signature
> > +        check_call('cd %s; cat TestSub.crt TestRoot.crt > TestSubRoot.crt; %ssbsign --key TestCert.key --cert TestCert.crt --addcert TestSubRoot.crt --out helloworld.efi.signed_abc helloworld.efi'
> > +                            % (mnt_point, SBSIGN_PATH), shell=True)
> > +
> > +        # Clean-up
> > +        check_call('sudo umount %s' % loop_dev, shell=True)
> > +        check_call('sudo losetup -d %s' % loop_dev, shell=True)
> > +
> > +    except CalledProcessError as e:
> > +        pytest.skip('Setup failed: %s' % e.cmd)
> > +        return
> > +    else:
> > +        yield image_path
> > +    finally:
> > +        call('rm -f %s' % image_path, shell=True)
> > diff --git a/test/py/tests/test_efi_secboot/defs.py b/test/py/tests/test_efi_secboot/defs.py
> > index 099f453979ff..c61f69a316f8 100644
> > --- a/test/py/tests/test_efi_secboot/defs.py
> > +++ b/test/py/tests/test_efi_secboot/defs.py
> > @@ -1,7 +1,7 @@
> >  # SPDX-License-Identifier:      GPL-2.0+
> >
> >  # Disk image name
> > -EFI_SECBOOT_IMAGE_NAME = 'test_efi_secboot.img'
> > +EFI_SECBOOT_IMAGE_NAME = 'test_efi_secboot'
> >
> >  # Size in MiB
> >  EFI_SECBOOT_IMAGE_SIZE = 16
> > @@ -10,12 +10,21 @@ EFI_SECBOOT_PART_SIZE = 8
> >  # Partition file system type
> >  EFI_SECBOOT_FS_TYPE = 'vfat'
> >
> > +# Mount point
> > +MNTPNT= 'mnt'
> > +
> >  # Owner guid
> >  GUID = '11111111-2222-3333-4444-123456789abc'
> >
> >  # v1.5.1 or earlier of efitools has a bug in sha256 calculation, and
> >  # you need build a newer version on your own.
> > +# The path must terminate with '/'.
> >  EFITOOLS_PATH = ''
> >
> > +# "--addcert" option of sbsign must be available, otherwise
> > +# you need build a newer version on your own.
> > +# The path must terminate with '/'.
> > +SBSIGN_PATH= '/home/akashi/arm/misc/sbsigntools/src/'
> > +
> >  # Hello World application for sandbox
> >  HELLO_PATH = ''
> > diff --git a/test/py/tests/test_efi_secboot/openssl.cnf b/test/py/tests/test_efi_secboot/openssl.cnf
> > new file mode 100644
> > index 000000000000..f684f1df7e69
> > --- /dev/null
> > +++ b/test/py/tests/test_efi_secboot/openssl.cnf
> > @@ -0,0 +1,48 @@
> > +[ ca ]
> > +default_ca = CA_default
> > +
> > +[ CA_default ]
> > +new_certs_dir = .
> > +database = ./index.txt
> > +serial = ./serial
> > +default_md = sha256
> > +policy = policy_min
> > +
> > +[ req ]
> > +distinguished_name = def_distinguished_name
> > +
> > +[def_distinguished_name]
> > +
> > +# Extensions
> > +#   -addext " ... = ..."
> > +#
> > +[ v3_ca ]
> > +   # Extensions for a typical Root CA.
> > +   basicConstraints = critical,CA:TRUE
> > +   keyUsage = critical, digitalSignature, cRLSign, keyCertSign
> > +   subjectKeyIdentifier = hash
> > +   authorityKeyIdentifier = keyid:always,issuer
> > +
> > +[ v3_int_ca ]
> > +   # Extensions for a typical intermediate CA.
> > +   basicConstraints = critical, CA:TRUE
> > +   keyUsage = critical, digitalSignature, cRLSign, keyCertSign
> > +   subjectKeyIdentifier = hash
> > +   authorityKeyIdentifier = keyid:always,issuer
> > +
> > +[ usr_cert ]
> > +   # Extensions for user end certificates.
> > +   basicConstraints = CA:FALSE
> > +   keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
> > +   extendedKeyUsage = clientAuth, emailProtection
> > +   subjectKeyIdentifier = hash
> > +   authorityKeyIdentifier = keyid,issuer
> > +
> > +[ policy_min ]
> > +   countryName		= optional
> > +   stateOrProvinceName	= optional
> > +   localityName		= optional
> > +   organizationName	= optional
> > +   organizationalUnitName = optional
> > +   commonName		= supplied
> > +   emailAddress		= optional
> > diff --git a/test/py/tests/test_efi_secboot/test_signed_intca.py b/test/py/tests/test_efi_secboot/test_signed_intca.py
> > new file mode 100644
> > index 000000000000..80c1917a2cd3
> > --- /dev/null
> > +++ b/test/py/tests/test_efi_secboot/test_signed_intca.py
> > @@ -0,0 +1,134 @@
> > +# SPDX-License-Identifier:      GPL-2.0+
> > +# Copyright (c) 2020, Linaro Limited
> > +# Author: AKASHI Takahiro <takahiro.akashi@linaro.org>
> > +#
> > +# U-Boot UEFI: Image Authentication Test (signature with certificates chain)
> > +
> > +"""
> > +This test verifies image authentication for a signed image which is signed
> > +by user certificate and contains additional intermediate certificates in its
> > +signature.
> > +"""
> > +
> > +import pytest
> > +
> > +@pytest.mark.boardspec('sandbox')
> > +@pytest.mark.buildconfigspec('efi_secure_boot')
> > +@pytest.mark.buildconfigspec('cmd_efidebug')
> > +@pytest.mark.buildconfigspec('cmd_fat')
> > +@pytest.mark.buildconfigspec('cmd_nvedit_efi')
> > +@pytest.mark.slow
> > +class TestEfiSignedImageExt(object):
> > +    def test_efi_signed_image_ext1(self, u_boot_console, efi_boot_env_intca):
> > +        """
> > +        Test Case 1 - authenticated by root CA in db
> > +        """
> > +        u_boot_console.restart_uboot()
> > +        disk_img = efi_boot_env_intca
> > +        with u_boot_console.log.section('Test Case 1a'):
> > +            # Test Case 1a, with no Int CA and not authenticated by root CA
> > +            output = u_boot_console.run_command_list([
> > +                'host bind 0 %s' % disk_img,
> > +                'fatload host 0:1 4000000 db_c.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize db',
> > +                'fatload host 0:1 4000000 KEK.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize KEK',
> > +                'fatload host 0:1 4000000 PK.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize PK'])
> > +            assert 'Failed to set EFI variable' not in ''.join(output)
> > +
> > +            output = u_boot_console.run_command_list([
> > +                'efidebug boot add 1 HELLO_a host 0:1 /helloworld.efi.signed_a ""',
> > +                'efidebug boot next 1',
> > +                'efidebug test bootmgr'])
> > +            assert '\'HELLO_a\' failed' in ''.join(output)
> > +            assert 'efi_start_image() returned: 26' in ''.join(output)
> > +
> > +        with u_boot_console.log.section('Test Case 1b'):
> > +            # Test Case 1b, signed and authenticated by root CA
> > +            output = u_boot_console.run_command_list([
> > +                'efidebug boot add 2 HELLO_ab host 0:1 /helloworld.efi.signed_ab ""',
> > +                'efidebug boot next 2',
> > +                'bootefi bootmgr'])
> > +            assert 'Hello, world!' in ''.join(output)
> > +
> > +    def test_efi_signed_image_ext2(self, u_boot_console, efi_boot_env_intca):
> > +        """
> > +        Test Case 2 - authenticated by root CA in db
> > +        """
> > +        u_boot_console.restart_uboot()
> > +        disk_img = efi_boot_env_intca
> > +        with u_boot_console.log.section('Test Case 2a'):
> > +            # Test Case 2a, unsigned and not authenticated by root CA
> > +            output = u_boot_console.run_command_list([
> > +                'host bind 0 %s' % disk_img,
> > +                'fatload host 0:1 4000000 KEK.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize KEK',
> > +                'fatload host 0:1 4000000 PK.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize PK'])
> > +            assert 'Failed to set EFI variable' not in ''.join(output)
> > +
> > +            output = u_boot_console.run_command_list([
> > +                'efidebug boot add 1 HELLO_abc host 0:1 /helloworld.efi.signed_abc ""',
> > +                'efidebug boot next 1',
> > +                'efidebug test bootmgr'])
> > +            assert '\'HELLO_abc\' failed' in ''.join(output)
> > +            assert 'efi_start_image() returned: 26' in ''.join(output)
> > +
> > +        with u_boot_console.log.section('Test Case 2b'):
> > +            # Test Case 2b, signed and authenticated by root CA
> > +            output = u_boot_console.run_command_list([
> > +                'fatload host 0:1 4000000 db_b.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize db',
> > +                'efidebug boot next 1',
> > +                'efidebug test bootmgr'])
> > +            assert '\'HELLO_abc\' failed' in ''.join(output)
> > +            assert 'efi_start_image() returned: 26' in ''.join(output)
> > +
> > +        with u_boot_console.log.section('Test Case 2c'):
> > +            # Test Case 2c, signed and authenticated by root CA
> > +            output = u_boot_console.run_command_list([
> > +                'fatload host 0:1 4000000 db_c.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize db',
> > +                'efidebug boot next 1',
> > +                'efidebug test bootmgr'])
> > +            assert 'Hello, world!' in ''.join(output)
> > +
> > +    def test_efi_signed_image_ext3(self, u_boot_console, efi_boot_env_intca):
> > +        """
> > +        Test Case 3 - revoked by dbx
> > +        """
> > +        u_boot_console.restart_uboot()
> > +        disk_img = efi_boot_env_intca
> > +        with u_boot_console.log.section('Test Case 3a'):
> > +            # Test Case 3a, revoked by root CA in dbx
> > +            output = u_boot_console.run_command_list([
> > +                'host bind 0 %s' % disk_img,
> > +                'fatload host 0:1 4000000 dbx_c.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize dbx',
> > +                'fatload host 0:1 4000000 db_c.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize db',
> > +                'fatload host 0:1 4000000 KEK.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize KEK',
> > +                'fatload host 0:1 4000000 PK.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize PK'])
> > +            assert 'Failed to set EFI variable' not in ''.join(output)
> > +
> > +            output = u_boot_console.run_command_list([
> > +                'efidebug boot add 1 HELLO_abc host 0:1 /helloworld.efi.signed_abc ""',
> > +                'efidebug boot next 1',
> > +                'efidebug test bootmgr'])
> > +            assert '\'HELLO_abc\' failed' in ''.join(output)
> > +            assert 'efi_start_image() returned: 26' in ''.join(output)
> > +
> > +        with u_boot_console.log.section('Test Case 3b'):
> > +            # Test Case 3b, revoked by int CA in dbx
> > +            output = u_boot_console.run_command_list([
> > +                'fatload host 0:1 4000000 dbx_b.auth',
> > +                'setenv -e -nv -bs -rt -at -i 4000000,$filesize dbx',
> > +                'efidebug boot next 1',
> > +                'efidebug test bootmgr'])
> > +            assert 'Hello, world!' in ''.join(output)
> > +            # Or,
> > +            # assert('\'HELLO_abc\' failed' in ''.join(output))
> > +            # assert('efi_start_image() returned: 26' in ''.join(output))
> >
>
Tom Rini July 9, 2020, 3:15 a.m. UTC | #4
On Thu, Jul 09, 2020 at 09:58:03AM +0900, AKASHI Takahiro wrote:

> Hi Tom,
> 
> I'd like to make sure of your policy about usage of "sudo" on CI.
> Do you think that we should always avoid using "sudo" in testing?
> 
> I remember that you had allowed us to run sudo in (python)
> test scripts on Travis CI when I requested this (for FAT filesystem?).

So, the best practices at this time are to have the code try and use
guestmount (or similar tools) when possible and fall back to sudo, as
Ubuntu breaks guestmount (and similar tools) by default.
AKASHI Takahiro July 9, 2020, 5:33 a.m. UTC | #5
Tom,

On Wed, Jul 08, 2020 at 11:15:26PM -0400, Tom Rini wrote:
> On Thu, Jul 09, 2020 at 09:58:03AM +0900, AKASHI Takahiro wrote:
> 
> > Hi Tom,
> > 
> > I'd like to make sure of your policy about usage of "sudo" on CI.
> > Do you think that we should always avoid using "sudo" in testing?
> > 
> > I remember that you had allowed us to run sudo in (python)
> > test scripts on Travis CI when I requested this (for FAT filesystem?).
> 
> So, the best practices at this time are to have the code try and use
> guestmount (or similar tools) when possible and fall back to sudo, as
> Ubuntu breaks guestmount (and similar tools) by default.

See the commands log (on my ubuntu 19.10) below:

===8<===
<< try 1 >>
tmp$ mkdir tmpdir
tmp$ virt-make-fs -t vfat -s +1M --partition=gpt ./tmpdir tmp.img
libguestfs: error: /usr/bin/supermin exited with error status 1.
To see full error messages you may need to enable debugging.
Do:
  export LIBGUESTFS_DEBUG=1 LIBGUESTFS_TRACE=1
and run the command again.  For further information, read:
  http://libguestfs.org/guestfs-faq.1.html#debugging-libguestfs
You can also run 'libguestfs-test-tool' and post the *complete* output
into a bug report or message to the libguestfs mailing list.

<< try 2 >>
tmp$ LIBGUESTFS_DEBUG=1 virt-make-fs -t vfat -s +1M --partition=gpt ./tmpdir tmp.img
...
supermin: kernel: kernel_version 5.3.0-62-generic
supermin: kernel: modpath /lib/modules/5.3.0-62-generic
cp: cannot open '/boot/vmlinuz-5.3.0-62-generic' for reading: Permission denied
supermin: cp -p '/boot/vmlinuz-5.3.0-62-generic' '/var/tmp/.guestfs-1000/appliance.d.op62psoy/kernel': command failed, see earlier errors
libguestfs: error: /usr/bin/supermin exited with error status 1, see debug messages above
...

<< try 3 >>
tmp$ sudo chmod a+rw /boot/vmlinuz-5.3.0-62-generic 
tmp$ LIBGUESTFS_DEBUG=1 virt-make-fs -t vfat -s +1M --partition=gpt ./tmpdir tmp.img
...
tmp$ ls -l tmp.img
-rw-r--r-- 1 akashi akashi 1341440 Jul  9 13:50 tmp.img
===>8===

As you can see, virt-make-fs will fail on *standard* ubuntu.
You have to change the permission of the current kernel's binary.

While I can't make sure, we will have the same issue with guestmount
as it will also create a minimum virtual machine before execution.

What does it mean?
You must change the permission every time when you re-install the OS
or re-bump the kernel version. Obviously, I can't do that from my own
test script (without sudo).
So if you don't have any way (or workaround) to deal with it,
libguestfs-tools or guestmount cannot be a solution here.

-Takahiro Akashi






> -- 
> Tom
Tom Rini July 9, 2020, 12:34 p.m. UTC | #6
On Thu, Jul 09, 2020 at 02:33:49PM +0900, AKASHI Takahiro wrote:
> Tom,
> 
> On Wed, Jul 08, 2020 at 11:15:26PM -0400, Tom Rini wrote:
> > On Thu, Jul 09, 2020 at 09:58:03AM +0900, AKASHI Takahiro wrote:
> > 
> > > Hi Tom,
> > > 
> > > I'd like to make sure of your policy about usage of "sudo" on CI.
> > > Do you think that we should always avoid using "sudo" in testing?
> > > 
> > > I remember that you had allowed us to run sudo in (python)
> > > test scripts on Travis CI when I requested this (for FAT filesystem?).
> > 
> > So, the best practices at this time are to have the code try and use
> > guestmount (or similar tools) when possible and fall back to sudo, as
> > Ubuntu breaks guestmount (and similar tools) by default.
> 
> See the commands log (on my ubuntu 19.10) below:
> 
> ===8<===
> << try 1 >>
> tmp$ mkdir tmpdir
> tmp$ virt-make-fs -t vfat -s +1M --partition=gpt ./tmpdir tmp.img
> libguestfs: error: /usr/bin/supermin exited with error status 1.
> To see full error messages you may need to enable debugging.
> Do:
>   export LIBGUESTFS_DEBUG=1 LIBGUESTFS_TRACE=1
> and run the command again.  For further information, read:
>   http://libguestfs.org/guestfs-faq.1.html#debugging-libguestfs
> You can also run 'libguestfs-test-tool' and post the *complete* output
> into a bug report or message to the libguestfs mailing list.
> 
> << try 2 >>
> tmp$ LIBGUESTFS_DEBUG=1 virt-make-fs -t vfat -s +1M --partition=gpt ./tmpdir tmp.img
> ...
> supermin: kernel: kernel_version 5.3.0-62-generic
> supermin: kernel: modpath /lib/modules/5.3.0-62-generic
> cp: cannot open '/boot/vmlinuz-5.3.0-62-generic' for reading: Permission denied
> supermin: cp -p '/boot/vmlinuz-5.3.0-62-generic' '/var/tmp/.guestfs-1000/appliance.d.op62psoy/kernel': command failed, see earlier errors
> libguestfs: error: /usr/bin/supermin exited with error status 1, see debug messages above
> ...
> 
> << try 3 >>
> tmp$ sudo chmod a+rw /boot/vmlinuz-5.3.0-62-generic 
> tmp$ LIBGUESTFS_DEBUG=1 virt-make-fs -t vfat -s +1M --partition=gpt ./tmpdir tmp.img
> ...
> tmp$ ls -l tmp.img
> -rw-r--r-- 1 akashi akashi 1341440 Jul  9 13:50 tmp.img
> ===>8===
> 
> As you can see, virt-make-fs will fail on *standard* ubuntu.
> You have to change the permission of the current kernel's binary.

Yes, exactly.  This is an intentional behavior in Ubuntu (and not
Debian) and why we cannot rely on the various virt tools working.

I fixed the current tests over in
http://patchwork.ozlabs.org/project/uboot/patch/20200707155309.24770-1-trini@konsulko.com/
but need to follow up and try what Stephen was saying to clean it up
more still.

> While I can't make sure, we will have the same issue with guestmount
> as it will also create a minimum virtual machine before execution.
> 
> What does it mean?
> You must change the permission every time when you re-install the OS
> or re-bump the kernel version. Obviously, I can't do that from my own
> test script (without sudo).
> So if you don't have any way (or workaround) to deal with it,
> libguestfs-tools or guestmount cannot be a solution here.

Well, just like the test_fs tests, we try guestmount, if it doesn't work
we fall back to just sudo'ing what we need to run directly.  I think
Ubuntu did something very stupid here.  I just don't know if moving CI
to be Debian based (and I guess Travis is just working-around the issue
by default for us, given the fs tests run there today) is good enough as
it will leave everyone else's Ubuntu-based setups broken.
diff mbox series

Patch

diff --git a/test/py/tests/test_efi_secboot/conftest.py b/test/py/tests/test_efi_secboot/conftest.py
index 34abcd79ae00..e5ac2a2a21b7 100644
--- a/test/py/tests/test_efi_secboot/conftest.py
+++ b/test/py/tests/test_efi_secboot/conftest.py
@@ -37,7 +37,7 @@  def efi_boot_env(request, u_boot_config):
     global HELLO_PATH
 
     image_path = u_boot_config.persistent_data_dir
-    image_path = image_path + '/' + EFI_SECBOOT_IMAGE_NAME
+    image_path = image_path + '/' + EFI_SECBOOT_IMAGE_NAME + '.img'
     image_size = EFI_SECBOOT_IMAGE_SIZE
     part_size = EFI_SECBOOT_PART_SIZE
     fs_type = EFI_SECBOOT_FS_TYPE
@@ -46,7 +46,7 @@  def efi_boot_env(request, u_boot_config):
         HELLO_PATH = u_boot_config.build_dir + '/lib/efi_loader/helloworld.efi'
 
     try:
-        mnt_point = u_boot_config.persistent_data_dir + '/mnt_efisecure'
+        mnt_point = u_boot_config.persistent_data_dir + MNTPNT
         check_call('mkdir -p {}'.format(mnt_point), shell=True)
 
         # create a disk/partition
@@ -170,3 +170,137 @@  def efi_boot_env(request, u_boot_config):
         yield image_path
     finally:
         call('rm -f %s' % image_path, shell=True)
+
+#
+# Fixture for UEFI secure boot test of intermediate certificates
+#
+@pytest.fixture(scope='session')
+def efi_boot_env_intca(request, u_boot_config):
+    """Set up a file system to be used in UEFI secure boot test
+    of intermediate certificates.
+
+    Args:
+        request: Pytest request object.
+	u_boot_config: U-boot configuration.
+
+    Return:
+        A path to disk image to be used for testing
+    """
+    global HELLO_PATH
+
+    image_path = u_boot_config.persistent_data_dir
+    image_path = image_path + '/' + EFI_SECBOOT_IMAGE_NAME + '_intca.img'
+    image_size = EFI_SECBOOT_IMAGE_SIZE
+    part_size = EFI_SECBOOT_PART_SIZE
+    fs_type = EFI_SECBOOT_FS_TYPE
+
+    if HELLO_PATH == '':
+        HELLO_PATH = u_boot_config.build_dir + '/lib/efi_loader/helloworld.efi'
+
+    try:
+        mnt_point = u_boot_config.persistent_data_dir + MNTPNT
+        check_call('mkdir -p {}'.format(mnt_point), shell=True)
+
+        # create a disk/partition
+        check_call('dd if=/dev/zero of=%s bs=1MiB count=%d'
+                            % (image_path, image_size), shell=True)
+        check_call('sgdisk %s -n 1:0:+%dMiB'
+                            % (image_path, part_size), shell=True)
+        # create a file system
+        check_call('dd if=/dev/zero of=%s.tmp bs=1MiB count=%d'
+                            % (image_path, part_size), shell=True)
+        check_call('mkfs -t %s %s.tmp' % (fs_type, image_path), shell=True)
+        check_call('dd if=%s.tmp of=%s bs=1MiB seek=1 count=%d conv=notrunc'
+                            % (image_path, image_path, 1), shell=True)
+        check_call('rm %s.tmp' % image_path, shell=True)
+        loop_dev = check_output('sudo losetup -o 1MiB --sizelimit %dMiB --show -f %s | tr -d "\n"'
+                                % (part_size, image_path), shell=True).decode()
+        check_output('sudo mount -t %s -o umask=000 %s %s'
+                                % (fs_type, loop_dev, mnt_point), shell=True)
+
+        # Create signature database
+        ## PK
+        check_call('cd %s; openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_PK/ -keyout PK.key -out PK.crt -nodes -days 365'
+                            % mnt_point, shell=True)
+        check_call('cd %s; %scert-to-efi-sig-list -g %s PK.crt PK.esl; %ssign-efi-sig-list -c PK.crt -k PK.key PK PK.esl PK.auth'
+                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                            shell=True)
+        ## KEK
+        check_call('cd %s; openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_KEK/ -keyout KEK.key -out KEK.crt -nodes -days 365'
+                            % mnt_point, shell=True)
+        check_call('cd %s; %scert-to-efi-sig-list -g %s KEK.crt KEK.esl; %ssign-efi-sig-list -c PK.crt -k PK.key KEK KEK.esl KEK.auth'
+                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                            shell=True)
+
+        # We will have three-tier hierarchy of certificates:
+        #   TestRoot: Root CA (self-signed)
+        #   TestSub: Intermediate CA (signed by Root CA)
+        #   TestCert: User certificate (signed by Intermediate CA, and used
+        #             for signing an image)
+        #
+        # NOTE:
+        # I consulted the following EDK2 document for certificate options:
+        #     BaseTools/Source/Python/Pkcs7Sign/Readme.md
+        # Please not use them as they are in product system. They are
+        # for test purpose only.
+
+        # TestRoot
+        check_call('cp %s/test/py/tests/test_efi_secboot/openssl.cnf %s' % (u_boot_config.source_dir, mnt_point), shell=True)
+        check_call('cd %s; openssl genrsa -out TestRoot.key 2048; openssl req --config openssl.cnf -extensions v3_ca -new -x509 -days 365 -key TestRoot.key -out TestRoot.crt -subj "/CN=TEST_root/"; touch index.txt' % mnt_point, shell=True)
+        # TestSub
+        check_call('cd %s; openssl genrsa -out TestSub.key 2048; openssl req -new -key TestSub.key -out TestSub.csr -subj "/CN=TEST_sub/"; openssl ca --config openssl.cnf -in TestSub.csr -out TestSub.crt -extensions v3_int_ca -days 365 -batch -rand_serial -cert TestRoot.crt -keyfile TestRoot.key' % mnt_point, shell=True)
+        # TestCert
+        check_call('cd %s; openssl genrsa -out TestCert.key 2048; openssl req -new -key TestCert.key -out TestCert.csr -subj "/CN=TEST_cert/"; openssl ca --config openssl.cnf -in TestCert.csr -out TestCert.crt -extensions usr_cert -days 365 -batch -rand_serial -cert TestSub.crt -keyfile TestSub.key' % mnt_point, shell=True)
+        ## db
+        #  for TestCert
+        check_call('cd %s; %scert-to-efi-sig-list -g %s TestCert.crt TestCert.esl; %ssign-efi-sig-list -c KEK.crt -k KEK.key db TestCert.esl db_a.auth'
+                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                            shell=True)
+        #  for TestSub
+        check_call('cd %s; %scert-to-efi-sig-list -g %s TestSub.crt TestSub.esl; %ssign-efi-sig-list -c KEK.crt -k KEK.key db TestSub.esl db_b.auth'
+                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                            shell=True)
+        #  for TestRoot
+        check_call('cd %s; %scert-to-efi-sig-list -g %s TestRoot.crt TestRoot.esl; %ssign-efi-sig-list -c KEK.crt -k KEK.key db TestRoot.esl db_c.auth'
+                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                            shell=True)
+        ## dbx (hash of certificate with revocation time)
+        #  for TestCert
+        check_call('cd %s; %scert-to-efi-hash-list -g %s -t 0 -s 256 TestCert.crt TestCert.crl; %ssign-efi-sig-list -c KEK.crt -k KEK.key dbx TestCert.crl dbx_a.auth'
+                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                            shell=True)
+        #  for TestSub
+        check_call('cd %s; %scert-to-efi-hash-list -g %s -t 0 -s 256 TestSub.crt TestSub.crl; %ssign-efi-sig-list -c KEK.crt -k KEK.key dbx TestSub.crl dbx_b.auth'
+                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                            shell=True)
+        #  for TestRoot
+        check_call('cd %s; %scert-to-efi-hash-list -g %s -t 0 -s 256 TestRoot.crt TestRoot.crl; %ssign-efi-sig-list -c KEK.crt -k KEK.key dbx TestRoot.crl dbx_c.auth'
+                            % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                            shell=True)
+
+        # Sign image
+        # additional intermediate certificates may be included
+        # in SignedData
+
+        check_call('cp %s %s' % (HELLO_PATH, mnt_point), shell=True)
+        ## signed by TestCert
+        check_call('cd %s; %ssbsign --key TestCert.key --cert TestCert.crt --out helloworld.efi.signed_a helloworld.efi'
+                            % (mnt_point, SBSIGN_PATH), shell=True)
+        ## signed by TestCert with TestSub in signature
+        check_call('cd %s; %ssbsign --key TestCert.key --cert TestCert.crt --addcert TestSub.crt --out helloworld.efi.signed_ab helloworld.efi'
+                            % (mnt_point, SBSIGN_PATH), shell=True)
+        ## signed by TestCert with TestSub and TestRoot in signature
+        check_call('cd %s; cat TestSub.crt TestRoot.crt > TestSubRoot.crt; %ssbsign --key TestCert.key --cert TestCert.crt --addcert TestSubRoot.crt --out helloworld.efi.signed_abc helloworld.efi'
+                            % (mnt_point, SBSIGN_PATH), shell=True)
+
+        # Clean-up
+        check_call('sudo umount %s' % loop_dev, shell=True)
+        check_call('sudo losetup -d %s' % loop_dev, shell=True)
+
+    except CalledProcessError as e:
+        pytest.skip('Setup failed: %s' % e.cmd)
+        return
+    else:
+        yield image_path
+    finally:
+        call('rm -f %s' % image_path, shell=True)
diff --git a/test/py/tests/test_efi_secboot/defs.py b/test/py/tests/test_efi_secboot/defs.py
index 099f453979ff..c61f69a316f8 100644
--- a/test/py/tests/test_efi_secboot/defs.py
+++ b/test/py/tests/test_efi_secboot/defs.py
@@ -1,7 +1,7 @@ 
 # SPDX-License-Identifier:      GPL-2.0+
 
 # Disk image name
-EFI_SECBOOT_IMAGE_NAME = 'test_efi_secboot.img'
+EFI_SECBOOT_IMAGE_NAME = 'test_efi_secboot'
 
 # Size in MiB
 EFI_SECBOOT_IMAGE_SIZE = 16
@@ -10,12 +10,21 @@  EFI_SECBOOT_PART_SIZE = 8
 # Partition file system type
 EFI_SECBOOT_FS_TYPE = 'vfat'
 
+# Mount point
+MNTPNT= 'mnt'
+
 # Owner guid
 GUID = '11111111-2222-3333-4444-123456789abc'
 
 # v1.5.1 or earlier of efitools has a bug in sha256 calculation, and
 # you need build a newer version on your own.
+# The path must terminate with '/'.
 EFITOOLS_PATH = ''
 
+# "--addcert" option of sbsign must be available, otherwise
+# you need build a newer version on your own.
+# The path must terminate with '/'.
+SBSIGN_PATH= '/home/akashi/arm/misc/sbsigntools/src/'
+
 # Hello World application for sandbox
 HELLO_PATH = ''
diff --git a/test/py/tests/test_efi_secboot/openssl.cnf b/test/py/tests/test_efi_secboot/openssl.cnf
new file mode 100644
index 000000000000..f684f1df7e69
--- /dev/null
+++ b/test/py/tests/test_efi_secboot/openssl.cnf
@@ -0,0 +1,48 @@ 
+[ ca ]
+default_ca = CA_default
+
+[ CA_default ]
+new_certs_dir = .
+database = ./index.txt
+serial = ./serial
+default_md = sha256
+policy = policy_min
+
+[ req ]
+distinguished_name = def_distinguished_name
+
+[def_distinguished_name]
+
+# Extensions
+#   -addext " ... = ..."
+#
+[ v3_ca ]
+   # Extensions for a typical Root CA.
+   basicConstraints = critical,CA:TRUE
+   keyUsage = critical, digitalSignature, cRLSign, keyCertSign
+   subjectKeyIdentifier = hash
+   authorityKeyIdentifier = keyid:always,issuer
+
+[ v3_int_ca ]
+   # Extensions for a typical intermediate CA.
+   basicConstraints = critical, CA:TRUE
+   keyUsage = critical, digitalSignature, cRLSign, keyCertSign
+   subjectKeyIdentifier = hash
+   authorityKeyIdentifier = keyid:always,issuer
+
+[ usr_cert ]
+   # Extensions for user end certificates.
+   basicConstraints = CA:FALSE
+   keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
+   extendedKeyUsage = clientAuth, emailProtection
+   subjectKeyIdentifier = hash
+   authorityKeyIdentifier = keyid,issuer
+
+[ policy_min ]
+   countryName		= optional
+   stateOrProvinceName	= optional
+   localityName		= optional
+   organizationName	= optional
+   organizationalUnitName = optional
+   commonName		= supplied
+   emailAddress		= optional
diff --git a/test/py/tests/test_efi_secboot/test_signed_intca.py b/test/py/tests/test_efi_secboot/test_signed_intca.py
new file mode 100644
index 000000000000..80c1917a2cd3
--- /dev/null
+++ b/test/py/tests/test_efi_secboot/test_signed_intca.py
@@ -0,0 +1,134 @@ 
+# SPDX-License-Identifier:      GPL-2.0+
+# Copyright (c) 2020, Linaro Limited
+# Author: AKASHI Takahiro <takahiro.akashi@linaro.org>
+#
+# U-Boot UEFI: Image Authentication Test (signature with certificates chain)
+
+"""
+This test verifies image authentication for a signed image which is signed
+by user certificate and contains additional intermediate certificates in its
+signature.
+"""
+
+import pytest
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('efi_secure_boot')
+@pytest.mark.buildconfigspec('cmd_efidebug')
+@pytest.mark.buildconfigspec('cmd_fat')
+@pytest.mark.buildconfigspec('cmd_nvedit_efi')
+@pytest.mark.slow
+class TestEfiSignedImageExt(object):
+    def test_efi_signed_image_ext1(self, u_boot_console, efi_boot_env_intca):
+        """
+        Test Case 1 - authenticated by root CA in db
+        """
+        u_boot_console.restart_uboot()
+        disk_img = efi_boot_env_intca
+        with u_boot_console.log.section('Test Case 1a'):
+            # Test Case 1a, with no Int CA and not authenticated by root CA
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatload host 0:1 4000000 db_c.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000,$filesize db',
+                'fatload host 0:1 4000000 KEK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000,$filesize KEK',
+                'fatload host 0:1 4000000 PK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000,$filesize PK'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'efidebug boot add 1 HELLO_a host 0:1 /helloworld.efi.signed_a ""',
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert '\'HELLO_a\' failed' in ''.join(output)
+            assert 'efi_start_image() returned: 26' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 1b'):
+            # Test Case 1b, signed and authenticated by root CA
+            output = u_boot_console.run_command_list([
+                'efidebug boot add 2 HELLO_ab host 0:1 /helloworld.efi.signed_ab ""',
+                'efidebug boot next 2',
+                'bootefi bootmgr'])
+            assert 'Hello, world!' in ''.join(output)
+
+    def test_efi_signed_image_ext2(self, u_boot_console, efi_boot_env_intca):
+        """
+        Test Case 2 - authenticated by root CA in db
+        """
+        u_boot_console.restart_uboot()
+        disk_img = efi_boot_env_intca
+        with u_boot_console.log.section('Test Case 2a'):
+            # Test Case 2a, unsigned and not authenticated by root CA
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatload host 0:1 4000000 KEK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000,$filesize KEK',
+                'fatload host 0:1 4000000 PK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000,$filesize PK'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'efidebug boot add 1 HELLO_abc host 0:1 /helloworld.efi.signed_abc ""',
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert '\'HELLO_abc\' failed' in ''.join(output)
+            assert 'efi_start_image() returned: 26' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 2b'):
+            # Test Case 2b, signed and authenticated by root CA
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 db_b.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000,$filesize db',
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert '\'HELLO_abc\' failed' in ''.join(output)
+            assert 'efi_start_image() returned: 26' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 2c'):
+            # Test Case 2c, signed and authenticated by root CA
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 db_c.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000,$filesize db',
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert 'Hello, world!' in ''.join(output)
+
+    def test_efi_signed_image_ext3(self, u_boot_console, efi_boot_env_intca):
+        """
+        Test Case 3 - revoked by dbx
+        """
+        u_boot_console.restart_uboot()
+        disk_img = efi_boot_env_intca
+        with u_boot_console.log.section('Test Case 3a'):
+            # Test Case 3a, revoked by root CA in dbx
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatload host 0:1 4000000 dbx_c.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000,$filesize dbx',
+                'fatload host 0:1 4000000 db_c.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000,$filesize db',
+                'fatload host 0:1 4000000 KEK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000,$filesize KEK',
+                'fatload host 0:1 4000000 PK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000,$filesize PK'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'efidebug boot add 1 HELLO_abc host 0:1 /helloworld.efi.signed_abc ""',
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert '\'HELLO_abc\' failed' in ''.join(output)
+            assert 'efi_start_image() returned: 26' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 3b'):
+            # Test Case 3b, revoked by int CA in dbx
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 dbx_b.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000,$filesize dbx',
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert 'Hello, world!' in ''.join(output)
+            # Or,
+            # assert('\'HELLO_abc\' failed' in ''.join(output))
+            # assert('efi_start_image() returned: 26' in ''.join(output))