diff options
-rw-r--r-- | MAINTAINERS | 1 | ||||
-rw-r--r-- | cmd/bootefi.c | 32 | ||||
-rw-r--r-- | configs/colibri_vf_defconfig | 2 | ||||
-rw-r--r-- | configs/tools-only_defconfig | 1 | ||||
-rw-r--r-- | doc/develop/uefi/uefi.rst | 147 | ||||
-rw-r--r-- | doc/mkeficapsule.1 | 111 | ||||
-rw-r--r-- | include/efi_loader.h | 1 | ||||
-rw-r--r-- | lib/efi_loader/efi_boottime.c | 6 | ||||
-rw-r--r-- | lib/efi_loader/efi_console.c | 52 | ||||
-rw-r--r-- | lib/efi_loader/efi_device_path.c | 24 | ||||
-rw-r--r-- | lib/efi_loader/efi_device_path_to_text.c | 45 | ||||
-rw-r--r-- | lib/efi_loader/efi_firmware.c | 4 | ||||
-rw-r--r-- | lib/efi_selftest/efi_selftest_fdt.c | 72 | ||||
-rw-r--r-- | lib/vsprintf.c | 14 | ||||
-rw-r--r-- | test/py/tests/test_efi_capsule/capsule_defs.py | 5 | ||||
-rw-r--r-- | test/py/tests/test_efi_capsule/conftest.py | 59 | ||||
-rw-r--r-- | test/py/tests/test_efi_capsule/signature.dts | 10 | ||||
-rw-r--r-- | test/py/tests/test_efi_capsule/test_capsule_firmware.py | 91 | ||||
-rw-r--r-- | test/py/tests/test_efi_capsule/test_capsule_firmware_signed.py | 254 | ||||
-rw-r--r-- | test/unicode_ut.c | 31 | ||||
-rw-r--r-- | tools/Kconfig | 8 | ||||
-rw-r--r-- | tools/Makefile | 4 | ||||
-rw-r--r-- | tools/eficapsule.h | 115 | ||||
-rw-r--r-- | tools/mkeficapsule.c | 459 |
24 files changed, 1320 insertions, 228 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index dcdd99e..2a8f70d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -749,6 +749,7 @@ S: Maintained T: git https://source.denx.de/u-boot/custodians/u-boot-efi.git F: doc/api/efi.rst F: doc/develop/uefi/* +F: doc/mkeficapsule.1 F: doc/usage/bootefi.rst F: drivers/rtc/emul_rtc.c F: include/capitalization.h diff --git a/cmd/bootefi.c b/cmd/bootefi.c index 3a8b2b6..94d18ca 100644 --- a/cmd/bootefi.c +++ b/cmd/bootefi.c @@ -65,6 +65,9 @@ void efi_set_bootdev(const char *dev, const char *devnr, const char *path, struct efi_device_path *device, *image; efi_status_t ret; + log_debug("dev=%s, devnr=%s, path=%s, buffer=%p, size=%zx\n", dev, + devnr, path, buffer, buffer_size); + /* Forget overwritten image */ if (buffer + buffer_size >= image_addr && image_addr + image_size >= buffer) @@ -72,18 +75,19 @@ void efi_set_bootdev(const char *dev, const char *devnr, const char *path, /* Remember only PE-COFF and FIT images */ if (efi_check_pe(buffer, buffer_size, NULL) != EFI_SUCCESS) { -#ifdef CONFIG_FIT - if (fit_check_format(buffer, IMAGE_SIZE_INVAL)) + if (IS_ENABLED(CONFIG_FIT) && + !fit_check_format(buffer, IMAGE_SIZE_INVAL)) { + /* + * FIT images of type EFI_OS are started via command + * bootm. We should not use their boot device with the + * bootefi command. + */ + buffer = 0; + buffer_size = 0; + } else { + log_debug("- not remembering image\n"); return; - /* - * FIT images of type EFI_OS are started via command bootm. - * We should not use their boot device with the bootefi command. - */ - buffer = 0; - buffer_size = 0; -#else - return; -#endif + } } /* efi_set_bootdev() is typically called repeatedly, recover memory */ @@ -103,7 +107,11 @@ void efi_set_bootdev(const char *dev, const char *devnr, const char *path, efi_free_pool(image_tmp); } bootefi_image_path = image; + log_debug("- recorded device %ls\n", efi_dp_str(device)); + if (image) + log_debug("- and image %ls\n", efi_dp_str(image)); } else { + log_debug("- efi_dp_from_name() failed, err=%lx\n", ret); efi_clear_bootdev(); } } @@ -451,6 +459,7 @@ efi_status_t efi_run_image(void *source_buffer, efi_uintn_t source_size) u16 *load_options; if (!bootefi_device_path || !bootefi_image_path) { + log_debug("Not loaded from disk\n"); /* * Special case for efi payload not loaded from disk, * such as 'bootefi hello' or for example payload @@ -476,6 +485,7 @@ efi_status_t efi_run_image(void *source_buffer, efi_uintn_t source_size) file_path = efi_dp_append(bootefi_device_path, bootefi_image_path); msg_path = bootefi_image_path; + log_debug("Loaded from disk\n"); } log_info("Booting %pD\n", msg_path); diff --git a/configs/colibri_vf_defconfig b/configs/colibri_vf_defconfig index 3d15f22..8cf8a31 100644 --- a/configs/colibri_vf_defconfig +++ b/configs/colibri_vf_defconfig @@ -105,4 +105,4 @@ CONFIG_VIDEO_FSL_DCU_FB=y CONFIG_SPLASH_SCREEN_ALIGN=y CONFIG_OF_LIBFDT_OVERLAY=y CONFIG_FDT_FIXUP_PARTITIONS=y -# CONFIG_EFI_UNICODE_CAPITALIZATION is not set +# CONFIG_EFI_LOADER is not set diff --git a/configs/tools-only_defconfig b/configs/tools-only_defconfig index 1f8e90a6..2246b3c 100644 --- a/configs/tools-only_defconfig +++ b/configs/tools-only_defconfig @@ -34,3 +34,4 @@ CONFIG_I2C_EDID=y # CONFIG_VIRTIO_SANDBOX is not set # CONFIG_GENERATE_ACPI_TABLE is not set # CONFIG_EFI_LOADER is not set +CONFIG_TOOLS_MKEFICAPSULE=y diff --git a/doc/develop/uefi/uefi.rst b/doc/develop/uefi/uefi.rst index 43fb10f..62122b1 100644 --- a/doc/develop/uefi/uefi.rst +++ b/doc/develop/uefi/uefi.rst @@ -284,37 +284,56 @@ Support has been added for the UEFI capsule update feature which enables updating the U-Boot image using the UEFI firmware management protocol (FMP). The capsules are not passed to the firmware through the UpdateCapsule runtime service. Instead, capsule-on-disk -functionality is used for fetching the capsule from the EFI System -Partition (ESP) by placing the capsule file under the -\EFI\UpdateCapsule directory. - -The directory \EFI\UpdateCapsule is checked for capsules only within the -EFI system partition on the device specified in the active boot option -determined by reference to BootNext variable or BootOrder variable processing. -The active Boot Variable is the variable with highest priority BootNext or -within BootOrder that refers to a device found to be present. Boot variables -in BootOrder but referring to devices not present are ignored when determining -active boot variable. -Before starting a capsule update make sure your capsules are installed in the -correct ESP partition or set BootNext. +functionality is used for fetching capsules from the EFI System +Partition (ESP) by placing capsule files under the directory:: + + \EFI\UpdateCapsule + +The directory is checked for capsules only within the +EFI system partition on the device specified in the active boot option, +which is determined by BootXXXX variable in BootNext, or if not, the highest +priority one within BootOrder. Any BootXXXX variables referring to devices +not present are ignored when determining the active boot option. + +Please note that capsules will be applied in the alphabetic order of +capsule file names. + +Creating a capsule file +*********************** + +A capsule file can be created by using tools/mkeficapsule. +To build this tool, enable:: + + CONFIG_TOOLS_MKEFICAPSULE=y + CONFIG_TOOLS_LIBCRYPTO=y + +Run the following command:: + +.. code-block:: console + + $ mkeficapsule \ + --index 1 --instance 0 \ + [--fit <FIT image> | --raw <raw image>] \ + <capsule_file_name> Performing the update ********************* -Since U-boot doesn't currently support SetVariable at runtime there's a Kconfig -option (CONFIG_EFI_IGNORE_OSINDICATIONS) to disable the OsIndications variable -check. If that option is enabled just copy your capsule to \EFI\UpdateCapsule. +Put capsule files under the directory mentioned above. +Then, following the UEFI specification, you'll need to set +the EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED +bit in OsIndications variable with:: -If that option is disabled, you'll need to set the OsIndications variable with:: +.. code-block:: console => setenv -e -nv -bs -rt -v OsIndications =0x04 -Finally, the capsule update can be initiated either by rebooting the board, -which is the preferred method, or by issuing the following command:: +Since U-boot doesn't currently support SetVariable at runtime, its value +won't be taken over across the reboot. If this is the case, you can skip +this feature check with the Kconfig option (CONFIG_EFI_IGNORE_OSINDICATIONS) +set. - => efidebug capsule disk-update - -**The efidebug command is should only be used during debugging/development.** +Finally, the capsule update can be initiated by rebooting the board. Enabling Capsule Authentication ******************************* @@ -324,82 +343,64 @@ be updated by verifying the capsule signature. The capsule signature is computed and prepended to the capsule payload at the time of capsule generation. This signature is then verified by using the public key stored as part of the X509 certificate. This certificate is -in the form of an efi signature list (esl) file, which is embedded as -part of U-Boot. +in the form of an efi signature list (esl) file, which is embedded in +a device tree. The capsule authentication feature can be enabled through the following config, in addition to the configs listed above for capsule update:: CONFIG_EFI_CAPSULE_AUTHENTICATE=y - CONFIG_EFI_CAPSULE_KEY_PATH=<path to .esl cert> The public and private keys used for the signing process are generated -and used by the steps highlighted below:: +and used by the steps highlighted below. - 1. Install utility commands on your host - * OPENSSL +1. Install utility commands on your host + * openssl * efitools - 2. Create signing keys and certificate files on your host +2. Create signing keys and certificate files on your host: + +.. code-block:: console $ openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=CRT/ \ -keyout CRT.key -out CRT.crt -nodes -days 365 $ cert-to-efi-sig-list CRT.crt CRT.esl - $ openssl x509 -in CRT.crt -out CRT.cer -outform DER - $ openssl x509 -inform DER -in CRT.cer -outform PEM -out CRT.pub.pem - - $ openssl pkcs12 -export -out CRT.pfx -inkey CRT.key -in CRT.crt - $ openssl pkcs12 -in CRT.pfx -nodes -out CRT.pem - -The capsule file can be generated by using the GenerateCapsule.py -script in EDKII:: - - $ ./BaseTools/BinWrappers/PosixLike/GenerateCapsule -e -o \ - <capsule_file_name> --monotonic-count <val> --fw-version \ - <val> --lsv <val> --guid \ - e2bb9c06-70e9-4b14-97a3-5a7913176e3f --verbose \ - --update-image-index <val> --signer-private-cert \ - /path/to/CRT.pem --trusted-public-cert \ - /path/to/CRT.pub.pem --other-public-cert /path/to/CRT.pub.pem \ - <u-boot.bin> +3. Run the following command to create and sign the capsule file: -Place the capsule generated in the above step on the EFI System -Partition under the EFI/UpdateCapsule directory +.. code-block:: console -Testing on QEMU -*************** + $ mkeficapsule --monotonic-count 1 \ + --private-key CRT.key \ + --certificate CRT.crt \ + --index 1 --instance 0 \ + [--fit | --raw | --guid <guid-string] \ + <image_blob> <capsule_file_name> -Currently, support has been added on the QEMU ARM64 virt platform for -updating the U-Boot binary as a raw image when the platform is booted -in non-secure mode, i.e. with CONFIG_TFABOOT disabled. For this -configuration, the QEMU platform needs to be booted with -'secure=off'. The U-Boot binary placed on the first bank of the NOR -flash at offset 0x0. The U-Boot environment is placed on the second -NOR flash bank at offset 0x4000000. +4. Insert the signature list into a device tree in the following format:: -The capsule update feature is enabled with the following configuration -settings:: + { + signature { + capsule-key = [ <binary of signature list> ]; + } + ... + } - CONFIG_MTD=y - CONFIG_FLASH_CFI_MTD=y - CONFIG_CMD_MTDPARTS=y - CONFIG_CMD_DFU=y - CONFIG_DFU_MTD=y - CONFIG_PCI_INIT_R=y - CONFIG_EFI_CAPSULE_ON_DISK=y - CONFIG_EFI_CAPSULE_FIRMWARE_MANAGEMENT=y - CONFIG_EFI_CAPSULE_FIRMWARE=y - CONFIG_EFI_CAPSULE_FIRMWARE_RAW=y + You can do this manually with: -In addition, the following config needs to be disabled(QEMU ARM specific):: +.. code-block:: console - CONFIG_TFABOOT + $ dtc -@ -I dts -O dtb -o signature.dtbo signature.dts + $ fdtoverlay -i orig.dtb -o new.dtb -v signature.dtbo -The capsule file can be generated by using the tools/mkeficapsule:: + where signature.dts looks like:: - $ mkeficapsule --raw <u-boot.bin> --index 1 <capsule_file_name> + &{/} { + signature { + capsule-key = /incbin/("CRT.esl"); + }; + }; Executing the boot manager ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/mkeficapsule.1 b/doc/mkeficapsule.1 new file mode 100644 index 0000000..8babb27 --- /dev/null +++ b/doc/mkeficapsule.1 @@ -0,0 +1,111 @@ +.\" SPDX-License-Identifier: GPL-2.0+ +.\" Copyright (c) 2021, Linaro Limited +.\" written by AKASHI Takahiro <takahiro.akashi@linaro.org> +.TH MAEFICAPSULE 1 "May 2021" + +.SH NAME +mkeficapsule \- Generate EFI capsule file for U-Boot + +.SH SYNOPSIS +.B mkeficapsule +.RI [ options "] " image-blob " " capsule-file + +.SH "DESCRIPTION" +.B mkeficapsule +command is used to create an EFI capsule file for use with the U-Boot +EFI capsule update. +A capsule file may contain various type of firmware blobs which +are to be applied to the system and must be placed in the specific +directory on the UEFI system partition. +An update will be automatically executed at next reboot. + +Optionally, a capsule file can be signed with a given private key. +In this case, the update will be authenticated by verifying the signature +before applying. + +.B mkeficapsule +takes any type of image files, including: +.TP +.I raw image +format is a single binary blob of any type of firmware. + +.TP +.I FIT (Flattened Image Tree) image +format is the same as used in the new uImage format and allows for +multiple binary blobs in a single capsule file. +This type of image file can be generated by +.BR mkimage . + +.PP +If you want to use other types than above two, you should explicitly +specify a guid for the FMP driver. + +.SH "OPTIONS" +One of +.BR --fit ", " --raw " or " --guid +option must be specified. + +.TP +.BR -f ", " --fit +Indicate that the blob is a FIT image file + +.TP +.BR -r ", " --raw +Indicate that the blob is a raw image file + +.TP +.BI "-g\fR,\fB --guid " guid-string +Specify guid for image blob type. The format is: + xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + +The first three elements are in little endian, while the rest +is in big endian. + +.TP +.BI "-i\fR,\fB --index " index +Specify an image index + +.TP +.BI "-I\fR,\fB --instance " instance +Specify a hardware instance + +.TP +.BR -h ", " --help +Print a help message + +.PP +With signing, +.BR --private-key ", " --certificate " and " --monotonic-count +are all mandatory. + +.TP +.BI "-p\fR,\fB --private-key " private-key-file +Specify signer's private key file in PEM + +.TP +.BI "-c\fR,\fB --certificate " certificate-file +Specify signer's certificate file in EFI certificate list format + +.TP +.BI "-m\fR,\fB --monotonic-count " count +Specify a monotonic count which is set to be monotonically incremented +at every firmware update. + +.TP +.B "-d\fR,\fB --dump_sig" +Dump signature data into *.p7 file + +.PP +.SH FILES +.TP +.I /EFI/UpdateCapsule +The directory in which all capsule files be placed + +.SH SEE ALSO +.BR mkimage (1) + +.SH AUTHORS +Written by AKASHI Takahiro <takahiro.akashi@linaro.org> + +.SH HOMEPAGE +http://www.denx.de/wiki/U-Boot/WebHome diff --git a/include/efi_loader.h b/include/efi_loader.h index f4ae84d..e390d32 100644 --- a/include/efi_loader.h +++ b/include/efi_loader.h @@ -769,6 +769,7 @@ const struct efi_device_path *efi_dp_last_node( efi_status_t efi_dp_split_file_path(struct efi_device_path *full_path, struct efi_device_path **device_path, struct efi_device_path **file_path); +struct efi_device_path *efi_dp_from_uart(void); efi_status_t efi_dp_from_name(const char *dev, const char *devnr, const char *path, struct efi_device_path **device, diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index 1ea04de..82128ac 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -467,7 +467,7 @@ static efi_status_t EFIAPI efi_allocate_pool_ext(int pool_type, { efi_status_t r; - EFI_ENTRY("%d, %zd, %p", pool_type, size, buffer); + EFI_ENTRY("%d, %zu, %p", pool_type, size, buffer); r = efi_allocate_pool(pool_type, size, buffer); return EFI_EXIT(r); } @@ -914,7 +914,7 @@ static efi_status_t EFIAPI efi_wait_for_event(efi_uintn_t num_events, { int i; - EFI_ENTRY("%zd, %p, %p", num_events, event, index); + EFI_ENTRY("%zu, %p, %p", num_events, event, index); /* Check parameters */ if (!num_events || !event) @@ -2028,7 +2028,7 @@ efi_status_t EFIAPI efi_load_image(bool boot_policy, efi_status_t ret; void *dest_buffer; - EFI_ENTRY("%d, %p, %pD, %p, %zd, %p", boot_policy, parent_image, + EFI_ENTRY("%d, %p, %pD, %p, %zu, %p", boot_policy, parent_image, file_path, source_buffer, source_size, image_handle); if (!image_handle || (!source_buffer && !file_path) || diff --git a/lib/efi_loader/efi_console.c b/lib/efi_loader/efi_console.c index 3b012e1..ba68a15 100644 --- a/lib/efi_loader/efi_console.c +++ b/lib/efi_loader/efi_console.c @@ -25,6 +25,8 @@ struct cout_mode { int present; }; +__maybe_unused static struct efi_object uart_obj; + static struct cout_mode efi_cout_modes[] = { /* EFI Mode 0 is 80x25 and always present */ { @@ -1258,37 +1260,33 @@ static void EFIAPI efi_key_notify(struct efi_event *event, void *context) efi_status_t efi_console_register(void) { efi_status_t r; - efi_handle_t console_output_handle; - efi_handle_t console_input_handle; + struct efi_device_path *dp; /* Set up mode information */ query_console_size(); - /* Create handles */ - r = efi_create_handle(&console_output_handle); - if (r != EFI_SUCCESS) - goto out_of_memory; - - r = efi_add_protocol(console_output_handle, - &efi_guid_text_output_protocol, &efi_con_out); - if (r != EFI_SUCCESS) - goto out_of_memory; - systab.con_out_handle = console_output_handle; - systab.stderr_handle = console_output_handle; - - r = efi_create_handle(&console_input_handle); - if (r != EFI_SUCCESS) - goto out_of_memory; - - r = efi_add_protocol(console_input_handle, - &efi_guid_text_input_protocol, &efi_con_in); - if (r != EFI_SUCCESS) - goto out_of_memory; - systab.con_in_handle = console_input_handle; - r = efi_add_protocol(console_input_handle, - &efi_guid_text_input_ex_protocol, &efi_con_in_ex); - if (r != EFI_SUCCESS) - goto out_of_memory; + /* Install protocols on root node */ + r = EFI_CALL(efi_install_multiple_protocol_interfaces + (&efi_root, + &efi_guid_text_output_protocol, &efi_con_out, + &efi_guid_text_input_protocol, &efi_con_in, + &efi_guid_text_input_ex_protocol, &efi_con_in_ex, + NULL)); + + /* Create console node and install device path protocols */ + if (CONFIG_IS_ENABLED(DM_SERIAL)) { + dp = efi_dp_from_uart(); + if (!dp) + goto out_of_memory; + + /* Hook UART up to the device list */ + efi_add_handle(&uart_obj); + + /* Install device path */ + r = efi_add_protocol(&uart_obj, &efi_guid_device_path, dp); + if (r != EFI_SUCCESS) + goto out_of_memory; + } /* Create console events */ r = efi_create_event(EVT_NOTIFY_WAIT, TPL_CALLBACK, efi_key_notify, diff --git a/lib/efi_loader/efi_device_path.c b/lib/efi_loader/efi_device_path.c index c61f485..dc787b4 100644 --- a/lib/efi_loader/efi_device_path.c +++ b/lib/efi_loader/efi_device_path.c @@ -494,7 +494,7 @@ __maybe_unused static unsigned int dp_size(struct udevice *dev) if (!dev || !dev->driver) return sizeof(ROOT); - switch (dev->driver->id) { + switch (device_get_uclass_id(dev)) { case UCLASS_ROOT: case UCLASS_SIMPLE_BUS: /* stop traversing parents at this point: */ @@ -579,7 +579,7 @@ __maybe_unused static void *dp_fill(void *buf, struct udevice *dev) if (!dev || !dev->driver) return buf; - switch (dev->driver->id) { + switch (device_get_uclass_id(dev)) { case UCLASS_ROOT: case UCLASS_SIMPLE_BUS: { /* stop traversing parents at this point: */ @@ -759,9 +759,9 @@ __maybe_unused static void *dp_fill(void *buf, struct udevice *dev) return &udp[1]; } default: - debug("%s(%u) %s: unhandled device class: %s (%u)\n", - __FILE__, __LINE__, __func__, - dev->name, dev->driver->id); + /* If the uclass driver is missing, this will show NULL */ + log_debug("unhandled device class: %s (%s)\n", dev->name, + dev_get_uclass_name(dev)); return dp_fill(buf, dev->parent); } } @@ -769,13 +769,8 @@ __maybe_unused static void *dp_fill(void *buf, struct udevice *dev) static unsigned dp_part_size(struct blk_desc *desc, int part) { unsigned dpsize; - struct udevice *dev; - int ret; + struct udevice *dev = desc->bdev; - ret = blk_find_device(desc->if_type, desc->devnum, &dev); - - if (ret) - dev = desc->bdev->parent; dpsize = dp_size(dev); if (part == 0) /* the actual disk, not a partition */ @@ -866,13 +861,8 @@ static void *dp_part_node(void *buf, struct blk_desc *desc, int part) */ static void *dp_part_fill(void *buf, struct blk_desc *desc, int part) { - struct udevice *dev; - int ret; - - ret = blk_find_device(desc->if_type, desc->devnum, &dev); + struct udevice *dev = desc->bdev; - if (ret) - dev = desc->bdev->parent; buf = dp_fill(buf, dev); if (part == 0) /* the actual disk, not a partition */ diff --git a/lib/efi_loader/efi_device_path_to_text.c b/lib/efi_loader/efi_device_path_to_text.c index d8a83c8..4d73954 100644 --- a/lib/efi_loader/efi_device_path_to_text.c +++ b/lib/efi_loader/efi_device_path_to_text.c @@ -8,6 +8,7 @@ #include <common.h> #include <blk.h> #include <efi_loader.h> +#include <malloc.h> #define MAC_OUTPUT_LEN 22 #define UNKNOWN_OUTPUT_LEN 23 @@ -121,16 +122,26 @@ static char *dp_msging(char *s, struct efi_device_path *dp) case DEVICE_PATH_SUB_TYPE_MSG_UART: { struct efi_device_path_uart *uart = (struct efi_device_path_uart *)dp; - s += sprintf(s, "Uart(%lld,%d,%d,", uart->baud_rate, - uart->data_bits, uart->parity); - switch (uart->stop_bits) { - case 2: - s += sprintf(s, "1.5)"); - break; - default: + const char parity_str[6] = {'D', 'N', 'E', 'O', 'M', 'S'}; + const char *stop_bits_str[4] = { "D", "1", "1.5", "2" }; + + s += sprintf(s, "Uart(%lld,%d,", uart->baud_rate, + uart->data_bits); + + /* + * Parity and stop bits can either both use keywords or both use + * numbers but numbers and keywords should not be mixed. Let's + * go for keywords as this is what EDK II does. For illegal + * values fall back to numbers. + */ + if (uart->parity < 6) + s += sprintf(s, "%c,", parity_str[uart->parity]); + else + s += sprintf(s, "%d,", uart->parity); + if (uart->stop_bits < 4) + s += sprintf(s, "%s)", stop_bits_str[uart->stop_bits]); + else s += sprintf(s, "%d)", uart->stop_bits); - break; - } break; } case DEVICE_PATH_SUB_TYPE_MSG_USB: { @@ -292,10 +303,18 @@ static char *dp_media(char *s, struct efi_device_path *dp) case DEVICE_PATH_SUB_TYPE_FILE_PATH: { struct efi_device_path_file_path *fp = (struct efi_device_path_file_path *)dp; - int slen = (dp->length - sizeof(*dp)) / 2; - if (slen > MAX_NODE_LEN - 2) - slen = MAX_NODE_LEN - 2; - s += sprintf(s, "%-.*ls", slen, fp->str); + u16 *buffer; + int slen = dp->length - sizeof(*dp); + + /* two bytes for \0, extra byte if dp->length is odd */ + buffer = calloc(1, slen + 3); + if (!buffer) { + log_err("Out of memory\n"); + return s; + } + memcpy(buffer, fp->str, dp->length - sizeof(*dp)); + s += snprintf(s, MAX_NODE_LEN - 1, "%ls", buffer); + free(buffer); break; } default: diff --git a/lib/efi_loader/efi_firmware.c b/lib/efi_loader/efi_firmware.c index 519a472..a5ff32f 100644 --- a/lib/efi_loader/efi_firmware.c +++ b/lib/efi_loader/efi_firmware.c @@ -302,7 +302,7 @@ efi_status_t EFIAPI efi_firmware_fit_set_image( efi_status_t (*progress)(efi_uintn_t completion), u16 **abort_reason) { - EFI_ENTRY("%p %d %p %zd %p %p %p\n", this, image_index, image, + EFI_ENTRY("%p %d %p %zu %p %p %p\n", this, image_index, image, image_size, vendor_code, progress, abort_reason); if (!image || image_index != 1) @@ -417,7 +417,7 @@ efi_status_t EFIAPI efi_firmware_raw_set_image( efi_status_t status; efi_uintn_t capsule_payload_size; - EFI_ENTRY("%p %d %p %zd %p %p %p\n", this, image_index, image, + EFI_ENTRY("%p %d %p %zu %p %p %p\n", this, image_index, image, image_size, vendor_code, progress, abort_reason); if (!image) diff --git a/lib/efi_selftest/efi_selftest_fdt.c b/lib/efi_selftest/efi_selftest_fdt.c index f4a7fcb..114ac58 100644 --- a/lib/efi_selftest/efi_selftest_fdt.c +++ b/lib/efi_selftest/efi_selftest_fdt.c @@ -1,15 +1,14 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * efi_selftest_pos + * efi_selftest_fdt * * Copyright (c) 2018 Heinrich Schuchardt <xypron.glpk@gmx.de> + * Copyright (c) 2022 Ventana Micro Systems Inc * - * Test the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL. - * - * The following services are tested: - * OutputString, TestString, SetAttribute. + * Check the device tree, test the RISCV_EFI_BOOT_PROTOCOL. */ +#include <efi_riscv.h> #include <efi_selftest.h> #include <linux/libfdt.h> @@ -22,6 +21,8 @@ static const char *fdt; static const efi_guid_t fdt_guid = EFI_FDT_GUID; static const efi_guid_t acpi_guid = EFI_ACPI_TABLE_GUID; +static const efi_guid_t riscv_efi_boot_protocol_guid = + RISCV_EFI_BOOT_PROTOCOL_GUID; /** * f2h() - convert FDT value to host endianness. @@ -189,6 +190,29 @@ static int setup(const efi_handle_t img_handle, return EFI_ST_SUCCESS; } +__maybe_unused static efi_status_t get_boot_hartid(efi_uintn_t *efi_hartid) +{ + efi_status_t ret; + struct riscv_efi_boot_protocol *prot; + + /* Get RISC-V boot protocol */ + ret = boottime->locate_protocol(&riscv_efi_boot_protocol_guid, NULL, + (void **)&prot); + if (ret != EFI_SUCCESS) { + efi_st_error("RISC-V Boot Protocol not available\n"); + return EFI_ST_FAILURE; + } + + /* Get boot hart ID from EFI protocol */ + ret = prot->get_boot_hartid(prot, efi_hartid); + if (ret != EFI_SUCCESS) { + efi_st_error("Could not retrieve boot hart ID\n"); + return EFI_ST_FAILURE; + } + + return EFI_ST_SUCCESS; +} + /* * Execute unit test. * @@ -220,19 +244,37 @@ static int execute(void) return EFI_ST_FAILURE; } } - str = get_property(u"boot-hartid", u"chosen"); if (IS_ENABLED(CONFIG_RISCV)) { - if (str) { - efi_st_printf("boot-hartid: %u\n", - f2h(*(fdt32_t *)str)); - ret = boottime->free_pool(str); - if (ret != EFI_SUCCESS) { - efi_st_error("FreePool failed\n"); + u32 fdt_hartid; + + str = get_property(u"boot-hartid", u"chosen"); + if (!str) { + efi_st_error("boot-hartid missing in devicetree\n"); + return EFI_ST_FAILURE; + } + fdt_hartid = f2h(*(fdt32_t *)str); + efi_st_printf("boot-hartid: %u\n", fdt_hartid); + + ret = boottime->free_pool(str); + if (ret != EFI_SUCCESS) { + efi_st_error("FreePool failed\n"); + return EFI_ST_FAILURE; + } + + if (IS_ENABLED(CONFIG_EFI_RISCV_BOOT_PROTOCOL)) { + efi_uintn_t efi_hartid; + int r; + + r = get_boot_hartid(&efi_hartid); + if (r != EFI_ST_SUCCESS) + return r; + /* Boot hart ID should be same */ + if (efi_hartid != fdt_hartid) { + efi_st_error("boot-hartid differs: prot 0x%p, DT 0x%.8x\n", + (void *)(uintptr_t)efi_hartid, + fdt_hartid); return EFI_ST_FAILURE; } - } else { - efi_st_error("boot-hartid not found\n"); - return EFI_ST_FAILURE; } } diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 69b2f6a..fe06aa2 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -285,15 +285,25 @@ static __maybe_unused char *string16(char *buf, char *end, u16 *s, if (!(flags & LEFT)) for (; len < field_width; --field_width) ADDCH(buf, ' '); - for (i = 0; i < len && buf + utf16_utf8_strnlen(str, 1) <= end; ++i) { + if (buf < end) + *buf = 0; + for (i = 0; i < len; ++i) { + int slen = utf16_utf8_strnlen(str, 1); s32 s = utf16_get(&str); if (s < 0) s = '?'; - utf8_put(s, &buf); + if (buf + slen < end) { + utf8_put(s, &buf); + if (buf < end) + *buf = 0; + } else { + buf += slen; + } } for (; len < field_width; --field_width) ADDCH(buf, ' '); + return buf; } diff --git a/test/py/tests/test_efi_capsule/capsule_defs.py b/test/py/tests/test_efi_capsule/capsule_defs.py index 4fd6353..59b40f1 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 '/' if it is not null. +EFITOOLS_PATH = '' diff --git a/test/py/tests/test_efi_capsule/conftest.py b/test/py/tests/test_efi_capsule/conftest.py index 6ad5608..9076087 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,36 @@ 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, @@ -50,12 +80,31 @@ def efi_capsule_data(request, u_boot_config): check_call('cd %s; %s/tools/mkimage -f uboot_bin_env.its uboot_bin_env.itb' % (data_dir, u_boot_config.build_dir), shell=True) - check_call('cd %s; %s/tools/mkeficapsule --fit uboot_bin_env.itb --index 1 Test01' % + check_call('cd %s; %s/tools/mkeficapsule --index 1 --fit uboot_bin_env.itb Test01' % + (data_dir, u_boot_config.build_dir), + shell=True) + check_call('cd %s; %s/tools/mkeficapsule --index 1 --raw u-boot.bin.new Test02' % (data_dir, u_boot_config.build_dir), shell=True) - check_call('cd %s; %s/tools/mkeficapsule --raw u-boot.bin.new --index 1 Test02' % + 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 Test11' + % (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 Test12' + % (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 0000000..078cfc7 --- /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.py b/test/py/tests/test_efi_capsule/test_capsule_firmware.py index 9eeaae2..6e803f6 100644 --- a/test/py/tests/test_efi_capsule/test_capsule_firmware.py +++ b/test/py/tests/test_efi_capsule/test_capsule_firmware.py @@ -148,6 +148,8 @@ class TestEfiCapsuleFirmwareFit(object): capsule_early = u_boot_config.buildconfig.get( 'config_efi_capsule_on_disk_early') + capsule_auth = u_boot_config.buildconfig.get( + 'config_efi_capsule_authenticate') 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 @@ -171,12 +173,18 @@ class TestEfiCapsuleFirmwareFit(object): 'sf probe 0:0', 'sf read 4000000 100000 10', 'md.b 4000000 10']) - assert 'u-boot:New' in ''.join(output) + if capsule_auth: + assert 'u-boot:Old' in ''.join(output) + else: + assert 'u-boot:New' in ''.join(output) output = u_boot_console.run_command_list([ 'sf read 4000000 150000 10', 'md.b 4000000 10']) - assert 'u-boot-env:New' in ''.join(output) + if capsule_auth: + assert 'u-boot-env:Old' in ''.join(output) + else: + assert 'u-boot-env:New' in ''.join(output) def test_efi_capsule_fw3( self, u_boot_config, u_boot_console, efi_capsule_data): @@ -215,6 +223,8 @@ class TestEfiCapsuleFirmwareFit(object): capsule_early = u_boot_config.buildconfig.get( 'config_efi_capsule_on_disk_early') + capsule_auth = u_boot_config.buildconfig.get( + 'config_efi_capsule_authenticate') 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 @@ -246,4 +256,79 @@ class TestEfiCapsuleFirmwareFit(object): 'sf probe 0:0', 'sf read 4000000 100000 10', 'md.b 4000000 10']) - assert 'u-boot:New' in ''.join(output) + if capsule_auth: + assert 'u-boot:Old' in ''.join(output) + else: + assert 'u-boot:New' in ''.join(output) + + def test_efi_capsule_fw4( + self, u_boot_config, u_boot_console, efi_capsule_data): + """ + Test Case 4 - Test "--guid" option of mkeficapsule + The test scenario is the same as Case 3. + """ + disk_img = efi_capsule_data + with u_boot_console.log.section('Test Case 4-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 -s ""', + '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/Test03' % CAPSULE_DATA_DIR, + 'fatwrite host 0:1 4000000 %s/Test03 $filesize' % CAPSULE_INSTALL_DIR, + 'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR]) + assert 'Test03' in ''.join(output) + + # reboot + u_boot_console.restart_uboot() + + capsule_early = u_boot_config.buildconfig.get( + 'config_efi_capsule_on_disk_early') + capsule_auth = u_boot_config.buildconfig.get( + 'config_efi_capsule_authenticate') + with u_boot_console.log.section('Test Case 4-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 'Test03' 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(['efidebug capsule esrt']) + + # ensure that EFI_FIRMWARE_IMAGE_TYPE_UBOOT_RAW_GUID is in the ESRT. + assert 'E2BB9C06-70E9-4B14-97A3-5A7913176E3F' in ''.join(output) + + output = u_boot_console.run_command_list([ + 'host bind 0 %s' % disk_img, + 'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR]) + assert 'Test03' not in ''.join(output) + + output = u_boot_console.run_command_list([ + 'sf probe 0:0', + 'sf read 4000000 100000 10', + 'md.b 4000000 10']) + if capsule_auth: + assert 'u-boot:Old' in ''.join(output) + else: + assert 'u-boot:New' in ''.join(output) 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 0000000..593b032 --- /dev/null +++ b/test/py/tests/test_efi_capsule/test_capsule_firmware_signed.py @@ -0,0 +1,254 @@ +# 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/Test11' % CAPSULE_DATA_DIR, + 'fatwrite host 0:1 4000000 %s/Test11 $filesize' + % CAPSULE_INSTALL_DIR, + 'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR]) + assert 'Test11' 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 'Test11' 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 'Test11' 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/Test12' % CAPSULE_DATA_DIR, + 'fatwrite host 0:1 4000000 %s/Test12 $filesize' + % CAPSULE_INSTALL_DIR, + 'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR]) + assert 'Test12' 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 'Test12' 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 'Test12' 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) diff --git a/test/unicode_ut.c b/test/unicode_ut.c index f821e5a..f2f63d5 100644 --- a/test/unicode_ut.c +++ b/test/unicode_ut.c @@ -97,6 +97,7 @@ UNICODE_TEST(unicode_test_u16_strcpy); static int unicode_test_string16(struct unit_test_state *uts) { char buf[20]; + int ret; /* Test length and precision */ memset(buf, 0xff, sizeof(buf)); @@ -130,6 +131,36 @@ static int unicode_test_string16(struct unit_test_state *uts) sprintf(buf, "%ls", i3); ut_asserteq_str("i3?", buf); + memset(buf, 0xff, sizeof(buf)); + ret = snprintf(buf, 4, "%ls", c1); + ut_asserteq(6, ret); + ut_asserteq_str("U-B", buf); + + memset(buf, 0xff, sizeof(buf)); + ret = snprintf(buf, 6, "%ls", c2); + ut_asserteq_str("kafb", buf); + ut_asserteq(9, ret); + + memset(buf, 0xff, sizeof(buf)); + ret = snprintf(buf, 7, "%ls", c2); + ut_asserteq_str("kafb\xC3\xA1", buf); + ut_asserteq(9, ret); + + memset(buf, 0xff, sizeof(buf)); + ret = snprintf(buf, 8, "%ls", c3); + ut_asserteq_str("\xE6\xBD\x9C\xE6\xB0\xB4", buf); + ut_asserteq(9, ret); + + memset(buf, 0xff, sizeof(buf)); + ret = snprintf(buf, 11, "%ls", c4); + ut_asserteq_str("\xF0\x90\x92\x8D\xF0\x90\x92\x96", buf); + ut_asserteq(12, ret); + + memset(buf, 0xff, sizeof(buf)); + ret = snprintf(buf, 4, "%ls", c4); + ut_asserteq_str("", buf); + ut_asserteq(12, ret); + return 0; } UNICODE_TEST(unicode_test_string16); diff --git a/tools/Kconfig b/tools/Kconfig index 91ce8ae..117c921 100644 --- a/tools/Kconfig +++ b/tools/Kconfig @@ -90,4 +90,12 @@ config TOOLS_SHA512 help Enable SHA512 support in the tools builds +config TOOLS_MKEFICAPSULE + bool "Build efimkcapsule command" + default y if EFI_CAPSULE_ON_DISK + help + This command allows users to create a UEFI capsule file and, + optionally sign that file. If you want to enable UEFI capsule + update feature on your target, you certainly need this. + endmenu diff --git a/tools/Makefile b/tools/Makefile index 1763f44..5409ff2 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -238,8 +238,8 @@ hostprogs-$(CONFIG_MIPS) += mips-relocs hostprogs-$(CONFIG_ASN1_COMPILER) += asn1_compiler HOSTCFLAGS_asn1_compiler.o = -idirafter $(srctree)/include -mkeficapsule-objs := mkeficapsule.o $(LIBFDT_OBJS) -hostprogs-$(CONFIG_EFI_HAVE_CAPSULE_SUPPORT) += mkeficapsule +HOSTLDLIBS_mkeficapsule += -lgnutls -luuid +hostprogs-$(CONFIG_TOOLS_MKEFICAPSULE) += mkeficapsule # We build some files with extra pedantic flags to try to minimize things # that won't build on some weird host compiler -- though there are lots of diff --git a/tools/eficapsule.h b/tools/eficapsule.h new file mode 100644 index 0000000..8c1560b --- /dev/null +++ b/tools/eficapsule.h @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2021 Linaro Limited + * Author: AKASHI Takahiro + * + * derived from efi.h and efi_api.h to make the file POSIX-compliant + */ + +#ifndef _EFI_CAPSULE_H +#define _EFI_CAPSULE_H + +#include <stdint.h> +#include <pe.h> /* WIN_CERTIFICATE */ + +/* + * Gcc's predefined attributes are not recognized by clang. + */ +#ifndef __packed +#define __packed __attribute__((__packed__)) +#endif + +#ifndef __aligned +#define __aligned(x) __attribute__((__aligned__(x))) +#endif + +typedef struct { + uint8_t b[16]; +} efi_guid_t __aligned(8); + +#define EFI_GUID(a, b, c, d0, d1, d2, d3, d4, d5, d6, d7) \ + {{ (a) & 0xff, ((a) >> 8) & 0xff, ((a) >> 16) & 0xff, \ + ((a) >> 24) & 0xff, \ + (b) & 0xff, ((b) >> 8) & 0xff, \ + (c) & 0xff, ((c) >> 8) & 0xff, \ + (d0), (d1), (d2), (d3), (d4), (d5), (d6), (d7) } } + +#define EFI_FIRMWARE_MANAGEMENT_CAPSULE_ID_GUID \ + EFI_GUID(0x6dcbd5ed, 0xe82d, 0x4c44, 0xbd, 0xa1, \ + 0x71, 0x94, 0x19, 0x9a, 0xd9, 0x2a) + +#define EFI_FIRMWARE_IMAGE_TYPE_UBOOT_FIT_GUID \ + EFI_GUID(0xae13ff2d, 0x9ad4, 0x4e25, 0x9a, 0xc8, \ + 0x6d, 0x80, 0xb3, 0xb2, 0x21, 0x47) + +#define EFI_FIRMWARE_IMAGE_TYPE_UBOOT_RAW_GUID \ + EFI_GUID(0xe2bb9c06, 0x70e9, 0x4b14, 0x97, 0xa3, \ + 0x5a, 0x79, 0x13, 0x17, 0x6e, 0x3f) + +#define EFI_CERT_TYPE_PKCS7_GUID \ + EFI_GUID(0x4aafd29d, 0x68df, 0x49ee, 0x8a, 0xa9, \ + 0x34, 0x7d, 0x37, 0x56, 0x65, 0xa7) + +/* flags */ +#define CAPSULE_FLAGS_PERSIST_ACROSS_RESET 0x00010000 + +struct efi_capsule_header { + efi_guid_t capsule_guid; + uint32_t header_size; + uint32_t flags; + uint32_t capsule_image_size; +} __packed; + +struct efi_firmware_management_capsule_header { + uint32_t version; + uint16_t embedded_driver_count; + uint16_t payload_item_count; + uint32_t item_offset_list[]; +} __packed; + +/* image_capsule_support */ +#define CAPSULE_SUPPORT_AUTHENTICATION 0x0000000000000001 + +struct efi_firmware_management_capsule_image_header { + uint32_t version; + efi_guid_t update_image_type_id; + uint8_t update_image_index; + uint8_t reserved[3]; + uint32_t update_image_size; + uint32_t update_vendor_code_size; + uint64_t update_hardware_instance; + uint64_t image_capsule_support; +} __packed; + +/** + * win_certificate_uefi_guid - A certificate that encapsulates + * a GUID-specific signature + * + * @hdr: Windows certificate header + * @cert_type: Certificate type + * @cert_data: Certificate data + */ +struct win_certificate_uefi_guid { + WIN_CERTIFICATE hdr; + efi_guid_t cert_type; + uint8_t cert_data[]; +} __packed; + +/** + * efi_firmware_image_authentication - Capsule authentication method + * descriptor + * + * This structure describes an authentication information for + * a capsule with IMAGE_ATTRIBUTE_AUTHENTICATION_REQUIRED set + * and should be included as part of the capsule. + * Only EFI_CERT_TYPE_PKCS7_GUID is accepted. + * + * @monotonic_count: Count to prevent replay + * @auth_info: Authentication info + */ +struct efi_firmware_image_authentication { + uint64_t monotonic_count; + struct win_certificate_uefi_guid auth_info; +} __packed; + +#endif /* _EFI_CAPSULE_H */ diff --git a/tools/mkeficapsule.c b/tools/mkeficapsule.c index 243fd6e..7ff1f99 100644 --- a/tools/mkeficapsule.c +++ b/tools/mkeficapsule.c @@ -15,22 +15,14 @@ #include <sys/stat.h> #include <sys/types.h> +#include <uuid/uuid.h> +#include <linux/kconfig.h> -typedef __u8 u8; -typedef __u16 u16; -typedef __u32 u32; -typedef __u64 u64; -typedef __s16 s16; -typedef __s32 s32; +#include <gnutls/gnutls.h> +#include <gnutls/pkcs7.h> +#include <gnutls/abstract.h> -#define aligned_u64 __aligned_u64 - -#ifndef __packed -#define __packed __attribute__((packed)) -#endif - -#include <efi.h> -#include <efi_api.h> +#include "eficapsule.h" static const char *tool_name = "mkeficapsule"; @@ -39,30 +31,69 @@ efi_guid_t efi_guid_image_type_uboot_fit = EFI_FIRMWARE_IMAGE_TYPE_UBOOT_FIT_GUID; efi_guid_t efi_guid_image_type_uboot_raw = EFI_FIRMWARE_IMAGE_TYPE_UBOOT_RAW_GUID; +efi_guid_t efi_guid_cert_type_pkcs7 = EFI_CERT_TYPE_PKCS7_GUID; + +static const char *opts_short = "frg:i:I:v:p:c:m:dh"; static struct option options[] = { - {"fit", required_argument, NULL, 'f'}, - {"raw", required_argument, NULL, 'r'}, + {"fit", no_argument, NULL, 'f'}, + {"raw", no_argument, NULL, 'r'}, + {"guid", required_argument, NULL, 'g'}, {"index", required_argument, NULL, 'i'}, {"instance", required_argument, NULL, 'I'}, + {"private-key", required_argument, NULL, 'p'}, + {"certificate", required_argument, NULL, 'c'}, + {"monotonic-count", required_argument, NULL, 'm'}, + {"dump-sig", no_argument, NULL, 'd'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0}, }; static void print_usage(void) { - printf("Usage: %s [options] <output file>\n" + fprintf(stderr, "Usage: %s [options] <image blob> <output file>\n" "Options:\n" - "\t-f, --fit <fit image> new FIT image file\n" - "\t-r, --raw <raw image> new raw image file\n" + "\t-f, --fit FIT image type\n" + "\t-r, --raw raw image type\n" + "\t-g, --guid <guid string> guid for image blob type\n" "\t-i, --index <index> update image index\n" "\t-I, --instance <instance> update hardware instance\n" + "\t-p, --private-key <privkey file> private key file\n" + "\t-c, --certificate <cert file> signer's certificate file\n" + "\t-m, --monotonic-count <count> monotonic count\n" + "\t-d, --dump_sig dump signature (*.p7)\n" "\t-h, --help print a help message\n", tool_name); } /** + * auth_context - authentication context + * @key_file: Path to a private key file + * @cert_file: Path to a certificate file + * @image_data: Pointer to firmware data + * @image_size: Size of firmware data + * @auth: Authentication header + * @sig_data: Signature data + * @sig_size: Size of signature data + * + * Data structure used in create_auth_data(). @key_file through + * @image_size are input parameters. @auth, @sig_data and @sig_size + * are filled in by create_auth_data(). + */ +struct auth_context { + char *key_file; + char *cert_file; + uint8_t *image_data; + size_t image_size; + struct efi_firmware_image_authentication auth; + uint8_t *sig_data; + size_t sig_size; +}; + +static int dump_sig; + +/** * read_bin_file - read a firmware binary file * @bin: Path to a firmware binary file * @data: Pointer to pointer of allocated buffer @@ -75,7 +106,7 @@ static void print_usage(void) * * 0 - on success * * -1 - on failure */ -static int read_bin_file(char *bin, void **data, off_t *bin_size) +static int read_bin_file(char *bin, uint8_t **data, off_t *bin_size) { FILE *g; struct stat bin_stat; @@ -148,6 +179,205 @@ static int write_capsule_file(FILE *f, void *data, size_t size, const char *msg) } /** + * create_auth_data - compose authentication data in capsule + * @auth_context: Pointer to authentication context + * + * Fill up an authentication header (.auth) and signature data (.sig_data) + * in @auth_context, using library functions from openssl. + * All the parameters in @auth_context must be filled in by a caller. + * + * Return: + * * 0 - on success + * * -1 - on failure + */ +static int create_auth_data(struct auth_context *ctx) +{ + gnutls_datum_t cert; + gnutls_datum_t key; + off_t file_size; + gnutls_privkey_t pkey; + gnutls_x509_crt_t x509; + gnutls_pkcs7_t pkcs7; + gnutls_datum_t data; + gnutls_datum_t signature; + int ret; + + ret = read_bin_file(ctx->cert_file, &cert.data, &file_size); + if (ret < 0) + return -1; + if (file_size > UINT_MAX) + return -1; + cert.size = file_size; + + ret = read_bin_file(ctx->key_file, &key.data, &file_size); + if (ret < 0) + return -1; + if (ret < 0) + return -1; + if (file_size > UINT_MAX) + return -1; + key.size = file_size; + + /* + * For debugging, + * gnutls_global_set_time_function(mytime); + * gnutls_global_set_log_function(tls_log_func); + * gnutls_global_set_log_level(6); + */ + + ret = gnutls_privkey_init(&pkey); + if (ret < 0) { + fprintf(stderr, "error in gnutls_privkey_init(): %s\n", + gnutls_strerror(ret)); + return -1; + } + + ret = gnutls_x509_crt_init(&x509); + if (ret < 0) { + fprintf(stderr, "error in gnutls_x509_crt_init(): %s\n", + gnutls_strerror(ret)); + return -1; + } + + /* load a private key */ + ret = gnutls_privkey_import_x509_raw(pkey, &key, GNUTLS_X509_FMT_PEM, + 0, 0); + if (ret < 0) { + fprintf(stderr, + "error in gnutls_privkey_import_x509_raw(): %s\n", + gnutls_strerror(ret)); + return -1; + } + + /* load x509 certificate */ + ret = gnutls_x509_crt_import(x509, &cert, GNUTLS_X509_FMT_PEM); + if (ret < 0) { + fprintf(stderr, "error in gnutls_x509_crt_import(): %s\n", + gnutls_strerror(ret)); + return -1; + } + + /* generate a PKCS #7 structure */ + ret = gnutls_pkcs7_init(&pkcs7); + if (ret < 0) { + fprintf(stderr, "error in gnutls_pkcs7_init(): %s\n", + gnutls_strerror(ret)); + return -1; + } + + /* sign */ + /* + * Data should have + * * firmware image + * * monotonic count + * in this order! + * See EDK2's FmpAuthenticatedHandlerRsa2048Sha256() + */ + data.size = ctx->image_size + sizeof(ctx->auth.monotonic_count); + data.data = malloc(data.size); + if (!data.data) { + fprintf(stderr, "allocating memory (0x%x) failed\n", data.size); + return -1; + } + memcpy(data.data, ctx->image_data, ctx->image_size); + memcpy(data.data + ctx->image_size, &ctx->auth.monotonic_count, + sizeof(ctx->auth.monotonic_count)); + + ret = gnutls_pkcs7_sign(pkcs7, x509, pkey, &data, NULL, NULL, + GNUTLS_DIG_SHA256, + /* GNUTLS_PKCS7_EMBED_DATA? */ + GNUTLS_PKCS7_INCLUDE_CERT | + GNUTLS_PKCS7_INCLUDE_TIME); + if (ret < 0) { + fprintf(stderr, "error in gnutls_pkcs7)sign(): %s\n", + gnutls_strerror(ret)); + return -1; + } + + /* export */ + ret = gnutls_pkcs7_export2(pkcs7, GNUTLS_X509_FMT_DER, &signature); + if (ret < 0) { + fprintf(stderr, "error in gnutls_pkcs7_export2: %s\n", + gnutls_strerror(ret)); + return -1; + } + ctx->sig_data = signature.data; + ctx->sig_size = signature.size; + + /* fill auth_info */ + ctx->auth.auth_info.hdr.dwLength = sizeof(ctx->auth.auth_info) + + ctx->sig_size; + ctx->auth.auth_info.hdr.wRevision = WIN_CERT_REVISION_2_0; + ctx->auth.auth_info.hdr.wCertificateType = WIN_CERT_TYPE_EFI_GUID; + memcpy(&ctx->auth.auth_info.cert_type, &efi_guid_cert_type_pkcs7, + sizeof(efi_guid_cert_type_pkcs7)); + + /* + * For better clean-ups, + * gnutls_pkcs7_deinit(pkcs7); + * gnutls_privkey_deinit(pkey); + * gnutls_x509_crt_deinit(x509); + * free(cert.data); + * free(key.data); + * if error + * gnutls_free(signature.data); + */ + + return 0; +} + +/** + * dump_signature - dump out a signature + * @path: Path to a capsule file + * @signature: Signature data + * @sig_size: Size of signature data + * + * Signature data pointed to by @signature will be saved into + * a file whose file name is @path with ".p7" suffix. + * + * Return: + * * 0 - on success + * * -1 - on failure + */ +static int dump_signature(const char *path, uint8_t *signature, size_t sig_size) +{ + char *sig_path; + FILE *f; + size_t size; + int ret = -1; + + sig_path = malloc(strlen(path) + 3 + 1); + if (!sig_path) + return ret; + + sprintf(sig_path, "%s.p7", path); + f = fopen(sig_path, "w"); + if (!f) + goto err; + + size = fwrite(signature, 1, sig_size, f); + if (size == sig_size) + ret = 0; + + fclose(f); +err: + free(sig_path); + return ret; +} + +/** + * free_sig_data - free out signature data + * @ctx: Pointer to authentication context + * + * Free signature data allocated in create_auth_data(). + */ +static void free_sig_data(struct auth_context *ctx) +{ + if (ctx->sig_size) + gnutls_free(ctx->sig_data); +} + +/** * create_fwbin - create an uefi capsule file * @path: Path to a created capsule file * @bin: Path to a firmware binary to encapsulate @@ -168,23 +398,25 @@ static int write_capsule_file(FILE *f, void *data, size_t size, const char *msg) * * -1 - on failure */ static int create_fwbin(char *path, char *bin, efi_guid_t *guid, - unsigned long index, unsigned long instance) + unsigned long index, unsigned long instance, + uint64_t mcount, char *privkey_file, char *cert_file) { struct efi_capsule_header header; struct efi_firmware_management_capsule_header capsule; struct efi_firmware_management_capsule_image_header image; + struct auth_context auth_context; FILE *f; - void *data; + uint8_t *data; off_t bin_size; - u64 offset; + uint64_t offset; int ret; #ifdef DEBUG - printf("For output: %s\n", path); - printf("\tbin: %s\n\ttype: %pUl\n", bin, guid); - printf("\tindex: %ld\n\tinstance: %ld\n", index, instance); + fprintf(stderr, "For output: %s\n", path); + fprintf(stderr, "\tbin: %s\n\ttype: %pUl\n", bin, guid); + fprintf(stderr, "\tindex: %lu\n\tinstance: %lu\n", index, instance); #endif - + auth_context.sig_size = 0; f = NULL; data = NULL; ret = -1; @@ -195,6 +427,27 @@ static int create_fwbin(char *path, char *bin, efi_guid_t *guid, if (read_bin_file(bin, &data, &bin_size)) goto err; + /* first, calculate signature to determine its size */ + if (privkey_file && cert_file) { + auth_context.key_file = privkey_file; + auth_context.cert_file = cert_file; + auth_context.auth.monotonic_count = mcount; + auth_context.image_data = data; + auth_context.image_size = bin_size; + + if (create_auth_data(&auth_context)) { + fprintf(stderr, "Signing firmware image failed\n"); + goto err; + } + + if (dump_sig && + dump_signature(path, auth_context.sig_data, + auth_context.sig_size)) { + fprintf(stderr, "Creating signature file failed\n"); + goto err; + } + } + /* * write a capsule file */ @@ -212,9 +465,12 @@ static int create_fwbin(char *path, char *bin, efi_guid_t *guid, /* TODO: The current implementation ignores flags */ header.flags = CAPSULE_FLAGS_PERSIST_ACROSS_RESET; header.capsule_image_size = sizeof(header) - + sizeof(capsule) + sizeof(u64) + + sizeof(capsule) + sizeof(uint64_t) + sizeof(image) + bin_size; + if (auth_context.sig_size) + header.capsule_image_size += sizeof(auth_context.auth) + + auth_context.sig_size; if (write_capsule_file(f, &header, sizeof(header), "Capsule header")) goto err; @@ -230,7 +486,7 @@ static int create_fwbin(char *path, char *bin, efi_guid_t *guid, "Firmware capsule header")) goto err; - offset = sizeof(capsule) + sizeof(u64); + offset = sizeof(capsule) + sizeof(uint64_t); if (write_capsule_file(f, &offset, sizeof(offset), "Offset to capsule image")) goto err; @@ -245,14 +501,33 @@ static int create_fwbin(char *path, char *bin, efi_guid_t *guid, image.reserved[1] = 0; image.reserved[2] = 0; image.update_image_size = bin_size; + if (auth_context.sig_size) + image.update_image_size += sizeof(auth_context.auth) + + auth_context.sig_size; image.update_vendor_code_size = 0; /* none */ image.update_hardware_instance = instance; image.image_capsule_support = 0; + if (auth_context.sig_size) + image.image_capsule_support |= CAPSULE_SUPPORT_AUTHENTICATION; if (write_capsule_file(f, &image, sizeof(image), "Firmware capsule image header")) goto err; /* + * signature + */ + if (auth_context.sig_size) { + if (write_capsule_file(f, &auth_context.auth, + sizeof(auth_context.auth), + "Authentication header")) + goto err; + + if (write_capsule_file(f, auth_context.sig_data, + auth_context.sig_size, "Signature")) + goto err; + } + + /* * firmware binary */ if (write_capsule_file(f, data, bin_size, "Firmware binary")) @@ -262,74 +537,150 @@ static int create_fwbin(char *path, char *bin, efi_guid_t *guid, err: if (f) fclose(f); + free_sig_data(&auth_context); free(data); return ret; } -/* - * Usage: - * $ mkeficapsule -f <firmware binary> <output file> +/** + * convert_uuid_to_guid() - convert UUID to GUID + * @buf: UUID binary + * + * UUID and GUID have the same data structure, but their binary + * formats are different due to the endianness. See lib/uuid.c. + * Since uuid_parse() can handle only UUID, this function must + * be called to get correct data for GUID when parsing a string. + * + * The correct data will be returned in @buf. + */ +void convert_uuid_to_guid(unsigned char *buf) +{ + unsigned char c; + + c = buf[0]; + buf[0] = buf[3]; + buf[3] = c; + c = buf[1]; + buf[1] = buf[2]; + buf[2] = c; + + c = buf[4]; + buf[4] = buf[5]; + buf[5] = c; + + c = buf[6]; + buf[6] = buf[7]; + buf[7] = c; +} + +/** + * main - main entry function of mkeficapsule + * @argc: Number of arguments + * @argv: Array of pointers to arguments + * + * Create an uefi capsule file, optionally signing it. + * Parse all the arguments and pass them on to create_fwbin(). + * + * Return: + * * 0 - on success + * * -1 - on failure */ int main(int argc, char **argv) { - char *file; efi_guid_t *guid; + unsigned char uuid_buf[16]; unsigned long index, instance; + uint64_t mcount; + char *privkey_file, *cert_file; int c, idx; - file = NULL; guid = NULL; index = 0; instance = 0; + mcount = 0; + privkey_file = NULL; + cert_file = NULL; + dump_sig = 0; for (;;) { - c = getopt_long(argc, argv, "f:r:i:I:v:h", options, &idx); + c = getopt_long(argc, argv, opts_short, options, &idx); if (c == -1) break; switch (c) { case 'f': - if (file) { - fprintf(stderr, "Image already specified\n"); - return -1; + if (guid) { + fprintf(stderr, + "Image type already specified\n"); + exit(EXIT_FAILURE); } - file = optarg; guid = &efi_guid_image_type_uboot_fit; break; case 'r': - if (file) { - fprintf(stderr, "Image already specified\n"); - return -1; + if (guid) { + fprintf(stderr, + "Image type already specified\n"); + exit(EXIT_FAILURE); } - file = optarg; guid = &efi_guid_image_type_uboot_raw; break; + case 'g': + if (guid) { + fprintf(stderr, + "Image type already specified\n"); + exit(EXIT_FAILURE); + } + if (uuid_parse(optarg, uuid_buf)) { + fprintf(stderr, "Wrong guid format\n"); + exit(EXIT_FAILURE); + } + convert_uuid_to_guid(uuid_buf); + guid = (efi_guid_t *)uuid_buf; + break; case 'i': index = strtoul(optarg, NULL, 0); break; case 'I': instance = strtoul(optarg, NULL, 0); break; + case 'p': + if (privkey_file) { + fprintf(stderr, + "Private Key already specified\n"); + exit(EXIT_FAILURE); + } + privkey_file = optarg; + break; + case 'c': + if (cert_file) { + fprintf(stderr, + "Certificate file already specified\n"); + exit(EXIT_FAILURE); + } + cert_file = optarg; + break; + case 'm': + mcount = strtoul(optarg, NULL, 0); + break; + case 'd': + dump_sig = 1; + break; case 'h': print_usage(); - return 0; + exit(EXIT_SUCCESS); } } - /* need an output file */ - if (argc != optind + 1) { + /* check necessary parameters */ + if ((argc != optind + 2) || !guid || + ((privkey_file && !cert_file) || + (!privkey_file && cert_file))) { print_usage(); exit(EXIT_FAILURE); } - /* need a fit image file or raw image file */ - if (!file) { - print_usage(); - exit(EXIT_SUCCESS); - } - - if (create_fwbin(argv[optind], file, guid, index, instance) - < 0) { + if (create_fwbin(argv[argc - 1], argv[argc - 2], guid, index, instance, + mcount, privkey_file, cert_file) < 0) { fprintf(stderr, "Creating firmware capsule failed\n"); exit(EXIT_FAILURE); } |