[PATCH v7 00/12] efi_loader: capsule: improve capsule authentication support

As I proposed and discussed in [1] and [2], I have made a couple of improvements on the current implementation of capsule update in this patch set.
* add signing feature to mkeficapsule * add "--guid" option to mkeficapsule * add man page of mkeficapsule * update uefi document regarding capsule update * revise pytests * (as RFC) add CONFIG_EFI_CAPSULE_KEY_PATH
# We have had some discussion about fdtsig.sh. # So RFCs (patch#11,#12) are still included for further discussion # if they are useful or not. # For smooth merge, the rest (patch#1-10) should work without them.
[1] https://lists.denx.de/pipermail/u-boot/2021-April/447918.html [2] https://lists.denx.de/pipermail/u-boot/2021-July/455292.html
Prerequisite patches ==================== None
Test ==== * locally passed the pytest which is included in this patch series on sandbox built. (CONFIG_EFI_CAPSULE_AUTHENTICATE should explicitly be turned on in order to exercise the authentication code.)
Changes ======= v7 (Nov 16, 2021) * rebased on pre-v2022.01-rc2 * drop already-merged patch * check for a size of firmware binary file (patch#1) * enable mkeficapsule in tools-only_defconfig (patch#2) * define eficapsule.h and include it from mkeficapsule (patch#3) Hopefully, the tool can now compile on non-linux host.
v6 (Nov 02, 2021) * rebased on pre-v2022.01-rc1 * add patch#2 to rework/refactor the code for better readability (patch#2) * use exit(EXIT_SUCCESS/FAILURE) (patch#3) * truncate >80chars lines in pytest scripts (patch#6)
v5 (Oct 27, 2021) * rebased on pre-v2022.01-rc1 (WIP/26Oct2021) * drop already-merged patches * drop __weak from efi_get_public_key_data() (patch#1) * describe the format of public key node in device tree (patch#4) * re-order patches by grouping closely-related patches (patch#6-8) * modify pytest to make the test results correctly verified either with or without CONFIG_EFI_CAPSULE_AUTHENTICATE (patch#9) * add RFCs for embedding public keys during the build process (patch#10,11)
v4 (Oct 7, 2021) * rebased on v2021.10 * align with "Revert "efi_capsule: Move signature from DTB to .rodata"" * add more missing *revert* commits (patch#1,#2,#3) * add fdtsig.sh, replacing dtb support in mkeficapsule (patch#4) * update/revise the man/uefi doc (patch#6,#7) * fix a bug in parsing guid string (patch#8) * add a test for "--guid" option (patch#10) * use dtb-based authentication test as done in v1 (patch#11)
v3 (Aug 31, 2021) * rebased on v2021.10-rc3 * remove pytest-related patches * add function descriptions in mkeficapsule.c * correct format specifiers in printf() * let main() return 0 or -1 only * update doc/develop/uefi/uefi.rst for syntax change of mkeficapsule
v2 (July 28, 2021) * rebased on v2021.10-rc* * removed dependency on target's configuration * removed fdtsig.sh and others * add man page * update the UEFI document * add dedicate defconfig for testing on sandbox * add gitlab CI support * add "--guid" option to mkeficapsule (yet rather RFC)
Initial release (May 12, 2021) * based on v2021.07-rc2
AKASHI Takahiro (12): tools: mkeficapsule: rework the code a little bit tools: build mkeficapsule with tools-only_defconfig tools: mkeficapsule: add firmwware image signing tools: mkeficapsule: add man page doc: update UEFI document for usage of mkeficapsule test/py: efi_capsule: add image authentication test tools: mkeficapsule: allow for specifying GUID explicitly test/py: efi_capsule: align with the syntax change of mkeficapsule test/py: efi_capsule: add a test for "--guid" option test/py: efi_capsule: check the results in case of CAPSULE_AUTHENTICATE (RFC) tools: add fdtsig.sh (RFC) efi_loader, dts: add public keys for capsules to device tree
MAINTAINERS | 2 + configs/tools-only_defconfig | 1 + doc/develop/uefi/uefi.rst | 143 ++-- doc/mkeficapsule.1 | 107 +++ dts/Makefile | 23 +- lib/efi_loader/Kconfig | 7 + .../py/tests/test_efi_capsule/capsule_defs.py | 5 + test/py/tests/test_efi_capsule/conftest.py | 59 +- test/py/tests/test_efi_capsule/signature.dts | 10 + .../test_efi_capsule/test_capsule_firmware.py | 91 ++- .../test_capsule_firmware_signed.py | 254 +++++++ tools/Kconfig | 8 + tools/Makefile | 8 +- tools/eficapsule.h | 115 +++ tools/fdtsig.sh | 40 ++ tools/mkeficapsule.c | 680 +++++++++++++++--- 16 files changed, 1360 insertions(+), 193 deletions(-) create mode 100644 doc/mkeficapsule.1 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 create mode 100644 tools/eficapsule.h create mode 100755 tools/fdtsig.sh

Abstract common routines to make the code easily understandable. No functional change.
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org Reviewed-by: Simon Glass sjg@chromium.org --- tools/mkeficapsule.c | 223 ++++++++++++++++++++++++++++++------------- 1 file changed, 159 insertions(+), 64 deletions(-)
diff --git a/tools/mkeficapsule.c b/tools/mkeficapsule.c index 4995ba4e0c2a..afdcaf7e7933 100644 --- a/tools/mkeficapsule.c +++ b/tools/mkeficapsule.c @@ -61,17 +61,122 @@ static void print_usage(void) tool_name); }
+/** + * read_bin_file - read a firmware binary file + * @bin: Path to a firmware binary file + * @data: Pointer to pointer of allocated buffer + * @bin_size: Size of allocated buffer + * + * Read out a content of binary, @bin, into @data. + * A caller should free @data. + * + * Return: + * * 0 - on success + * * -1 - on failure + */ +static int read_bin_file(char *bin, void **data, off_t *bin_size) +{ + FILE *g; + struct stat bin_stat; + void *buf; + size_t size; + int ret = 0; + + g = fopen(bin, "r"); + if (!g) { + printf("cannot open %s\n", bin); + return -1; + } + if (stat(bin, &bin_stat) < 0) { + printf("cannot determine the size of %s\n", bin); + ret = -1; + goto err; + } + if (bin_stat.st_size > (u32)~0U) { + printf("file size is too large: %s\n", bin); + ret = -1; + goto err; + } + buf = malloc(bin_stat.st_size); + if (!buf) { + printf("cannot allocate memory: %zx\n", + (size_t)bin_stat.st_size); + ret = -1; + goto err; + } + + size = fread(buf, 1, bin_stat.st_size, g); + if (size < bin_stat.st_size) { + printf("read failed (%zx)\n", size); + ret = -1; + goto err; + } + + *data = buf; + *bin_size = bin_stat.st_size; +err: + fclose(g); + + return ret; +} + +/** + * write_capsule_file - write a capsule file + * @bin: FILE stream + * @data: Pointer to data + * @bin_size: Size of data + * + * Write out data, @data, with the size @bin_size. + * + * Return: + * * 0 - on success + * * -1 - on failure + */ +static int write_capsule_file(FILE *f, void *data, size_t size, const char *msg) +{ + size_t size_written; + + size_written = fwrite(data, 1, size, f); + if (size_written < size) { + printf("%s: write failed (%zx != %zx)\n", msg, + size_written, size); + return -1; + } + + return 0; +} + +/** + * create_fwbin - create an uefi capsule file + * @path: Path to a created capsule file + * @bin: Path to a firmware binary to encapsulate + * @guid: GUID of related FMP driver + * @index: Index number in capsule + * @instance: Instance number in capsule + * @mcount: Monotonic count in authentication information + * @private_file: Path to a private key file + * @cert_file: Path to a certificate file + * + * This function actually does the job of creating an uefi capsule file. + * All the arguments must be supplied. + * If either @private_file ror @cert_file is NULL, the capsule file + * won't be signed. + * + * Return: + * * 0 - on success + * * -1 - on failure + */ static int create_fwbin(char *path, char *bin, efi_guid_t *guid, unsigned long index, unsigned long instance) { struct efi_capsule_header header; struct efi_firmware_management_capsule_header capsule; struct efi_firmware_management_capsule_image_header image; - FILE *f, *g; - struct stat bin_stat; - u8 *data; - size_t size; + FILE *f; + void *data; + off_t bin_size; u64 offset; + int ret;
#ifdef DEBUG printf("For output: %s\n", path); @@ -79,25 +184,28 @@ static int create_fwbin(char *path, char *bin, efi_guid_t *guid, printf("\tindex: %ld\n\tinstance: %ld\n", index, instance); #endif
- g = fopen(bin, "r"); - if (!g) { - printf("cannot open %s\n", bin); - return -1; - } - if (stat(bin, &bin_stat) < 0) { - printf("cannot determine the size of %s\n", bin); - goto err_1; - } - data = malloc(bin_stat.st_size); - if (!data) { - printf("cannot allocate memory: %zx\n", (size_t)bin_stat.st_size); - goto err_1; - } + f = NULL; + data = NULL; + ret = -1; + + /* + * read a firmware binary + */ + if (read_bin_file(bin, &data, &bin_size)) + goto err; + + /* + * write a capsule file + */ f = fopen(path, "w"); if (!f) { printf("cannot open %s\n", path); - goto err_2; + goto err; } + + /* + * capsule file header + */ header.capsule_guid = efi_guid_fm_capsule; header.header_size = sizeof(header); /* TODO: The current implementation ignores flags */ @@ -105,70 +213,57 @@ static int create_fwbin(char *path, char *bin, efi_guid_t *guid, header.capsule_image_size = sizeof(header) + sizeof(capsule) + sizeof(u64) + sizeof(image) - + bin_stat.st_size; - - size = fwrite(&header, 1, sizeof(header), f); - if (size < sizeof(header)) { - printf("write failed (%zx)\n", size); - goto err_3; - } + + bin_size; + if (write_capsule_file(f, &header, sizeof(header), + "Capsule header")) + goto err;
+ /* + * firmware capsule header + * This capsule has only one firmware capsule image. + */ capsule.version = 0x00000001; capsule.embedded_driver_count = 0; capsule.payload_item_count = 1; - size = fwrite(&capsule, 1, sizeof(capsule), f); - if (size < (sizeof(capsule))) { - printf("write failed (%zx)\n", size); - goto err_3; - } + if (write_capsule_file(f, &capsule, sizeof(capsule), + "Firmware capsule header")) + goto err; + offset = sizeof(capsule) + sizeof(u64); - size = fwrite(&offset, 1, sizeof(offset), f); - if (size < sizeof(offset)) { - printf("write failed (%zx)\n", size); - goto err_3; - } + if (write_capsule_file(f, &offset, sizeof(offset), + "Offset to capsule image")) + goto err;
+ /* + * firmware capsule image header + */ image.version = 0x00000003; memcpy(&image.update_image_type_id, guid, sizeof(*guid)); image.update_image_index = index; image.reserved[0] = 0; image.reserved[1] = 0; image.reserved[2] = 0; - image.update_image_size = bin_stat.st_size; + image.update_image_size = bin_size; image.update_vendor_code_size = 0; /* none */ image.update_hardware_instance = instance; image.image_capsule_support = 0; + if (write_capsule_file(f, &image, sizeof(image), + "Firmware capsule image header")) + goto err;
- size = fwrite(&image, 1, sizeof(image), f); - if (size < sizeof(image)) { - printf("write failed (%zx)\n", size); - goto err_3; - } - size = fread(data, 1, bin_stat.st_size, g); - if (size < bin_stat.st_size) { - printf("read failed (%zx)\n", size); - goto err_3; - } - size = fwrite(data, 1, bin_stat.st_size, f); - if (size < bin_stat.st_size) { - printf("write failed (%zx)\n", size); - goto err_3; - } - - fclose(f); - fclose(g); - free(data); - - return 0; + /* + * firmware binary + */ + if (write_capsule_file(f, data, bin_size, "Firmware binary")) + goto err;
-err_3: - fclose(f); -err_2: + ret = 0; +err: + if (f) + fclose(f); free(data); -err_1: - fclose(g);
- return -1; + return ret; }
/*

On 11/16/21 05:32, AKASHI Takahiro wrote:
Abstract common routines to make the code easily understandable. No functional change.
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org Reviewed-by: Simon Glass sjg@chromium.org
tools/mkeficapsule.c | 223 ++++++++++++++++++++++++++++++------------- 1 file changed, 159 insertions(+), 64 deletions(-)
diff --git a/tools/mkeficapsule.c b/tools/mkeficapsule.c index 4995ba4e0c2a..afdcaf7e7933 100644 --- a/tools/mkeficapsule.c +++ b/tools/mkeficapsule.c @@ -61,17 +61,122 @@ static void print_usage(void) tool_name); }
+/**
- read_bin_file - read a firmware binary file
- @bin: Path to a firmware binary file
- @data: Pointer to pointer of allocated buffer
- @bin_size: Size of allocated buffer
- Read out a content of binary, @bin, into @data.
- A caller should free @data.
- Return:
- 0 - on success
- -1 - on failure
- */
+static int read_bin_file(char *bin, void **data, off_t *bin_size) +{
- FILE *g;
- struct stat bin_stat;
- void *buf;
- size_t size;
- int ret = 0;
- g = fopen(bin, "r");
- if (!g) {
printf("cannot open %s\n", bin);
Error messages should be written to stderr. Use perror(bin) to get a message like:
<filename>: No such file or directory
Or use fprintf(stderr, ...).
return -1;
- }
- if (stat(bin, &bin_stat) < 0) {
printf("cannot determine the size of %s\n", bin);
ret = -1;
goto err;
- }
- if (bin_stat.st_size > (u32)~0U) {
printf("file size is too large: %s\n", bin);
ret = -1;
goto err;
- }
- buf = malloc(bin_stat.st_size);
For a symbolic link st_size is the length of the pathname it contains. So this function is of no help here.
- if (!buf) {
printf("cannot allocate memory: %zx\n",
(size_t)bin_stat.st_size);
ret = -1;
goto err;
- }
- size = fread(buf, 1, bin_stat.st_size, g);
- if (size < bin_stat.st_size) {
fread() reads from a stream which is not necessarily a file but can be a named pipe. stat() does not indicate when the stream will end.
You have to run fread() in a loop until feof() or ferror() indicate that there is nothing more to be read from the stream.
Best regards
Heinrich
printf("read failed (%zx)\n", size);
ret = -1;
goto err;
- }
- *data = buf;
- *bin_size = bin_stat.st_size;
+err:
- fclose(g);
- return ret;
+}
+/**
- write_capsule_file - write a capsule file
- @bin: FILE stream
- @data: Pointer to data
- @bin_size: Size of data
- Write out data, @data, with the size @bin_size.
- Return:
- 0 - on success
- -1 - on failure
- */
+static int write_capsule_file(FILE *f, void *data, size_t size, const char *msg) +{
- size_t size_written;
- size_written = fwrite(data, 1, size, f);
- if (size_written < size) {
printf("%s: write failed (%zx != %zx)\n", msg,
size_written, size);
return -1;
- }
- return 0;
+}
+/**
- create_fwbin - create an uefi capsule file
- @path: Path to a created capsule file
- @bin: Path to a firmware binary to encapsulate
- @guid: GUID of related FMP driver
- @index: Index number in capsule
- @instance: Instance number in capsule
- @mcount: Monotonic count in authentication information
- @private_file: Path to a private key file
- @cert_file: Path to a certificate file
- This function actually does the job of creating an uefi capsule file.
- All the arguments must be supplied.
- If either @private_file ror @cert_file is NULL, the capsule file
- won't be signed.
- Return:
- 0 - on success
- -1 - on failure
- */ static int create_fwbin(char *path, char *bin, efi_guid_t *guid, unsigned long index, unsigned long instance) { struct efi_capsule_header header; struct efi_firmware_management_capsule_header capsule; struct efi_firmware_management_capsule_image_header image;
- FILE *f, *g;
- struct stat bin_stat;
- u8 *data;
- size_t size;
FILE *f;
void *data;
off_t bin_size; u64 offset;
int ret;
#ifdef DEBUG printf("For output: %s\n", path);
@@ -79,25 +184,28 @@ static int create_fwbin(char *path, char *bin, efi_guid_t *guid, printf("\tindex: %ld\n\tinstance: %ld\n", index, instance); #endif
- g = fopen(bin, "r");
- if (!g) {
printf("cannot open %s\n", bin);
return -1;
- }
- if (stat(bin, &bin_stat) < 0) {
printf("cannot determine the size of %s\n", bin);
goto err_1;
- }
- data = malloc(bin_stat.st_size);
- if (!data) {
printf("cannot allocate memory: %zx\n", (size_t)bin_stat.st_size);
goto err_1;
- }
- f = NULL;
- data = NULL;
- ret = -1;
- /*
* read a firmware binary
*/
- if (read_bin_file(bin, &data, &bin_size))
goto err;
- /*
* write a capsule file
f = fopen(path, "w"); if (!f) { printf("cannot open %s\n", path);*/
goto err_2;
}goto err;
- /*
* capsule file header
header.capsule_guid = efi_guid_fm_capsule; header.header_size = sizeof(header); /* TODO: The current implementation ignores flags */*/
@@ -105,70 +213,57 @@ static int create_fwbin(char *path, char *bin, efi_guid_t *guid, header.capsule_image_size = sizeof(header) + sizeof(capsule) + sizeof(u64) + sizeof(image)
+ bin_stat.st_size;
- size = fwrite(&header, 1, sizeof(header), f);
- if (size < sizeof(header)) {
printf("write failed (%zx)\n", size);
goto err_3;
- }
+ bin_size;
if (write_capsule_file(f, &header, sizeof(header),
"Capsule header"))
goto err;
/*
* firmware capsule header
* This capsule has only one firmware capsule image.
*/
capsule.version = 0x00000001; capsule.embedded_driver_count = 0; capsule.payload_item_count = 1;
- size = fwrite(&capsule, 1, sizeof(capsule), f);
- if (size < (sizeof(capsule))) {
printf("write failed (%zx)\n", size);
goto err_3;
- }
- if (write_capsule_file(f, &capsule, sizeof(capsule),
"Firmware capsule header"))
goto err;
- offset = sizeof(capsule) + sizeof(u64);
- size = fwrite(&offset, 1, sizeof(offset), f);
- if (size < sizeof(offset)) {
printf("write failed (%zx)\n", size);
goto err_3;
- }
if (write_capsule_file(f, &offset, sizeof(offset),
"Offset to capsule image"))
goto err;
/*
* firmware capsule image header
*/
image.version = 0x00000003; memcpy(&image.update_image_type_id, guid, sizeof(*guid)); image.update_image_index = index; image.reserved[0] = 0; image.reserved[1] = 0; image.reserved[2] = 0;
- image.update_image_size = bin_stat.st_size;
- image.update_image_size = bin_size; image.update_vendor_code_size = 0; /* none */ image.update_hardware_instance = instance; image.image_capsule_support = 0;
- if (write_capsule_file(f, &image, sizeof(image),
"Firmware capsule image header"))
goto err;
- size = fwrite(&image, 1, sizeof(image), f);
- if (size < sizeof(image)) {
printf("write failed (%zx)\n", size);
goto err_3;
- }
- size = fread(data, 1, bin_stat.st_size, g);
- if (size < bin_stat.st_size) {
printf("read failed (%zx)\n", size);
goto err_3;
- }
- size = fwrite(data, 1, bin_stat.st_size, f);
- if (size < bin_stat.st_size) {
printf("write failed (%zx)\n", size);
goto err_3;
- }
- fclose(f);
- fclose(g);
- free(data);
- return 0;
- /*
* firmware binary
*/
- if (write_capsule_file(f, data, bin_size, "Firmware binary"))
goto err;
-err_3:
- fclose(f);
-err_2:
- ret = 0;
+err:
- if (f)
free(data);fclose(f);
-err_1:
fclose(g);
return -1;
return ret; }
/*

On Tue, Nov 16, 2021 at 01:19:10PM +0100, Heinrich Schuchardt wrote:
On 11/16/21 05:32, AKASHI Takahiro wrote:
Abstract common routines to make the code easily understandable. No functional change.
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org Reviewed-by: Simon Glass sjg@chromium.org
tools/mkeficapsule.c | 223 ++++++++++++++++++++++++++++++------------- 1 file changed, 159 insertions(+), 64 deletions(-)
diff --git a/tools/mkeficapsule.c b/tools/mkeficapsule.c index 4995ba4e0c2a..afdcaf7e7933 100644 --- a/tools/mkeficapsule.c +++ b/tools/mkeficapsule.c @@ -61,17 +61,122 @@ static void print_usage(void) tool_name); }
+/**
- read_bin_file - read a firmware binary file
- @bin: Path to a firmware binary file
- @data: Pointer to pointer of allocated buffer
- @bin_size: Size of allocated buffer
- Read out a content of binary, @bin, into @data.
- A caller should free @data.
- Return:
- 0 - on success
- -1 - on failure
- */
+static int read_bin_file(char *bin, void **data, off_t *bin_size) +{
- FILE *g;
- struct stat bin_stat;
- void *buf;
- size_t size;
- int ret = 0;
- g = fopen(bin, "r");
- if (!g) {
printf("cannot open %s\n", bin);
Error messages should be written to stderr. Use perror(bin) to get a message like:
<filename>: No such file or directory
Or use fprintf(stderr, ...).
OK. But remember that I have used printf() throughout mkeficapsule.c even before this patch. So I will add similar changes in patch#1 for consistency.
return -1;
- }
- if (stat(bin, &bin_stat) < 0) {
printf("cannot determine the size of %s\n", bin);
ret = -1;
goto err;
- }
- if (bin_stat.st_size > (u32)~0U) {
printf("file size is too large: %s\n", bin);
ret = -1;
goto err;
- }
- buf = malloc(bin_stat.st_size);
For a symbolic link st_size is the length of the pathname it contains. So this function is of no help here.
Not true. According to stat() manpage,
"lstat() is identical to stat(), except that if pathname is a symbolic link, then it returns information about the link itself, not the file that it refers to."
So stat() is a right system call.
- if (!buf) {
printf("cannot allocate memory: %zx\n",
(size_t)bin_stat.st_size);
ret = -1;
goto err;
- }
- size = fread(buf, 1, bin_stat.st_size, g);
- if (size < bin_stat.st_size) {
fread() reads from a stream which is not necessarily a file but can be a named pipe. stat() does not indicate when the stream will end.
You have to run fread() in a loop until feof() or ferror() indicate that there is nothing more to be read from the stream.
No. "a stream which is not necessarily a file but can be a named pipe" may be true, but we don't handle such a use case with this tool.
-Takahiro Akashi
Best regards
Heinrich
printf("read failed (%zx)\n", size);
ret = -1;
goto err;
- }
- *data = buf;
- *bin_size = bin_stat.st_size;
+err:
- fclose(g);
- return ret;
+}
+/**
- write_capsule_file - write a capsule file
- @bin: FILE stream
- @data: Pointer to data
- @bin_size: Size of data
- Write out data, @data, with the size @bin_size.
- Return:
- 0 - on success
- -1 - on failure
- */
+static int write_capsule_file(FILE *f, void *data, size_t size, const char *msg) +{
- size_t size_written;
- size_written = fwrite(data, 1, size, f);
- if (size_written < size) {
printf("%s: write failed (%zx != %zx)\n", msg,
size_written, size);
return -1;
- }
- return 0;
+}
+/**
- create_fwbin - create an uefi capsule file
- @path: Path to a created capsule file
- @bin: Path to a firmware binary to encapsulate
- @guid: GUID of related FMP driver
- @index: Index number in capsule
- @instance: Instance number in capsule
- @mcount: Monotonic count in authentication information
- @private_file: Path to a private key file
- @cert_file: Path to a certificate file
- This function actually does the job of creating an uefi capsule file.
- All the arguments must be supplied.
- If either @private_file ror @cert_file is NULL, the capsule file
- won't be signed.
- Return:
- 0 - on success
- -1 - on failure
- */ static int create_fwbin(char *path, char *bin, efi_guid_t *guid, unsigned long index, unsigned long instance) { struct efi_capsule_header header; struct efi_firmware_management_capsule_header capsule; struct efi_firmware_management_capsule_image_header image;
- FILE *f, *g;
- struct stat bin_stat;
- u8 *data;
- size_t size;
FILE *f;
void *data;
off_t bin_size; u64 offset;
int ret;
#ifdef DEBUG printf("For output: %s\n", path);
@@ -79,25 +184,28 @@ static int create_fwbin(char *path, char *bin, efi_guid_t *guid, printf("\tindex: %ld\n\tinstance: %ld\n", index, instance); #endif
- g = fopen(bin, "r");
- if (!g) {
printf("cannot open %s\n", bin);
return -1;
- }
- if (stat(bin, &bin_stat) < 0) {
printf("cannot determine the size of %s\n", bin);
goto err_1;
- }
- data = malloc(bin_stat.st_size);
- if (!data) {
printf("cannot allocate memory: %zx\n", (size_t)bin_stat.st_size);
goto err_1;
- }
- f = NULL;
- data = NULL;
- ret = -1;
- /*
* read a firmware binary
*/
- if (read_bin_file(bin, &data, &bin_size))
goto err;
- /*
* write a capsule file
f = fopen(path, "w"); if (!f) { printf("cannot open %s\n", path);*/
goto err_2;
}goto err;
- /*
* capsule file header
header.capsule_guid = efi_guid_fm_capsule; header.header_size = sizeof(header); /* TODO: The current implementation ignores flags */*/
@@ -105,70 +213,57 @@ static int create_fwbin(char *path, char *bin, efi_guid_t *guid, header.capsule_image_size = sizeof(header) + sizeof(capsule) + sizeof(u64) + sizeof(image)
+ bin_stat.st_size;
- size = fwrite(&header, 1, sizeof(header), f);
- if (size < sizeof(header)) {
printf("write failed (%zx)\n", size);
goto err_3;
- }
+ bin_size;
if (write_capsule_file(f, &header, sizeof(header),
"Capsule header"))
goto err;
/*
* firmware capsule header
* This capsule has only one firmware capsule image.
*/
capsule.version = 0x00000001; capsule.embedded_driver_count = 0; capsule.payload_item_count = 1;
- size = fwrite(&capsule, 1, sizeof(capsule), f);
- if (size < (sizeof(capsule))) {
printf("write failed (%zx)\n", size);
goto err_3;
- }
- if (write_capsule_file(f, &capsule, sizeof(capsule),
"Firmware capsule header"))
goto err;
- offset = sizeof(capsule) + sizeof(u64);
- size = fwrite(&offset, 1, sizeof(offset), f);
- if (size < sizeof(offset)) {
printf("write failed (%zx)\n", size);
goto err_3;
- }
if (write_capsule_file(f, &offset, sizeof(offset),
"Offset to capsule image"))
goto err;
/*
* firmware capsule image header
*/
image.version = 0x00000003; memcpy(&image.update_image_type_id, guid, sizeof(*guid)); image.update_image_index = index; image.reserved[0] = 0; image.reserved[1] = 0; image.reserved[2] = 0;
- image.update_image_size = bin_stat.st_size;
- image.update_image_size = bin_size; image.update_vendor_code_size = 0; /* none */ image.update_hardware_instance = instance; image.image_capsule_support = 0;
- if (write_capsule_file(f, &image, sizeof(image),
"Firmware capsule image header"))
goto err;
- size = fwrite(&image, 1, sizeof(image), f);
- if (size < sizeof(image)) {
printf("write failed (%zx)\n", size);
goto err_3;
- }
- size = fread(data, 1, bin_stat.st_size, g);
- if (size < bin_stat.st_size) {
printf("read failed (%zx)\n", size);
goto err_3;
- }
- size = fwrite(data, 1, bin_stat.st_size, f);
- if (size < bin_stat.st_size) {
printf("write failed (%zx)\n", size);
goto err_3;
- }
- fclose(f);
- fclose(g);
- free(data);
- return 0;
- /*
* firmware binary
*/
- if (write_capsule_file(f, data, bin_size, "Firmware binary"))
goto err;
-err_3:
- fclose(f);
-err_2:
- ret = 0;
+err:
- if (f)
free(data);fclose(f);
-err_1:
fclose(g);
return -1;
return ret; }
/*

We want to always build mkeficapsule if tools-only_defconfig is used.
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org --- configs/tools-only_defconfig | 1 + 1 file changed, 1 insertion(+)
diff --git a/configs/tools-only_defconfig b/configs/tools-only_defconfig index f482c9a1c1b0..5427797dd4c3 100644 --- a/configs/tools-only_defconfig +++ b/configs/tools-only_defconfig @@ -31,3 +31,4 @@ CONFIG_I2C_EDID=y # CONFIG_VIRTIO_MMIO is not set # CONFIG_VIRTIO_PCI is not set # CONFIG_VIRTIO_SANDBOX is not set +CONFIG_TOOLS_MKEFICAPSULE=y

On 11/16/21 05:32, AKASHI Takahiro wrote:
We want to always build mkeficapsule if tools-only_defconfig is used.
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org
Reviewed-by: Heinrich Schuchardt xypron.glpk@gmx.de
configs/tools-only_defconfig | 1 + 1 file changed, 1 insertion(+)
diff --git a/configs/tools-only_defconfig b/configs/tools-only_defconfig index f482c9a1c1b0..5427797dd4c3 100644 --- a/configs/tools-only_defconfig +++ b/configs/tools-only_defconfig @@ -31,3 +31,4 @@ CONFIG_I2C_EDID=y # CONFIG_VIRTIO_MMIO is not set # CONFIG_VIRTIO_PCI is not set # CONFIG_VIRTIO_SANDBOX is not set +CONFIG_TOOLS_MKEFICAPSULE=y

On 11/16/21 13:20, Heinrich Schuchardt wrote:
On 11/16/21 05:32, AKASHI Takahiro wrote:
We want to always build mkeficapsule if tools-only_defconfig is used.
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org
Reviewed-by: Heinrich Schuchardt xypron.glpk@gmx.de
configs/tools-only_defconfig | 1 + Â 1 file changed, 1 insertion(+)
diff --git a/configs/tools-only_defconfig b/configs/tools-only_defconfig index f482c9a1c1b0..5427797dd4c3 100644 --- a/configs/tools-only_defconfig +++ b/configs/tools-only_defconfig @@ -31,3 +31,4 @@ CONFIG_I2C_EDID=y  # CONFIG_VIRTIO_MMIO is not set  # CONFIG_VIRTIO_PCI is not set  # CONFIG_VIRTIO_SANDBOX is not set +CONFIG_TOOLS_MKEFICAPSULE=y
You cannot use a symbol that is not yet defined. Please, swap patches 2 and 3 when resubmitting.
Best regards
Heinrich

On Tue, Nov 16, 2021 at 02:58:39PM +0100, Heinrich Schuchardt wrote:
On 11/16/21 13:20, Heinrich Schuchardt wrote:
On 11/16/21 05:32, AKASHI Takahiro wrote:
We want to always build mkeficapsule if tools-only_defconfig is used.
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org
Reviewed-by: Heinrich Schuchardt xypron.glpk@gmx.de
configs/tools-only_defconfig | 1 + Â 1 file changed, 1 insertion(+)
diff --git a/configs/tools-only_defconfig b/configs/tools-only_defconfig index f482c9a1c1b0..5427797dd4c3 100644 --- a/configs/tools-only_defconfig +++ b/configs/tools-only_defconfig @@ -31,3 +31,4 @@ CONFIG_I2C_EDID=y  # CONFIG_VIRTIO_MMIO is not set  # CONFIG_VIRTIO_PCI is not set  # CONFIG_VIRTIO_SANDBOX is not set +CONFIG_TOOLS_MKEFICAPSULE=y
You cannot use a symbol that is not yet defined. Please, swap patches 2 and 3 when resubmitting.
Instead of swapping, I will move the definition of TOOLS_MKEFICAPSULE from patch#3 to patch#2. That would clarify the intension.
-Takahiro Akashi
Best regards
Heinrich

With this enhancement, mkeficapsule will be able to sign a capsule file when it is created. A signature added will be used later in the verification at FMP's SetImage() call.
To do that, We need specify additional command parameters: -monotonic-cout <count> : monotonic count -private-key <private key file> : private key file -certificate <certificate file> : certificate file Only when all of those parameters are given, a signature will be added to a capsule file.
Users are expected to maintain and increment the monotonic count at every time of the update for each firmware image.
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org Reviewed-by: Simon Glass sjg@chromium.org --- tools/Kconfig | 8 + tools/Makefile | 8 +- tools/eficapsule.h | 115 +++++++++++++ tools/mkeficapsule.c | 401 +++++++++++++++++++++++++++++++++++++++---- 4 files changed, 494 insertions(+), 38 deletions(-) create mode 100644 tools/eficapsule.h
diff --git a/tools/Kconfig b/tools/Kconfig index 91ce8ae3e516..117c921da3fe 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 1763f44cac43..a10aa091021f 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -238,8 +238,12 @@ 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 += -luuid +ifeq ($(CONFIG_TOOLS_LIBCRYPTO),y) +HOSTLDLIBS_mkeficapsule += \ + $(shell pkg-config --libs libssl libcrypto 2> /dev/null || echo "-lssl -lcrypto") +endif +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 000000000000..8c1560bb0671 --- /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 afdcaf7e7933..3e6f36430d74 100644 --- a/tools/mkeficapsule.c +++ b/tools/mkeficapsule.c @@ -15,21 +15,17 @@ #include <sys/stat.h> #include <sys/types.h>
-typedef __u8 u8; -typedef __u16 u16; -typedef __u32 u32; -typedef __u64 u64; -typedef __s16 s16; -typedef __s32 s32; - -#define aligned_u64 __aligned_u64 - -#ifndef __packed -#define __packed __attribute__((packed)) +#include <linux/kconfig.h> +#ifdef CONFIG_TOOLS_LIBCRYPTO +#include <openssl/asn1.h> +#include <openssl/bio.h> +#include <openssl/evp.h> +#include <openssl/err.h> +#include <openssl/pem.h> +#include <openssl/pkcs7.h> #endif
-#include <efi.h> -#include <efi_api.h> +#include "eficapsule.h"
static const char *tool_name = "mkeficapsule";
@@ -38,12 +34,25 @@ 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; + +#ifdef CONFIG_TOOLS_LIBCRYPTO +static const char *opts_short = "f:r:i:I:v:p:c:m:dh"; +#else +static const char *opts_short = "f:r:i:I:v:h"; +#endif
static struct option options[] = { {"fit", required_argument, NULL, 'f'}, {"raw", required_argument, NULL, 'r'}, {"index", required_argument, NULL, 'i'}, {"instance", required_argument, NULL, 'I'}, +#ifdef CONFIG_TOOLS_LIBCRYPTO + {"private-key", required_argument, NULL, 'p'}, + {"certificate", required_argument, NULL, 'c'}, + {"monotonic-count", required_argument, NULL, 'm'}, + {"dump-sig", no_argument, NULL, 'd'}, +#endif {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0}, }; @@ -57,10 +66,252 @@ static void print_usage(void) "\t-r, --raw <raw image> new raw image file\n" "\t-i, --index <index> update image index\n" "\t-I, --instance <instance> update hardware instance\n" +#ifdef CONFIG_TOOLS_LIBCRYPTO + "\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" +#endif "\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; + +#ifdef CONFIG_TOOLS_LIBCRYPTO +/** + * fileio-read_pkey - read out a private key + * @filename: Path to a private key file + * + * Read out a private key file and parse it into "EVP_PKEY" structure. + * + * Return: + * * Pointer to private key structure - on success + * * NULL - on failure + */ +static EVP_PKEY *fileio_read_pkey(const char *filename) +{ + EVP_PKEY *key = NULL; + BIO *bio; + + bio = BIO_new_file(filename, "r"); + if (!bio) + goto out; + + key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); + +out: + BIO_free_all(bio); + if (!key) { + printf("Can't load key from file '%s'\n", filename); + ERR_print_errors_fp(stderr); + } + + return key; +} + +/** + * fileio-read_cert - read out a certificate + * @filename: Path to a certificate file + * + * Read out a certificate file and parse it into "X509" structure. + * + * Return: + * * Pointer to certificate structure - on success + * * NULL - on failure + */ +static X509 *fileio_read_cert(const char *filename) +{ + X509 *cert = NULL; + BIO *bio; + + bio = BIO_new_file(filename, "r"); + if (!bio) + goto out; + + cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); + +out: + BIO_free_all(bio); + if (!cert) { + printf("Can't load certificate from file '%s'\n", filename); + ERR_print_errors_fp(stderr); + } + + return cert; +} + +/** + * 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) +{ + EVP_PKEY *key = NULL; + X509 *cert = NULL; + BIO *data_bio = NULL; + const EVP_MD *md; + PKCS7 *p7; + int flags, ret = -1; + + OpenSSL_add_all_digests(); + OpenSSL_add_all_ciphers(); + ERR_load_crypto_strings(); + + key = fileio_read_pkey(ctx->key_file); + if (!key) + goto err; + cert = fileio_read_cert(ctx->cert_file); + if (!cert) + goto err; + + /* + * create a BIO, containing: + * * firmware image + * * monotonic count + * in this order! + * See EDK2's FmpAuthenticatedHandlerRsa2048Sha256() + */ + data_bio = BIO_new(BIO_s_mem()); + BIO_write(data_bio, ctx->image_data, ctx->image_size); + BIO_write(data_bio, &ctx->auth.monotonic_count, + sizeof(ctx->auth.monotonic_count)); + + md = EVP_get_digestbyname("SHA256"); + if (!md) + goto err; + + /* create signature */ + /* TODO: maybe add PKCS7_NOATTR and PKCS7_NOSMIMECAP */ + flags = PKCS7_BINARY | PKCS7_DETACHED; + p7 = PKCS7_sign(NULL, NULL, NULL, data_bio, flags | PKCS7_PARTIAL); + if (!p7) + goto err; + if (!PKCS7_sign_add_signer(p7, cert, key, md, flags)) + goto err; + if (!PKCS7_final(p7, data_bio, flags)) + goto err; + + /* convert pkcs7 into DER */ + ctx->sig_data = NULL; + ctx->sig_size = ASN1_item_i2d((ASN1_VALUE *)p7, &ctx->sig_data, + ASN1_ITEM_rptr(PKCS7)); + if (!ctx->sig_size) + goto err; + + /* 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)); + + ret = 0; +err: + BIO_free_all(data_bio); + EVP_PKEY_free(key); + X509_free(cert); + + return ret; +} + +/** + * 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) + OPENSSL_free(ctx->sig_data); +} +#else +static int create_auth_data(struct auth_context *ctx) +{ + return 0; +} + +static int dump_signature(const char *path, uint8_t *signature, size_t sig_size) +{ + return 0; +} + +static void free_sig_data(struct auth_context *ctx) {} +#endif + /** * read_bin_file - read a firmware binary file * @bin: Path to a firmware binary file @@ -167,23 +418,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; 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); + printf("\tindex: %lu\n\tinstance: %lu\n", index, instance); #endif - + auth_context.sig_size = 0; f = NULL; data = NULL; ret = -1; @@ -194,6 +447,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)) { + printf("Signing firmware image failed\n"); + goto err; + } + + if (dump_sig && + dump_signature(path, auth_context.sig_data, + auth_context.sig_size)) { + printf("Creating signature file failed\n"); + goto err; + } + } + /* * write a capsule file */ @@ -211,9 +485,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; @@ -229,7 +506,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; @@ -244,13 +521,32 @@ 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 */ @@ -261,28 +557,43 @@ 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> +/** + * 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 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;
@@ -290,7 +601,7 @@ int main(int argc, char **argv) case 'f': if (file) { printf("Image already specified\n"); - return -1; + exit(EXIT_FAILURE); } file = optarg; guid = &efi_guid_image_type_uboot_fit; @@ -298,7 +609,7 @@ int main(int argc, char **argv) case 'r': if (file) { printf("Image already specified\n"); - return -1; + exit(EXIT_FAILURE); } file = optarg; guid = &efi_guid_image_type_uboot_raw; @@ -309,26 +620,44 @@ int main(int argc, char **argv) case 'I': instance = strtoul(optarg, NULL, 0); break; +#ifdef CONFIG_TOOLS_LIBCRYPTO + case 'p': + if (privkey_file) { + printf("Private Key already specified\n"); + exit(EXIT_FAILURE); + } + privkey_file = optarg; + break; + case 'c': + if (cert_file) { + printf("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; +#endif /* CONFIG_TOOLS_LIBCRYPTO */ case 'h': print_usage(); - return 0; + exit(EXIT_SUCCESS); } }
- /* need an output file */ - if (argc != optind + 1) { + /* check necessary parameters */ + if ((argc != optind + 1) || !file || + ((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[optind], file, guid, index, instance, + mcount, privkey_file, cert_file) < 0) { printf("Creating firmware capsule failed\n"); exit(EXIT_FAILURE); }

On Tue, 16 Nov 2021 at 06:33, AKASHI Takahiro takahiro.akashi@linaro.org wrote:
With this enhancement, mkeficapsule will be able to sign a capsule file when it is created. A signature added will be used later in the verification at FMP's SetImage() call.
To do that, We need specify additional command parameters: -monotonic-cout <count> : monotonic count -private-key <private key file> : private key file -certificate <certificate file> : certificate file Only when all of those parameters are given, a signature will be added to a capsule file.
Users are expected to maintain and increment the monotonic count at every time of the update for each firmware image.
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org Reviewed-by: Simon Glass sjg@chromium.org
tools/Kconfig | 8 + tools/Makefile | 8 +- tools/eficapsule.h | 115 +++++++++++++ tools/mkeficapsule.c | 401 +++++++++++++++++++++++++++++++++++++++---- 4 files changed, 494 insertions(+), 38 deletions(-) create mode 100644 tools/eficapsule.h
diff --git a/tools/Kconfig b/tools/Kconfig index 91ce8ae3e516..117c921da3fe 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 1763f44cac43..a10aa091021f 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -238,8 +238,12 @@ 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 += -luuid +ifeq ($(CONFIG_TOOLS_LIBCRYPTO),y) +HOSTLDLIBS_mkeficapsule += \
$(shell pkg-config --libs libssl libcrypto 2> /dev/null || echo "-lssl -lcrypto")
+endif +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 000000000000..8c1560bb0671 --- /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 afdcaf7e7933..3e6f36430d74 100644 --- a/tools/mkeficapsule.c +++ b/tools/mkeficapsule.c @@ -15,21 +15,17 @@ #include <sys/stat.h> #include <sys/types.h>
-typedef __u8 u8; -typedef __u16 u16; -typedef __u32 u32; -typedef __u64 u64; -typedef __s16 s16; -typedef __s32 s32;
-#define aligned_u64 __aligned_u64
-#ifndef __packed -#define __packed __attribute__((packed)) +#include <linux/kconfig.h> +#ifdef CONFIG_TOOLS_LIBCRYPTO +#include <openssl/asn1.h> +#include <openssl/bio.h> +#include <openssl/evp.h> +#include <openssl/err.h> +#include <openssl/pem.h> +#include <openssl/pkcs7.h> #endif
-#include <efi.h> -#include <efi_api.h> +#include "eficapsule.h"
static const char *tool_name = "mkeficapsule";
@@ -38,12 +34,25 @@ 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;
+#ifdef CONFIG_TOOLS_LIBCRYPTO +static const char *opts_short = "f:r:i:I:v:p:c:m:dh"; +#else +static const char *opts_short = "f:r:i:I:v:h"; +#endif
static struct option options[] = { {"fit", required_argument, NULL, 'f'}, {"raw", required_argument, NULL, 'r'}, {"index", required_argument, NULL, 'i'}, {"instance", required_argument, NULL, 'I'}, +#ifdef CONFIG_TOOLS_LIBCRYPTO
{"private-key", required_argument, NULL, 'p'},
{"certificate", required_argument, NULL, 'c'},
{"monotonic-count", required_argument, NULL, 'm'},
{"dump-sig", no_argument, NULL, 'd'},
+#endif {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0}, }; @@ -57,10 +66,252 @@ static void print_usage(void) "\t-r, --raw <raw image> new raw image file\n" "\t-i, --index <index> update image index\n" "\t-I, --instance <instance> update hardware instance\n" +#ifdef CONFIG_TOOLS_LIBCRYPTO
"\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"
+#endif "\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;
+#ifdef CONFIG_TOOLS_LIBCRYPTO +/**
- fileio-read_pkey - read out a private key
- @filename: Path to a private key file
- Read out a private key file and parse it into "EVP_PKEY" structure.
- Return:
- Pointer to private key structure - on success
- NULL - on failure
- */
+static EVP_PKEY *fileio_read_pkey(const char *filename) +{
EVP_PKEY *key = NULL;
BIO *bio;
bio = BIO_new_file(filename, "r");
if (!bio)
goto out;
key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
+out:
BIO_free_all(bio);
if (!key) {
printf("Can't load key from file '%s'\n", filename);
ERR_print_errors_fp(stderr);
}
return key;
+}
+/**
- fileio-read_cert - read out a certificate
- @filename: Path to a certificate file
- Read out a certificate file and parse it into "X509" structure.
- Return:
- Pointer to certificate structure - on success
- NULL - on failure
- */
+static X509 *fileio_read_cert(const char *filename) +{
X509 *cert = NULL;
BIO *bio;
bio = BIO_new_file(filename, "r");
if (!bio)
goto out;
cert = PEM_read_bio_X509(bio, NULL, NULL, NULL);
+out:
BIO_free_all(bio);
if (!cert) {
printf("Can't load certificate from file '%s'\n", filename);
ERR_print_errors_fp(stderr);
}
return cert;
+}
+/**
- 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) +{
EVP_PKEY *key = NULL;
X509 *cert = NULL;
BIO *data_bio = NULL;
const EVP_MD *md;
PKCS7 *p7;
int flags, ret = -1;
OpenSSL_add_all_digests();
OpenSSL_add_all_ciphers();
ERR_load_crypto_strings();
key = fileio_read_pkey(ctx->key_file);
if (!key)
goto err;
cert = fileio_read_cert(ctx->cert_file);
if (!cert)
goto err;
/*
* create a BIO, containing:
* * firmware image
* * monotonic count
* in this order!
* See EDK2's FmpAuthenticatedHandlerRsa2048Sha256()
*/
data_bio = BIO_new(BIO_s_mem());
BIO_write(data_bio, ctx->image_data, ctx->image_size);
BIO_write(data_bio, &ctx->auth.monotonic_count,
sizeof(ctx->auth.monotonic_count));
md = EVP_get_digestbyname("SHA256");
if (!md)
goto err;
/* create signature */
/* TODO: maybe add PKCS7_NOATTR and PKCS7_NOSMIMECAP */
flags = PKCS7_BINARY | PKCS7_DETACHED;
p7 = PKCS7_sign(NULL, NULL, NULL, data_bio, flags | PKCS7_PARTIAL);
if (!p7)
goto err;
if (!PKCS7_sign_add_signer(p7, cert, key, md, flags))
goto err;
if (!PKCS7_final(p7, data_bio, flags))
goto err;
/* convert pkcs7 into DER */
ctx->sig_data = NULL;
ctx->sig_size = ASN1_item_i2d((ASN1_VALUE *)p7, &ctx->sig_data,
ASN1_ITEM_rptr(PKCS7));
if (!ctx->sig_size)
goto err;
/* 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));
ret = 0;
+err:
BIO_free_all(data_bio);
EVP_PKEY_free(key);
X509_free(cert);
return ret;
+}
+/**
- 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)
OPENSSL_free(ctx->sig_data);
+} +#else +static int create_auth_data(struct auth_context *ctx) +{
return 0;
+}
+static int dump_signature(const char *path, uint8_t *signature, size_t sig_size) +{
return 0;
+}
+static void free_sig_data(struct auth_context *ctx) {} +#endif
/**
- read_bin_file - read a firmware binary file
- @bin: Path to a firmware binary file
@@ -167,23 +418,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; 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);
printf("\tindex: %lu\n\tinstance: %lu\n", index, instance);
#endif
auth_context.sig_size = 0; f = NULL; data = NULL; ret = -1;
@@ -194,6 +447,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)) {
printf("Signing firmware image failed\n");
goto err;
}
if (dump_sig &&
dump_signature(path, auth_context.sig_data,
auth_context.sig_size)) {
printf("Creating signature file failed\n");
goto err;
}
}
/* * write a capsule file */
@@ -211,9 +485,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;
@@ -229,7 +506,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;
@@ -244,13 +521,32 @@ 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 */
@@ -261,28 +557,43 @@ 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>
+/**
- 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 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;
@@ -290,7 +601,7 @@ int main(int argc, char **argv) case 'f': if (file) { printf("Image already specified\n");
return -1;
exit(EXIT_FAILURE); } file = optarg; guid = &efi_guid_image_type_uboot_fit;
@@ -298,7 +609,7 @@ int main(int argc, char **argv) case 'r': if (file) { printf("Image already specified\n");
return -1;
exit(EXIT_FAILURE); } file = optarg; guid = &efi_guid_image_type_uboot_raw;
@@ -309,26 +620,44 @@ int main(int argc, char **argv) case 'I': instance = strtoul(optarg, NULL, 0); break; +#ifdef CONFIG_TOOLS_LIBCRYPTO
case 'p':
if (privkey_file) {
printf("Private Key already specified\n");
exit(EXIT_FAILURE);
}
privkey_file = optarg;
break;
case 'c':
if (cert_file) {
printf("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;
+#endif /* CONFIG_TOOLS_LIBCRYPTO */ case 'h': print_usage();
return 0;
exit(EXIT_SUCCESS); } }
/* need an output file */
if (argc != optind + 1) {
/* check necessary parameters */
if ((argc != optind + 1) || !file ||
((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[optind], file, guid, index, instance,
mcount, privkey_file, cert_file) < 0) { printf("Creating firmware capsule failed\n"); exit(EXIT_FAILURE); }
-- 2.33.0
Acked-by: Ilias Apalodimas ilias.apalodimas@linaro.org

Add a man page for mkeficapsule command.
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org Reviewed-by: Simon Glass sjg@chromium.org --- MAINTAINERS | 1 + doc/mkeficapsule.1 | 95 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 doc/mkeficapsule.1
diff --git a/MAINTAINERS b/MAINTAINERS index 6db5354322fe..813674eb2898 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -722,6 +722,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/doc/mkeficapsule.1 b/doc/mkeficapsule.1 new file mode 100644 index 000000000000..837e09ab451e --- /dev/null +++ b/doc/mkeficapsule.1 @@ -0,0 +1,95 @@ +.TH MAEFICAPSULE 1 "May 2021" + +.SH NAME +mkeficapsule - Generate EFI capsule file for U-Boot + +.SH SYNOPSIS +.B mkeficapsule +.RB [\fIoptions\fP] " \fIcapsule-file\fP" + +.SH "DESCRIPTION" +The +\fBmkeficapsule\fP +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. + +\fBmkeficapsule\fP supports two different format of image files: +.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 \fIuImage\fP format and allows for +multiple binary blobs in a single capsule file. +This type of image file can be generated by \fBmkimage\fP. + +.SH "OPTIONS" +One of \fB--fit\fP or \fB--raw\fP option must be specified. + +.TP +.BI "-f, --fit \fIfit-image-file\fP" +Specify a FIT image file + +.TP +.BI "-r, --raw \fIraw-image-file\fP" +Specify a raw image file + +.TP +.BI "-i, --index \fIindex\fP" +Specify an image index + +.TP +.BI "-I, --instance \fIinstance\fP" +Specify a hardware instance + +.TP +.BI "-h, --help" +Print a help message + +.TP 0 +.B With signing: + +\fB--private-key\fP, \fB--certificate\fP and \fB--monotonic-count\fP are +all mandatory. + +.TP +.BI "-p, --private-key \fIprivate-key-file\fP" +Specify signer's private key file in PEM + +.TP +.BI "-c, --certificate \fIcertificate-file\fP" +Specify signer's certificate file in EFI certificate list format + +.TP +.BI "-m, --monotonic-count \fIcount\fP" +Specify a monotonic count which is set to be monotonically incremented +at every firmware update. + +.TP +.BI "-d, --dump_sig" +Dump signature data into *.p7 file + +.PP +.SH FILES +.TP +.BI "\fI/EFI/UpdateCapsule\fP" +The directory in which all capsule files be placed + +.SH SEE ALSO +.B mkimage + +.SH AUTHORS +Written by AKASHI Takahiro takahiro.akashi@linaro.org + +.SH HOMEPAGE +http://www.denx.de/wiki/U-Boot/WebHome

On Tue, 16 Nov 2021 at 06:33, AKASHI Takahiro takahiro.akashi@linaro.org wrote:
Add a man page for mkeficapsule command.
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org Reviewed-by: Simon Glass sjg@chromium.org
MAINTAINERS | 1 + doc/mkeficapsule.1 | 95 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 doc/mkeficapsule.1
diff --git a/MAINTAINERS b/MAINTAINERS index 6db5354322fe..813674eb2898 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -722,6 +722,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/doc/mkeficapsule.1 b/doc/mkeficapsule.1 new file mode 100644 index 000000000000..837e09ab451e --- /dev/null +++ b/doc/mkeficapsule.1 @@ -0,0 +1,95 @@ +.TH MAEFICAPSULE 1 "May 2021"
+.SH NAME +mkeficapsule - Generate EFI capsule file for U-Boot
+.SH SYNOPSIS +.B mkeficapsule +.RB [\fIoptions\fP] " \fIcapsule-file\fP"
+.SH "DESCRIPTION" +The +\fBmkeficapsule\fP +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.
+\fBmkeficapsule\fP supports two different format of image files: +.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 \fIuImage\fP format and allows for +multiple binary blobs in a single capsule file. +This type of image file can be generated by \fBmkimage\fP.
+.SH "OPTIONS" +One of \fB--fit\fP or \fB--raw\fP option must be specified.
+.TP +.BI "-f, --fit \fIfit-image-file\fP" +Specify a FIT image file
+.TP +.BI "-r, --raw \fIraw-image-file\fP" +Specify a raw image file
+.TP +.BI "-i, --index \fIindex\fP" +Specify an image index
+.TP +.BI "-I, --instance \fIinstance\fP" +Specify a hardware instance
+.TP +.BI "-h, --help" +Print a help message
+.TP 0 +.B With signing:
+\fB--private-key\fP, \fB--certificate\fP and \fB--monotonic-count\fP are +all mandatory.
+.TP +.BI "-p, --private-key \fIprivate-key-file\fP" +Specify signer's private key file in PEM
+.TP +.BI "-c, --certificate \fIcertificate-file\fP" +Specify signer's certificate file in EFI certificate list format
+.TP +.BI "-m, --monotonic-count \fIcount\fP" +Specify a monotonic count which is set to be monotonically incremented +at every firmware update.
+.TP +.BI "-d, --dump_sig" +Dump signature data into *.p7 file
+.PP +.SH FILES +.TP +.BI "\fI/EFI/UpdateCapsule\fP" +The directory in which all capsule files be placed
+.SH SEE ALSO +.B mkimage
+.SH AUTHORS +Written by AKASHI Takahiro takahiro.akashi@linaro.org
+.SH HOMEPAGE
+http://www.denx.de/wiki/U-Boot/WebHome
2.33.0
Acked-by: Ilias Apalodimas ilias.apalodimas@linaro.org

Now we can use mkeficapsule command instead of EDK-II's script to create a signed capsule file. So update the instruction for capsule authentication.
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org Reviewed-by: Simon Glass sjg@chromium.org --- doc/develop/uefi/uefi.rst | 143 ++++++++++++++++++-------------------- 1 file changed, 67 insertions(+), 76 deletions(-)
diff --git a/doc/develop/uefi/uefi.rst b/doc/develop/uefi/uefi.rst index f17138f5c765..864d61734bee 100644 --- a/doc/develop/uefi/uefi.rst +++ b/doc/develop/uefi/uefi.rst @@ -284,37 +284,52 @@ 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:: + + $ 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. - -If that option is disabled, you'll need to set the OsIndications variable with:: +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::
=> 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 +339,58 @@ 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::
$ 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> - -Place the capsule generated in the above step on the EFI System -Partition under the EFI/UpdateCapsule directory - -Testing on QEMU -*************** +3. Run the following command to create and sign the capsule file::
-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. + $ mkeficapsule --monotonic-count 1 \ + --private-key CRT.key \ + --certificate CRT.crt \ + --index 1 --instance 0 \ + [--fit <FIT image> | --raw <raw image>] \ + <capsule_file_name>
-The capsule update feature is enabled with the following configuration -settings:: +4. Insert the signature list into a device tree in the following format::
- 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 + { + signature { + capsule-key = [ <binary of signature list> ]; + } + ... + }
-In addition, the following config needs to be disabled(QEMU ARM specific):: + You can do this manually with::
- 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~

Akashi-san,
[...]
-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.
-If that option is disabled, you'll need to set the OsIndications variable with:: +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::
=> 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 +339,58 @@ 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.
- Install utility commands on your host
* OPENSSL
+1. Install utility commands on your host
* openssl * efitools
- Create signing keys and certificate files on your host
+2. Create signing keys and certificate files on your host::
$ 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>
-Place the capsule generated in the above step on the EFI System -Partition under the EFI/UpdateCapsule directory
-Testing on QEMU -*************** +3. Run the following command to create and sign the capsule file::
-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.
- $ mkeficapsule --monotonic-count 1 \
--private-key CRT.key \
--certificate CRT.crt \
--index 1 --instance 0 \
[--fit <FIT image> | --raw <raw image>] \
<capsule_file_name>
-The capsule update feature is enabled with the following configuration -settings:: +4. Insert the signature list into a device tree in the following format::
- 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
{
signature {
capsule-key = [ <binary of signature list> ];
}
...
}
-In addition, the following config needs to be disabled(QEMU ARM specific)::
- You can do this manually with::
- 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
-- 2.33.0
Acked-by: Ilias Apalodimas ilias.apalodimas@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 Reviewed-by: Simon Glass sjg@chromium.org Acked-by: Ilias Apalodimas ilias.apalodimas@linaro.org --- .../py/tests/test_efi_capsule/capsule_defs.py | 5 + test/py/tests/test_efi_capsule/conftest.py | 52 +++- test/py/tests/test_efi_capsule/signature.dts | 10 + .../test_capsule_firmware_signed.py | 254 ++++++++++++++++++ 4 files changed, 318 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 6ad5608cd71c..27c05971ca32 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, @@ -56,6 +86,22 @@ def efi_capsule_data(request, u_boot_config): check_call('cd %s; %s/tools/mkeficapsule --raw u-boot.bin.new --index 1 Test02' % (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 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..593b032e9015 --- /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)

The existing options, "--fit" and "--raw," are only used to put a proper GUID in a capsule header, where GUID identifies a particular FMP (Firmware Management Protocol) driver which then would handle the firmware binary in a capsule. In fact, mkeficapsule does the exact same job in creating a capsule file whatever the firmware binary type is.
To prepare for the future extension, the command syntax will be a bit modified to allow users to specify arbitrary GUID for their own FMP driver. OLD: [--fit <image> | --raw <image>] <capsule file> NEW: [--fit | --raw | --guid <guid-string>] <image> <capsule file>
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org Reviewed-by: Simon Glass sjg@chromium.org --- doc/develop/uefi/uefi.rst | 4 +- doc/mkeficapsule.1 | 26 +++++++++---- tools/mkeficapsule.c | 78 ++++++++++++++++++++++++++++++--------- 3 files changed, 81 insertions(+), 27 deletions(-)
diff --git a/doc/develop/uefi/uefi.rst b/doc/develop/uefi/uefi.rst index 864d61734bee..54fefd76f0f5 100644 --- a/doc/develop/uefi/uefi.rst +++ b/doc/develop/uefi/uefi.rst @@ -367,8 +367,8 @@ and used by the steps highlighted below. --private-key CRT.key \ --certificate CRT.crt \ --index 1 --instance 0 \ - [--fit <FIT image> | --raw <raw image>] \ - <capsule_file_name> + [--fit | --raw | --guid <guid-string] \ + <image_blob> <capsule_file_name>
4. Insert the signature list into a device tree in the following format::
diff --git a/doc/mkeficapsule.1 b/doc/mkeficapsule.1 index 837e09ab451e..312e8a8b3188 100644 --- a/doc/mkeficapsule.1 +++ b/doc/mkeficapsule.1 @@ -5,7 +5,7 @@ mkeficapsule - Generate EFI capsule file for U-Boot
.SH SYNOPSIS .B mkeficapsule -.RB [\fIoptions\fP] " \fIcapsule-file\fP" +.RB [\fIoptions\fP] " \fIimage-blob\fP \fIcapsule-file\fP"
.SH "DESCRIPTION" The @@ -21,7 +21,7 @@ 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.
-\fBmkeficapsule\fP supports two different format of image files: +\fBmkeficapsule\fP takes any type of image files, including: .TP .I raw image format is a single binary blob of any type of firmware. @@ -33,16 +33,28 @@ is the same as used in the new \fIuImage\fP format and allows for multiple binary blobs in a single capsule file. This type of image file can be generated by \fBmkimage\fP.
+.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 \fB--fit\fP or \fB--raw\fP option must be specified. +One of \fB--fit\fP, \fB--raw\fP or \fB--guid\fP option must be specified.
.TP -.BI "-f, --fit \fIfit-image-file\fP" -Specify a FIT image file +.BI "-f, --fit +Indicate that the blob is a FIT image file
.TP -.BI "-r, --raw \fIraw-image-file\fP" -Specify a raw image file +.BI "-r, --raw +Indicate that the blob is a raw image file + +.TP +.BI "-g, --guid \fIguid-string\fP" +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, --index \fIindex\fP" diff --git a/tools/mkeficapsule.c b/tools/mkeficapsule.c index 3e6f36430d74..8891496d1564 100644 --- a/tools/mkeficapsule.c +++ b/tools/mkeficapsule.c @@ -14,7 +14,7 @@
#include <sys/stat.h> #include <sys/types.h> - +#include <uuid/uuid.h> #include <linux/kconfig.h> #ifdef CONFIG_TOOLS_LIBCRYPTO #include <openssl/asn1.h> @@ -37,14 +37,15 @@ efi_guid_t efi_guid_image_type_uboot_raw = efi_guid_t efi_guid_cert_type_pkcs7 = EFI_CERT_TYPE_PKCS7_GUID;
#ifdef CONFIG_TOOLS_LIBCRYPTO -static const char *opts_short = "f:r:i:I:v:p:c:m:dh"; +static const char *opts_short = "frg:i:I:v:p:c:m:dh"; #else -static const char *opts_short = "f:r:i:I:v:h"; +static const char *opts_short = "frg:i:I:v:h"; #endif
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'}, #ifdef CONFIG_TOOLS_LIBCRYPTO @@ -59,11 +60,12 @@ static struct option options[] = {
static void print_usage(void) { - printf("Usage: %s [options] <output file>\n" + printf("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" #ifdef CONFIG_TOOLS_LIBCRYPTO @@ -563,6 +565,37 @@ err: return ret; }
+/** + * convert_uuid_to_guid() - convert uuid string to guid string + * @buf: String for UUID + * + * UUID and GUID have the same data structure, but their string + * 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 @@ -577,14 +610,13 @@ err: */ 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; @@ -599,21 +631,31 @@ int main(int argc, char **argv)
switch (c) { case 'f': - if (file) { - printf("Image already specified\n"); + if (guid) { + printf("Image type already specified\n"); exit(EXIT_FAILURE); } - file = optarg; guid = &efi_guid_image_type_uboot_fit; break; case 'r': - if (file) { - printf("Image already specified\n"); + if (guid) { + printf("Image type already specified\n"); exit(EXIT_FAILURE); } - file = optarg; guid = &efi_guid_image_type_uboot_raw; break; + case 'g': + if (guid) { + printf("Image type already specified\n"); + exit(EXIT_FAILURE); + } + if (uuid_parse(optarg, uuid_buf)) { + printf("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; @@ -649,14 +691,14 @@ int main(int argc, char **argv) }
/* check necessary parameters */ - if ((argc != optind + 1) || !file || + if ((argc != optind + 2) || !guid || ((privkey_file && !cert_file) || (!privkey_file && cert_file))) { print_usage(); exit(EXIT_FAILURE); }
- if (create_fwbin(argv[optind], file, guid, index, instance, + if (create_fwbin(argv[argc - 1], argv[argc - 2], guid, index, instance, mcount, privkey_file, cert_file) < 0) { printf("Creating firmware capsule failed\n"); exit(EXIT_FAILURE);

Since the syntax of mkeficapsule was changed in the previous commit, we need to modify command line arguments in a pytest script.
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org Reviewed-by: Simon Glass sjg@chromium.org --- test/py/tests/test_efi_capsule/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/test/py/tests/test_efi_capsule/conftest.py b/test/py/tests/test_efi_capsule/conftest.py index 27c05971ca32..a5a25c53dcb4 100644 --- a/test/py/tests/test_efi_capsule/conftest.py +++ b/test/py/tests/test_efi_capsule/conftest.py @@ -80,10 +80,10 @@ 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 --raw u-boot.bin.new --index 1 Test02' % + check_call('cd %s; %s/tools/mkeficapsule --index 1 --raw u-boot.bin.new Test02' % (data_dir, u_boot_config.build_dir), shell=True) if capsule_auth_enabled:

This test scenario tests a new feature of mkeficapsule, "--guid" option, which allows us to specify FMP driver's guid explicitly at the command line.
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org --- test/py/tests/test_efi_capsule/conftest.py | 3 + .../test_efi_capsule/test_capsule_firmware.py | 67 +++++++++++++++++++ 2 files changed, 70 insertions(+)
diff --git a/test/py/tests/test_efi_capsule/conftest.py b/test/py/tests/test_efi_capsule/conftest.py index a5a25c53dcb4..9076087a12b7 100644 --- a/test/py/tests/test_efi_capsule/conftest.py +++ b/test/py/tests/test_efi_capsule/conftest.py @@ -86,6 +86,9 @@ def efi_capsule_data(request, u_boot_config): 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 --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; ' 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 9eeaae27d626..9cc973560fa1 100644 --- a/test/py/tests/test_efi_capsule/test_capsule_firmware.py +++ b/test/py/tests/test_efi_capsule/test_capsule_firmware.py @@ -247,3 +247,70 @@ class TestEfiCapsuleFirmwareFit(object): 'sf read 4000000 100000 10', 'md.b 4000000 10']) 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') + 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']) + assert 'u-boot:New' in ''.join(output)

Before the capsule authentication is supported, this test script works correctly, but with the feature enabled, most tests will fail due to unsigned capsules. So check the results depending on CAPSULE_AUTHENTICATE or not.
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org Reviewed-by: Simon Glass sjg@chromium.org --- .../test_efi_capsule/test_capsule_firmware.py | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-)
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 9cc973560fa1..6e803f699f2f 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,7 +256,10 @@ 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): @@ -285,6 +298,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 4-b, after reboot'): if not capsule_early: # make sure that dfu_alt_info exists even persistent variables @@ -313,4 +328,7 @@ 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)

With this script, a public key is added to a device tree blob as the default efi_get_public_key_data() expects.
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org --- MAINTAINERS | 1 + tools/fdtsig.sh | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100755 tools/fdtsig.sh
diff --git a/MAINTAINERS b/MAINTAINERS index 813674eb2898..8233a53c29dc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -742,6 +742,7 @@ F: cmd/bootefi.c F: cmd/efidebug.c F: cmd/nvedit_efi.c F: tools/efivar.py +F: tools/fdtsig.sh F: tools/file2include.c F: tools/mkeficapsule.c
diff --git a/tools/fdtsig.sh b/tools/fdtsig.sh new file mode 100755 index 000000000000..c2b2a6dc5ec8 --- /dev/null +++ b/tools/fdtsig.sh @@ -0,0 +1,40 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0+ +# +# script to add a certificate (efi-signature-list) to dtb blob + +usage() { + if [ -n "$*" ]; then + echo "ERROR: $*" + fi + echo "Usage: "$(basename $0) " <esl file> <dtb file>" +} + +if [ "$#" -ne 2 ]; then + usage "Arguments missing" + exit 1 +fi + +ESL=$1 +DTB=$2 +NEW_DTB=$(basename $DTB)_tmp +SIG=signature + +cat << 'EOF' > $SIG.dts +/dts-v1/; +/plugin/; + +&{/} { + signature { +EOF +echo "capsule-key = /incbin/("$ESL");" >> $SIG.dts +cat << 'EOF' >> $SIG.dts + }; +}; +EOF + +dtc -@ -I dts -O dtb -o $SIG.dtbo $SIG.dts +fdtoverlay -i $DTB -o $NEW_DTB $SIG.dtbo +mv $NEW_DTB $DTB + +rm $SIG.dts $SIG.dtsn $SIG.dtbo

Hi Takahiro,
On Mon, 15 Nov 2021 at 21:33, AKASHI Takahiro takahiro.akashi@linaro.org wrote:
With this script, a public key is added to a device tree blob as the default efi_get_public_key_data() expects.
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org
MAINTAINERS | 1 + tools/fdtsig.sh | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100755 tools/fdtsig.sh
diff --git a/MAINTAINERS b/MAINTAINERS index 813674eb2898..8233a53c29dc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -742,6 +742,7 @@ F: cmd/bootefi.c F: cmd/efidebug.c F: cmd/nvedit_efi.c F: tools/efivar.py +F: tools/fdtsig.sh F: tools/file2include.c F: tools/mkeficapsule.c
diff --git a/tools/fdtsig.sh b/tools/fdtsig.sh new file mode 100755 index 000000000000..c2b2a6dc5ec8 --- /dev/null +++ b/tools/fdtsig.sh @@ -0,0 +1,40 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0+ +# +# script to add a certificate (efi-signature-list) to dtb blob
+usage() {
if [ -n "$*" ]; then
echo "ERROR: $*"
fi
echo "Usage: "$(basename $0) " <esl file> <dtb file>"
+}
+if [ "$#" -ne 2 ]; then
usage "Arguments missing"
exit 1
+fi
+ESL=$1 +DTB=$2 +NEW_DTB=$(basename $DTB)_tmp +SIG=signature
+cat << 'EOF' > $SIG.dts +/dts-v1/; +/plugin/;
+&{/} {
- signature {
+EOF +echo "capsule-key = /incbin/("$ESL");" >> $SIG.dts +cat << 'EOF' >> $SIG.dts
- };
+}; +EOF
+dtc -@ -I dts -O dtb -o $SIG.dtbo $SIG.dts +fdtoverlay -i $DTB -o $NEW_DTB $SIG.dtbo +mv $NEW_DTB $DTB
+rm $SIG.dts $SIG.dtsn $SIG.dtbo
2.33.0
As mentioned, let's get binman working here.
Regards, Simon

By specifying CONFIG_EFI_CAPSULE_KEY_PATH, the build process will automatically insert the given key into the device tree. Otherwise, users are required to do so manually, possibly, with the utility script, fdtsig.sh.
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org --- doc/develop/uefi/uefi.rst | 4 ++++ dts/Makefile | 23 +++++++++++++++++++++-- lib/efi_loader/Kconfig | 7 +++++++ 3 files changed, 32 insertions(+), 2 deletions(-)
diff --git a/doc/develop/uefi/uefi.rst b/doc/develop/uefi/uefi.rst index 54fefd76f0f5..7f85b9e5a4a6 100644 --- a/doc/develop/uefi/uefi.rst +++ b/doc/develop/uefi/uefi.rst @@ -347,6 +347,7 @@ 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. @@ -392,6 +393,9 @@ and used by the steps highlighted below. }; };
+ If CONFIG_EFI_CAPSULE_KEY_PATH is specified, the build process will + take care of it for you. + Executing the boot manager ~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/dts/Makefile b/dts/Makefile index cb3111382959..6c5486719ecd 100644 --- a/dts/Makefile +++ b/dts/Makefile @@ -20,11 +20,30 @@ $(obj)/dt-$(SPL_NAME).dtb: dts/dt.dtb $(objtree)/tools/fdtgrep FORCE mkdir -p $(dir $@) $(call if_changed,fdtgrep)
+quiet_cmd_fdtsig = FDTSIG $@ + cmd_fdtsig = \ + cat $< > $@; \ + $(srctree)/tools/fdtsig.sh \ + $(patsubst "%",%,$(CONFIG_EFI_CAPSULE_KEY_PATH)) $@ + +ifeq ($(CONFIG_EFI_CAPSULE_AUTHENTICATE),y) +ifneq ($(patsubst "%",%,$(CONFIG_EFI_CAPSULE_KEY_PATH)),) +DTB_ov := $(obj)/dt.dtb_ov + +$(obj)/dt.dtb_ov: $(DTB) FORCE + $(call if_changed,fdtsig) +else +DTB_ov := $(DTB) +endif +else +DTB_ov := $(DTB) +endif + ifeq ($(CONFIG_OF_DTB_PROPS_REMOVE),y) -$(obj)/dt.dtb: $(DTB) $(objtree)/tools/fdtgrep FORCE +$(obj)/dt.dtb: $(DTB_ov) $(objtree)/tools/fdtgrep FORCE $(call if_changed,fdt_rm_props) else -$(obj)/dt.dtb: $(DTB) FORCE +$(obj)/dt.dtb: $(DTB_ov) FORCE $(call if_changed,shipped) endif
diff --git a/lib/efi_loader/Kconfig b/lib/efi_loader/Kconfig index 700dc838ddb9..8c8d14d46433 100644 --- a/lib/efi_loader/Kconfig +++ b/lib/efi_loader/Kconfig @@ -209,6 +209,13 @@ config EFI_CAPSULE_AUTHENTICATE Select this option if you want to enable capsule authentication
+config EFI_CAPSULE_KEY_PATH + string "Path to .esl cert for capsule authentication" + depends on EFI_CAPSULE_AUTHENTICATE + help + Provide the EFI signature list (esl) certificate used for capsule + authentication + config EFI_DEVICE_PATH_TO_TEXT bool "Device path to text protocol" default y

Hi Takahiro,
On Mon, 15 Nov 2021 at 21:33, AKASHI Takahiro takahiro.akashi@linaro.org wrote:
By specifying CONFIG_EFI_CAPSULE_KEY_PATH, the build process will automatically insert the given key into the device tree. Otherwise, users are required to do so manually, possibly, with the utility script, fdtsig.sh.
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org
doc/develop/uefi/uefi.rst | 4 ++++ dts/Makefile | 23 +++++++++++++++++++++-- lib/efi_loader/Kconfig | 7 +++++++ 3 files changed, 32 insertions(+), 2 deletions(-)
This should be handled by binman. I can create an etype for it if you like.
Regards, Simon

Hi Simon,
On Wed, Nov 24, 2021 at 05:11:49PM -0700, Simon Glass wrote:
Hi Takahiro,
On Mon, 15 Nov 2021 at 21:33, AKASHI Takahiro takahiro.akashi@linaro.org wrote:
By specifying CONFIG_EFI_CAPSULE_KEY_PATH, the build process will automatically insert the given key into the device tree. Otherwise, users are required to do so manually, possibly, with the utility script, fdtsig.sh.
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org
doc/develop/uefi/uefi.rst | 4 ++++ dts/Makefile | 23 +++++++++++++++++++++-- lib/efi_loader/Kconfig | 7 +++++++ 3 files changed, 32 insertions(+), 2 deletions(-)
This should be handled by binman. I can create an etype for it if you like.
Basically I'd defer to you, but I don't still understand when and how binman be invoked in this particular use case.
Regards, Simon

Hi Takahiro,
On Wed, 24 Nov 2021 at 19:21, AKASHI Takahiro takahiro.akashi@linaro.org wrote:
Hi Simon,
On Wed, Nov 24, 2021 at 05:11:49PM -0700, Simon Glass wrote:
Hi Takahiro,
On Mon, 15 Nov 2021 at 21:33, AKASHI Takahiro takahiro.akashi@linaro.org wrote:
By specifying CONFIG_EFI_CAPSULE_KEY_PATH, the build process will automatically insert the given key into the device tree. Otherwise, users are required to do so manually, possibly, with the utility script, fdtsig.sh.
Signed-off-by: AKASHI Takahiro takahiro.akashi@linaro.org
doc/develop/uefi/uefi.rst | 4 ++++ dts/Makefile | 23 +++++++++++++++++++++-- lib/efi_loader/Kconfig | 7 +++++++ 3 files changed, 32 insertions(+), 2 deletions(-)
This should be handled by binman. I can create an etype for it if you like.
Basically I'd defer to you, but I don't still understand when and how binman be invoked in this particular use case.
OK I will give it a try. Basically we need to create an etype for with, with a entry argument that specifies the key file. See
https://github.com/u-boot/u-boot/blob/master/tools/binman/etype/vblock.py
This one collects the data to sign, calls a tool to sign it, then puts the signature in the entry.
It is really easy (TM).
Regards, SImon

Hi Heinrich
On Tue, Nov 16, 2021 at 01:32:26PM +0900, AKASHI Takahiro wrote:
As I proposed and discussed in [1] and [2], I have made a couple of improvements on the current implementation of capsule update in this patch set.
For this version(v7), I have seen your review comments only on patch#1 and #2. Please take your time to review the rest (the main part of commits) as well. I don't want to respin the patch series and post its new version which is almost the same as the old one(v7).
-Takahiro Akashi
- add signing feature to mkeficapsule
- add "--guid" option to mkeficapsule
- add man page of mkeficapsule
- update uefi document regarding capsule update
- revise pytests
- (as RFC) add CONFIG_EFI_CAPSULE_KEY_PATH
# We have had some discussion about fdtsig.sh. # So RFCs (patch#11,#12) are still included for further discussion # if they are useful or not. # For smooth merge, the rest (patch#1-10) should work without them.
[1] https://lists.denx.de/pipermail/u-boot/2021-April/447918.html [2] https://lists.denx.de/pipermail/u-boot/2021-July/455292.html
Prerequisite patches
None
Test
- locally passed the pytest which is included in this patch series on sandbox built. (CONFIG_EFI_CAPSULE_AUTHENTICATE should explicitly be turned on in order to exercise the authentication code.)
Changes
v7 (Nov 16, 2021)
- rebased on pre-v2022.01-rc2
- drop already-merged patch
- check for a size of firmware binary file (patch#1)
- enable mkeficapsule in tools-only_defconfig (patch#2)
- define eficapsule.h and include it from mkeficapsule (patch#3) Hopefully, the tool can now compile on non-linux host.
v6 (Nov 02, 2021)
- rebased on pre-v2022.01-rc1
- add patch#2 to rework/refactor the code for better readability (patch#2)
- use exit(EXIT_SUCCESS/FAILURE) (patch#3)
- truncate >80chars lines in pytest scripts (patch#6)
v5 (Oct 27, 2021)
- rebased on pre-v2022.01-rc1 (WIP/26Oct2021)
- drop already-merged patches
- drop __weak from efi_get_public_key_data() (patch#1)
- describe the format of public key node in device tree (patch#4)
- re-order patches by grouping closely-related patches (patch#6-8)
- modify pytest to make the test results correctly verified either with or without CONFIG_EFI_CAPSULE_AUTHENTICATE (patch#9)
- add RFCs for embedding public keys during the build process (patch#10,11)
v4 (Oct 7, 2021)
- rebased on v2021.10
- align with "Revert "efi_capsule: Move signature from DTB to .rodata""
- add more missing *revert* commits (patch#1,#2,#3)
- add fdtsig.sh, replacing dtb support in mkeficapsule (patch#4)
- update/revise the man/uefi doc (patch#6,#7)
- fix a bug in parsing guid string (patch#8)
- add a test for "--guid" option (patch#10)
- use dtb-based authentication test as done in v1 (patch#11)
v3 (Aug 31, 2021)
- rebased on v2021.10-rc3
- remove pytest-related patches
- add function descriptions in mkeficapsule.c
- correct format specifiers in printf()
- let main() return 0 or -1 only
- update doc/develop/uefi/uefi.rst for syntax change of mkeficapsule
v2 (July 28, 2021)
- rebased on v2021.10-rc*
- removed dependency on target's configuration
- removed fdtsig.sh and others
- add man page
- update the UEFI document
- add dedicate defconfig for testing on sandbox
- add gitlab CI support
- add "--guid" option to mkeficapsule (yet rather RFC)
Initial release (May 12, 2021)
- based on v2021.07-rc2
AKASHI Takahiro (12): tools: mkeficapsule: rework the code a little bit tools: build mkeficapsule with tools-only_defconfig tools: mkeficapsule: add firmwware image signing tools: mkeficapsule: add man page doc: update UEFI document for usage of mkeficapsule test/py: efi_capsule: add image authentication test tools: mkeficapsule: allow for specifying GUID explicitly test/py: efi_capsule: align with the syntax change of mkeficapsule test/py: efi_capsule: add a test for "--guid" option test/py: efi_capsule: check the results in case of CAPSULE_AUTHENTICATE (RFC) tools: add fdtsig.sh (RFC) efi_loader, dts: add public keys for capsules to device tree
MAINTAINERS | 2 + configs/tools-only_defconfig | 1 + doc/develop/uefi/uefi.rst | 143 ++-- doc/mkeficapsule.1 | 107 +++ dts/Makefile | 23 +- lib/efi_loader/Kconfig | 7 + .../py/tests/test_efi_capsule/capsule_defs.py | 5 + test/py/tests/test_efi_capsule/conftest.py | 59 +- test/py/tests/test_efi_capsule/signature.dts | 10 + .../test_efi_capsule/test_capsule_firmware.py | 91 ++- .../test_capsule_firmware_signed.py | 254 +++++++ tools/Kconfig | 8 + tools/Makefile | 8 +- tools/eficapsule.h | 115 +++ tools/fdtsig.sh | 40 ++ tools/mkeficapsule.c | 680 +++++++++++++++--- 16 files changed, 1360 insertions(+), 193 deletions(-) create mode 100644 doc/mkeficapsule.1 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 create mode 100644 tools/eficapsule.h create mode 100755 tools/fdtsig.sh
-- 2.33.0

Heinrich,
On Thu, Nov 25, 2021 at 03:02:35PM +0900, AKASHI Takahiro wrote:
Hi Heinrich
On Tue, Nov 16, 2021 at 01:32:26PM +0900, AKASHI Takahiro wrote:
As I proposed and discussed in [1] and [2], I have made a couple of improvements on the current implementation of capsule update in this patch set.
For this version(v7), I have seen your review comments only on patch#1 and #2. Please take your time to review the rest (the main part of commits) as well. I don't want to respin the patch series and post its new version which is almost the same as the old one(v7).
Ping.
-Takahiro Akashi
-Takahiro Akashi
- add signing feature to mkeficapsule
- add "--guid" option to mkeficapsule
- add man page of mkeficapsule
- update uefi document regarding capsule update
- revise pytests
- (as RFC) add CONFIG_EFI_CAPSULE_KEY_PATH
# We have had some discussion about fdtsig.sh. # So RFCs (patch#11,#12) are still included for further discussion # if they are useful or not. # For smooth merge, the rest (patch#1-10) should work without them.
[1] https://lists.denx.de/pipermail/u-boot/2021-April/447918.html [2] https://lists.denx.de/pipermail/u-boot/2021-July/455292.html
Prerequisite patches
None
Test
- locally passed the pytest which is included in this patch series on sandbox built. (CONFIG_EFI_CAPSULE_AUTHENTICATE should explicitly be turned on in order to exercise the authentication code.)
Changes
v7 (Nov 16, 2021)
- rebased on pre-v2022.01-rc2
- drop already-merged patch
- check for a size of firmware binary file (patch#1)
- enable mkeficapsule in tools-only_defconfig (patch#2)
- define eficapsule.h and include it from mkeficapsule (patch#3) Hopefully, the tool can now compile on non-linux host.
v6 (Nov 02, 2021)
- rebased on pre-v2022.01-rc1
- add patch#2 to rework/refactor the code for better readability (patch#2)
- use exit(EXIT_SUCCESS/FAILURE) (patch#3)
- truncate >80chars lines in pytest scripts (patch#6)
v5 (Oct 27, 2021)
- rebased on pre-v2022.01-rc1 (WIP/26Oct2021)
- drop already-merged patches
- drop __weak from efi_get_public_key_data() (patch#1)
- describe the format of public key node in device tree (patch#4)
- re-order patches by grouping closely-related patches (patch#6-8)
- modify pytest to make the test results correctly verified either with or without CONFIG_EFI_CAPSULE_AUTHENTICATE (patch#9)
- add RFCs for embedding public keys during the build process (patch#10,11)
v4 (Oct 7, 2021)
- rebased on v2021.10
- align with "Revert "efi_capsule: Move signature from DTB to .rodata""
- add more missing *revert* commits (patch#1,#2,#3)
- add fdtsig.sh, replacing dtb support in mkeficapsule (patch#4)
- update/revise the man/uefi doc (patch#6,#7)
- fix a bug in parsing guid string (patch#8)
- add a test for "--guid" option (patch#10)
- use dtb-based authentication test as done in v1 (patch#11)
v3 (Aug 31, 2021)
- rebased on v2021.10-rc3
- remove pytest-related patches
- add function descriptions in mkeficapsule.c
- correct format specifiers in printf()
- let main() return 0 or -1 only
- update doc/develop/uefi/uefi.rst for syntax change of mkeficapsule
v2 (July 28, 2021)
- rebased on v2021.10-rc*
- removed dependency on target's configuration
- removed fdtsig.sh and others
- add man page
- update the UEFI document
- add dedicate defconfig for testing on sandbox
- add gitlab CI support
- add "--guid" option to mkeficapsule (yet rather RFC)
Initial release (May 12, 2021)
- based on v2021.07-rc2
AKASHI Takahiro (12): tools: mkeficapsule: rework the code a little bit tools: build mkeficapsule with tools-only_defconfig tools: mkeficapsule: add firmwware image signing tools: mkeficapsule: add man page doc: update UEFI document for usage of mkeficapsule test/py: efi_capsule: add image authentication test tools: mkeficapsule: allow for specifying GUID explicitly test/py: efi_capsule: align with the syntax change of mkeficapsule test/py: efi_capsule: add a test for "--guid" option test/py: efi_capsule: check the results in case of CAPSULE_AUTHENTICATE (RFC) tools: add fdtsig.sh (RFC) efi_loader, dts: add public keys for capsules to device tree
MAINTAINERS | 2 + configs/tools-only_defconfig | 1 + doc/develop/uefi/uefi.rst | 143 ++-- doc/mkeficapsule.1 | 107 +++ dts/Makefile | 23 +- lib/efi_loader/Kconfig | 7 + .../py/tests/test_efi_capsule/capsule_defs.py | 5 + test/py/tests/test_efi_capsule/conftest.py | 59 +- test/py/tests/test_efi_capsule/signature.dts | 10 + .../test_efi_capsule/test_capsule_firmware.py | 91 ++- .../test_capsule_firmware_signed.py | 254 +++++++ tools/Kconfig | 8 + tools/Makefile | 8 +- tools/eficapsule.h | 115 +++ tools/fdtsig.sh | 40 ++ tools/mkeficapsule.c | 680 +++++++++++++++--- 16 files changed, 1360 insertions(+), 193 deletions(-) create mode 100644 doc/mkeficapsule.1 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 create mode 100644 tools/eficapsule.h create mode 100755 tools/fdtsig.sh
-- 2.33.0

Heinrich,
On Fri, Dec 03, 2021 at 04:09:58PM +0900, AKASHI Takahiro wrote:
Heinrich,
On Thu, Nov 25, 2021 at 03:02:35PM +0900, AKASHI Takahiro wrote:
Hi Heinrich
On Tue, Nov 16, 2021 at 01:32:26PM +0900, AKASHI Takahiro wrote:
As I proposed and discussed in [1] and [2], I have made a couple of improvements on the current implementation of capsule update in this patch set.
For this version(v7), I have seen your review comments only on patch#1 and #2. Please take your time to review the rest (the main part of commits) as well. I don't want to respin the patch series and post its new version which is almost the same as the old one(v7).
Ping.
Ping, again. Can you reivew the *main* part of this patch series in any time soon?
-Takahiro Akashi
-Takahiro Akashi
-Takahiro Akashi
- add signing feature to mkeficapsule
- add "--guid" option to mkeficapsule
- add man page of mkeficapsule
- update uefi document regarding capsule update
- revise pytests
- (as RFC) add CONFIG_EFI_CAPSULE_KEY_PATH
# We have had some discussion about fdtsig.sh. # So RFCs (patch#11,#12) are still included for further discussion # if they are useful or not. # For smooth merge, the rest (patch#1-10) should work without them.
[1] https://lists.denx.de/pipermail/u-boot/2021-April/447918.html [2] https://lists.denx.de/pipermail/u-boot/2021-July/455292.html
Prerequisite patches
None
Test
- locally passed the pytest which is included in this patch series on sandbox built. (CONFIG_EFI_CAPSULE_AUTHENTICATE should explicitly be turned on in order to exercise the authentication code.)
Changes
v7 (Nov 16, 2021)
- rebased on pre-v2022.01-rc2
- drop already-merged patch
- check for a size of firmware binary file (patch#1)
- enable mkeficapsule in tools-only_defconfig (patch#2)
- define eficapsule.h and include it from mkeficapsule (patch#3) Hopefully, the tool can now compile on non-linux host.
v6 (Nov 02, 2021)
- rebased on pre-v2022.01-rc1
- add patch#2 to rework/refactor the code for better readability (patch#2)
- use exit(EXIT_SUCCESS/FAILURE) (patch#3)
- truncate >80chars lines in pytest scripts (patch#6)
v5 (Oct 27, 2021)
- rebased on pre-v2022.01-rc1 (WIP/26Oct2021)
- drop already-merged patches
- drop __weak from efi_get_public_key_data() (patch#1)
- describe the format of public key node in device tree (patch#4)
- re-order patches by grouping closely-related patches (patch#6-8)
- modify pytest to make the test results correctly verified either with or without CONFIG_EFI_CAPSULE_AUTHENTICATE (patch#9)
- add RFCs for embedding public keys during the build process (patch#10,11)
v4 (Oct 7, 2021)
- rebased on v2021.10
- align with "Revert "efi_capsule: Move signature from DTB to .rodata""
- add more missing *revert* commits (patch#1,#2,#3)
- add fdtsig.sh, replacing dtb support in mkeficapsule (patch#4)
- update/revise the man/uefi doc (patch#6,#7)
- fix a bug in parsing guid string (patch#8)
- add a test for "--guid" option (patch#10)
- use dtb-based authentication test as done in v1 (patch#11)
v3 (Aug 31, 2021)
- rebased on v2021.10-rc3
- remove pytest-related patches
- add function descriptions in mkeficapsule.c
- correct format specifiers in printf()
- let main() return 0 or -1 only
- update doc/develop/uefi/uefi.rst for syntax change of mkeficapsule
v2 (July 28, 2021)
- rebased on v2021.10-rc*
- removed dependency on target's configuration
- removed fdtsig.sh and others
- add man page
- update the UEFI document
- add dedicate defconfig for testing on sandbox
- add gitlab CI support
- add "--guid" option to mkeficapsule (yet rather RFC)
Initial release (May 12, 2021)
- based on v2021.07-rc2
AKASHI Takahiro (12): tools: mkeficapsule: rework the code a little bit tools: build mkeficapsule with tools-only_defconfig tools: mkeficapsule: add firmwware image signing tools: mkeficapsule: add man page doc: update UEFI document for usage of mkeficapsule test/py: efi_capsule: add image authentication test tools: mkeficapsule: allow for specifying GUID explicitly test/py: efi_capsule: align with the syntax change of mkeficapsule test/py: efi_capsule: add a test for "--guid" option test/py: efi_capsule: check the results in case of CAPSULE_AUTHENTICATE (RFC) tools: add fdtsig.sh (RFC) efi_loader, dts: add public keys for capsules to device tree
MAINTAINERS | 2 + configs/tools-only_defconfig | 1 + doc/develop/uefi/uefi.rst | 143 ++-- doc/mkeficapsule.1 | 107 +++ dts/Makefile | 23 +- lib/efi_loader/Kconfig | 7 + .../py/tests/test_efi_capsule/capsule_defs.py | 5 + test/py/tests/test_efi_capsule/conftest.py | 59 +- test/py/tests/test_efi_capsule/signature.dts | 10 + .../test_efi_capsule/test_capsule_firmware.py | 91 ++- .../test_capsule_firmware_signed.py | 254 +++++++ tools/Kconfig | 8 + tools/Makefile | 8 +- tools/eficapsule.h | 115 +++ tools/fdtsig.sh | 40 ++ tools/mkeficapsule.c | 680 +++++++++++++++--- 16 files changed, 1360 insertions(+), 193 deletions(-) create mode 100644 doc/mkeficapsule.1 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 create mode 100644 tools/eficapsule.h create mode 100755 tools/fdtsig.sh
-- 2.33.0
participants (4)
-
AKASHI Takahiro
-
Heinrich Schuchardt
-
Ilias Apalodimas
-
Simon Glass