u-boot.lists.denx.de archive mirror
 help / color / mirror / Atom feed
From: AKASHI Takahiro <takahiro.akashi@linaro.org>
To: xypron.glpk@gmx.de, agraf@csgraf.de
Cc: ilias.apalodimas@linaro.org, sughosh.ganu@linaro.org,
	masami.hiramatsu@linaro.org, u-boot@lists.denx.de,
	AKASHI Takahiro <takahiro.akashi@linaro.org>
Subject: [PATCH v4 11/11] test/py: efi_capsule: add image authentication test
Date: Thu,  7 Oct 2021 15:23:40 +0900	[thread overview]
Message-ID: <20211007062340.72207-12-takahiro.akashi@linaro.org> (raw)
In-Reply-To: <20211007062340.72207-1-takahiro.akashi@linaro.org>

Add a couple of test cases against capsule image authentication
for capsule-on-disk, where only a signed capsule file with the verified
signature will be applied to the system.

Due to the difficulty of embedding a public key (esl file) in U-Boot
binary during pytest setup time, all the keys/certificates are pre-created.

Signed-off-by: AKASHI Takahiro <takahiro.akashi@linaro.org>
---
 .../py/tests/test_efi_capsule/capsule_defs.py |   5 +
 test/py/tests/test_efi_capsule/conftest.py    |  35 ++-
 test/py/tests/test_efi_capsule/signature.dts  |  10 +
 .../test_capsule_firmware_signed.py           | 233 ++++++++++++++++++
 4 files changed, 280 insertions(+), 3 deletions(-)
 create mode 100644 test/py/tests/test_efi_capsule/signature.dts
 create mode 100644 test/py/tests/test_efi_capsule/test_capsule_firmware_signed.py

diff --git a/test/py/tests/test_efi_capsule/capsule_defs.py b/test/py/tests/test_efi_capsule/capsule_defs.py
index 4fd6353c2040..aa9bf5eee3aa 100644
--- a/test/py/tests/test_efi_capsule/capsule_defs.py
+++ b/test/py/tests/test_efi_capsule/capsule_defs.py
@@ -3,3 +3,8 @@
 # Directories
 CAPSULE_DATA_DIR = '/EFI/CapsuleTestData'
 CAPSULE_INSTALL_DIR = '/EFI/UpdateCapsule'
+
+# 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 = ''
diff --git a/test/py/tests/test_efi_capsule/conftest.py b/test/py/tests/test_efi_capsule/conftest.py
index cd750347879e..ab4787c4d26c 100644
--- a/test/py/tests/test_efi_capsule/conftest.py
+++ b/test/py/tests/test_efi_capsule/conftest.py
@@ -10,13 +10,13 @@ import pytest
 from capsule_defs import *
 
 #
-# Fixture for UEFI secure boot test
+# Fixture for UEFI capsule test
 #
 
-
 @pytest.fixture(scope='session')
 def efi_capsule_data(request, u_boot_config):
-    """Set up a file system to be used in UEFI capsule test.
+    """Set up a file system to be used in UEFI capsule and
+       authentication test.
 
     Args:
         request: Pytest request object.
@@ -40,6 +40,26 @@ def efi_capsule_data(request, u_boot_config):
         check_call('mkdir -p %s' % data_dir, shell=True)
         check_call('mkdir -p %s' % install_dir, shell=True)
 
+        capsule_auth_enabled = u_boot_config.buildconfig.get(
+                    'config_efi_capsule_authenticate')
+        if capsule_auth_enabled:
+            # Create private key (SIGNER.key) and certificate (SIGNER.crt)
+            check_call('cd %s; openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_SIGNER/ -keyout SIGNER.key -out SIGNER.crt -nodes -days 365'
+                       % data_dir, shell=True)
+            check_call('cd %s; %scert-to-efi-sig-list SIGNER.crt SIGNER.esl'
+                       % (data_dir, EFITOOLS_PATH), shell=True)
+
+            # Update dtb adding capsule certificate
+            check_call('cd %s; cp %s/test/py/tests/test_efi_capsule/signature.dts .'
+                       % (data_dir, u_boot_config.source_dir), shell=True)
+            check_call('cd %s; dtc -@ -I dts -O dtb -o signature.dtbo signature.dts; fdtoverlay -i %s/arch/sandbox/dts/test.dtb -o test_sig.dtb signature.dtbo'
+                       % (data_dir, u_boot_config.build_dir), shell=True)
+
+            # Create *malicious* private key (SIGNER2.key) and certificate
+            # (SIGNER2.crt)
+            check_call('cd %s; openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_SIGNER/ -keyout SIGNER2.key -out SIGNER2.crt -nodes -days 365'
+                       % data_dir, shell=True)
+
         # Create capsule files
         # two regions: one for u-boot.bin and the other for u-boot.env
         check_call('cd %s; echo -n u-boot:Old > u-boot.bin.old; echo -n u-boot:New > u-boot.bin.new; echo -n u-boot-env:Old -> u-boot.env.old; echo -n u-boot-env:New > u-boot.env.new' % data_dir,
@@ -59,6 +79,15 @@ def efi_capsule_data(request, u_boot_config):
         check_call('cd %s; %s/tools/mkeficapsule --index 1 --guid E2BB9C06-70E9-4B14-97A3-5A7913176E3F u-boot.bin.new Test03' %
                    (data_dir, u_boot_config.build_dir),
                    shell=True)
+        if capsule_auth_enabled:
+            # firmware signed with proper key
+            check_call('cd %s; %s/tools/mkeficapsule --index 1 --monotonic-count 1 --private-key SIGNER.key --certificate SIGNER.crt --raw u-boot.bin.new Test04' %
+                       (data_dir, u_boot_config.build_dir),
+                       shell=True)
+            # firmware signed with *mal* key
+            check_call('cd %s; %s/tools/mkeficapsule --index 1 --monotonic-count 1 --private-key SIGNER2.key --certificate SIGNER2.crt --raw u-boot.bin.new Test05' %
+                       (data_dir, u_boot_config.build_dir),
+                       shell=True)
 
         # Create a disk image with EFI system partition
         check_call('virt-make-fs --partition=gpt --size=+1M --type=vfat %s %s' %
diff --git a/test/py/tests/test_efi_capsule/signature.dts b/test/py/tests/test_efi_capsule/signature.dts
new file mode 100644
index 000000000000..078cfc76c93c
--- /dev/null
+++ b/test/py/tests/test_efi_capsule/signature.dts
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+/plugin/;
+
+&{/} {
+	signature {
+		capsule-key = /incbin/("SIGNER.esl");
+	};
+};
diff --git a/test/py/tests/test_efi_capsule/test_capsule_firmware_signed.py b/test/py/tests/test_efi_capsule/test_capsule_firmware_signed.py
new file mode 100644
index 000000000000..9230d14a1871
--- /dev/null
+++ b/test/py/tests/test_efi_capsule/test_capsule_firmware_signed.py
@@ -0,0 +1,233 @@
+# SPDX-License-Identifier:      GPL-2.0+
+# Copyright (c) 2021, Linaro Limited
+# Author: AKASHI Takahiro <takahiro.akashi@linaro.org>
+#
+# U-Boot UEFI: Firmware Update (Signed capsule) Test
+
+"""
+This test verifies capsule-on-disk firmware update
+with signed capsule files
+"""
+
+import pytest
+from capsule_defs import CAPSULE_DATA_DIR, CAPSULE_INSTALL_DIR
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('efi_capsule_firmware_raw')
+@pytest.mark.buildconfigspec('efi_capsule_authenticate')
+@pytest.mark.buildconfigspec('dfu')
+@pytest.mark.buildconfigspec('dfu_sf')
+@pytest.mark.buildconfigspec('cmd_efidebug')
+@pytest.mark.buildconfigspec('cmd_fat')
+@pytest.mark.buildconfigspec('cmd_memory')
+@pytest.mark.buildconfigspec('cmd_nvedit_efi')
+@pytest.mark.buildconfigspec('cmd_sf')
+@pytest.mark.slow
+class TestEfiCapsuleFirmwareSigned(object):
+    def test_efi_capsule_auth1(
+            self, u_boot_config, u_boot_console, efi_capsule_data):
+        """
+        Test Case 1 - Update U-Boot on SPI Flash, raw image format
+                      0x100000-0x150000: U-Boot binary (but dummy)
+
+                      If the capsule is properly signed, the authentication
+                      should pass and the firmware be updated.
+        """
+        disk_img = efi_capsule_data
+        with u_boot_console.log.section('Test Case 1-a, before reboot'):
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'efidebug boot add -b 1 TEST host 0:1 /helloworld.efi',
+                'efidebug boot order 1',
+                'env set -e -nv -bs -rt OsIndications =0x0000000000000004',
+                'env set dfu_alt_info "sf 0:0=u-boot-bin raw 0x100000 0x50000;u-boot-env raw 0x150000 0x200000"',
+                'env save'])
+
+            # initialize content
+            output = u_boot_console.run_command_list([
+                'sf probe 0:0',
+                'fatload host 0:1 4000000 %s/u-boot.bin.old' % CAPSULE_DATA_DIR,
+                'sf write 4000000 100000 10',
+                'sf read 5000000 100000 10',
+                'md.b 5000000 10'])
+            assert 'Old' in ''.join(output)
+
+            # place a capsule file
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 %s/Test04' % CAPSULE_DATA_DIR,
+                'fatwrite host 0:1 4000000 %s/Test04 $filesize' % CAPSULE_INSTALL_DIR,
+                'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR])
+            assert 'Test04' in ''.join(output)
+
+        # reboot
+        mnt_point = u_boot_config.persistent_data_dir + '/test_efi_capsule'
+        u_boot_console.config.dtb = mnt_point + CAPSULE_DATA_DIR + '/test_sig.dtb'
+        u_boot_console.restart_uboot()
+
+        capsule_early = u_boot_config.buildconfig.get(
+            'config_efi_capsule_on_disk_early')
+        with u_boot_console.log.section('Test Case 1-b, after reboot'):
+            if not capsule_early:
+                # make sure that dfu_alt_info exists even persistent variables
+                # are not available.
+                output = u_boot_console.run_command_list([
+                    'env set dfu_alt_info "sf 0:0=u-boot-bin raw 0x100000 0x50000;u-boot-env raw 0x150000 0x200000"',
+                    'host bind 0 %s' % disk_img,
+                    'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR])
+                assert 'Test04' in ''.join(output)
+
+                # need to run uefi command to initiate capsule handling
+                output = u_boot_console.run_command(
+                    'env print -e Capsule0000')
+
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR])
+            assert 'Test04' not in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'sf probe 0:0',
+                'sf read 4000000 100000 10',
+                'md.b 4000000 10'])
+            assert 'u-boot:New' in ''.join(output)
+
+    def test_efi_capsule_auth2(
+            self, u_boot_config, u_boot_console, efi_capsule_data):
+        """
+        Test Case 2 - Update U-Boot on SPI Flash, raw image format
+                      0x100000-0x150000: U-Boot binary (but dummy)
+
+                      If the capsule is signed but with an invalid key,
+                      the authentication should fail and the firmware
+                      not be updated.
+        """
+        disk_img = efi_capsule_data
+        with u_boot_console.log.section('Test Case 2-a, before reboot'):
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'efidebug boot add -b 1 TEST host 0:1 /helloworld.efi',
+                'efidebug boot order 1',
+                'env set -e -nv -bs -rt OsIndications =0x0000000000000004',
+                'env set dfu_alt_info "sf 0:0=u-boot-bin raw 0x100000 0x50000;u-boot-env raw 0x150000 0x200000"',
+                'env save'])
+
+            # initialize content
+            output = u_boot_console.run_command_list([
+                'sf probe 0:0',
+                'fatload host 0:1 4000000 %s/u-boot.bin.old' % CAPSULE_DATA_DIR,
+                'sf write 4000000 100000 10',
+                'sf read 5000000 100000 10',
+                'md.b 5000000 10'])
+            assert 'Old' in ''.join(output)
+
+            # place a capsule file
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 %s/Test05' % CAPSULE_DATA_DIR,
+                'fatwrite host 0:1 4000000 %s/Test05 $filesize' % CAPSULE_INSTALL_DIR,
+                'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR])
+            assert 'Test05' in ''.join(output)
+
+        # reboot
+        mnt_point = u_boot_config.persistent_data_dir + '/test_efi_capsule'
+        u_boot_console.config.dtb = mnt_point + CAPSULE_DATA_DIR + '/test_sig.dtb'
+        u_boot_console.restart_uboot()
+
+        capsule_early = u_boot_config.buildconfig.get(
+            'config_efi_capsule_on_disk_early')
+        with u_boot_console.log.section('Test Case 2-b, after reboot'):
+            if not capsule_early:
+                # make sure that dfu_alt_info exists even persistent variables
+                # are not available.
+                output = u_boot_console.run_command_list([
+                    'env set dfu_alt_info "sf 0:0=u-boot-bin raw 0x100000 0x50000;u-boot-env raw 0x150000 0x200000"',
+                    'host bind 0 %s' % disk_img,
+                    'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR])
+                assert 'Test05' in ''.join(output)
+
+                # need to run uefi command to initiate capsule handling
+                output = u_boot_console.run_command(
+                    'env print -e Capsule0000')
+
+            # deleted any way
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR])
+            assert 'Test05' not in ''.join(output)
+
+            # TODO: check CapsuleStatus in CapsuleXXXX
+
+            output = u_boot_console.run_command_list([
+                'sf probe 0:0',
+                'sf read 4000000 100000 10',
+                'md.b 4000000 10'])
+            assert 'u-boot:Old' in ''.join(output)
+
+    def test_efi_capsule_auth3(
+            self, u_boot_config, u_boot_console, efi_capsule_data):
+        """
+        Test Case 3 - Update U-Boot on SPI Flash, raw image format
+                      0x100000-0x150000: U-Boot binary (but dummy)
+
+                      If the capsule is not signed, the authentication
+                      should fail and the firmware not be updated.
+        """
+        disk_img = efi_capsule_data
+        with u_boot_console.log.section('Test Case 3-a, before reboot'):
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'efidebug boot add -b 1 TEST host 0:1 /helloworld.efi',
+                'efidebug boot order 1',
+                'env set -e -nv -bs -rt OsIndications =0x0000000000000004',
+                'env set dfu_alt_info "sf 0:0=u-boot-bin raw 0x100000 0x50000;u-boot-env raw 0x150000 0x200000"',
+                'env save'])
+
+            # initialize content
+            output = u_boot_console.run_command_list([
+                'sf probe 0:0',
+                'fatload host 0:1 4000000 %s/u-boot.bin.old' % CAPSULE_DATA_DIR,
+                'sf write 4000000 100000 10',
+                'sf read 5000000 100000 10',
+                'md.b 5000000 10'])
+            assert 'Old' in ''.join(output)
+
+            # place a capsule file
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 %s/Test02' % CAPSULE_DATA_DIR,
+                'fatwrite host 0:1 4000000 %s/Test02 $filesize' % CAPSULE_INSTALL_DIR,
+                'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR])
+            assert 'Test02' in ''.join(output)
+
+        # reboot
+        mnt_point = u_boot_config.persistent_data_dir + '/test_efi_capsule'
+        u_boot_console.config.dtb = mnt_point + CAPSULE_DATA_DIR + '/test_sig.dtb'
+        u_boot_console.restart_uboot()
+
+        capsule_early = u_boot_config.buildconfig.get(
+            'config_efi_capsule_on_disk_early')
+        with u_boot_console.log.section('Test Case 3-b, after reboot'):
+            if not capsule_early:
+                # make sure that dfu_alt_info exists even persistent variables
+                # are not available.
+                output = u_boot_console.run_command_list([
+                    'env set dfu_alt_info "sf 0:0=u-boot-bin raw 0x100000 0x50000;u-boot-env raw 0x150000 0x200000"',
+                    'host bind 0 %s' % disk_img,
+                    'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR])
+                assert 'Test02' in ''.join(output)
+
+                # need to run uefi command to initiate capsule handling
+                output = u_boot_console.run_command(
+                    'env print -e Capsule0000')
+
+            # deleted any way
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR])
+            assert 'Test02' not in ''.join(output)
+
+            # TODO: check CapsuleStatus in CapsuleXXXX
+
+            output = u_boot_console.run_command_list([
+                'sf probe 0:0',
+                'sf read 4000000 100000 10',
+                'md.b 4000000 10'])
+            assert 'u-boot:Old' in ''.join(output)
-- 
2.33.0


      parent reply	other threads:[~2021-10-07  6:27 UTC|newest]

Thread overview: 40+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-10-07  6:23 [PATCH v4 00/11] efi_loader: capsule: improve capsule authentication support AKASHI Takahiro
2021-10-07  6:23 ` [PATCH v4 01/11] Revert "Revert "mkeficapsule: Remove dtb related options"" AKASHI Takahiro
2021-10-08 15:38   ` Simon Glass
2021-10-08 19:11     ` Ilias Apalodimas
2021-10-11  0:29       ` AKASHI Takahiro
2021-10-11 14:54         ` Simon Glass
2021-10-12  1:15           ` AKASHI Takahiro
2021-10-07  6:23 ` [PATCH v4 02/11] Revert "Revert "doc: Update CapsuleUpdate READMEs"" AKASHI Takahiro
2021-10-07  6:23 ` [PATCH v4 03/11] efi_loader: capsule: add back efi_get_public_key_data() AKASHI Takahiro
2021-10-08 19:25   ` Ilias Apalodimas
2021-10-15  0:40   ` Simon Glass
2021-10-20  8:18     ` Masami Hiramatsu
2021-10-20  9:08       ` François Ozog
2021-10-20 13:39       ` Simon Glass
2021-10-25  5:14         ` AKASHI Takahiro
2021-10-25  6:28           ` François Ozog
2021-10-25  7:04             ` Masami Hiramatsu
2021-10-25  7:14               ` François Ozog
2021-10-25 15:18                 ` Simon Glass
2021-10-07  6:23 ` [PATCH v4 04/11] tools: add fdtsig.sh AKASHI Takahiro
2021-10-11 14:54   ` Simon Glass
2021-10-12  1:42     ` AKASHI Takahiro
2021-10-15  0:40       ` Simon Glass
2021-10-25  3:06         ` AKASHI Takahiro
2021-10-26  6:00           ` AKASHI Takahiro
2021-10-27 14:05             ` Simon Glass
2021-10-07  6:23 ` [PATCH v4 05/11] tools: mkeficapsule: add firmwware image signing AKASHI Takahiro
2021-10-20  8:17   ` Masami Hiramatsu
2021-10-25  3:12     ` AKASHI Takahiro
2021-10-25  5:40       ` Masami Hiramatsu
2021-10-25  6:09         ` AKASHI Takahiro
2021-10-25  7:04           ` Masami Hiramatsu
2021-10-25  9:58         ` Sughosh Ganu
2021-10-25 12:46           ` Masami Hiramatsu
2021-10-07  6:23 ` [PATCH v4 06/11] tools: mkeficapsule: add man page AKASHI Takahiro
2021-10-07  6:23 ` [PATCH v4 07/11] doc: update UEFI document for usage of mkeficapsule AKASHI Takahiro
2021-10-07  6:23 ` [PATCH v4 08/11] tools: mkeficapsule: allow for specifying GUID explicitly AKASHI Takahiro
2021-10-07  6:23 ` [PATCH v4 09/11] test/py: efi_capsule: align with the syntax change of mkeficapsule AKASHI Takahiro
2021-10-07  6:23 ` [PATCH v4 10/11] test/py: efi_capsule: add a test for "--guid" option AKASHI Takahiro
2021-10-07  6:23 ` AKASHI Takahiro [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20211007062340.72207-12-takahiro.akashi@linaro.org \
    --to=takahiro.akashi@linaro.org \
    --cc=agraf@csgraf.de \
    --cc=ilias.apalodimas@linaro.org \
    --cc=masami.hiramatsu@linaro.org \
    --cc=sughosh.ganu@linaro.org \
    --cc=u-boot@lists.denx.de \
    --cc=xypron.glpk@gmx.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).