diff --git a/docs/.gitignore b/docs/.gitignore index fad99b16..88221481 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1 +1,2 @@ efisecdb.1 +sbchooser.1 diff --git a/docs/Makefile b/docs/Makefile index c9bf585f..4d1ec7fa 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -6,7 +6,8 @@ include $(TOPDIR)/src/include/rules.mk include $(TOPDIR)/src/include/defaults.mk MAN1TARGETS = efisecdb.1 \ - efivar.1 + efivar.1 \ + sbchooser.1 MAN3TARGETS = efi_append_variable.3 \ efi_del_variable.3 \ @@ -41,7 +42,7 @@ MAN3TARGETS = efi_append_variable.3 \ all : $(MAN1TARGETS) $(MAN3TARGETS) clean : - @rm -f efisecdb.1 + @rm -f efisecdb.1 sbchooser.1 prep : diff --git a/docs/sbchooser.1.mdoc b/docs/sbchooser.1.mdoc new file mode 100644 index 00000000..2e5a5721 --- /dev/null +++ b/docs/sbchooser.1.mdoc @@ -0,0 +1,227 @@ +.Dd $Mdocdate: Mar 3 2026$ +.Dt SBCHOOSER 1 +.Sh NAME +.Nm sbchooser +.Nd Utility to sort bootloaders by trust +.Nm +.Sh SYNOPSIS +.Nm +.Oo OPTIONS +.Ek +.Sh DESCRIPTION +.Nm +is a command line utility to determine which of several UEFI bootloaders is +most preferred based on what certificates it is signed by. The output is a +list, one entry per line, of the acceptable loaders, with the best choice +first. + +This utility is meant to be used by system installation and upgrade software, +in order to choose the most appropriate bootloader to install based on the +system trust, especially during periods of transition between trust anchors. + +When sorting, \fBsbchooser\fR chooses the most preferred binary using the +following criteria, from highest priority to lowest: +.Bl -dash +.It +binaries hashed directly into DB, according to the security strength of the digest used +.It +binaries signed by a certificate in db or chaining to one, sorted by the +security strength of the signature. A binary signed by a certificate with 80 +bits of security strength and another signature with a 256-bit security +strength signature which are both trusted is scored as 80 bits; if only the +256-bit signature is trusted, it's scored as 256 bits. +.It +if all that is equal, it prefers the binary with the latest not_after in the +certificate chain. +.It +if all that is equal, it prefers the binary with the earliest not_before in +the certificate chain. +.El +.Pp +When an individual signature is scored, it is much the same. Each +signature's security strength is computed by checking each X509 certificate +in its signatures and taking the lowest security strength of any encryption +algorithm or message digest in use. That is to say, if an individual +signature has (for example) if there are 2 X509 certs co-signing a +signature and one has an RSA-1024 key but the other has an RSA-2048 key, +it's an RSA-1024 (80-bit) signature. In comparing signatures, again the +validity dates are used as a last resort in determining a preference. +.Sh OPTIONS +.Bl -tag +.It Ao Fl d | Fl Fl db Ar db-file Ac +Load a UEFI trusted key database from \fIdb\-file\fR +.It Ao Fl D | Fl Fl no-system-db Ac +Do not load the UEFI trusted key database from this system. +.It Ao Fl s | Fl Fl system-db Ac +Load the UEFI trusted key database from this system (default) +.It Ao Fl f | Fl Fl first-sig-only Ac +Only consider the first signature on an input file +.It Ao Fl x | Fl Fl dbx Ar dbx-file Ac +Load a UEFI revoked key database from \fIdbx\-file\fR +.It Ao Fl X | Fl Fl no-system-dbx Ac +Do not load the UEFI revoked key database from this system. +.It Ao Fl S | Fl Fl system-dbx Ac +Load the UEFI revoked key database from this system (default) +.It Ao Fl i | Fl Fl input Ar pe-file Ac +Load an EFI binary from \fIpe\-file\fR. + +By default, if \fB-i\fR is not used, \fBsbchooser\fR reads a list of input files on \fIstandard in\fR. If \fIpe-file\fR is \fB-\fR, \fBsbchooser\fR will look for input files on \fIstandard in\fR as well as any \fB-i\fR input options. +.It Ao Fl e | Fl Fl explain Ac +Instead of producing the normal results, attempt to explain the reason for +trusting or distrusting each input PE file. +.It Fl Fl Ec +All following options are treated as input files. Can be used with \fB-i -\fR to suppliment \fIstandard in\fR. +.El +.Sh EXAMPLES +In the following examples, the following conventions are observed: +.Bl -dash +.It +An EFI binary with \fB.nosigs.\fR in its name has no signatures. +.It +An EFI binary with \fB.msft2011.\fR in its name is signed by the UEFI 2011 certificate. +.It +An EFI binary with \fB.msft2023.\fR in its name is signed by the UEFI 2023 certificate. +.It +When an EFI binary has more than one signature, the order in the filename is the same as the order of the signatures on the binary. +.It +\fBdb.msft2011\fR is a security database containing the UEFI CA 2011 certificate. +.It +\fBdb.msft2023\fR is a security database containing the UEFI CA 2023 certificate. +.It +Database files of the form \fBdb.shim-15.5.el7.x64.sha256\fR contain a sha256 authenticode sum of \fBshim-15.5.el7.x64.efi\fR. +.El +.Ss Choosing which bootloader is most appropriate: +.Bd -literal +host:~$ sbchooser -- \\ + /usr/lib/shim/shim-13-0.2.fedora.x64.nosigs.efi \\ + /usr/lib/shim/shim-15-2.fedora.x64.msft2011.efi \\ + /usr/lib/shim/shim-15-7.el7_2.x64.msft2011.efi \\ + /usr/lib/shim/shim-15-7.el7_2.x64.nosigs.efi \\ + /usr/lib/shim/shim-15.5-1.el9.x64.msft2011.efi \\ + /usr/lib/shim/shim-15.5.el7.x64.nosigs.efi \\ + /usr/lib/shim/shim-16.1-4.el10.x64.msft2011.efi \\ + /usr/lib/shim/shim-16.1-4.el10.x64.msft2011.msft2023.efi \\ + /usr/lib/shim/shim-16.1-4.el10.x64.msft2023.efi +/usr/lib/shim/shim-16.1-4.el10.x64.msft2011.msft2023.efi +/usr/lib/shim/shim-16.1-4.el10.x64.msft2023.efi +/usr/lib/shim/shim-16.1-4.el10.x64.msft2011.efi +/usr/lib/shim/shim-15.5-1.el9.x64.msft2011.efi +/usr/lib/shim/shim-15-7.el7_2.x64.msft2011.efi +/usr/lib/shim/shim-15-2.fedora.x64.msft2011.efi +host:~$ +.Ed +.Ss Choosing which bootloader is most appropriate with local security databases: +.Bd -literal +host:~$ sbchooser --db db.msft2023 --dbx db.msft2011 -- \\ + /usr/lib/shim/shim-13-0.2.fedora.x64.nosigs.efi \\ + /usr/lib/shim/shim-15-2.fedora.x64.msft2011.efi \\ + /usr/lib/shim/shim-15-7.el7_2.x64.msft2011.efi \\ + /usr/lib/shim/shim-15-7.el7_2.x64.nosigs.efi \\ + /usr/lib/shim/shim-15.5-1.el9.x64.msft2011.efi \\ + /usr/lib/shim/shim-15.5.el7.x64.nosigs.efi \\ + /usr/lib/shim/shim-16.1-4.el10.x64.msft2011.efi \\ + /usr/lib/shim/shim-16.1-4.el10.x64.msft2011.msft2023.efi \\ + /usr/lib/shim/shim-16.1-4.el10.x64.msft2023.efi +/usr/lib/shim/shim-16.1-4.el10.x64.msft2011.msft2023.efi +/usr/lib/shim/shim-16.1-4.el10.x64.msft2023.efi +host:~$ +.Ed +.Ss Choosing which bootloader is most appropriate with the system trusted database but a local revocation database: +.Bd -literal +host:~$ sbchooser --dbx db.msft2023 -- \\ + /usr/lib/shim/shim-13-0.2.fedora.x64.nosigs.efi \\ + /usr/lib/shim/shim-15-2.fedora.x64.msft2011.efi \\ + /usr/lib/shim/shim-15-7.el7_2.x64.msft2011.efi \\ + /usr/lib/shim/shim-15-7.el7_2.x64.nosigs.efi \\ + /usr/lib/shim/shim-15.5-1.el9.x64.msft2011.efi \\ + /usr/lib/shim/shim-15.5.el7.x64.nosigs.efi \\ + /usr/lib/shim/shim-16.1-4.el10.x64.msft2011.efi \\ + /usr/lib/shim/shim-16.1-4.el10.x64.msft2011.msft2023.efi \\ + /usr/lib/shim/shim-16.1-4.el10.x64.msft2023.efi +/usr/lib/shim/shim-16.1-4.el10.x64.msft2011.msft2023.efi +/usr/lib/shim/shim-16.1-4.el10.x64.msft2011.efi +/usr/lib/shim/shim-15.5-1.el9.x64.msft2011.efi +/usr/lib/shim/shim-15-7.el7_2.x64.msft2011.efi +/usr/lib/shim/shim-15-2.fedora.x64.msft2011.efi +host:~$ +.Ed +.Ss Choosing which bootloader is most appropriate with a local trusted database, but only considering the first signature: +.Bd -literal +host:~$ sbchooser -f -d db.msft2023 -- \\ + /usr/lib/shim/shim-13-0.2.fedora.x64.nosigs.efi \\ + /usr/lib/shim/shim-15-2.fedora.x64.msft2011.efi \\ + /usr/lib/shim/shim-15-7.el7_2.x64.msft2011.efi \\ + /usr/lib/shim/shim-15-7.el7_2.x64.nosigs.efi \\ + /usr/lib/shim/shim-15.5-1.el9.x64.msft2011.efi \\ + /usr/lib/shim/shim-15.5.el7.x64.nosigs.efi \\ + /usr/lib/shim/shim-16.1-4.el10.x64.msft2011.efi \\ + /usr/lib/shim/shim-16.1-4.el10.x64.msft2011.msft2023.efi \\ + /usr/lib/shim/shim-16.1-4.el10.x64.msft2023.efi +/usr/lib/shim/shim-16.1-4.el10.x64.msft2023.efi +host:~$ +.Ed +.Ss Augmenting the system trust database with only msft2011 enrolled: +.Bd -literal +host:~$ sbchooser -s -d db.shim-15.5.el7.x64.sha256 -- \\ + /usr/lib/shim/shim-13-0.2.fedora.x64.nosigs.efi \\ + /usr/lib/shim/shim-15-2.fedora.x64.msft2011.efi \\ + /usr/lib/shim/shim-15-7.el7_2.x64.msft2011.efi \\ + /usr/lib/shim/shim-15-7.el7_2.x64.nosigs.efi \\ + /usr/lib/shim/shim-15.5-1.el9.x64.msft2011.efi \\ + /usr/lib/shim/shim-15.5.el7.x64.nosigs.efi \\ + /usr/lib/shim/shim-16.1-4.el10.x64.msft2011.efi \\ + /usr/lib/shim/shim-16.1-4.el10.x64.msft2011.msft2023.efi \\ + /usr/lib/shim/shim-16.1-4.el10.x64.msft2023.efi +/usr/lib/shim/shim-15.5.el7.x64.nosigs.efi +/usr/lib/shim/shim-15.5-1.el9.x64.msft2011.efi +/usr/lib/shim/shim-16.1-4.el10.x64.msft2011.msft2023.efi +/usr/lib/shim/shim-16.1-4.el10.x64.msft2011.efi +/usr/lib/shim/shim-15-7.el7_2.x64.msft2011.efi +/usr/lib/shim/shim-15-2.fedora.x64.msft2011.efi +host:~$ +.Ed +.Ss Explanation of choosing which bootloader is most appropriate with local security databases: +.Bd -literal +host:~$ sbchooser --db db.msft2023 --dbx db.msft2011 --explain -- \\ + /usr/lib/shim/shim-13-0.2.fedora.x64.nosigs.efi \\ + /usr/lib/shim/shim-15-2.fedora.x64.msft2011.efi \\ + /usr/lib/shim/shim-15-7.el7_2.x64.msft2011.efi \\ + /usr/lib/shim/shim-15-7.el7_2.x64.nosigs.efi \\ + /usr/lib/shim/shim-15.5-1.el9.x64.msft2011.efi \\ + /usr/lib/shim/shim-15.5.el7.x64.nosigs.efi \\ + /usr/lib/shim/shim-16.1-4.el10.x64.msft2011.efi \\ + /usr/lib/shim/shim-16.1-4.el10.x64.msft2011.msft2023.efi \\ + /usr/lib/shim/shim-16.1-4.el10.x64.msft2023.efi +/usr/lib/shim/shim-16.1-4.el10.x64.msft2011.msft2023.efi is trusted because cert "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft UEFI CA 2023 signer" is trusted by "/C=US/O=Microsoft Corporation/CN=Microsoft UEFI CA 2023" in db +/usr/lib/shim/shim-16.1-4.el10.x64.msft2023.efi is trusted because cert "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft UEFI CA 2023 signer" is trusted by "/C=US/O=Microsoft Corporation/CN=Microsoft UEFI CA 2023" in db +/usr/lib/shim/shim-16.1-4.el10.x64.msft2011.efi is not trusted because cert "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Windows UEFI Driver Publisher" is revoked by "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation UEFI CA 2011" in dbx +/usr/lib/shim/shim-15.5-1.el9.x64.msft2011.efi is not trusted because cert "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Windows UEFI Driver Publisher" is revoked by "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation UEFI CA 2011" in dbx +/usr/lib/shim/shim-15-7.el7_2.x64.msft2011.efi is not trusted because cert "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Windows UEFI Driver Publisher" is revoked by "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation UEFI CA 2011" in dbx +/usr/lib/shim/shim-15-2.fedora.x64.msft2011.efi is not trusted because cert "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Windows UEFI Driver Publisher" is revoked by "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation UEFI CA 2011" in dbx +/usr/lib/shim/shim-13-0.2.fedora.x64.nosigs.efi is not trusted because no certs or hashes trust it +/usr/lib/shim/shim-15-7.el7_2.x64.nosigs.efi is not trusted because no certs or hashes trust it +/usr/lib/shim/shim-15.5.el7.x64.nosigs.efi is not trusted because no certs or hashes trust it +host:~$ +.Ed +.Sh STANDARDS +.Rs +.%A UEFI Specification Working Group +.%B Unified Extensible Firmware Interface (UEFI) Specification Version 2.11 +.%I UEFI Forum +.%D March 2026 +.%U https://uefi.org/specifications\ \& +.Sh SEE ALSO +.Xr pesign 1 , +.Xr efikeygen 1 , +.Xr efisecdb 1 +.Sh AUTHORS +.An Peter Jones +.Sh BUGS +.Bl -dash +.It +.Nm +currently ignores \fI.sbat\fR revocation sections, and does not consider that a bootloader may be revoked by the \fISBAT\fR self-check. +.It +.Nm +currently does not treat certificates revoked by their \fITo-Be-Signed\fR hash in \fIdbx\fR as revoked. +.El diff --git a/src/.gitignore b/src/.gitignore index 9e1cc235..2a5d3515 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -8,5 +8,6 @@ efisecdb-static makeguids guid-symbols.c guids.lds +sbchooser thread-test util-makeguids.c diff --git a/src/Makefile b/src/Makefile index 3ec7f18e..04f6400f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -7,8 +7,8 @@ include $(TOPDIR)/src/include/defaults.mk LIBTARGETS=libefivar.so libefiboot.so libefisec.so STATICLIBTARGETS=libefivar.a libefiboot.a libefisec.a -BINTARGETS=efivar efisecdb thread-test -STATICBINTARGETS=efivar-static efisecdb-static +BINTARGETS=efivar efisecdb sbchooser thread-test +STATICBINTARGETS=efivar-static efisecdb-static sbchooser-static PCTARGETS=efivar.pc efiboot.pc efisec.pc TARGETS=$(LIBTARGETS) $(BINTARGETS) $(PCTARGETS) STATICTARGETS=$(STATICLIBTARGETS) $(STATICBINTARGETS) @@ -26,6 +26,8 @@ EFIVAR_SOURCES = efivar.c guid.c util.c EFIVAR_OBJECTS = $(patsubst %.S,%.o,$(patsubst %.c,%.o,$(EFIVAR_SOURCES))) EFISECDB_SOURCES = efisecdb.c guid-symbols.c secdb-dump.c util.c EFISECDB_OBJECTS = $(patsubst %.S,%.o,$(patsubst %.c,%.o,$(EFISECDB_SOURCES))) +SBCHOOSER_SOURCES = sbchooser.c sbchooser-pe.c sbchooser-db.c sbchooser-x509.c authenticode.c error.c +SBCHOOSER_OBJECTS = $(patsubst %.S,%.o,$(patsubst %.c,%.o,$(SBCHOOSER_SOURCES))) GENERATED_SOURCES = include/efivar/efivar-guids.h guid-symbols.c MAKEGUIDS_SOURCES = makeguids.c util-makeguids.c MAKEGUIDS_OBJECTS = $(patsubst %.S,%.o,$(patsubst %.c,%.o,$(MAKEGUIDS_SOURCES))) @@ -36,7 +38,7 @@ util-makeguids.c : util.c ALL_SOURCES=$(LIBEFISEC_SOURCES) $(LIBEFIBOOT_SOURCES) $(LIBEFIVAR_SOURCES) \ $(MAKEGUIDS_SOURCES) $(GENERATED_SOURCES) $(EFIVAR_SOURCES) \ - $(EFISECDB_SOURCES) \ + $(EFISECDB_SOURCES) $(SBCHOOSER_SOURCES) \ $(sort $(wildcard include/efivar/*.h)) ifneq ($(MAKECMDGOALS),clean) @@ -126,6 +128,14 @@ efisecdb-static : $(patsubst %.o,%.static.o,$(LIBEFISEC_OBJECTS) $(LIBEFIVAR_OBJ efisecdb-static : | $(GENERATED_SOURCES) efisecdb-static : private LIBS=crypto dl +sbchooser : private LIBS=crypto efisec efivar +sbchooser : $(SBCHOOSER_OBJECTS) +sbchooser : | $(GENERATED_SOURCES) + +sbchooser-static : private LIBS=crypto efisec efivar +sbchooser-static : $(SBCHOOSER_OBJECTS) +sbchooser-static : | $(GENERATED_SOURCES) + thread-test : libefivar.so # make sure we don't propagate CFLAGS to object files used by 'libefivar.so' thread-test.o : private CFLAGS=$(HOST_CFLAGS) -I$(TOPDIR)/src/include/efivar diff --git a/src/authenticode.c b/src/authenticode.c new file mode 100644 index 00000000..5e4d055f --- /dev/null +++ b/src/authenticode.c @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-v3-or-later +/* + * authenticode.c - implement the authenticode digest function + * Copyright Peter Jones + */ + +#include "sbchooser.h" // IWYU pragma: keep + +void +fmt_digest(digest_data_t *dgst, char *buf, size_t bufsz) +{ + size_t pos = 0; + memset(buf, 0, bufsz); + + if (!dgst) { + debug("dgst is NULL!"); + return; + } + + if (!dgst->data || dgst->datasz == 0) { + debug("dgst->data:%p dgst->datasz:%zu", dgst->data, dgst->datasz); + return; + } + for (size_t i = 0; i < dgst->datasz; i++) { + size_t rc; + + rc = snprintf(&buf[pos], bufsz - pos, "%02x", dgst->data[i]); + if (rc == 0) { + buf[pos] = '\0'; + break; + } + pos += rc; + } + buf[bufsz - 1] = '\0'; +} + +/* + * most of this is just used as intermediates - when we're done computing + * things, only digest_size and digest are really useful. + */ +struct digest_buffer { + /* + * the openssl short name for the algorithm. This is used to look + * up openssl's NID representation of the algorithm. + */ + char algorithm[256]; + int algorithm_nid; // used to look up the algorithm + EVP_MD_CTX *mdctx; // openssl MD context + digest_data_t dgst; // output buffer +}; +typedef struct digest_buffer digest_buffer_t; + +static int +generate_authenticode_begin(digest_buffer_t **dbufs) +{ + digest_buffer_t *dbp = NULL; + for (size_t i = 0; dbufs[i] != NULL; i++) { + const EVP_MD *md; + + dbp = dbufs[i]; + + dbp->mdctx = EVP_MD_CTX_new(); + if (!dbp->mdctx) { + warnx("Could not create message digest context"); + goto err; + } + + dbp->algorithm_nid = OBJ_sn2nid(dbp->algorithm); + if (!strcmp(dbp->algorithm, "SHA256")) { + md = EVP_sha256(); + } else if (!strcmp(dbp->algorithm, "SHA384")) { + md = EVP_sha384(); + } else if (!strcmp(dbp->algorithm, "SHA512")) { + md = EVP_sha512(); + } else { + warnx("Unsupported digest \"%s\"", dbp->algorithm); + continue; + } + dbp->dgst.datasz = EVP_MD_get_size(md); + dbp->dgst.data = calloc(1, dbp->dgst.datasz); + if (!dbp->dgst.data) { + warn("Couldn't allocate digest buffer"); + goto err; + } + if (!EVP_DigestInit(dbp->mdctx, md)) { + warnx("Couldn't initialize digest %s", dbp->algorithm); + goto err; + } + } + return 0; + +err: + for (size_t i = 0; dbufs[i] != NULL; i++) { + dbp = dbufs[i]; + + if (dbp->mdctx) { + EVP_MD_CTX_free(dbp->mdctx); + dbp->mdctx = NULL; + } + dbp->dgst.datasz = 0; + if (dbp->dgst.data) { + free(dbp->dgst.data); + dbp->dgst.data = NULL; + } + } + return -1; + +} + +static void +update_all_hashes(digest_buffer_t **dbufs, void *data, size_t size) +{ + for (size_t i = 0; dbufs[i] != NULL; i++) { + digest_buffer_t *dbp = dbufs[i]; + + if (dbp->mdctx == NULL) + continue; + + EVP_DigestUpdate(dbp->mdctx, data, size); + } +} + +static int +generate_authenticode_digest(pe_file_t *pe, digest_buffer_t **dbufs) +{ + char *hashbase; + unsigned int hashsize; + + unsigned int sum_of_bytes_hashed; + unsigned int sum_of_section_bytes; + unsigned int index; + unsigned int pos; + + efi_image_section_header_t *section; + efi_image_section_header_t *section_header = NULL; + + pe_image_context_t *ctx = &pe->ctx; + + errno = EINVAL; + + // hash start to checksum + hashbase = pe->map; + hashsize = (char *)&ctx->pe_header->pe32.optional_header.checksum - hashbase; + update_all_hashes(dbufs, hashbase, hashsize); + + // hash post-checksum to start of cert table + hashbase = (char *)&ctx->pe_header->pe32.optional_header.checksum + sizeof (int); + hashsize = (char *)ctx->sec_dir - hashbase; + update_all_hashes(dbufs, hashbase, hashsize); + + // hash end of cert table to end of image header + efi_image_data_directory_t *dd = ctx->sec_dir + 1; + hashbase = (char *)dd; + hashsize = ctx->size_of_headers - (unsigned long)((char *)dd - (char *)pe->map); + if (hashsize > pe->mapsz) { + warnx("Data directory is invalid"); + goto err; + } + update_all_hashes(dbufs, hashbase, hashsize); + + // sort sections... + sum_of_bytes_hashed = ctx->size_of_headers; + + section_header = calloc(ctx->number_of_sections, sizeof (efi_image_section_header_t)); + if (!section_header) + goto err; + + /* + * validate section locations and sizes, and sort the table into + * our newly allocated copy + */ + sum_of_section_bytes = 0; + section = ctx->first_section; + for (index = 0; index < ctx->number_of_sections; index++) { + efi_image_section_header_t *secp; + char *base; + size_t size; + int rc; + + rc = get_section_vma(pe, index, &base, &size, &secp); + if (rc < 0) { + if (errno == ENOENT) { + break; + } else { + warnx("Malformed section header"); + goto err; + } + } + + // validate section size is within image + if (secp->size_of_raw_data > + pe->mapsz - sum_of_bytes_hashed - sum_of_section_bytes) { + warnx("Malformed section %d size", index); + errno = EINVAL; + goto err; + } + sum_of_section_bytes += secp->size_of_raw_data; + + pos = index; + while ((pos > 0) && + (section->pointer_to_raw_data < section_header[pos - 1].pointer_to_raw_data)) { + memcpy(§ion_header[pos], §ion_header[pos - 1], + sizeof(efi_image_section_header_t)); + pos--; + } + memcpy(§ion_header[pos], section, sizeof (efi_image_section_header_t)); + section += 1; + } + errno = EINVAL; + + // hash the sections + for (index = 0; index < ctx->number_of_sections; index++) { + section = §ion_header[index]; + if (section->size_of_raw_data == 0) { + continue; + } + + hashbase = pe->map + section->pointer_to_raw_data; + + if (section->size_of_raw_data > + pe->mapsz - section->pointer_to_raw_data) { + warnx("Malformed section %u raw size", index); + goto err; + } + hashsize = (unsigned int)section->size_of_raw_data; + update_all_hashes(dbufs, hashbase, hashsize); + + sum_of_bytes_hashed += section->size_of_raw_data; + } + + // hash all remaining data up to sec_dir if sec_dir->size is not 0 + if (pe->mapsz > sum_of_bytes_hashed && ctx->sec_dir->size) { + hashbase = pe->map + sum_of_bytes_hashed; + hashsize = pe->mapsz - ctx->sec_dir->size - sum_of_bytes_hashed; + + update_all_hashes(dbufs, hashbase, hashsize); + + sum_of_bytes_hashed += hashsize; + } + + /* + * Hash all remaining data. If sec_dir->size is > 0 this code should + * not be entered. If it is, there are still things to hash. For + * a file without a sec_dir, we need to hash what remains. + */ + if (pe->mapsz > sum_of_bytes_hashed + ctx->sec_dir->size) { + debug("sobh:%zu mapsz:%zu secdir size:%zu", sum_of_bytes_hashed, pe->mapsz, ctx->sec_dir->size); + + hashbase = pe->map + sum_of_bytes_hashed; + hashsize = pe->mapsz - sum_of_bytes_hashed - ctx->sec_dir->size; + + update_all_hashes(dbufs, hashbase, hashsize); + sum_of_bytes_hashed += hashsize; + if (sum_of_bytes_hashed % 8 != 0) { + char padbuf[8]; + + memset(padbuf, 0, sizeof(padbuf)); + hashsize = ALIGNMENT_PADDING(sum_of_bytes_hashed, 8); + update_all_hashes(dbufs, padbuf, hashsize); + } + } + + free(section_header); + return 0; +err: + if (section_header) + free(section_header); + return -1; +} + +static int +generate_authenticode_final(digest_buffer_t **dbufs) +{ + for (size_t i = 0; dbufs[i] != NULL; i++) { + digest_buffer_t *dbp = dbufs[i]; + + EVP_DigestFinal(dbp->mdctx, dbp->dgst.data, NULL); + EVP_MD_CTX_free(dbp->mdctx); + dbp->mdctx = NULL; + } + return 0; +} + +int +generate_authenticode(pe_file_t *pe) +{ + int rc; + + digest_buffer_t sha256_dbuf = { + .algorithm = "SHA256", + .algorithm_nid = -1, + .dgst = { + .data = NULL, + .datasz = 0, + }, + .mdctx = NULL, + }; + digest_buffer_t sha384_dbuf = { + .algorithm = "SHA384", + .algorithm_nid = -1, + .dgst = { + .data = NULL, + .datasz = 0, + }, + .mdctx = NULL, + }; + digest_buffer_t sha512_dbuf = { + .algorithm = "SHA512", + .algorithm_nid = -1, + .dgst = { + .data = NULL, + .datasz = 0, + }, + .mdctx = NULL, + }; + digest_buffer_t *dbufs[] = { + &sha256_dbuf, + &sha384_dbuf, + &sha512_dbuf, + NULL + }; + rc = generate_authenticode_begin(dbufs); + if (rc < 0) + goto err; + + rc = generate_authenticode_digest(pe, dbufs); + if (rc < 0) + goto err; + + rc = generate_authenticode_final(dbufs); + if (rc < 0) + goto err; + + for (size_t i = 0; dbufs[i] != NULL; i++) { + digest_buffer_t *dbp = dbufs[i]; + + if (!strcmp(dbp->algorithm, "SHA256")) { + pe->sha256.data = dbp->dgst.data; + pe->sha256.datasz = dbp->dgst.datasz; + } else if (!strcmp(dbp->algorithm, "SHA384")) { + pe->sha384.data = dbp->dgst.data; + pe->sha384.datasz = dbp->dgst.datasz; + } else if (!strcmp(dbp->algorithm, "SHA512")) { + pe->sha512.data = dbp->dgst.data; + pe->sha512.datasz = dbp->dgst.datasz; + } + EVP_MD_CTX_free(dbp->mdctx); + } + + if (efi_get_verbose() >= DEBUG_LEVEL) { + for (size_t i = 0; dbufs[i] != NULL; i++) { + char buf[1024]; + digest_buffer_t *dbp = dbufs[i]; + + fmt_digest(&dbp->dgst, buf, 1024); + fprintf(efi_get_logfile(), "%s:%s\n", + dbp->algorithm, buf); + } + } + + return 0; +err: + for (size_t i = 0; dbufs[i] != NULL; i++) { + digest_buffer_t *dbp = dbufs[i]; + + dbp->dgst.datasz = 0; + if (dbp->dgst.data != NULL) { + free(dbp->dgst.data); + dbp->dgst.data = NULL; + } + if (dbp->mdctx != NULL) { + EVP_MD_CTX_free(dbp->mdctx); + dbp->mdctx = NULL; + } + } + + return -1; +} + +// vim:fenc=utf-8:tw=75:noet diff --git a/src/compiler.h b/src/compiler.h index 2700fe64..96c53f0b 100644 --- a/src/compiler.h +++ b/src/compiler.h @@ -79,4 +79,39 @@ #define ALIGNMENT_PADDING(value, align) ((align - (value % align)) % align) #define ALIGN_UP(value, align) ((value) + ALIGNMENT_PADDING(value, align)) +#if GNUC_PREREQ(5, 1) || CLANG_PREREQ(3, 8) +#define checked_add(addend0, addend1, sum) \ + __builtin_add_overflow(addend0, addend1, sum) +#define checked_sub(minuend, subtrahend, difference) \ + __builtin_sub_overflow(minuend, subtrahend, difference) +#define checked_mul(factor0, factor1, product) \ + __builtin_mul_overflow(factor0, factor1, product) +#else +#define checked_add(a0, a1, s) \ + ({ \ + (*s) = ((a0) + (a1)); \ + 0; \ + }) +#define checked_sub(s0, s1, d) \ + ({ \ + (*d) = ((s0) - (s1)); \ + 0; \ + }) +#define checked_mul(f0, f1, p) \ + ({ \ + (*p) = ((f0) * (f1)); \ + 0; \ + }) +#endif + +#define checked_div(dividend, divisor, quotient) \ + ({ \ + bool _ret = True; \ + if ((divisor) != 0) { \ + _ret = False; \ + (quotient) = (dividend) / (divisor); \ + } \ + _ret; \ + }) + // vim:fenc=utf-8:tw=75:noet diff --git a/src/peimage.h b/src/peimage.h new file mode 100644 index 00000000..e56ca914 --- /dev/null +++ b/src/peimage.h @@ -0,0 +1,817 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * EFI image format for PE32, PE32+ and TE. Please note some data structures + * are different for PE32 and PE32+. efi_image_nt_headers32_t is for PE32 and + * efi_image_nt_headers64_t is for PE32+. + * + * This file is coded to the Visual Studio, Microsoft Portable Executable and + * Common Object File Format Specification, Revision 8.0 - May 16, 2006. This + * file also includes some definitions in PI Specification, Revision 1.0. + * + * Copyright (c) 2006 - 2010, Intel Corporation. All rights reserved. + * Portions copyright (c) 2008 - 2009, Apple Inc. All rights reserved. + */ + +#pragma once + +#include + +#include "efivar/efivar-types.h" +#include "efivar/efisec-types.h" +#include "uchar.h" + +#define SIGNATURE_16(A, B) \ + ((uint16_t)(((uint16_t)(A)) | (((uint16_t)(B)) << ((uint16_t)8)))) +#define SIGNATURE_32(A, B, C, D) \ + ((uint32_t)(((uint32_t)SIGNATURE_16(A, B)) | \ + (((uint32_t)SIGNATURE_16(C, D)) << (uint32_t)16))) +#define SIGNATURE_64(A, B, C, D, E, F, G, H) \ + ((uint64_t)((uint64_t)SIGNATURE_32(A, B, C, D) | \ + ((uint64_t)(SIGNATURE_32(E, F, G, H)) << (uint64_t)32))) + +#define ALIGN_VALUE(Value, Alignment) ((Value) + (((Alignment) - (Value)) & ((Alignment) - 1))) +#define ALIGN_POINTER(Pointer, Alignment) ((VOID *) (ALIGN_VALUE ((uintptr_t)(Pointer), (Alignment)))) + +// Check if `val` is evenly aligned to the page size. +#define IS_PAGE_ALIGNED(val) (!((val) & EFI_PAGE_MASK)) + +/* + * PE32+ Subsystem type for EFI images + */ +#define EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION 10 +#define EFI_IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER 11 +#define EFI_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER 12 +#define EFI_IMAGE_SUBSYSTEM_SAL_RUNTIME_DRIVER 13 // defined PI Specification, 1.0 + + +/* + * PE32+ Machine type for EFI images + */ +#define IMAGE_FILE_MACHINE_I386 0x014c +#define IMAGE_FILE_MACHINE_IA64 0x0200 +#define IMAGE_FILE_MACHINE_EBC 0x0EBC +#define IMAGE_FILE_MACHINE_X64 0x8664 +#define IMAGE_FILE_MACHINE_ARMTHUMB_MIXED 0x01c2 +#define IMAGE_FILE_MACHINE_ARM64 0xaa64 + +/* + * EXE file formats + */ +#define EFI_IMAGE_DOS_SIGNATURE SIGNATURE_16('M', 'Z') +#define EFI_IMAGE_OS2_SIGNATURE SIGNATURE_16('N', 'E') +#define EFI_IMAGE_OS2_SIGNATURE_LE SIGNATURE_16('L', 'E') +#define EFI_IMAGE_NT_SIGNATURE SIGNATURE_32('P', 'E', '\0', '\0') + +/* + * PE images can start with an optional DOS header, so if an image is run + * under DOS it can print an error message. + */ +typedef struct { + uint16_t e_magic; // Magic number. + uint16_t e_cblp; // Bytes on last page of file. + uint16_t e_cp; // Pages in file. + uint16_t e_crlc; // Relocations. + uint16_t e_cparhdr; // Size of header in paragraphs. + uint16_t e_minalloc; // Minimum extra paragraphs needed. + uint16_t e_maxalloc; // Maximum extra paragraphs needed. + uint16_t e_ss; // Initial (relative) SS value. + uint16_t e_sp; // Initial SP value. + uint16_t e_csum; // Checksum. + uint16_t e_ip; // Initial IP value. + uint16_t e_cs; // Initial (relative) CS value. + uint16_t e_lfarlc; // File address of relocation table. + uint16_t e_ovno; // Overlay number. + uint16_t e_res[4]; // Reserved words. + uint16_t e_oemid; // OEM identifier (for e_oeminfo). + uint16_t e_oeminfo; // OEM information; e_oemid specific. + uint16_t e_res2[10]; // Reserved words. + uint32_t e_lfanew; // File address of new exe header. +} efi_image_dos_header_t; + +/* + * COFF File Header (Object and Image). + */ +typedef struct { + uint16_t machine; + uint16_t number_of_sections; + uint32_t time_date_stamp; + uint32_t pointer_to_symbol_table; + uint32_t number_of_symbols; + uint16_t size_of_optional_header; + uint16_t characteristics; +} efi_image_file_header_t; + +/* + * Size of efi_image_file_header_t. + */ +#define EFI_IMAGE_SIZEOF_FILE_HEADER 20 + +/* + * characteristics + */ +#define EFI_IMAGE_FILE_RELOCS_STRIPPED (1 << 0) // 0x0001 Relocation info stripped from file. +#define EFI_IMAGE_FILE_EXECUTABLE_IMAGE (1 << 1) // 0x0002 File is executable (i.e. no unresolved externel references). +#define EFI_IMAGE_FILE_LINE_NUMS_STRIPPED (1 << 2) // 0x0004 Line nunbers stripped from file. +#define EFI_IMAGE_FILE_LOCAL_SYMS_STRIPPED (1 << 3) // 0x0008 Local symbols stripped from file. +#define EFI_IMAGE_FILE_BYTES_REVERSED_LO (1 << 7) // 0x0080 Bytes of machine word are reversed. +#define EFI_IMAGE_FILE_32BIT_MACHINE (1 << 8) // 0x0100 32 bit word machine. +#define EFI_IMAGE_FILE_DEBUG_STRIPPED (1 << 9) // 0x0200 Debugging info stripped from file in .DBG file. +#define EFI_IMAGE_FILE_SYSTEM (1 << 12) // 0x1000 system File. +#define EFI_IMAGE_FILE_DLL (1 << 13) // 0x2000 File is a DLL. +#define EFI_IMAGE_FILE_BYTES_REVERSED_HI (1 << 15) // 0x8000 Bytes of machine word are reversed. + +/* + * Header Data Directories. + */ +typedef struct { + uint32_t virtual_address; + uint32_t size; +} efi_image_data_directory_t; + +/* + * Directory Entries + */ +#define EFI_IMAGE_DIRECTORY_ENTRY_EXPORT 0 +#define EFI_IMAGE_DIRECTORY_ENTRY_IMPORT 1 +#define EFI_IMAGE_DIRECTORY_ENTRY_RESOURCE 2 +#define EFI_IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 +#define EFI_IMAGE_DIRECTORY_ENTRY_SECURITY 4 +#define EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC 5 +#define EFI_IMAGE_DIRECTORY_ENTRY_DEBUG 6 +#define EFI_IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 +#define EFI_IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 +#define EFI_IMAGE_DIRECTORY_ENTRY_TLS 9 +#define EFI_IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 + +#define EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES 16 + +/* + * @attention + * EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC means PE32 and + * efi_image_optional_header32_t must be used. The data structures only vary + * after NT additional fields. + */ +#define EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b + +/* + * Optional Header Standard Fields for PE32. + */ +typedef struct { + /* + * Standard fields. + */ + uint16_t magic; + uint8_t major_linker_version; + uint8_t minor_linker_version; + uint32_t size_of_code; + uint32_t size_of_initialized_data; + uint32_t size_of_uninitialized_data; + uint32_t address_of_entry_point; + uint32_t base_of_code; + uint32_t base_of_data; // PE32 contains this additional field, which is absent in PE32+. + /* + * Optional Header Windows-Specific Fields. + */ + uint32_t image_base; + uint32_t section_alignment; + uint32_t file_alignment; + uint16_t major_operating_system_version; + uint16_t minor_operating_system_version; + uint16_t major_image_version;; + uint16_t minor_image_version;; + uint16_t major_subsystem_version; + uint16_t minor_subsystem_version; + uint32_t win32_version_value; + uint32_t size_of_image; + uint32_t size_of_headers; + uint32_t checksum; + uint16_t subsystem; + uint16_t dll_characteristics; + uint32_t size_of_stack_reserve; + uint32_t size_of_stack_commit; + uint32_t size_of_heap_reserve; + uint32_t size_of_heap_commit; + uint32_t loader_flags; + uint32_t number_of_rva_and_sizes; + efi_image_data_directory_t data_directory[EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES]; +} efi_image_optional_header32_t; + +/* + * @attention + * EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC means PE32+ and + * efi_image_optional_header64_t must be used. The data structures only vary + * after NT additional fields. + */ +#define EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b + +/* + * Optional Header Standard Fields for PE32+. + */ +typedef struct { + /* + * Standard fields. + */ + uint16_t magic; + uint8_t major_linker_version; + uint8_t minor_linker_version; + uint32_t size_of_code; + uint32_t size_of_initialized_data; + uint32_t size_of_uninitialized_data; + uint32_t address_of_entry_point; + uint32_t base_of_code; + /* + * Optional Header Windows-Specific Fields. + */ + uint64_t image_base; + uint32_t section_alignment; + uint32_t file_alignment; + uint16_t major_operating_system_version; + uint16_t minor_operating_system_version; + uint16_t major_image_version;; + uint16_t minor_image_version;; + uint16_t major_subsystem_version; + uint16_t minor_subsystem_version; + uint32_t win32_version_value; + uint32_t size_of_image; + uint32_t size_of_headers; + uint32_t checksum; + uint16_t subsystem; + uint16_t dll_characteristics; + uint64_t size_of_stack_reserve; + uint64_t size_of_stack_commit; + uint64_t size_of_heap_reserve; + uint64_t size_of_heap_commit; + uint32_t loader_flags; + uint32_t number_of_rva_and_sizes; + efi_image_data_directory_t data_directory[EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES]; +} efi_image_optional_header64_t; + +#define EFI_IMAGE_DLLCHARACTERISTICS_RESERVED_0001 0x0001 +#define EFI_IMAGE_DLLCHARACTERISTICS_RESERVED_0002 0x0002 +#define EFI_IMAGE_DLLCHARACTERISTICS_RESERVED_0004 0x0004 +#define EFI_IMAGE_DLLCHARACTERISTICS_RESERVED_0008 0x0008 +#if 0 /* This is not in the PE spec. */ +#define EFI_IMAGE_DLLCHARACTERISTICS_RESERVED_0010 0x0010 +#endif +#define EFI_IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA 0x0020 +#define EFI_IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 0x0040 +#define EFI_IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 0x0080 +#define EFI_IMAGE_DLLCHARACTERISTICS_NX_COMPAT 0x0100 +#define EFI_IMAGE_DLLCHARACTERISTICS_NO_ISOLATION 0x0200 +#define EFI_IMAGE_DLLCHARACTERISTICS_NO_SEH 0x0400 +#define EFI_IMAGE_DLLCHARACTERISTICS_NO_BIND 0x0800 +#define EFI_IMAGE_DLLCHARACTERISTICS_APPCONTAINER 0x1000 +#define EFI_IMAGE_DLLCHARACTERISTICS_WDM_DRIVER 0x2000 +#define EFI_IMAGE_DLLCHARACTERISTICS_GUARD_CF 0x4000 +#define EFI_IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE 0x8000 + +/* + * @attention + * efi_image_nt_headers32_t is for use ONLY by tools. + */ +typedef struct { + uint32_t signature; + efi_image_file_header_t file_header; + efi_image_optional_header32_t optional_header; +} efi_image_nt_headers32_t; + +#define EFI_IMAGE_SIZEOF_NT_OPTIONAL32_HEADER sizeof (efi_image_nt_headers32_t) + +/* + * @attention + * efi_image_nt_headers64_t is for use ONLY by tools. + */ +typedef struct { + uint32_t signature; + efi_image_file_header_t file_header; + efi_image_optional_header64_t optional_header; +} efi_image_nt_headers64_t; + +#define EFI_IMAGE_SIZEOF_NT_OPTIONAL64_HEADER sizeof (efi_image_nt_headers64_t) + +/* + * Other Windows Subsystem Values + */ +#define EFI_IMAGE_SUBSYSTEM_UNKNOWN 0 +#define EFI_IMAGE_SUBSYSTEM_NATIVE 1 +#define EFI_IMAGE_SUBSYSTEM_WINDOWS_GUI 2 +#define EFI_IMAGE_SUBSYSTEM_WINDOWS_CUI 3 +#define EFI_IMAGE_SUBSYSTEM_OS2_CUI 5 +#define EFI_IMAGE_SUBSYSTEM_POSIX_CUI 7 + +/* + * Length of ShortName. + */ +#define EFI_IMAGE_SIZEOF_SHORT_NAME 8 + +/* + * Section Table. This table immediately follows the optional header. + */ +typedef struct { + uint8_t name[EFI_IMAGE_SIZEOF_SHORT_NAME]; + union { + uint32_t physical_address; + uint32_t virtual_size; + } misc; + uint32_t virtual_address; + uint32_t size_of_raw_data; + uint32_t pointer_to_raw_data; + uint32_t pointer_to_relocations; + uint32_t pointer_to_linenumbers; + uint16_t number_of_relocations; + uint16_t number_of_linenumbers; + uint32_t characteristics; +} efi_image_section_header_t; + +/* + * Size of efi_image_section_header_t. + */ +#define EFI_IMAGE_SIZEOF_SECTION_HEADER 40 + +/* + * Section Flags Values + */ +#define EFI_IMAGE_SCN_RESERVED_00000000 0x00000000 +#define EFI_IMAGE_SCN_RESERVED_00000001 0x00000001 +#define EFI_IMAGE_SCN_RESERVED_00000002 0x00000002 +#define EFI_IMAGE_SCN_RESERVED_00000004 0x00000004 +#define EFI_IMAGE_SCN_TYPE_NO_PAD 0x00000008 +#define EFI_IMAGE_SCN_RESERVED_00000010 0x00000010 +#define EFI_IMAGE_SCN_CNT_CODE 0x00000020 +#define EFI_IMAGE_SCN_CNT_INITIALIZED_DATA 0x00000040 +#define EFI_IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080 +#define EFI_IMAGE_SCN_LNK_OTHER 0x00000100 +#define EFI_IMAGE_SCN_LNK_INFO 0x00000200 +#define EFI_IMAGE_SCN_RESERVED_00000400 0x00000400 +#define EFI_IMAGE_SCN_LNK_REMOVE 0x00000800 +#define EFI_IMAGE_SCN_LNK_COMDAT 0x00001000 +#define EFI_IMAGE_SCN_RESERVED_00002000 0x00002000 +#define EFI_IMAGE_SCN_RESERVED_00004000 0x00004000 +#define EFI_IMAGE_SCN_GPREL 0x00008000 +/* + * PE 9.3 says both IMAGE_SCN_MEM_PURGEABLE and IMAGE_SCN_MEM_16BIT are + * 0x00020000, but I think it's wrong. --pjones + */ +#define EFI_IMAGE_SCN_MEM_PURGEABLE 0x00010000 // "Reserved for future use." +#define EFI_IMAGE_SCN_MEM_16BIT 0x00020000 // "Reserved for future use." +#define EFI_IMAGE_SCN_MEM_LOCKED 0x00040000 // "Reserved for future use." +#define EFI_IMAGE_SCN_MEM_PRELOAD 0x00080000 // "Reserved for future use." +#define EFI_IMAGE_SCN_ALIGN_1BYTES 0x00100000 +#define EFI_IMAGE_SCN_ALIGN_2BYTES 0x00200000 +#define EFI_IMAGE_SCN_ALIGN_4BYTES 0x00300000 +#define EFI_IMAGE_SCN_ALIGN_8BYTES 0x00400000 +#define EFI_IMAGE_SCN_ALIGN_16BYTES 0x00500000 +#define EFI_IMAGE_SCN_ALIGN_32BYTES 0x00600000 +#define EFI_IMAGE_SCN_ALIGN_64BYTES 0x00700000 +#define EFI_IMAGE_SCN_ALIGN_128BYTES 0x00800000 +#define EFI_IMAGE_SCN_ALIGN_256BYTES 0x00900000 +#define EFI_IMAGE_SCN_ALIGN_512BYTES 0x00a00000 +#define EFI_IMAGE_SCN_ALIGN_1024BYTES 0x00b00000 +#define EFI_IMAGE_SCN_ALIGN_2048BYTES 0x00c00000 +#define EFI_IMAGE_SCN_ALIGN_4096BYTES 0x00d00000 +#define EFI_IMAGE_SCN_ALIGN_8192BYTES 0x00e00000 +#define EFI_IMAGE_SCN_LNK_NRELOC_OVFL 0x01000000 +#define EFI_IMAGE_SCN_MEM_DISCARDABLE 0x02000000 +#define EFI_IMAGE_SCN_MEM_NOT_CACHED 0x04000000 +#define EFI_IMAGE_SCN_MEM_NOT_PAGED 0x08000000 +#define EFI_IMAGE_SCN_MEM_SHARED 0x10000000 +#define EFI_IMAGE_SCN_MEM_EXECUTE 0x20000000 +#define EFI_IMAGE_SCN_MEM_READ 0x40000000 +#define EFI_IMAGE_SCN_MEM_WRITE 0x80000000 + +/* + * Size of a Symbol Table Record. + */ +#define EFI_IMAGE_SIZEOF_SYMBOL 18 + +/* + * Symbols have a section number of the section in which they are + * defined. Otherwise, section numbers have the following meanings: + */ +#define EFI_IMAGE_SYM_UNDEFINED (uint16_t) 0 // Symbol is undefined or is common. +#define EFI_IMAGE_SYM_ABSOLUTE (uint16_t) -1 // Symbol is an absolute value. +#define EFI_IMAGE_SYM_DEBUG (uint16_t) -2 // Symbol is a special debug item. + +/* + * Symbol Type (fundamental) values. + */ +#define EFI_IMAGE_SYM_TYPE_NULL 0 // no type. +#define EFI_IMAGE_SYM_TYPE_VOID 1 // no valid type. +#define EFI_IMAGE_SYM_TYPE_CHAR 2 // type character. +#define EFI_IMAGE_SYM_TYPE_SHORT 3 // type short integer. +#define EFI_IMAGE_SYM_TYPE_INT 4 +#define EFI_IMAGE_SYM_TYPE_LONG 5 +#define EFI_IMAGE_SYM_TYPE_FLOAT 6 +#define EFI_IMAGE_SYM_TYPE_DOUBLE 7 +#define EFI_IMAGE_SYM_TYPE_STRUCT 8 +#define EFI_IMAGE_SYM_TYPE_UNION 9 +#define EFI_IMAGE_SYM_TYPE_ENUM 10 // enumeration. +#define EFI_IMAGE_SYM_TYPE_MOE 11 // member of enumeration. +#define EFI_IMAGE_SYM_TYPE_BYTE 12 +#define EFI_IMAGE_SYM_TYPE_WORD 13 +#define EFI_IMAGE_SYM_TYPE_UINT 14 +#define EFI_IMAGE_SYM_TYPE_DWORD 15 + +/* + * Symbol Type (derived) values. + */ +#define EFI_IMAGE_SYM_DTYPE_NULL 0 // no derived type. +#define EFI_IMAGE_SYM_DTYPE_POINTER 1 +#define EFI_IMAGE_SYM_DTYPE_FUNCTION 2 +#define EFI_IMAGE_SYM_DTYPE_ARRAY 3 + +/* + * Storage classes. + */ +#define EFI_IMAGE_SYM_CLASS_END_OF_FUNCTION ((uint8_t) -1) +#define EFI_IMAGE_SYM_CLASS_NULL 0 +#define EFI_IMAGE_SYM_CLASS_AUTOMATIC 1 +#define EFI_IMAGE_SYM_CLASS_EXTERNAL 2 +#define EFI_IMAGE_SYM_CLASS_STATIC 3 +#define EFI_IMAGE_SYM_CLASS_REGISTER 4 +#define EFI_IMAGE_SYM_CLASS_EXTERNAL_DEF 5 +#define EFI_IMAGE_SYM_CLASS_LABEL 6 +#define EFI_IMAGE_SYM_CLASS_UNDEFINED_LABEL 7 +#define EFI_IMAGE_SYM_CLASS_MEMBER_OF_STRUCT 8 +#define EFI_IMAGE_SYM_CLASS_ARGUMENT 9 +#define EFI_IMAGE_SYM_CLASS_STRUCT_TAG 10 +#define EFI_IMAGE_SYM_CLASS_MEMBER_OF_UNION 11 +#define EFI_IMAGE_SYM_CLASS_UNION_TAG 12 +#define EFI_IMAGE_SYM_CLASS_TYPE_DEFINITION 13 +#define EFI_IMAGE_SYM_CLASS_UNDEFINED_STATIC 14 +#define EFI_IMAGE_SYM_CLASS_ENUM_TAG 15 +#define EFI_IMAGE_SYM_CLASS_MEMBER_OF_ENUM 16 +#define EFI_IMAGE_SYM_CLASS_REGISTER_PARAM 17 +#define EFI_IMAGE_SYM_CLASS_BIT_FIELD 18 +#define EFI_IMAGE_SYM_CLASS_BLOCK 100 +#define EFI_IMAGE_SYM_CLASS_FUNCTION 101 +#define EFI_IMAGE_SYM_CLASS_END_OF_STRUCT 102 +#define EFI_IMAGE_SYM_CLASS_FILE 103 +#define EFI_IMAGE_SYM_CLASS_SECTION 104 +#define EFI_IMAGE_SYM_CLASS_WEAK_EXTERNAL 105 + +/* + * type packing constants + */ +#define EFI_IMAGE_N_BTMASK 017 +#define EFI_IMAGE_N_TMASK 060 +#define EFI_IMAGE_N_TMASK1 0300 +#define EFI_IMAGE_N_TMASK2 0360 +#define EFI_IMAGE_N_BTSHFT 4 +#define EFI_IMAGE_N_TSHIFT 2 + +/* + * Communal selection types. + */ +#define EFI_IMAGE_COMDAT_SELECT_NODUPLICATES 1 +#define EFI_IMAGE_COMDAT_SELECT_ANY 2 +#define EFI_IMAGE_COMDAT_SELECT_SAME_SIZE 3 +#define EFI_IMAGE_COMDAT_SELECT_EXACT_MATCH 4 +#define EFI_IMAGE_COMDAT_SELECT_ASSOCIATIVE 5 + +/* + * "the following values only be referred in PeCoff, not defined in PECOFF." + */ +#define EFI_IMAGE_WEAK_EXTERN_SEARCH_NOLIBRARY 1 +#define EFI_IMAGE_WEAK_EXTERN_SEARCH_LIBRARY 2 +#define EFI_IMAGE_WEAK_EXTERN_SEARCH_ALIAS 3 + +/* + * Relocation format. + */ +typedef struct { + uint32_t virtual_address; + uint32_t symbol_table_index; + uint16_t type; +} efi_image_relocation_t; + +/* + * Size of efi_image_relocation_t + */ +#define EFI_IMAGE_SIZEOF_RELOCATION 10 + +/* + * I386 relocation types. + */ +#define EFI_IMAGE_REL_I386_ABSOLUTE 0x0000 // Reference is absolute, no relocation is necessary. +#define EFI_IMAGE_REL_I386_DIR16 0x0001 // Direct 16-bit reference to the symbols virtual address. +#define EFI_IMAGE_REL_I386_REL16 0x0002 // PC-relative 16-bit reference to the symbols virtual address. +#define EFI_IMAGE_REL_I386_DIR32 0x0006 // Direct 32-bit reference to the symbols virtual address. +#define EFI_IMAGE_REL_I386_DIR32NB 0x0007 // Direct 32-bit reference to the symbols virtual address, base not included. +#define EFI_IMAGE_REL_I386_SEG12 0x0009 // Direct 16-bit reference to the segment-selector bits of a 32-bit virtual address. +#define EFI_IMAGE_REL_I386_SECTION 0x000A +#define EFI_IMAGE_REL_I386_SECREL 0x000B +#define EFI_IMAGE_REL_I386_REL32 0x0014 // PC-relative 32-bit reference to the symbols virtual address. + +/* + * x64 processor relocation types. + */ +#define IMAGE_REL_AMD64_ABSOLUTE 0x0000 +#define IMAGE_REL_AMD64_ADDR64 0x0001 +#define IMAGE_REL_AMD64_ADDR32 0x0002 +#define IMAGE_REL_AMD64_ADDR32NB 0x0003 +#define IMAGE_REL_AMD64_REL32 0x0004 +#define IMAGE_REL_AMD64_REL32_1 0x0005 +#define IMAGE_REL_AMD64_REL32_2 0x0006 +#define IMAGE_REL_AMD64_REL32_3 0x0007 +#define IMAGE_REL_AMD64_REL32_4 0x0008 +#define IMAGE_REL_AMD64_REL32_5 0x0009 +#define IMAGE_REL_AMD64_SECTION 0x000A +#define IMAGE_REL_AMD64_SECREL 0x000B +#define IMAGE_REL_AMD64_SECREL7 0x000C +#define IMAGE_REL_AMD64_TOKEN 0x000D +#define IMAGE_REL_AMD64_SREL32 0x000E +#define IMAGE_REL_AMD64_PAIR 0x000F +#define IMAGE_REL_AMD64_SSPAN32 0x0010 + +/* + * Based relocation format. + */ +typedef struct { + uint32_t virtual_address; + uint32_t size_of_block; +} efi_image_base_relocation_t; + +/* + * Size of efi_image_base_relocation_t. + */ +#define EFI_IMAGE_SIZEOF_BASE_RELOCATION 8 + +/* + * Based relocation types. + */ +#define EFI_IMAGE_REL_BASED_ABSOLUTE 0 +#define EFI_IMAGE_REL_BASED_HIGH 1 +#define EFI_IMAGE_REL_BASED_LOW 2 +#define EFI_IMAGE_REL_BASED_HIGHLOW 3 +#define EFI_IMAGE_REL_BASED_HIGHADJ 4 +#define EFI_IMAGE_REL_BASED_MIPS_JMPADDR 5 +#define EFI_IMAGE_REL_BASED_ARM_MOV32A 5 +#define EFI_IMAGE_REL_BASED_ARM_MOV32T 7 +#define EFI_IMAGE_REL_BASED_IA64_IMM64 9 +#define EFI_IMAGE_REL_BASED_MIPS_JMPADDR16 9 +#define EFI_IMAGE_REL_BASED_DIR64 10 + +/* + * Line number format. + */ +typedef struct { + union { + uint32_t symbol_table_index; // Symbol table index of function name if Linenumber is 0. + uint32_t virtual_address; // Virtual address of line number. + } type; + uint16_t linenumber; +} efi_image_linenumber_t; + +/* + * Size of efi_image_linenumber_t. + */ +#define EFI_IMAGE_SIZEOF_LINENUMBER 6 + +/* + * Archive format. + */ +#define EFI_IMAGE_ARCHIVE_START_SIZE 8 +#define EFI_IMAGE_ARCHIVE_START "!\n" +#define EFI_IMAGE_ARCHIVE_END "`\n" +#define EFI_IMAGE_ARCHIVE_PAD "\n" +#define EFI_IMAGE_ARCHIVE_LINKER_MEMBER "/ " +#define EFI_IMAGE_ARCHIVE_LONGNAMES_MEMBER "// " + +/* + * Archive Member Headers + */ +typedef struct { + uint8_t name[16]; // File member name - `/' terminated. + uint8_t date[12]; // File member date - decimal. + uint8_t user_id[6]; // File member user id - decimal. + uint8_t group_id[6]; // File member group id - decimal. + uint8_t mode[8]; // File member mode - octal. + uint8_t size[10]; // File member size - decimal. + uint8_t end_header[2]; // String to end header. (0x60 0x0A). +} efi_image_archive_member_header_t; + +/* + * + * Size of efi_image_archive_member_header_t. + * + */ +#define EFI_IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR 60 + + +/************************** + * DLL Support + *************************/ + +/* + * Export Directory Table. + */ +typedef struct { + uint32_t characteristics; + uint32_t time_date_stamp; + uint16_t major_version; + uint16_t minor_version; + uint32_t name; + uint32_t base; + uint32_t number_of_functions; + uint32_t number_of_names; + uint32_t address_of_functions; + uint32_t address_of_names; + uint32_t address_of_name_ordinals; +} efi_image_export_directory_t; + +/* + * Hint/Name Table. + */ +typedef struct { + uint16_t hint; + uint8_t name[1]; +} efi_image_import_by_name_t; + +/* + * Import Address Table rva (Thunk Table). + */ +typedef struct { + union { + uint32_t function; + uint32_t ordinal; + efi_image_import_by_name_t *address_of_data; + } u1; +} efi_image_thunk_data_t; + +#define EFI_IMAGE_ORDINAL_FLAG BIT31 // Flag for PE32. +#define EFI_IMAGE_SNAP_BY_ORDINAL(Ordinal) ((Ordinal & EFI_IMAGE_ORDINAL_FLAG) != 0) +#define EFI_IMAGE_ORDINAL(Ordinal) (Ordinal & 0xffff) + +/* + * Import Directory Table + */ +typedef struct { + uint32_t characteristics; + uint32_t time_date_stamp; + uint32_t forwarder_chain; + uint32_t name; + efi_image_thunk_data_t *first_thunk; +} efi_image_import_descriptor_t; + +/* + * Debug Directory Format. + */ +typedef struct { + uint32_t characteristics; + uint32_t time_date_stamp; + uint16_t major_version; + uint16_t minor_version; + uint32_t type; + uint32_t size_of_data; + uint32_t rva; // The address of the debug data when loaded, relative to the image base. + uint32_t file_offset; // The file pointer to the debug data. +} efi_image_debug_directory_entry_t; + +#define EFI_IMAGE_DEBUG_TYPE_CODEVIEW 2 // The Visual C++ debug information. + +/* + * Debug Data Structure defined in Microsoft C++. + */ +#define CODEVIEW_SIGNATURE_NB10 SIGNATURE_32('N', 'B', '1', '0') +typedef struct { + uint32_t signature; // "NB10" + uint32_t unknown; + uint32_t unknown2; + uint32_t unknown3; + /* + * Filename of .PDB goes here + */ +} efi_image_debug_codeview_nb10_entry_t; + +/* + * Debug Data Structure defined in Microsoft C++. + */ +#define CODEVIEW_SIGNATURE_RSDS SIGNATURE_32('R', 'S', 'D', 'S') +typedef struct { + uint32_t signature; // "RSDS". + uint32_t unknown; + uint32_t unknown2; + uint32_t unknown3; + uint32_t unknown4; + uint32_t unknown5; + /* + * Filename of .PDB goes here + */ +} efi_image_debug_codeview_rsds_entry_t; + + +/* + * Debug Data Structure defined by Apple Mach-O to Coff utility. + */ +#define CODEVIEW_SIGNATURE_MTOC SIGNATURE_32('M', 'T', 'O', 'C') +typedef struct { + uint32_t signature; // "MTOC". + efi_guid_t mach_o_uuid; + /* + * Filename of .DLL (Mach-O with debug info) goes here + */ +} efi_image_debug_codeview_mtoc_entry_t; + +/* + * + * Resource format. + * + */ +typedef struct { + uint32_t characteristics; + uint32_t time_date_stamp; + uint16_t major_version; + uint16_t minor_version; + uint16_t number_of_named_entities; + uint16_t number_of_id_entries; + /* + * Array of efi_image_resource_directory_entry_t entries goes here. + */ +} efi_image_resource_directory_t; + +/* + * Resource directory entry format. + */ +typedef struct { + union { + struct { + uint32_t name_offset:31; + uint32_t name_is_string:1; + } s; + uint32_t id; + } u1; + union { + uint32_t offset_to_data; + struct { + uint32_t offset_to_directory:31; + uint32_t data_is_directory:1; + } s; + } u2; +} efi_image_resource_directory_entry_t; + +/* + * Resource directory entry for string. + */ +typedef struct { + uint16_t length; + char16_t string[1]; +} efi_image_resource_directory_string_t; + +/* + * Resource directory entry for data array. + */ +typedef struct { + uint32_t offset_to_data; + uint32_t size; + uint32_t code_page; + uint32_t reserved; +} efi_image_resource_data_entry_t; + +/* + * Header format for TE images, defined in the PI Specification, 1.0. + */ +typedef struct { + uint16_t signature; // The signature for TE format = "VZ". + uint16_t machine; // From the original file header. + uint8_t number_of_ections; // From the original file header. + uint8_t subsystem; // From original optional header. + uint16_t stripped_size; // Number of bytes we removed from the header. + uint32_t address_of_entry_point;// Offset to entry point -- from original optional header. + uint32_t base_of_code; // From original image -- required for ITP debug. + uint64_t image_base; // From original file header. + efi_image_data_directory_t data_directory[2]; // Only base relocation and debug directory. +} efi_te_image_header_t; + + +#define EFI_TE_IMAGE_HEADER_T_SIGNATURE SIGNATURE_16('V', 'Z') + +/* + * Data directory indexes in our TE image header + */ +#define EFI_TE_IMAGE_DIRECTORY_ENTRY_BASERELOC 0 +#define EFI_TE_IMAGE_DIRECTORY_ENTRY_DEBUG 1 + +/* + * + * Union of PE32, PE32+, and TE headers. + */ +typedef union { + efi_image_nt_headers32_t pe32; + efi_image_nt_headers64_t pe32plus; + efi_te_image_header_t te; +} efi_image_optional_header_union_t; + +typedef union { + efi_image_nt_headers32_t *pe32; + efi_image_nt_headers64_t *pe32plus; + efi_te_image_header_t *te; + efi_image_optional_header_union_t *header_union; +} efi_image_optional_header_ptr_union_t; + +typedef struct { + win_certificate_header_t hdr; + uint8_t cert_data[1]; +} win_certificate_efi_pkcs_t; + +// vim:fenc=utf-8:tw=75:noet diff --git a/src/sbchooser-db.c b/src/sbchooser-db.c new file mode 100644 index 00000000..e49ce533 --- /dev/null +++ b/src/sbchooser-db.c @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-v3-or-later +/* + * sbchooser-db.c - handling of the UEFI key database for sbchooser + * Copyright Peter Jones + */ + +#include "sbchooser.h" // IWYU pragma: keep + +struct db_parse_context { + /* + * true=db + * false=dbx + */ + bool db; + sbchooser_context_t *ctx; +}; + +static int +add_cert(sbchooser_context_t *ctx, bool db, + const efi_secdb_data_t * const data, const size_t datasz) +{ + cert_data_t *cert = NULL; + cert_data_t **new_certs = NULL; + cert_data_t ***old_certsp = NULL; + size_t n_certs = 0; + size_t *n_certsp = NULL; + int rc; + + if (db) { + n_certs = ctx->n_db_certs; + n_certsp = &ctx->n_db_certs; + old_certsp = &ctx->db_certs; + } else { + n_certs = ctx->n_dbx_certs; + n_certsp = &ctx->n_dbx_certs; + old_certsp = &ctx->dbx_certs; + } + + /* + * We don't update ctx->n_XXX_certs until the end, so we can + * avoid stacked cleanup. + */ + new_certs = reallocarray(*old_certsp, n_certs + 1, sizeof (cert_data_t)); + if (!new_certs) + return -1; + + *old_certsp = new_certs; + + cert = calloc(1, sizeof(*cert)); + if (!cert) + return -1; + + cert->x509 = d2i_X509(NULL, (const unsigned char **)&data, datasz); + cert->free_x509 = true; + debug("alloc cert->x509:%p\n", cert->x509); + if (!cert->x509) { + // PJFIX: report errors better + warnx("couldn't make new X509"); + return -1; + } + + rc = elaborate_x509_info(cert); + if (rc < 0) { + memset(cert, 0, sizeof(*cert)); + free(cert); + return rc; + } + + new_certs[n_certs] = cert; + *n_certsp = n_certs + 1; + return 0; +} + +static int +add_digest(sbchooser_context_t *ctx, bool db, + const efi_secdb_data_t * const data, const size_t datasz) +{ + digest_data_t *digest = NULL; + digest_data_t **new_digests = NULL; + digest_data_t ***old_digestsp = NULL; + size_t n_digests = 0; + size_t *n_digestsp = NULL; + + if (db) { + n_digests = ctx->n_db_digests; + n_digestsp = &ctx->n_db_digests; + old_digestsp = &ctx->db_digests; + } else { + n_digests = ctx->n_dbx_digests; + n_digestsp = &ctx->n_dbx_digests; + old_digestsp = &ctx->dbx_digests; + } + + /* + * We don't update ctx->n_XXX_digests until the end, so we can + * avoid stacked cleanup. + */ + debug("old_digestsp:%p *old_digestsp:%p, n_digests:%zu, sizeof(digest_data_t):%zu\n", + old_digestsp, *old_digestsp, n_digests, sizeof(digest_data_t)); + new_digests = reallocarray(*old_digestsp, n_digests + 1, sizeof (digest_data_t)); + if (!new_digests) + return -1; + + *old_digestsp = new_digests; + + digest = calloc(1, sizeof(*digest) + datasz); + if (!digest) + return -1; + + digest->data = (uint8_t *)((uintptr_t)digest + sizeof(*digest)); + memcpy(digest->data, data, datasz); + digest->datasz = datasz; + + new_digests[n_digests] = digest; + *n_digestsp = n_digests + 1; + return 0; +} + +static efi_secdb_visitor_status_t +parse_one_secdb_cert(unsigned int listnum UNUSED, + unsigned int signum UNUSED, + const efi_guid_t * const owner UNUSED, + const efi_secdb_type_t algorithm, + const void * const header UNUSED, + const size_t headersz UNUSED, + const efi_secdb_data_t * const data, + const size_t datasz, + void *ctxp) +{ + int rc; + struct db_parse_context *dbctx = ctxp; + sbchooser_context_t *ctx = dbctx->ctx; + + switch (algorithm) { + case EFI_SECDB_TYPE_SHA1: + case EFI_SECDB_TYPE_SHA224: + case EFI_SECDB_TYPE_SHA256: + case EFI_SECDB_TYPE_SHA384: + case EFI_SECDB_TYPE_SHA512: + rc = add_digest(ctx, dbctx->db, data, datasz); + if (rc < 0) + return EFI_SECDB_VISITOR_ERROR; + return EFI_SECDB_VISITOR_CONTINUE; + case EFI_SECDB_TYPE_RSA2048: + case EFI_SECDB_TYPE_RSA2048_SHA1: + case EFI_SECDB_TYPE_RSA2048_SHA256: + case EFI_SECDB_TYPE_X509_SHA256: + case EFI_SECDB_TYPE_X509_SHA384: + case EFI_SECDB_TYPE_X509_SHA512: + return EFI_SECDB_VISITOR_CONTINUE; + case EFI_SECDB_TYPE_X509_CERT: + rc = add_cert(ctx, dbctx->db, data, datasz); + if (rc < 0) + return EFI_SECDB_VISITOR_ERROR; + return EFI_SECDB_VISITOR_CONTINUE; + default: + debug("unknown algorithm %u\n", algorithm); + return EFI_SECDB_VISITOR_CONTINUE; + } + + return EFI_SECDB_VISITOR_CONTINUE; +} + +int +parse_secdb_info(sbchooser_context_t *ctx) +{ + int rc; + struct db_parse_context dbctx = { + .db = false, + .ctx = ctx, + }; + + dbctx.db = true; + rc = efi_secdb_visit_entries(ctx->db, parse_one_secdb_cert, &dbctx); + if (rc < 0) { + warnx("couldn't visit them all?"); + return rc; + } + + dbctx.db = false; + rc = efi_secdb_visit_entries(ctx->dbx, parse_one_secdb_cert, &dbctx); + if (rc < 0) { + warnx("couldn't visit them all?"); + return rc; + } + + return 0; +} + +void +free_secdb_info(sbchooser_context_t *ctx) +{ + for (size_t i = 0; i < ctx->n_db_digests; i++) { + digest_data_t *dgst = ctx->db_digests[i]; + + free(dgst); + ctx->db_digests[i] = NULL; + } + free(ctx->db_digests); + ctx->db_digests = NULL; + ctx->n_db_digests = 0; + + for (size_t i = 0; i < ctx->n_db_certs; i++) { + free_cert(ctx->db_certs[i]); + ctx->db_certs[i] = NULL; + } + free(ctx->db_certs); + ctx->db_certs = NULL; + ctx->n_db_certs = 0; + + for (size_t i = 0; i < ctx->n_dbx_digests; i++) { + digest_data_t *dgst = ctx->dbx_digests[i]; + + free(dgst); + ctx->dbx_digests[i] = NULL; + } + free(ctx->dbx_digests); + ctx->dbx_digests = NULL; + ctx->n_dbx_digests = 0; + + for (size_t i = 0; i < ctx->n_dbx_certs; i++) { + free_cert(ctx->dbx_certs[i]); + ctx->dbx_certs[i] = NULL; + } + + if (ctx->db) { + efi_secdb_free(ctx->db); + ctx->db = NULL; + } + + if (ctx->dbx) { + efi_secdb_free(ctx->dbx); + ctx->dbx = NULL; + } + + free(ctx->dbx_certs); + ctx->dbx_certs = NULL; + ctx->n_dbx_certs = 0; +} + +int +load_secdb_from_file(const char * const filename, efi_secdb_t **secdbp) +{ + int rc; + uint8_t *data = NULL; + size_t data_size = 0; + int fd; + + fd = open(filename, O_RDONLY|O_CLOEXEC); + if (fd < 0) { + efi_error("Could not open file \"%s\": %m", filename); + return fd; + } + + rc = read_file(fd, &data, &data_size); + close(fd); + if (rc < 0) { + efi_error("Could not read file \"%s\": %m", filename); + return fd; + } + data_size -= 1; + + rc = efi_secdb_parse(data, data_size, secdbp); + free(data); + if (rc < 0) { + efi_error("Could not parse security database \"%s\"", filename); + return rc; + } + + return 0; +} + +int +load_secdb_from_var(const char * const name, const efi_guid_t * const guidp, + efi_secdb_t **secdbp) +{ + uint8_t *data; + size_t data_size; + uint32_t attrs; + int rc; + + if (!efi_variables_supported()) + return -1; + + rc = efi_get_variable(*guidp, name, &data, &data_size, &attrs); + if (rc < 0) { + efi_error("Could not get variable \"%s\"", name); + return rc; + } + + rc = efi_secdb_parse(data, data_size, secdbp); + free(data); + if (rc < 0) { + efi_error("Could not parse security database \"%s\"", name); + return rc; + } + + return 0; +} + +// vim:fenc=utf-8:tw=75:noet diff --git a/src/sbchooser-db.h b/src/sbchooser-db.h new file mode 100644 index 00000000..14b6916f --- /dev/null +++ b/src/sbchooser-db.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-v3-or-later +/* + * sbchooser-db.h - includes for sbchooser's db + * Copyright Peter Jones + */ + +#pragma once + +#include "sbchooser.h" // IWYU pragma: keep + +int load_secdb_from_file(const char * const filename, efi_secdb_t **secdbp); +int load_secdb_from_var(const char * const name, + const efi_guid_t * const guidp, efi_secdb_t **secdbp); +int parse_secdb_info(sbchooser_context_t *ctx); +void free_secdb_info(sbchooser_context_t *ctx); + +// vim:fenc=utf-8:tw=75:noet diff --git a/src/sbchooser-pe.c b/src/sbchooser-pe.c new file mode 100644 index 00000000..dd4aca18 --- /dev/null +++ b/src/sbchooser-pe.c @@ -0,0 +1,1127 @@ +// SPDX-License-Identifier: GPL-v3-or-later +/* + * sbchooser-pe.c - pe support for sbchooser + * Copyright Peter Jones + */ + +#include "sbchooser.h" // IWYU pragma: keep +#include + +#include + +static void +debug_print_openssl_errors(void) +{ + while (true) { + unsigned long err; + const char *file = NULL, *func = NULL, *data = NULL; + int line = 0; + int flags = 0; + + err = ERR_get_error_all(&file, &line, &func, &data, &flags); + if (err == 0) + break; + debug("openssl error 0x%016llx %s:%d:%s flags:%d data:\"%s\"", + err, file, line, func, flags, data); + } +} + +static void +free_sig(sig_data_t *sig) +{ + if (!sig) + return; + + for (size_t i = 0; i < sig->n_certs; i++) { + free_cert(sig->certs[i]); + sig->certs[i] = NULL; + } + if (sig->certs) { + sig->n_certs = 0; + free(sig->certs); + sig->certs = NULL; + } + + if (sig->p7) { + PKCS7_free(sig->p7); + sig->p7 = NULL; + } + + if (sig->x509s) { + sk_X509_free(sig->x509s); + sig->x509s = NULL; + } + + free(sig); +} + +void +free_pe(pe_file_t **pe_p) +{ + pe_file_t *pe; + int rc; + + if (!pe_p) + return; + pe = *pe_p; + + if (pe->filename) + free(pe->filename); + + if (pe->map && pe->mapsz) { + rc = munmap(pe->map, pe->mapsz); + if (rc < 0) + warn("munmap(%p, %zu) failed", pe->map, pe->mapsz); + } + + if (pe->sha256.data) { + free(pe->sha256.data); + pe->sha256.data = NULL; + pe->sha256.datasz = 0; + } + if (pe->sha384.data) { + free(pe->sha384.data); + pe->sha384.data = NULL; + pe->sha384.datasz = 0; + } + if (pe->sha512.data) { + free(pe->sha512.data); + pe->sha512.data = NULL; + pe->sha512.datasz = 0; + } + + if (pe->sigs) { + for (size_t i = 0; i < pe->n_sigs; i++) { + free_sig(pe->sigs[i]); + pe->sigs[i] = NULL; + } + free(pe->sigs); + } + + memset(pe, 0, sizeof(*pe)); + free(pe); + *pe_p = NULL; +} + +static bool +image_is_64_bit(efi_image_optional_header_union_t *pehdr) +{ + /* .Magic is the same offset in all cases */ + if (pehdr->pe32plus.optional_header.magic == + EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC) + return true; + return false; +} + +static bool +get_revocation(sbchooser_context_t *ctx, cert_data_t *sigcert) +{ + debug("looking for subject or issuer in %d dbx certs", ctx->n_dbx_certs); + + for (size_t i = 0; i < ctx->n_dbx_certs; i++) { + cert_data_t *dbxcert = ctx->dbx_certs[i]; + + if (is_same_cert(sigcert, dbxcert)) { + if (!sigcert->revoked_cert) + sigcert->revoked_cert = dbxcert; + debug("found"); + return true; + } + + if (is_issuing_cert(sigcert, dbxcert)) { + if (!sigcert->revoked_cert) + sigcert->revoked_cert = dbxcert; + debug("found"); + return true; + } + /* + * XXX PJFIX: right now we don't check cert revocations by + * TBS hash. I think we could solve this with + * X509_digest() and looking them up, but I don't have any + * dbx examples handy. + */ + } + debug("none found"); + return false; +} + +static bool +get_authorization(sbchooser_context_t *ctx, cert_data_t *sigcert) +{ + debug("looking for subject or issuer in %d db certs", ctx->n_db_certs); + + for (size_t i = 0; i < ctx->n_db_certs; i++) { + cert_data_t *dbcert = ctx->db_certs[i]; + + if (is_same_cert(sigcert, dbcert)) { + if (!sigcert->trust_anchor_cert) + sigcert->trust_anchor_cert = dbcert; + debug("found"); + return true; + } + + if (is_issuing_cert(sigcert, dbcert)) { + if (!sigcert->trust_anchor_cert) + sigcert->trust_anchor_cert = dbcert; + debug("found"); + return true; + } + /* + * XXX PJFIX: right now we don't check cert authorizations + * by TBS hash. I think we could solve this with + * X509_digest() and looking them up, but I don't have any + * db examples handy. + */ + } + debug("none found"); + return false; +} + +static void +update_cert_trust(sbchooser_context_t *ctx, cert_data_t *cert) +{ + char subject[4096]; + memset(subject, 0, sizeof(subject)); + X509_NAME_oneline(cert->subject, subject, 4095); + + if (get_revocation(ctx, cert)) { + char revoker[4096]; + + memset(revoker, 0, sizeof(revoker)); + + X509_NAME_oneline(cert->revoked_cert->subject, revoker, 4095); + + if (cert->rationale) { + free(cert->rationale); + cert->rationale = NULL; + debug("updating cert rationale to revoked"); + } + asprintf(&cert->rationale, "cert \"%s\" is revoked by \"%s\" in dbx", + subject, revoker); + debug("cert \"%s\" revoked by \"%s\"", subject, revoker); + cert->revoked = true; + } else { + debug("no revocations for \"%s\"", subject); + } + + if (get_authorization(ctx, cert)) { + char trust_anchor[4096]; + + memset(trust_anchor, 0, sizeof(trust_anchor)); + + X509_NAME_oneline(cert->trust_anchor_cert->subject, + trust_anchor, 4095); + + if (!cert->rationale) { + debug("updating cert rationale to trusted"); + asprintf(&cert->rationale, "cert \"%s\" is trusted by \"%s\" in db", + subject, trust_anchor); + } + debug("cert \"%s\" trusted by \"%s\"", subject, trust_anchor); + cert->trusted = true; + } else { + debug("no trust for \"%s\"", subject); + } + debug("cert \"%s\" trust is: trusted:%s revoked:%s", subject, + cert->trusted ? "true" : "false", + cert->revoked ? "true" : "false"); +} + +static int +add_one_cert(sig_data_t *sig, X509 *x509, cert_data_t **worst_cert) +{ + cert_data_t *cert = NULL; + cert_data_t **new_certs = NULL; + size_t n_certs = sig->n_certs; + int rc; + char buf0[1024]; + + memset(buf0, 0, sizeof(buf0)); + + new_certs = reallocarray(sig->certs, n_certs + 1, sizeof(cert_data_t)); + if (!new_certs) + return -1; + + sig->certs = new_certs; + + cert = calloc(1, sizeof(*cert)); + if (!cert) + return -1; + + cert->free_x509 = false; + cert->x509 = x509; + + rc = elaborate_x509_info(cert); + if (rc < 0) { + memset(cert, 0, sizeof(*cert)); + free(cert); + return rc; + } + + if (!worst_cert || !*worst_cert || + cert_sec_cmp(cert, *worst_cert) < 0) { + *worst_cert = cert; + } + + if (!sig->earliest_not_before || + time_cmp(cert->not_before, sig->earliest_not_before) < 0) { + fmt_time(cert->not_before, buf0); + debug("setting sig->earliest_not_before to %s", buf0); + sig->earliest_not_before = cert->not_before; + } + + if (!sig->latest_not_after || + time_cmp(cert->not_after, sig->latest_not_after) > 0) { + fmt_time(cert->not_after, buf0); + debug("setting sig->latest_not_after to %s", buf0); + sig->latest_not_after = cert->not_after; + } + + new_certs[n_certs] = cert; + sig->n_certs += 1; + return 0; +} + +static int +parse_pkcs7(PKCS7 *p7, sig_data_t *sig, cert_data_t **worst_cert) +{ + STACK_OF(X509) *certs; + int rc = 0; + + sig->p7 = p7; + + certs = PKCS7_get0_signers(p7, NULL, 0); + if (!certs) { + warnx("failed to parse X509 certs"); + debug_print_openssl_errors(); + errno = EINVAL; + goto err; + } + sig->x509s = certs; + + for (int i = 0; i < sk_X509_num(certs); i++) { + X509 *x = sk_X509_value(certs, i); + + rc = add_one_cert(sig, x, worst_cert); + if (rc < 0) + goto err; + } + + return rc; +err: + return -1; +} + +static void +update_sig_trust(sbchooser_context_t *ctx, sig_data_t *sig) +{ + for (size_t j = 0; j < sig->n_certs; j++) { + cert_data_t *sigcert = sig->certs[j]; + + update_cert_trust(ctx, sigcert); + + if (sigcert->trusted) { + if (!sig->revoked) { + debug("updating sig rationale to trusted"); + sig->rationale = sigcert->rationale; + } + sig->trusted = true; + } + + if (sigcert->revoked) { + debug("updating sig rationale to revoked"); + sig->rationale = sigcert->rationale; + sig->revoked = true; + } + } + if (sig->revoked) + sig->trusted = false; +} + +static int +add_one_sig(pe_file_t *pe, uint8_t *data, size_t datasz) +{ + sig_data_t *sig = NULL; + const unsigned char *ppin = (const unsigned char *)data; + sig_data_t **sigs = NULL; + size_t n_sigs = pe->n_sigs + 1; + int rc; + cert_data_t *worst_cert = NULL; + char buf0[1024]; + + memset(buf0, 0, sizeof(buf0)); + + sigs = reallocarray(pe->sigs, n_sigs, sizeof(*sigs)); + if (!sigs) + return -1; + pe->sigs = sigs; + + sig = calloc(1, sizeof(*sig)); + if (!sig) + return -1; + + PKCS7 *p7; + p7 = d2i_PKCS7(NULL, &ppin, datasz); + if (!p7) { + warnx("failed to parse X509 signature"); + debug_print_openssl_errors(); + errno = EINVAL; + goto err; + } + + rc = parse_pkcs7(p7, sig, &worst_cert); + if (rc < 0) { + debug("parsing pkcs7 data failed"); + goto err; + } + + if (worst_cert) { + sig->lowest_md_secbits = worst_cert->md_secbits; + sig->lowest_pk_secbits = worst_cert->pk_secbits; + } + + if (!pe->earliest_not_before || + time_cmp(pe->earliest_not_before, sig->earliest_not_before) > 0) { + pe->earliest_not_before = sig->earliest_not_before; + } + fmt_time(pe->earliest_not_before, buf0); + debug("set pe->earliest_not_before to %s", buf0); + + if (!pe->latest_not_after || + time_cmp(pe->latest_not_after, sig->latest_not_after) < 0) { + pe->latest_not_after = sig->latest_not_after; + } + fmt_time(pe->latest_not_after, buf0); + debug("set pe->latest_not_after to %s", buf0); + + sigs[pe->n_sigs] = sig; + pe->n_sigs = n_sigs; + + return 0; +err: + if (p7) { + debug("Freeing PKCS7_SIGNED %p", p7); + PKCS7_free(p7); + p7 = NULL; + } + + free(sig); + return -1; +} + +static int +parse_sigs(pe_file_t *pe) +{ + int rc = 0; + uintptr_t pos = 0; + uintptr_t dd = (uintptr_t)(pe->map) + pe->ctx.sec_dir->virtual_address; + + debug("security directory is at %p + 0x%p (%p)", pe->map, + (void *)(uintptr_t)pe->ctx.sec_dir->virtual_address, (void *)dd); + while (pos < pe->ctx.sec_dir->size) { + win_certificate_header_t *wincert = (win_certificate_header_t *)(dd + pos); + win_certificate_pkcs_signed_data_t *pkcs7 = NULL; + size_t data_len; + + if (pe->ctx.sec_dir->size - pos < sizeof(*wincert)) { + debug("secdir is %d bytes long, ignoring", + pe->ctx.sec_dir->size - pos); + break; + } + + debug("win_certificate_t at pos:0x%"PRIx32" (0x%"PRIx32") ", + pos, pe->ctx.sec_dir->virtual_address + pos); + debug("length:%"PRIu32" (0x%08"PRIx32") revision:0x%04"PRIx16" type:0x%04"PRIx16, + wincert->length, wincert->length, wincert->revision, wincert->cert_type); + if (wincert->revision != WIN_CERT_REVISION_2_0) { + debug("weird win_cert revision 0x%04"PRIx16, wincert->revision); + goto next; + } + + if (wincert->cert_type != WIN_CERT_TYPE_PKCS_SIGNED_DATA) { + debug("weird win_cert type 0x%04"PRIx16, wincert->cert_type); + goto next; + } + + data_len = wincert->length - sizeof(*wincert); + pkcs7 = (win_certificate_pkcs_signed_data_t *)wincert; + + rc = add_one_sig(pe, pkcs7->data, data_len); + if (rc < 0) { + warn("adding signature failed"); + break; + } +next: + if (wincert->length == 0) + break; + pos += wincert->length; + } + + return rc; +} + +int +load_pe(sbchooser_context_t *ctx, + const char * const filename, + pe_file_t **pe_p) +{ + int ret = -1; + pe_file_t *pe = NULL; + int fd = -1; + int rc; + struct stat statbuf; + efi_image_dos_header_t *doshdr = NULL; + efi_image_optional_header_union_t *pehdr = NULL; + pe_image_context_t *pe_ctx = NULL; + unsigned long opt_header_size; + unsigned long file_alignment = 0; + size_t page_size = sysconf(_SC_PAGE_SIZE); + + if (!filename || !pe_p) { + errno = EINVAL; + goto err; + } + + debug("loading PE from \"%s\"", filename); + + fd = open(filename, O_RDONLY|O_CLOEXEC); + if (fd < 0) + goto err; + + rc = fstat(fd, &statbuf); + if (rc < 0) + goto err; + + pe = calloc(1, sizeof (*pe)); + if (!pe) + goto err; + + pe_ctx = &pe->ctx; + pe->mapsz = statbuf.st_size; + pe->map = mmap(NULL, pe->mapsz, PROT_READ, MAP_SHARED, fd, 0); + if (pe->map == MAP_FAILED) + err(ERR_BAD_PE, "Could not map \"%s\"", filename); + close(fd); + fd = -1; + + pe->filename = strdup(filename); + if (!pe->filename) + goto err; + + if (pe->mapsz < sizeof(*pehdr) + sizeof(*doshdr) + sizeof(pehdr->pe32)) { + debug("PE file is too small (%zu bytes < %zu bytes", pe->mapsz, + sizeof(*pehdr) + sizeof(*doshdr) + sizeof(pehdr->pe32)); + goto err; + } + + doshdr = (efi_image_dos_header_t *) pe->map; + if (doshdr->e_magic != EFI_IMAGE_DOS_SIGNATURE) { + debug("PE Image has bad DOS signature %02x%02x", + (doshdr->e_magic & 0xff00) >> 8, + (doshdr->e_magic & 0x00ff)); + goto err; + } + + if (doshdr->e_lfanew >= pe->mapsz) { + debug("PE Image has invalid PE header location 0x%x", + doshdr->e_lfanew); + goto err; + } + + pehdr = (efi_image_optional_header_union_t *)(void *)((uint8_t *)pe->map + doshdr->e_lfanew); + if (pehdr->pe32.signature != EFI_IMAGE_NT_SIGNATURE) { + debug("PE Image has invalid PE header signature 0x%08x", + pehdr->pe32.signature); + goto err; + } + + pe_ctx->pe_header = pehdr; + + if (image_is_64_bit(pehdr)) { + pe_ctx->size_of_image = + pehdr->pe32plus.optional_header.size_of_image; + pe_ctx->size_of_headers = + pehdr->pe32plus.optional_header.size_of_headers; + pe_ctx->number_of_sections = + pehdr->pe32plus.file_header.number_of_sections; + pe_ctx->section_alignment = + pehdr->pe32plus.optional_header.section_alignment; + pe_ctx->number_of_rva_and_sizes = + pehdr->pe32plus.optional_header.number_of_rva_and_sizes; + pe_ctx->dll_characteristics = + pehdr->pe32plus.optional_header.dll_characteristics; + file_alignment = pehdr->pe32plus.optional_header.file_alignment; + opt_header_size = sizeof(efi_image_optional_header64_t); + } else { + pe_ctx->size_of_image = + (uint64_t)pehdr->pe32.optional_header.size_of_image; + pe_ctx->size_of_headers = + pehdr->pe32.optional_header.size_of_headers; + pe_ctx->number_of_sections = + pehdr->pe32.file_header.number_of_sections; + pe_ctx->section_alignment = + pehdr->pe32.optional_header.section_alignment; + pe_ctx->number_of_rva_and_sizes = + pehdr->pe32.optional_header.number_of_rva_and_sizes; + pe_ctx->dll_characteristics = + pehdr->pe32.optional_header.dll_characteristics; + file_alignment = pehdr->pe32.optional_header.file_alignment; + opt_header_size = sizeof(efi_image_optional_header32_t); + } + + errno = EINVAL; + + /* + * Set up our file alignment and section alignment expectations to + * be mostly sane. + * + * This probably should have a check for /power/ of two not just + * multiple, but in practice it hasn't been an issue. + */ + if (file_alignment % 2 != 0) { + debug("File alignment is invalid (%lu)\n", file_alignment); + goto err; + } + if (file_alignment == 0) + file_alignment = 0x200; + if (pe_ctx->section_alignment == 0) + pe_ctx->section_alignment = page_size; + if (pe_ctx->section_alignment < file_alignment) + pe_ctx->section_alignment = file_alignment; + + /* + * Check and make sure the space for data directory entries is as + * large as we expect. + * + * In truth we could set this number smaller if we needed to - + * currently it's 16 but we only care about #4 and #5 (the fifth + * and sixth ones) - but it hasn't been a problem. If it's too + * weird we'll fail trying to allocate it. + */ + if (EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES < pe_ctx->number_of_rva_and_sizes) { + debug("Invalid number of RVAs (%lu)", pe_ctx->number_of_rva_and_sizes); + goto err; + } + + size_t tmpsz0 = 0; + size_t tmpsz1 = 0; + + /* + * Check that the optional_headersize and the end of the Data + * Directory match up sanely + */ + unsigned long header_without_datadir = 0; + unsigned long rva_sizes = 0; + if (checked_mul(sizeof(efi_image_data_directory_t), EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES, &tmpsz0) || + checked_sub(opt_header_size, tmpsz0, &header_without_datadir) || + checked_sub((size_t)pehdr->pe32.file_header.size_of_optional_header, header_without_datadir, &tmpsz0) || + checked_mul((size_t)pe_ctx->number_of_rva_and_sizes, sizeof (efi_image_data_directory_t), &tmpsz1) || + (tmpsz0 != tmpsz1)) { + debug("RVA space isn't equal to leftover header space (%lu != %lu)", + rva_sizes, header_without_datadir); + goto err; + } + + /* + * Check that the SectionHeaderOffset field is within the image. + */ + unsigned long section_header_offset; + if (checked_add((size_t)doshdr->e_lfanew, sizeof(uint32_t), &tmpsz0) || + checked_add(tmpsz0, sizeof(efi_image_file_header_t), &tmpsz0) || + checked_add(tmpsz0, pehdr->pe32.file_header.size_of_optional_header, §ion_header_offset)) { + debug("Image sections overflow image size"); + goto err; + } + + /* + * Check that the sections headers themselves are within the image + */ + if (checked_sub((size_t)pe_ctx->size_of_image, section_header_offset, &tmpsz0) || + (tmpsz0 / EFI_IMAGE_SIZEOF_SECTION_HEADER <= pe_ctx->number_of_sections)) { + debug("Image sections overflow image size"); + goto err; + } + + /* + * Check that the section headers fit within the total headers + */ + if (checked_sub((size_t)pe_ctx->size_of_headers, section_header_offset, &tmpsz0) || + (tmpsz0 / EFI_IMAGE_SIZEOF_SECTION_HEADER < (uint32_t)pe_ctx->number_of_sections)) { + debug("Image sections overflow section headers"); + goto err; + } + + /* + * Check that the section headers are actually within the data + * we've read. Might be duplicative of the size_of_image one, but + * it won't hurt. + */ + if (checked_mul((size_t)pe_ctx->number_of_sections, sizeof(efi_image_section_header_t), &tmpsz0) || + checked_add(tmpsz0, section_header_offset, &tmpsz0) || + (tmpsz0 > pe->mapsz)) { + debug("Image sections overflow section headers"); + goto err; + } + + /* + * Check that the optional header fits in the image. + */ + if (checked_sub((size_t)(uintptr_t)pehdr, (size_t)(uintptr_t)pe->map, &tmpsz0) || + checked_add(tmpsz0, sizeof(efi_image_optional_header_union_t), &tmpsz0) || + (tmpsz0 > pe->mapsz)) { + debug("Invalid image"); + goto err; + } + + /* + * Check that this claims to be a PE binary + */ + if (pehdr->te.signature != EFI_IMAGE_NT_SIGNATURE) { + debug("Unsupported image type"); + goto err; + } + + /* + * Check that relocations aren't stripped, because that won't work. + */ + if (pehdr->pe32.file_header.characteristics & EFI_IMAGE_FILE_RELOCS_STRIPPED) { + debug("Unsupported image - Relocations have been stripped"); + goto err; + } + + /* + * We didn't load these earlier because we hadn't verified the size + * yet. + */ + if (image_is_64_bit(pehdr)) { + pe_ctx->sec_dir = &pehdr->pe32plus.optional_header.data_directory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; + } else { + pe_ctx->sec_dir = &pehdr->pe32.optional_header.data_directory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; + } + + /* + * Check that the file header fits within the image. + */ + if (checked_add((size_t)(uintptr_t)pehdr, pehdr->pe32.file_header.size_of_optional_header, &tmpsz0) || + checked_add(tmpsz0, sizeof(uint32_t), &tmpsz0) || + checked_add(tmpsz0, sizeof(efi_image_file_header_t), &tmpsz0)) { + debug("Invalid image"); + goto err; + } + + /* + * Check that the first section header is within the image data + */ + pe_ctx->first_section = (efi_image_section_header_t *)(uintptr_t)tmpsz0; + if ((uint64_t)(uintptr_t)(pe_ctx->first_section) > (uint64_t)(uintptr_t)pe->map + pe->mapsz) { + debug("Invalid image"); + goto err; + } + + /* + * Check that the headers fit within the image. + */ + if (pe_ctx->size_of_image < pe_ctx->size_of_headers) { + debug("Invalid image"); + goto err; + } + + /* + * check that the data directory fits within the image. + */ + if (checked_sub((size_t)(uintptr_t)pe_ctx->sec_dir, (size_t)(uintptr_t)pe->map, &tmpsz0) || + (tmpsz0 > pe->mapsz - sizeof(efi_image_data_directory_t))) { + debug("Invalid image"); + goto err; + } + + /* + * Check that the certificate table is within the binary - + * "virtual_address" is a misnomer here, it's a relative offset to the + * image's load address, so compared to datasize it should be + * absolute. + */ + if (pe_ctx->sec_dir->virtual_address > pe->mapsz || + (pe_ctx->sec_dir->virtual_address == pe->mapsz && pe_ctx->sec_dir->size > 0)) { + debug("pe_ctx->sec_dir->virtual_address:0x%llx pe_ctx->sec_dir->size:0x%llx datasize:0x%llx\n", + pe_ctx->sec_dir->virtual_address, pe_ctx->sec_dir->size, pe->mapsz); + debug("Malformed security header"); + goto err; + } + + pe->first_sig_only = ctx->first_sig_only; + + rc = parse_sigs(pe); + if (rc < 0) + goto err; + + rc = generate_authenticode(pe); + if (rc < 0) + goto err; + + *pe_p = pe; + return 0; +err: + { + typeof(errno) error = errno; + if (pe) + free_pe(&pe); + if (fd >= 0) + close(fd); + errno = error; + } + *pe_p = NULL; + return ret; +} + +int +get_section_vma (pe_file_t *pe, unsigned int section_num, + char **basep, size_t *sizep, + efi_image_section_header_t **sectionp) +{ + efi_image_section_header_t *sections = pe->ctx.first_section; + efi_image_section_header_t *section; + char *base = NULL, *end = NULL; + + if (section_num >= pe->ctx.number_of_sections) { + errno = ENOENT; + return -1; + } + + errno = EINVAL; + + if (pe->ctx.first_section == NULL) { + debug("Invalid section %u requested", section_num); + goto err; + } + + section = §ions[section_num]; + + base = pe->map + section->virtual_address; + end = pe->map + (section->virtual_address + section->misc.virtual_size - 1); + + if (!(section->characteristics & EFI_IMAGE_SCN_MEM_DISCARDABLE)) { + if (!base) { + debug("Section %u has invalid base address", section_num); + goto err; + } + if (!end) { + debug("Section %u has zero size", section_num); + goto err; + } + } + + if (!(section->characteristics & EFI_IMAGE_SCN_CNT_UNINITIALIZED_DATA) && + (section->virtual_address < pe->ctx.size_of_headers || + section->pointer_to_raw_data < pe->ctx.size_of_headers)) { + debug("Section %u is inside image headers", section_num); + goto err; + } + + if (end < base) { + debug("Section %u has negative size", section_num); + goto err; + } + + *basep = base; + *sizep = end - base; + *sectionp = section; + return 0; +err: + return -1; +} + +static void +check_secdb_hash(char *dbname, digest_data_t **digests, size_t n_digests, + char *dgstname, digest_data_t *candidate, bool *found) +{ + char buf[1024]; + + fmt_digest(candidate, buf, sizeof(buf)); + debug("candidate:%s", buf); + + for (size_t i = 0; i < n_digests; i++) { + digest_data_t *dgst = digests[i]; + + if (candidate->datasz != dgst->datasz) + continue; + + fmt_digest(dgst, buf, sizeof(buf)); + log(LOG_DEBUG_DUMPER, "%s:%s", dbname, buf); + + if (memcmp(dgst->data, candidate->data, dgst->datasz) == 0) { + debug("%s hash is in %s", dgstname, dbname); + *found = true; + } + } +} + +static void +check_dbx_hashes(sbchooser_context_t *ctx, pe_file_t *pe) +{ + debug("%zu digests in dbx\n", ctx->n_dbx_digests); + + check_secdb_hash("dbx", ctx->dbx_digests, ctx->n_dbx_digests, + "sha512", &pe->sha512, &pe->sha512_revoked); + check_secdb_hash("dbx", ctx->dbx_digests, ctx->n_dbx_digests, + "sha384", &pe->sha384, &pe->sha384_revoked); + check_secdb_hash("dbx", ctx->dbx_digests, ctx->n_dbx_digests, + "sha256", &pe->sha256, &pe->sha256_revoked); +} + +static void +check_db_hashes(sbchooser_context_t *ctx, pe_file_t *pe) +{ + debug("%zu digests in db\n", ctx->n_db_digests); + + check_secdb_hash("db", ctx->db_digests, ctx->n_db_digests, + "sha512", &pe->sha512, &pe->sha512_trusted); + check_secdb_hash("db", ctx->db_digests, ctx->n_db_digests, + "sha384", &pe->sha384, &pe->sha384_trusted); + check_secdb_hash("db", ctx->db_digests, ctx->n_db_digests, + "sha256", &pe->sha256, &pe->sha256_trusted); +} + +bool +is_revoked_by_hash(pe_file_t *pe, digest_data_t **revoking_digest) +{ + if (pe->sha512_revoked) { + if (revoking_digest) + *revoking_digest = &pe->sha512; + return true; + } + + if (pe->sha384_revoked) { + if (revoking_digest) + *revoking_digest = &pe->sha384; + return true; + } + + if (pe->sha256_revoked) { + if (revoking_digest) + *revoking_digest = &pe->sha256; + return true; + } + + return false; +} + +bool +is_trusted_by_hash(pe_file_t *pe, digest_data_t **trusting_digest) +{ + if (pe->sha512_trusted) { + if (trusting_digest) + *trusting_digest = &pe->sha512; + return true; + } + + if (pe->sha384_trusted) { + if (trusting_digest) + *trusting_digest = &pe->sha384; + return true; + } + + if (pe->sha256_trusted) { + if (trusting_digest) + *trusting_digest = &pe->sha256; + return true; + } + + return false; +} + +static uint32_t +get_highest_hash_secbits(pe_file_t *pe) +{ + if (is_revoked_by_hash(pe, NULL)) + return 0; + + if (pe->sha512_trusted) + return 256; + if (pe->sha384_trusted) + return 192; + if (pe->sha256_trusted) + return 128; + + return 0; +} + +void +update_pe_security(sbchooser_context_t *ctx, pe_file_t *pe) +{ + debug("scoring \"%s\"", pe->filename); + + check_dbx_hashes(ctx, pe); + check_db_hashes(ctx, pe); + + uint32_t lowest_pk_secbits = 0xffffffffull; + uint32_t lowest_md_secbits = 0xffffffffull; + + bool found_trusted_sig = false; + for (size_t i = 0; i < pe->n_sigs; i++) { + sig_data_t *sig = pe->sigs[i]; + + update_sig_trust(ctx, sig); + + if (sig->rationale && !pe->rationale) { + debug("updating pe rationale to %s", sig->revoked ? "revoked" : (sig->trusted ? "trusted" : "")); + pe->rationale = sig->rationale; + } + + if (sig->trusted) { + found_trusted_sig = true; + if (!pe->has_trusted_signature && pe->rationale) { + debug("updating pe rationale to %s", sig->revoked ? "revoked" : (sig->trusted ? "trusted" : "")); + pe->rationale = sig->rationale; + } + pe->has_trusted_signature = true; + + if (sig->lowest_md_secbits < lowest_md_secbits) { + lowest_md_secbits = sig->lowest_md_secbits; + } + if (sig->lowest_pk_secbits < lowest_pk_secbits) { + lowest_pk_secbits = sig->lowest_pk_secbits; + } + + if (!pe->earliest_not_before || + time_cmp(sig->earliest_not_before, pe->earliest_not_before) < 0) { + pe->earliest_not_before = sig->earliest_not_before; + } + + if (!pe->latest_not_after || + time_cmp(sig->latest_not_after, pe->latest_not_after) > 0) { + pe->latest_not_after = sig->latest_not_after; + } + } + + if (pe->first_sig_only) + break; + } + if (!found_trusted_sig) { + pe->secbits = 0; + return; + } + if (lowest_md_secbits == 0xffffffffull) { + lowest_md_secbits = 0; + } + if (lowest_pk_secbits == 0xffffffffull) { + lowest_pk_secbits = 0; + } + pe->secbits = lowest_md_secbits < lowest_pk_secbits ? lowest_md_secbits : lowest_pk_secbits; +} + +static int +compare_validities(pe_file_t *pe0, pe_file_t *pe1) +{ + int rc = 0; + char buf0[1024]; + char buf1[1024]; + char default_pref[] = "no preference"; + char *pref; + + /* + * Dunno when this can happen, but always prefer the one that's + * signed if we get this far... + */ + if (pe0->latest_not_after && !pe1->latest_not_after) + return -1; + if (!pe0->latest_not_after && pe1->latest_not_after) + return 1; + + /* + * prefer the one that has certs expiring the latest + */ + fmt_time(pe0->latest_not_after, buf0); + fmt_time(pe1->latest_not_after, buf1); + rc = time_cmp(pe0->latest_not_after, pe1->latest_not_after); + if (rc < 0) + pref = buf1; + else if (rc > 0) + pref = buf0; + else + pref = default_pref; + + debug("finding latest of \"%s\" and \"%s\": %s", buf0, buf1, pref); + if (rc < 0) + return 1; + if (rc > 0) + return -1; + + /* + * Dunno when this can happen, but always prefer the one that's + * signed if we get this far... + */ + if (pe0->earliest_not_before && !pe1->earliest_not_before) + return -1; + if (!pe0->earliest_not_before && pe1->earliest_not_before) + return 1; + + /* + * prefer the one that has certs starting the earliest + */ + fmt_time(pe0->earliest_not_before, buf0); + fmt_time(pe1->earliest_not_before, buf1); + rc = time_cmp(pe1->earliest_not_before, pe0->earliest_not_before); + if (rc < 0) + pref = buf1; + else if (rc > 0) + pref = buf0; + else + pref = default_pref; + + debug("finding earliest of \"%s\" and \"%s\": %s", buf0, buf1, pref); + if (rc < 0) + return 1; + if (rc > 0) + return -1; + return 0; +} + +int +pe_cmp(const void *p0, const void *p1) +{ + pe_file_t *pe0 = *(pe_file_t **)p0; + pe_file_t *pe1 = *(pe_file_t **)p1; + int score; + + bool pe0_revoked = is_revoked_by_hash(pe0, NULL); + bool pe1_revoked = is_revoked_by_hash(pe1, NULL); + if (!pe1_revoked && pe0_revoked) + return -1; + if (pe1_revoked && !pe0_revoked) + return 1; + + bool pe0_trusted = is_trusted_by_hash(pe0, NULL); + bool pe1_trusted = is_trusted_by_hash(pe1, NULL); + if (pe1_trusted && !pe0_trusted) + return 1; + if (!pe1_trusted && pe0_trusted) + return -1; + + uint32_t pe0_hash_secbits = get_highest_hash_secbits(pe0); + uint32_t pe1_hash_secbits = get_highest_hash_secbits(pe1); + + score = pe1_hash_secbits - pe0_hash_secbits; + if (score != 0) + return score; + + score = pe1->secbits - pe0->secbits; + debug("\"%s\" (secbits %"PRIu32") vs \"%s\" (secbits %"PRIu32"): %d", + pe0->filename, pe0->secbits, pe1->filename, pe1->secbits, score); + if (score != 0) + return score; + + debug("security strength is equal; comparing validities"); + score = compare_validities(pe0, pe1); + if (score < 0) { + debug("prefer \"%s\"", pe1->filename); + } else if (score > 0) { + debug("prefer \"%s\"", pe0->filename); + } else { + debug("no preference"); + return strcmp(pe0->filename, pe1->filename); + } + return score; +} + +// vim:fenc=utf-8:tw=75:noet diff --git a/src/sbchooser-pe.h b/src/sbchooser-pe.h new file mode 100644 index 00000000..eb59f242 --- /dev/null +++ b/src/sbchooser-pe.h @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-v3-or-later +/* + * sbchooser-pe.h - includes for sbchooser's pe support + * Copyright Peter Jones + */ + +#pragma once + +#include "sbchooser.h" // IWYU pragma: keep +#include "peimage.h" // IWYU pragma: keep + +/* + * This is a cache of things from the image headers, so we don't have to + * write this all over the place: + * if (is_64_bit_image(...) { + * x = pehdr->pe32plus.optional_header.x; + * } else { + * x = pehdr->pe32.optional_header.x; + * } + */ +typedef struct pe_image_context { + efi_image_optional_header_union_t *pe_header; // the main pe header + uint64_t size_of_image; // optional_header.size_of_image + unsigned long size_of_headers; // optional_header.size_of_headers + uint16_t number_of_sections; // file_header.number_of_sections + uint32_t section_alignment; // optional_header.section_alignment + uint64_t number_of_rva_and_sizes; // optional_header.number_of_rva_and_sizes + uint16_t dll_characteristics; // optional_header.dll_characteristics + efi_image_section_header_t *first_section; // first section header + efi_image_data_directory_t *sec_dir; // security directory +} pe_image_context_t; + +struct sig_data { + /* + * Stuff from OpenSSL that we need to free later. + */ + PKCS7 *p7; + /* + * This is procured with PKCS7_get0_signers(), which + * ossl-guide-libraries-introduction(7ossl) claims means the p7 + * object still owns it, and it should get freed when we free that. + * + * This doesn't actually happen - the contents get freed, but + * there's still 128 bytes of overhead that got allocated. + * + * openssl/apps/smime.c frees these with sk_X509_free(). That + * seems to work, and valgrind like it just fine, but I can't find + * any documentation of that at all. + */ + STACK_OF(X509) *x509s; + + size_t n_certs; + cert_data_t **certs; + + bool trusted; // sig is in db + bool revoked; // sig is in dbx + + /* + * validity info from this signature's signing certs + */ + uint32_t lowest_md_secbits; + uint32_t lowest_pk_secbits; + + /* + * the earliest not_before and latest not_after validation date + * from our signature's issuers. + * + * Strictly this isn't necessary, but if everything has the same + * security strength, we'd prefer the "newest" binary, so we need + * some heuristic for that. + */ + const ASN1_TIME *earliest_not_before; + const ASN1_TIME *latest_not_after; + + /* + * why was this revoked or trusted? Borrowed from certs + */ + char *rationale; +}; + +typedef struct sig_data sig_data_t; + +struct pe_file { + char *filename; // for display later + void *map; // where the file is mapped + size_t mapsz; // how big the map is + pe_image_context_t ctx; // context built from "loading" it. + + /* + * authenticode hashes of this binary, using different digest + * functions. + */ + digest_data_t sha256; + bool sha256_revoked; + bool sha256_trusted; + + digest_data_t sha384; + bool sha384_revoked; + bool sha384_trusted; + + digest_data_t sha512; + bool sha512_revoked; + bool sha512_trusted; + + /* + * each authenticode signature found on this binary + */ + size_t n_sigs; + sig_data_t **sigs; + + /* + * information for sorting and reporting + */ + bool has_trusted_signature; + uint32_t secbits; + + /* + * the earliest not_before and latest not_after validation date + * from our signature's issuers. + * + * Strictly this isn't necessary, but if everything has the same + * security strength, we'd prefer the "newest" binary, so we need + * some heuristic for that. + */ + const ASN1_TIME *earliest_not_before; + const ASN1_TIME *latest_not_after; + + /* + * why was this revoked or trusted? Borrowed from sigs + */ + char *rationale; + + bool first_sig_only; // should only the first signature be scored? +}; + +/* + * load_pe() allocates a pe_file_t, maps it, and "loads" it, validating + * the input as well as filling out the "context" structure. + * + * If "first_sig_only" is true, only the first signature will be scored and + * used for sorting. + * + * returns 0 on success, negative on error + */ +int load_pe(sbchooser_context_t *ctx, + const char * const filename, pe_file_t **pe_file); + +/* + * free_pe() cleans up the pe_file_t, unmapping and freeing all associated + * memory. + */ +void free_pe(pe_file_t **pe_file); + +/* + * finds the given numbered section... + * inputs: + * pe - the pe_file_t that's been loaded + * section_num - the section number, 0-indexed + * outputs: + * *basep - pointer to the section data + * *sizep - size of the section data + * *sectionp - pointer to the section header + * + * returns 0 on success, negative on failure + */ +int get_section_vma (pe_file_t *pe, unsigned int section_num, + char **basep, size_t *sizep, + efi_image_section_header_t **sectionp); + +void fmt_digest(digest_data_t *dgst, char *buf, size_t bufsz); +int generate_authenticode(pe_file_t *pe); + +/* + * evaluate the security posture of the PE file provided, in the context of + * the security databases in ctx. + */ +void update_pe_security(sbchooser_context_t *ctx, pe_file_t *pe); + +bool is_revoked_by_hash(pe_file_t *pe, digest_data_t **revoking_digest); +bool is_trusted_by_hash(pe_file_t *pe, digest_data_t **trusting_digest); + +/* + * PE comparison function, suitable for use with qsort(3) + * Lower return value is most desirable. + */ +int pe_cmp(const void *p0, const void *p1); + +// vim:fenc=utf-8:tw=75:noet diff --git a/src/sbchooser-x509.c b/src/sbchooser-x509.c new file mode 100644 index 00000000..92895009 --- /dev/null +++ b/src/sbchooser-x509.c @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: GPL-v3-or-later +/* + * sbchooser-x509.c - x509 handling + * Copyright Peter Jones + */ + +#include "sbchooser.h" // IWYU pragma: keep + +void +fmt_time(const ASN1_TIME *asn1, char buf[1024]) +{ + struct tm tm; + + memset(buf, 0, 1024); + if (!asn1) { + strcpy(buf, "(null)"); + return; + } + ASN1_TIME_to_tm(asn1, &tm); + asctime_r(&tm, buf); + + buf[1023] = '\0'; + for (size_t i = 0; i < 1024; i++) { + if (buf[i] == '\r' || buf[i] == 'n' || !isprint(buf[i])) + buf[i] = '\0'; + } +} + +int +time_cmp(const ASN1_TIME *t0, const ASN1_TIME *t1) +{ + int rc = 0; + char str0[1024]; + char str1[1024]; + + memset(str0, 0, 1024); + memset(str1, 0, 1024); + fmt_time(t0, str0); + fmt_time(t1, str1); + + if (t0 && t1) + rc = ASN1_TIME_compare(t0, t1); + if (t0 && !t1) + rc = -1; + if (t1 && !t0) + rc = 1; + debug("comparing \"%s\" to \"%s\": %d", str0, str1, rc); + return rc; +} + +/* + * note that none of this checks any /cryptographic/ properties. If you've + * got two certs with the same issuer, and serial, we'll believe they're + * the same, even if one of them is "fake" and has a pubkey that won't + * verify signatures from the other one. + */ +bool +is_same_cert(cert_data_t *cert0, cert_data_t *cert1) +{ + int rc; + char buf0[4096], buf1[4096]; + + memset(buf0, 0, 4096); + memset(buf1, 0, 4096); + + X509_NAME_oneline(cert0->issuer, buf0, 4095); + X509_NAME_oneline(cert1->issuer, buf1, 4095); + + rc = X509_NAME_cmp(cert0->issuer, cert1->issuer); + debug(" comparing issuers for \"%s\" and \"%s\": %d", buf0, buf1, rc); + if (rc != 0) + return false; + + uint64_t a, b; + + ASN1_INTEGER_get_uint64(&a, cert0->serial); + ASN1_INTEGER_get_uint64(&b, cert1->serial); + rc = ASN1_INTEGER_cmp(cert0->serial, cert1->serial); + debug(" serial cmp(0x%"PRIx64",0x%"PRIx64"):%d", a, b, rc); + if (!rc) + return false; + + return true; +} + +bool +is_issuing_cert(cert_data_t *subject, cert_data_t *candidate_issuer) +{ + int rc; + char buf0[4096], buf1[4096]; + + memset(buf0, 0, 4096); + memset(buf1, 0, 4096); + + X509_NAME_oneline(subject->issuer, buf0, 4095); + X509_NAME_oneline(candidate_issuer->subject, buf1, 4095); + + rc = X509_NAME_cmp(subject->issuer, candidate_issuer->subject); + debug(" comparing issuers for \"%s\" and \"%s\": %d", buf0, buf1, rc); + if (rc == 0) + return true; + return false; +} + +void +free_cert(cert_data_t *cert) +{ + if (!cert) + return; + + if (cert->free_x509 && cert->x509) { + X509_free(cert->x509); + cert->x509 = NULL; + } + + if (cert->rationale) { + free(cert->rationale); + cert->rationale = NULL; + } + + free(cert); +} + +int +elaborate_x509_info(cert_data_t *cert) +{ + int rc; + const char *mdsn = NULL; + const char *pksn = NULL; + + cert->subject = X509_get_subject_name(cert->x509); + if (!cert->subject) { + warnx("couldn't get cert subject"); + goto err; + } + + cert->issuer = X509_get_issuer_name(cert->x509); + if (!cert->issuer) { + warnx("couldn't get cert issuer"); + goto err; + } + + cert->serial = X509_get_serialNumber(cert->x509); + if (cert->serial == NULL) { + warnx("couldn't get cert serial"); + goto err; + } + + cert->not_before = X509_get0_notBefore(cert->x509); + if (cert->not_before == NULL) { + warnx("couldn't get not_before"); + goto err; + } + + cert->not_after = X509_get0_notAfter(cert->x509); + if (cert->not_after == NULL) { + warnx("couldn't get not_after"); + goto err; + } + + rc = X509_get_signature_info(cert->x509, &cert->md_nid, &cert->pk_nid, + NULL, NULL); + if (rc != 1) { + warnx("couldn't get signature info"); + goto err; + } + + debug("md_nid:%d pk_nid:%d secbits:%d", + cert->md_nid, cert->pk_nid, cert->md_secbits); + mdsn = OBJ_nid2sn(cert->md_nid); + pksn = OBJ_nid2ln(cert->pk_nid); + + /* + * We do not believe in OpenSSL's concept of md secbits; we believe + * in NIST SP 800-57 Part 1 Rev. 5 page 69's. + */ + if (!strcmp(mdsn, "SHA512")) { + cert->md_secbits = 256; + } else if (!strcmp(mdsn, "SHA384")) { + cert->md_secbits = 192; + } else if (!strcmp(mdsn, "SHA256")) { + cert->md_secbits = 128; + } else if (!strcmp(mdsn, "SHA224")) { + cert->md_secbits = 112; + } else if (!strcmp(mdsn, "SHA1")) { + cert->md_secbits = 80; + } + + debug("md:%s md_secbits:%d", mdsn, cert->md_secbits); + + /* + * XXX:PJFIX PQC isn't implemented here yet + */ + while (true) { + if (!strcmp(pksn, "rsaEncryption")) { + X509_PUBKEY *xpk; + ASN1_OBJECT *ppkalg = NULL; + const unsigned char *pk = NULL; + int pklen = 0; + X509_ALGOR *pa = NULL; + int rc = 0; + + xpk = X509_get_X509_PUBKEY(cert->x509); + if (!xpk) { + debug("This certificate has no public key. Not scoring."); + break; + } + + rc = X509_PUBKEY_get0_param(&ppkalg, &pk, &pklen, &pa, xpk); + if (!rc) { + debug("This public key has no algorithm parameters. Not scoring."); + break; + } + /* + * PK here is the ASN1 encoded modulus and + * exponent, so pklen (in bytes) includes a few + * extra bytes here and there, but these values are + * far enough apart it shouldn't matter... + */ + pklen *= 8; + if (pklen >= 15360) { + cert->pk_secbits = 256; + } else if (pklen >= 7690) { + cert->pk_secbits = 192; + } else if (pklen >= 3072) { + cert->pk_secbits = 128; + } else if (pklen >= 2048) { + cert->pk_secbits = 112; + } else if (pklen >= 1024) { + cert->pk_secbits = 80; + } + } + break; + } + debug("pk:%s pk_secbits:%d", pksn, cert->pk_secbits); + + return 0; +err: + X509_free(cert->x509); + cert->x509 = NULL; + return -1; +} + +int +cert_sec_cmp(cert_data_t *cert0, cert_data_t *cert1) +{ + int cert0_secbits = cert0->md_secbits < cert0->pk_secbits ? cert0->md_secbits : cert0->pk_secbits; + int cert1_secbits = cert1->md_secbits < cert1->pk_secbits ? cert1->md_secbits : cert1->pk_secbits; + + return cert1_secbits - cert0_secbits; +} + +// vim:fenc=utf-8:tw=75:noet diff --git a/src/sbchooser-x509.h b/src/sbchooser-x509.h new file mode 100644 index 00000000..2163b145 --- /dev/null +++ b/src/sbchooser-x509.h @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-v3-or-later +/* + * sbchooser-x509.h - x509 handling + * Copyright Peter Jones + */ + +#pragma once + +#include "sbchooser.h" // IWYU pragma: keep + +struct cert_data { + bool free_x509; + X509 *x509; + + /* + * OpenSSL owns these and they should not be individually freed + */ + X509_NAME *issuer; + X509_NAME *subject; + ASN1_INTEGER *serial; + + const ASN1_TIME *not_before; + const ASN1_TIME *not_after; + + /* + * Info about the signature on the cert + */ + int md_nid; // nid for the message digest algorithm + int md_secbits; // security strength of the digest + int pk_nid; // nid for the public key algorithm + int pk_secbits; // security strenght of the pubkey + + /********* + * stuff below here is only for certs on a signature, not for certs + * in db/dbx + *********/ + /* + * Are all the certs in this x509 signature trusted by db? Are any + * revoked? + */ + bool trusted; + cert_data_t *trust_anchor_cert; // cert it's trusted by (self or + // issuer) + + bool revoked; + cert_data_t *revoked_cert; // the cert that's actually in dbx + + char *rationale; // why was this revoked or trusted +}; + +void free_cert(cert_data_t *cert); + +int elaborate_x509_info(cert_data_t *cert); + +/* + * Returns true if these certs have the same issuer and serial number. + * There's no cryptography here. + */ +bool is_same_cert(cert_data_t *cert0, cert_data_t *cert1); + +/* + * returns true if the subject issuer name matches the candidate issuer + * name. There's no cryptography here. + */ +bool is_issuing_cert(cert_data_t *subject, cert_data_t *candidate_issuer); + +/* + * Format an ASN1_TIME * into the buffer + */ +void fmt_time(const ASN1_TIME *asn1, char buf[1024]); + +/* + * Compare ASN1_TIMEs. + * return values: + * < 0: t0 is before t1 + * 0: t0 is the same time as t1 + * > 0: t0 is after t1 + */ +int time_cmp(const ASN1_TIME *t0, const ASN1_TIME *t1); + +/* + * Compare the security strength of two certs. + * return values: + * < 0: cert0 has higher security strength than cert1 + * 0: they're the same + * > 0: cert1 has higher security strength than cert0 + */ +int cert_sec_cmp(cert_data_t *cert0, cert_data_t *cert1); + +// vim:fenc=utf-8:tw=75:noet diff --git a/src/sbchooser.c b/src/sbchooser.c new file mode 100644 index 00000000..734049c3 --- /dev/null +++ b/src/sbchooser.c @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: GPL-v3-or-later +/* + * sbchooser.c - utility to choose between secure boot enabled bootloaders + * to install. + * Copyright Peter Jones + */ + +#include "sbchooser.h" + +extern char *optarg; +extern int optind, opterr, optopt; + +static void NORETURN +usage(int status) +{ + fprintf(status == 0 ? stdout : stderr, + "Usage: %s [OPTION...]\n" + "\nOptions:\n" + " -d, --db= UEFI trusted key database\n" + " -D, --no-system-db Do not load the UEFI trusted key database\n" + " -s, --system-db Load the UEFI trusted key database from\n" + " this system (default)\n" + " -e, --explain Instead of acting as a sorter, explain choices\n" + " -f, --first-sig-only Only consider the first signature on an input\n" + " -x, --dbx= UEFI revoked key database\n" + " -X, --no-system-dbx Do not load the UEFI revoked key database\n" + " -S, --system-dbx Load the UEFI revoked key database from\n" + " this system (default)\n" + " -i, --input= EFI binary for sorting\n" + "Help options:\n" + " -?, --help Show this help message\n" + " --usage Display brief usage message\n", + program_invocation_short_name); + exit(status); +} + +static void +clean_up_context(sbchooser_context_t *ctxp) +{ + free_secdb_info(ctxp); + + for (size_t i = 0; i < ctxp->n_files; i++) { + if (ctxp->files[i] != NULL) + free_pe(&ctxp->files[i]); + } + free(ctxp->files); + ctxp->n_files = 0; + ctxp->files = NULL; + + memset(ctxp, 0, sizeof (*ctxp)); +} + +static int +add_file_to_ctx(sbchooser_context_t *ctxp, pe_file_t *pe) +{ + size_t n_files = ctxp->n_files + 1; + pe_file_t **files; + + files = reallocarray(ctxp->files, n_files, sizeof (*files)); + if (!files) + return -1; + + files[ctxp->n_files] = pe; + + ctxp->files = files; + ctxp->n_files = n_files; + + return 0; +} + +static void +add_one_pe_to_ctx(sbchooser_context_t *ctx, const char *filename) +{ + int rc; + pe_file_t *pe = NULL; + + rc = load_pe(ctx, filename, &pe); + if (rc < 0) { + if (filename[0] == '-') { + warnx("Unknown argument:\"%s\"", filename); + usage(ERR_USAGE); + } + err(ERR_BAD_PE, "Could not open \"%s\"", filename); + } + rc = add_file_to_ctx(ctx, pe); + if (rc < 0) + err(ERR_BAD_PE, "Could not add \"%s\" to context", filename); +} + +int +main(int argc, char *argv[]) +{ + const char sopts[] = ":d:Defi:sSx:Xvh"; + const struct option lopts[] = { + {"db", required_argument, NULL, 'd' }, + {"no-system-db", no_argument, NULL, 'D' }, + {"system-db", no_argument, NULL, 's' }, + {"dbx", required_argument, NULL, 'x' }, + {"no-system-dbx", no_argument, NULL, 'X' }, + {"system-dbx", no_argument, NULL, 'S' }, + {"first-sig-only", no_argument, NULL, 'f' }, + {"explain", no_argument, NULL, 'e' }, + {"in", required_argument, NULL, 'i' }, + {"verbose", no_argument, NULL, 'v' }, + {"usage", no_argument, NULL, 'h' }, + {"help", no_argument, NULL, 'h' }, + {NULL, 0, NULL, '\0' } + }; + int c; + int verbose = 0; + int rc; + bool needs_db = true; + bool needs_dbx = true; + bool read_inputs_from_stdin = false; + bool explain = false; + + sbchooser_context_t ctx; + + memset(&ctx, 0, sizeof(ctx)); + + ctx.db = efi_secdb_new(); + if (!ctx.db) + err(ERR_SECDB, "could not allocate memory"); + efi_secdb_set_bool(ctx.db, EFI_SECDB_SORT, false); + efi_secdb_set_bool(ctx.db, EFI_SECDB_SORT_DATA, false); + efi_secdb_set_bool(ctx.db, EFI_SECDB_SORT_DESCENDING, false); + ctx.dbx = efi_secdb_new(); + if (!ctx.dbx) + err(ERR_SECDB, "could not allocate memory"); + efi_secdb_set_bool(ctx.dbx, EFI_SECDB_SORT, false); + efi_secdb_set_bool(ctx.dbx, EFI_SECDB_SORT_DATA, false); + efi_secdb_set_bool(ctx.dbx, EFI_SECDB_SORT_DESCENDING, false); + + while (true) { + int option_index = 0; + + opterr = 0; + c = getopt_long(argc, argv, sopts, lopts, &option_index); + if (c == -1) + break; + + switch (c) { + case ':': + errx(ERR_USAGE, "Error: '--%s' requires an argument", lopts[optind].name); + break; + case 'D': + needs_db = false; + break; + case 'd': + rc = load_secdb_from_file(argv[optind-1], &ctx.db); + if (rc < 0) + err(ERR_SECDB, "Could not load db from \"%s\"", + argv[optind-1]); + needs_db = false; + break; + case 'e': + explain = true; + break; + case 'f': + ctx.first_sig_only = true; + break; + case 'h': + usage(ERR_SUCCESS); + break; + case 's': + rc = load_secdb_from_var("db", &efi_guid_security, &ctx.db); + if (rc < 0 && errno != ENOENT) + err(ERR_SECDB, "Could not load db from EFI variable"); + needs_db = false; + break; + case 'S': + rc = load_secdb_from_var("dbx", &efi_guid_security, &ctx.dbx); + if (rc < 0 && errno != ENOENT) + err(ERR_SECDB, "Could not load dbx from EFI variable"); + needs_dbx = false; + break; + case 'X': + needs_dbx = false; + break; + case 'x': + rc = load_secdb_from_file(argv[optind-1], &ctx.dbx); + if (rc < 0) + err(ERR_SECDB, "Could not load dbx from \"%s\"", + argv[optind-1]); + needs_dbx = false; + break; + case 'i': + if (strcmp(argv[optind-1], "-") == 0) { + read_inputs_from_stdin = true; + break; + } + add_one_pe_to_ctx(&ctx, argv[optind-1]); + break; + case 'v': + verbose += 1; + efi_set_verbose(verbose, stderr); + if (verbose) { + setvbuf(stdout, NULL, _IONBF, 0); + } + break; + case '?': + if (optopt == '?') + usage(ERR_SUCCESS); + warnx("Unknown argument:\"%s\"", argv[optind-1]); + usage(ERR_USAGE); + break; + default: + if (strcmp(argv[optind-1], "-?") == 0) + usage(ERR_SUCCESS); + + add_one_pe_to_ctx(&ctx, argv[optind-1]); + break; + } + } + + if (optind > 0 && !strcmp(argv[optind-1], "--")) { + while (optind < argc) { + add_one_pe_to_ctx(&ctx, argv[optind]); + optind += 1; + } + } + + if ((c == -1 && argc > 1) && optind < argc) { + warnx("Unknown argument:\"%s\"", argv[optind]); + usage(ERR_USAGE); + } + + if (needs_db) { + rc = load_secdb_from_var("db", &efi_guid_security, &ctx.db); + if (rc < 0 && errno != ENOENT) + err(ERR_SECDB, "Could not load db from EFI variable"); + } + + if (needs_dbx) { + rc = load_secdb_from_var("dbx", &efi_guid_security, &ctx.dbx); + if (rc < 0 && errno != ENOENT) + err(ERR_SECDB, "Could not load db from EFI variable"); + } + + rc = parse_secdb_info(&ctx); + if (rc < 0) { + errx(ERR_SECDB, "couldn't parse secdb info"); + } + + if (ctx.n_files == 0 && !isatty(STDIN_FILENO)) { + read_inputs_from_stdin = true; + } + + if (read_inputs_from_stdin) { + while (true) { + char filename[PATH_MAX+1]; + char *fret; + + memset(filename, 0, sizeof(filename)); + fret = fgets(filename, PATH_MAX, stdin); + if (fret == NULL) { + if (feof(stdin)) + break; + err(ERR_INPUT, "Could not read from stdin"); + } + for (size_t i = 0; filename[i] != 0; i++) { + switch (filename[i]) { + case '\r': + case '\n': + filename[i] = '\0'; + break; + default: + continue; + } + } + add_one_pe_to_ctx(&ctx, filename); + } + } + + if (ctx.n_files == 0) { + warnx("no input files!"); + exit(ERR_USAGE); + } + + for (size_t i = 0; i < ctx.n_files; i++) { + update_pe_security(&ctx, ctx.files[i]); + } + + qsort(ctx.files, ctx.n_files, sizeof(ctx.files[0]), pe_cmp); + + for (size_t i = 0; i < ctx.n_files; i++) { + pe_file_t *pe = ctx.files[i]; + digest_data_t *dgst = NULL; + char buf[1024]; + + /* + * UEFI spec 2.12ish says: + * – C. Any entry with SignatureListType of EFI_CERT_X509_GUID, + * with SignatureData which contains a certificate with the + * same Issuer, Serial Number, and To-Be-Signed hash included + * in any certificate in the signing chain of the signature + * being verified. + * + * Multiple signatures are allowed to exist in the binary's + * certificate table (as per the "Attribute Certificate Table" + * section of the Microsoft PE/COFF Specification). The + * firmware must do the validation according to the following: + * + * - If the hash of the binary is in dbx, then the image shall + * fail the validation. + * - Else if the hash of the binary is in db, then the image + * shall pass the validation. + * - Else if one of signatures is in db and is not in dbx, then + * the image shall pass the validation. + * - Else the image shall fail the validation. + * + * And so we check dbx hashes first, then db. + */ + if (is_revoked_by_hash(pe, &dgst)) { + fmt_digest(dgst, buf, sizeof(buf)); + debug("PE \"%s\" is revoked by hash %s", pe->filename, buf); + if (explain) { + printf("%s is revoked by hash %s in dbx\n", pe->filename, buf); + } + continue; + } + dgst = NULL; + if (is_trusted_by_hash(pe, &dgst)) { + fmt_digest(dgst, buf, sizeof(buf)); + debug("PE \"%s\" is trusted by hash %s", pe->filename, buf); + if (explain) { + printf("%s is trusted by hash %s in db\n", pe->filename, buf); + } else { + printf("%s\n", pe->filename); + } + continue; + } else { + debug("PE \"%s\" is not trusted by hash", pe->filename); + } + + debug("PE \"%s\" score 0x%"PRIx32, pe->filename, pe->secbits); + if (pe->secbits == 0) { + if (explain) { + if (!pe->rationale) { + printf("%s is not trusted because no certs or hashes trust it\n", pe->filename); + } else { + printf("%s is not trusted because %s\n", pe->filename, pe->rationale); + } + } + } else { + if (explain) { + printf("%s is trusted because %s\n", pe->filename, pe->rationale); + } else { + printf("%s\n", pe->filename); + } + } + } + + clean_up_context(&ctx); + OPENSSL_cleanup(); + + return 0; +} + +// vim:fenc=utf-8:tw=75:noet diff --git a/src/sbchooser.h b/src/sbchooser.h new file mode 100644 index 00000000..d89dd9bc --- /dev/null +++ b/src/sbchooser.h @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-v3-or-later +/* + * sbchooser.h - includes for sbchooser + * Copyright Peter Jones + */ + +#pragma once + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif /* defined(_GNU_SOURCE) */ + +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include +#include +#include +#include + +#define OPENSSL_NO_DEPRECATED +#include + +#include "efivar/efisec.h" // IWYU pragma: export + +/* + * exit status codes from sbchooser + */ +enum { + ERR_SUCCESS = 0, + ERR_USAGE, + ERR_SECDB, + ERR_BAD_PE, + ERR_INPUT, +}; + +typedef struct sbchooser_context sbchooser_context_t; +typedef struct cert_data cert_data_t; + +struct digest_data { + uint8_t *data; + size_t datasz; +}; +typedef struct digest_data digest_data_t; + +typedef struct pe_file pe_file_t; + +#include "compiler.h" // IWYU pragma: export +#include "util.h" // IWYU pragma: export +#include "sbchooser-pe.h" // IWYU pragma: export +#include "sbchooser-db.h" // IWYU pragma: export +#include "sbchooser-x509.h" // IWYU pragma: export + +/* + * sbchooser's main context + */ +struct sbchooser_context { + /* + * our input pe files + */ + size_t n_files; + pe_file_t **files; + + efi_secdb_t *db; + size_t n_db_digests; + digest_data_t **db_digests; + size_t n_db_certs; + cert_data_t **db_certs; + + efi_secdb_t *dbx; + size_t n_dbx_digests; + digest_data_t **dbx_digests; + size_t n_dbx_certs; + cert_data_t **dbx_certs; + // XXX PJFIX: support cert TBS hash revocations + + bool first_sig_only; // should only the first signature be scored? +}; + +// vim:fenc=utf-8:tw=75:noet diff --git a/tests/Makefile b/tests/Makefile index aa903615..a4cb0f15 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -16,7 +16,22 @@ TESTS = test.dmpstore.export \ test.esl.sha256.removal.descending \ test.esl.sha256.addition.unsorted \ test.esl.cert.addition \ - test.esl.cert.removal + test.esl.cert.removal \ + test.sbchooser.sha512.vs.sha256 \ + test.sbchooser.sha512.vs.sha256.explain \ + test.sbchooser.sha512.vs.untrusted \ + test.sbchooser.sha512.vs.untrusted.explain \ + test.sbchooser.sha512.vs.sha256.vs.dbx \ + test.sbchooser.sha512.vs.sha256.vs.dbx.explain \ + test.sbchooser.sha512.vs.db \ + test.sbchooser.sha512.vs.db.explain \ + test.sbchooser.db.vs.dbx \ + test.sbchooser.db.vs.dbx.explain \ + test.sbchooser.identical.secbits \ + test.sbchooser.identical.secbits.explain \ + test.sbchooser.first.sig.only \ + test.sbchooser.first.sig.only.explain \ + test.sbchooser.padded.secdir.explain \ all: clean $(TESTS) @@ -39,6 +54,8 @@ else loud= endif +MALLOC_PERTURB_=$(shell echo $$(($${RANDOM} % 128))) + EFIVAR ?= $(VALGRIND) $(TOPDIR)/src/efivar $(loud) EFISECDB ?= $(VALGRIND) $(TOPDIR)/src/efisecdb $(loud) @@ -283,6 +300,358 @@ test.esl.cert.removal: $(quiet)rm -f test.esl.cert.removal.esl.result $(quiet)echo passed +test.sbchooser.sha512.vs.sha256.result: + $(quiet)ls -1 shim-13-0.2.fedora.x64.nosigs.efi \ + shim-15-7.el7_2.x64.nosigs.efi \ + shim-13-0.2.fedora.x64.nosigs.efi \ + shim-15-7.el7_2.x64.nosigs.efi \ + | sort -R | MALLOC_PERTURB_=$(MALLOC_PERTURB_) LD_LIBRARY_PATH=../src \ + ../src/sbchooser -d db.msft2011 \ + -d db.msft2023 \ + -d db.shim-15-7.el7_2.x64.sha512 \ + -d db.shim-13-0.2.fedora.x64.sha256 \ + --no-system-dbx \ + > "$@" + +test.sbchooser.sha512.vs.sha256: + $(quiet)echo testing sbchooser sorting: sha512 db vs sha256 db + $(quiet)$(MAKE) $(makequiet) test.sbchooser.sha512.vs.sha256.result + $(quiet)if ! cmp test.sbchooser.sha512.vs.sha256.goal.txt test.sbchooser.sha512.vs.sha256.result ; then \ + diff -U 200 test.sbchooser.sha512.vs.sha256.goal.txt test.sbchooser.sha512.vs.sha256.result ; \ + exit 1 ; \ + fi + $(quiet)cmp test.sbchooser.sha512.vs.sha256.goal.txt test.sbchooser.sha512.vs.sha256.result + $(quiet)rm -f test.sbchooser.sha512.vs.sha256.result + $(quiet)echo passed + +test.sbchooser.sha512.vs.untrusted.result: + $(quiet)ls -1 shim-13-0.2.fedora.x64.nosigs.efi \ + shim-15-7.el7_2.x64.nosigs.efi \ + shim-13-0.2.fedora.x64.nosigs.efi \ + shim-15-7.el7_2.x64.nosigs.efi \ + | sort -R | MALLOC_PERTURB_=$(MALLOC_PERTURB_) LD_LIBRARY_PATH=../src \ + ../src/sbchooser -d db.msft2011 \ + -d db.msft2023 \ + -d db.shim-15-7.el7_2.x64.sha512 \ + --no-system-dbx \ + > "$@" + +test.sbchooser.sha512.vs.untrusted: + $(quiet)echo testing sbchooser sorting: sha512 db vs untrusted + $(quiet)$(MAKE) $(makequiet) test.sbchooser.sha512.vs.untrusted.result + $(quiet)if ! cmp test.sbchooser.sha512.vs.untrusted.goal.txt test.sbchooser.sha512.vs.untrusted.result ; then \ + diff -U 200 test.sbchooser.sha512.vs.untrusted.goal.txt test.sbchooser.sha512.vs.untrusted.result ; \ + exit 1 ; \ + fi + $(quiet)cmp test.sbchooser.sha512.vs.untrusted.goal.txt test.sbchooser.sha512.vs.untrusted.result + $(quiet)rm -f test.sbchooser.sha512.vs.untrusted.result + $(quiet)echo passed + +test.sbchooser.sha512.vs.sha256.vs.dbx.result: + $(quiet)ls -1 shim-13-0.2.fedora.x64.nosigs.efi \ + shim-15-7.el7_2.x64.nosigs.efi \ + shim-13-0.2.fedora.x64.nosigs.efi \ + shim-15-7.el7_2.x64.nosigs.efi \ + | sort -R | MALLOC_PERTURB_=$(MALLOC_PERTURB_) LD_LIBRARY_PATH=../src \ + ../src/sbchooser -d db.msft2011 \ + -d db.msft2023 \ + -d db.shim-15-7.el7_2.x64.sha512 \ + -d db.shim-13-0.2.fedora.x64.sha256 \ + -x db.shim-13-0.2.fedora.x64.sha256 \ + > "$@" + +test.sbchooser.sha512.vs.sha256.vs.dbx: + $(quiet)echo testing sbchooser sorting: sha512 db vs sha256 db vs dbx + $(quiet)$(MAKE) $(makequiet) test.sbchooser.sha512.vs.sha256.vs.dbx.result + $(quiet)if ! cmp test.sbchooser.sha512.vs.sha256.vs.dbx.goal.txt test.sbchooser.sha512.vs.sha256.vs.dbx.result ; then \ + diff -U 200 test.sbchooser.sha512.vs.sha256.vs.dbx.goal.txt test.sbchooser.sha512.vs.sha256.vs.dbx.result ; \ + exit 1 ; \ + fi + $(quiet)cmp test.sbchooser.sha512.vs.sha256.vs.dbx.goal.txt test.sbchooser.sha512.vs.sha256.vs.dbx.result + $(quiet)rm -f test.sbchooser.sha512.vs.sha256.vs.dbx.result + $(quiet)echo passed + +test.sbchooser.sha512.vs.db.result: + $(quiet)ls -1 shim-13-0.2.fedora.x64.nosigs.efi \ + shim-15-7.el7_2.x64.msft2011.efi \ + shim-13-0.2.fedora.x64.nosigs.efi \ + shim-15-7.el7_2.x64.msft2011.efi \ + | sort -R | MALLOC_PERTURB_=$(MALLOC_PERTURB_) LD_LIBRARY_PATH=../src \ + ../src/sbchooser -d db.msft2011 \ + -d db.shim-13-0.2.fedora.x64.sha512 \ + --no-system-dbx \ + > "$@" + +test.sbchooser.sha512.vs.db: + $(quiet)echo testing sbchooser sorting: sha512 db vs RSA 2K cert in db + $(quiet)$(MAKE) $(makequiet) test.sbchooser.sha512.vs.db.result + $(quiet)if ! cmp test.sbchooser.sha512.vs.db.goal.txt test.sbchooser.sha512.vs.db.result ; then \ + diff -U 200 test.sbchooser.sha512.vs.db.goal.txt test.sbchooser.sha512.vs.db.result ; \ + exit 1 ; \ + fi + $(quiet)cmp test.sbchooser.sha512.vs.db.goal.txt test.sbchooser.sha512.vs.db.result + $(quiet)rm -f test.sbchooser.sha512.vs.db.result + $(quiet)echo passed + +test.sbchooser.db.vs.dbx.result: + $(quiet)ls -1 shim-15-7.el7_2.x64.msft2011.efi \ + shim-16.1-4.el10.x64.msft2011.msft2023.efi \ + shim-15-7.el7_2.x64.msft2011.efi \ + shim-16.1-4.el10.x64.msft2011.msft2023.efi \ + | sort -R | MALLOC_PERTURB_=$(MALLOC_PERTURB_) LD_LIBRARY_PATH=../src \ + ../src/sbchooser -d db.msft2023 \ + -d db.msft2011 \ + -x db.msft2011 \ + > "$@" + +test.sbchooser.db.vs.dbx: + $(quiet)echo testing sbchooser sorting: db vs dbx + $(quiet)$(MAKE) $(makequiet) test.sbchooser.db.vs.dbx.result + $(quiet)if ! cmp test.sbchooser.db.vs.dbx.goal.txt test.sbchooser.db.vs.dbx.result ; then \ + diff -U 200 test.sbchooser.db.vs.dbx.goal.txt test.sbchooser.db.vs.dbx.result ; \ + exit 1 ; \ + fi + $(quiet)cmp test.sbchooser.db.vs.dbx.goal.txt test.sbchooser.db.vs.dbx.result + $(quiet)rm -f test.sbchooser.db.vs.dbx.result + $(quiet)echo passed + +test.sbchooser.identical.secbits.result: + $(quiet)ls -1 shim-16.1-4.el10.x64.msft2011.efi \ + shim-16.1-4.el10.x64.msft2011.msft2023.efi \ + shim-16.1-4.el10.x64.msft2023.efi \ + shim-16.1-4.el10.x64.msft2011.efi \ + shim-16.1-4.el10.x64.msft2011.msft2023.efi \ + shim-16.1-4.el10.x64.msft2023.efi \ + | sort -R | MALLOC_PERTURB_=$(MALLOC_PERTURB_) LD_LIBRARY_PATH=../src \ + ../src/sbchooser -d db.msft2023 \ + -d db.msft2011 \ + -x db.msft2011 \ + > "$@" + +test.sbchooser.identical.secbits: + $(quiet)echo testing sbchooser sorting: identical security strength sort order + $(quiet)$(MAKE) $(makequiet) test.sbchooser.identical.secbits.result + $(quiet)if ! cmp test.sbchooser.identical.secbits.goal.txt test.sbchooser.identical.secbits.result ; then \ + diff -U 200 test.sbchooser.identical.secbits.goal.txt test.sbchooser.identical.secbits.result ; \ + exit 1 ; \ + fi + $(quiet)cmp test.sbchooser.identical.secbits.goal.txt test.sbchooser.identical.secbits.result + $(quiet)rm -f test.sbchooser.identical.secbits.result + $(quiet)echo passed + +test.sbchooser.first.sig.only.result: + $(quiet)ls -1 shim-16.1-4.el10.x64.msft2011.efi \ + shim-16.1-4.el10.x64.msft2011.msft2023.efi \ + shim-16.1-4.el10.x64.msft2023.efi \ + shim-16.1-4.el10.x64.msft2011.efi \ + shim-16.1-4.el10.x64.msft2011.msft2023.efi \ + shim-16.1-4.el10.x64.msft2023.efi \ + | sort -R | MALLOC_PERTURB_=$(MALLOC_PERTURB_) LD_LIBRARY_PATH=../src \ + ../src/sbchooser -d db.msft2023 \ + --no-system-dbx \ + --first-sig-only \ + > "$@" + +test.sbchooser.first.sig.only: + $(quiet)echo testing sbchooser sorting with only the first signature + $(quiet)$(MAKE) $(makequiet) test.sbchooser.first.sig.only.result + $(quiet)if ! cmp test.sbchooser.first.sig.only.goal.txt test.sbchooser.first.sig.only.result ; then \ + diff -U 200 test.sbchooser.first.sig.only.goal.txt test.sbchooser.first.sig.only.result ; \ + exit 1 ; \ + fi + $(quiet)cmp test.sbchooser.first.sig.only.goal.txt test.sbchooser.first.sig.only.result + $(quiet)rm -f test.sbchooser.first.sig.only.result + $(quiet)echo passed + +test.sbchooser.sha512.vs.sha256.explain.result: + $(quiet)ls -1 shim-13-0.2.fedora.x64.nosigs.efi \ + shim-15-7.el7_2.x64.nosigs.efi \ + shim-13-0.2.fedora.x64.nosigs.efi \ + shim-15-7.el7_2.x64.nosigs.efi \ + | sort -R | MALLOC_PERTURB_=$(MALLOC_PERTURB_) LD_LIBRARY_PATH=../src \ + ../src/sbchooser --explain \ + -d db.msft2011 \ + -d db.msft2023 \ + -d db.shim-15-7.el7_2.x64.sha512 \ + -d db.shim-13-0.2.fedora.x64.sha256 \ + --no-system-dbx \ + > "$@" + +test.sbchooser.sha512.vs.sha256.explain: + $(quiet)echo testing sbchooser explanation: sha512 db vs sha256 db + $(quiet)$(MAKE) $(makequiet) test.sbchooser.sha512.vs.sha256.explain.result + $(quiet)if ! cmp test.sbchooser.sha512.vs.sha256.explain.goal.txt test.sbchooser.sha512.vs.sha256.explain.result ; then \ + diff -U 200 test.sbchooser.sha512.vs.sha256.explain.goal.txt test.sbchooser.sha512.vs.sha256.explain.result ; \ + exit 1 ; \ + fi + $(quiet)cmp test.sbchooser.sha512.vs.sha256.explain.goal.txt test.sbchooser.sha512.vs.sha256.explain.result + $(quiet)rm -f test.sbchooser.sha512.vs.sha256.explain.result + $(quiet)echo passed + +test.sbchooser.sha512.vs.untrusted.explain.result: + $(quiet)ls -1 shim-13-0.2.fedora.x64.nosigs.efi \ + shim-15-7.el7_2.x64.nosigs.efi \ + shim-13-0.2.fedora.x64.nosigs.efi \ + shim-15-7.el7_2.x64.nosigs.efi \ + | sort -R | MALLOC_PERTURB_=$(MALLOC_PERTURB_) LD_LIBRARY_PATH=../src \ + ../src/sbchooser --explain \ + -d db.msft2011 \ + -d db.msft2023 \ + -d db.shim-15-7.el7_2.x64.sha512 \ + --no-system-dbx \ + > "$@" + +test.sbchooser.sha512.vs.untrusted.explain: + $(quiet)echo testing sbchooser explanation: sha512 db vs untrusted + $(quiet)$(MAKE) $(makequiet) test.sbchooser.sha512.vs.untrusted.explain.result + $(quiet)if ! cmp test.sbchooser.sha512.vs.untrusted.explain.goal.txt test.sbchooser.sha512.vs.untrusted.explain.result ; then \ + diff -U 200 test.sbchooser.sha512.vs.untrusted.explain.goal.txt test.sbchooser.sha512.vs.untrusted.explain.result ; \ + exit 1 ; \ + fi + $(quiet)cmp test.sbchooser.sha512.vs.untrusted.explain.goal.txt test.sbchooser.sha512.vs.untrusted.explain.result + $(quiet)rm -f test.sbchooser.sha512.vs.untrusted.explain.result + $(quiet)echo passed + +test.sbchooser.sha512.vs.sha256.vs.dbx.explain.result: + $(quiet)ls -1 shim-13-0.2.fedora.x64.nosigs.efi \ + shim-15-7.el7_2.x64.nosigs.efi \ + shim-13-0.2.fedora.x64.nosigs.efi \ + shim-15-7.el7_2.x64.nosigs.efi \ + | sort -R | MALLOC_PERTURB_=$(MALLOC_PERTURB_) LD_LIBRARY_PATH=../src \ + ../src/sbchooser --explain \ + -d db.msft2011 \ + -d db.msft2023 \ + -d db.shim-15-7.el7_2.x64.sha512 \ + -d db.shim-13-0.2.fedora.x64.sha256 \ + -x db.shim-13-0.2.fedora.x64.sha256 \ + > "$@" + +test.sbchooser.sha512.vs.sha256.vs.dbx.explain: + $(quiet)echo testing sbchooser explanation: sha512 db vs sha256 db vs dbx + $(quiet)$(MAKE) $(makequiet) test.sbchooser.sha512.vs.sha256.vs.dbx.explain.result + $(quiet)if ! cmp test.sbchooser.sha512.vs.sha256.vs.dbx.explain.goal.txt test.sbchooser.sha512.vs.sha256.vs.dbx.explain.result ; then \ + diff -U 200 test.sbchooser.sha512.vs.sha256.vs.dbx.explain.goal.txt test.sbchooser.sha512.vs.sha256.vs.dbx.explain.result ; \ + exit 1 ; \ + fi + $(quiet)cmp test.sbchooser.sha512.vs.sha256.vs.dbx.explain.goal.txt test.sbchooser.sha512.vs.sha256.vs.dbx.explain.result + $(quiet)rm -f test.sbchooser.sha512.vs.sha256.vs.dbx.explain.result + $(quiet)echo passed + +test.sbchooser.sha512.vs.db.explain.result: + $(quiet)ls -1 shim-13-0.2.fedora.x64.nosigs.efi \ + shim-15-7.el7_2.x64.msft2011.efi \ + shim-13-0.2.fedora.x64.nosigs.efi \ + shim-15-7.el7_2.x64.msft2011.efi \ + | sort -R | MALLOC_PERTURB_=$(MALLOC_PERTURB_) LD_LIBRARY_PATH=../src \ + ../src/sbchooser --explain \ + -d db.msft2011 \ + -d db.shim-13-0.2.fedora.x64.sha512 \ + --no-system-dbx \ + > "$@" + +test.sbchooser.sha512.vs.db.explain: + $(quiet)echo testing sbchooser explanation: sha512 db vs RSA 2K cert in db + $(quiet)$(MAKE) $(makequiet) test.sbchooser.sha512.vs.db.explain.result + $(quiet)if ! cmp test.sbchooser.sha512.vs.db.explain.goal.txt test.sbchooser.sha512.vs.db.explain.result ; then \ + diff -U 200 test.sbchooser.sha512.vs.db.explain.goal.txt test.sbchooser.sha512.vs.db.explain.result ; \ + exit 1 ; \ + fi + $(quiet)cmp test.sbchooser.sha512.vs.db.explain.goal.txt test.sbchooser.sha512.vs.db.explain.result + $(quiet)rm -f test.sbchooser.sha512.vs.db.explain.result + $(quiet)echo passed + +test.sbchooser.db.vs.dbx.explain.result: + $(quiet)ls -1 shim-15-7.el7_2.x64.msft2011.efi \ + shim-16.1-4.el10.x64.msft2011.msft2023.efi \ + shim-15-7.el7_2.x64.msft2011.efi \ + shim-16.1-4.el10.x64.msft2011.msft2023.efi \ + | sort -R | MALLOC_PERTURB_=$(MALLOC_PERTURB_) LD_LIBRARY_PATH=../src \ + ../src/sbchooser --explain \ + -d db.msft2023 \ + -d db.msft2011 \ + -x db.msft2011 \ + > "$@" + +test.sbchooser.db.vs.dbx.explain: + $(quiet)echo testing sbchooser explanation: db vs dbx + $(quiet)$(MAKE) $(makequiet) test.sbchooser.db.vs.dbx.explain.result + $(quiet)if ! cmp test.sbchooser.db.vs.dbx.explain.goal.txt test.sbchooser.db.vs.dbx.explain.result ; then \ + diff -U 200 test.sbchooser.db.vs.dbx.explain.goal.txt test.sbchooser.db.vs.dbx.explain.result ; \ + exit 1 ; \ + fi + $(quiet)cmp test.sbchooser.db.vs.dbx.explain.goal.txt test.sbchooser.db.vs.dbx.explain.result + $(quiet)rm -f test.sbchooser.db.vs.dbx.explain.result + $(quiet)echo passed + +test.sbchooser.identical.secbits.explain.result: + $(quiet)ls -1 shim-16.1-4.el10.x64.msft2011.efi \ + shim-16.1-4.el10.x64.msft2011.msft2023.efi \ + shim-16.1-4.el10.x64.msft2023.efi \ + shim-16.1-4.el10.x64.msft2011.efi \ + shim-16.1-4.el10.x64.msft2011.msft2023.efi \ + shim-16.1-4.el10.x64.msft2023.efi \ + | sort -R | MALLOC_PERTURB_=$(MALLOC_PERTURB_) LD_LIBRARY_PATH=../src \ + ../src/sbchooser --explain \ + -d db.msft2023 \ + -d db.msft2011 \ + -x db.msft2011 \ + > "$@" + +test.sbchooser.identical.secbits.explain: + $(quiet)echo testing sbchooser explanation: identical security strength sort order + $(quiet)$(MAKE) $(makequiet) test.sbchooser.identical.secbits.explain.result + $(quiet)if ! cmp test.sbchooser.identical.secbits.explain.goal.txt test.sbchooser.identical.secbits.explain.result ; then \ + diff -U 200 test.sbchooser.identical.secbits.explain.goal.txt test.sbchooser.identical.secbits.explain.result ; \ + exit 1 ; \ + fi + $(quiet)cmp test.sbchooser.identical.secbits.explain.goal.txt test.sbchooser.identical.secbits.explain.result + $(quiet)rm -f test.sbchooser.identical.secbits.explain.result + $(quiet)echo passed + +test.sbchooser.first.sig.only.explain.result: + $(quiet)ls -1 shim-16.1-4.el10.x64.msft2011.efi \ + shim-16.1-4.el10.x64.msft2011.msft2023.efi \ + shim-16.1-4.el10.x64.msft2023.efi \ + shim-16.1-4.el10.x64.msft2011.efi \ + shim-16.1-4.el10.x64.msft2011.msft2023.efi \ + shim-16.1-4.el10.x64.msft2023.efi \ + | sort -R | MALLOC_PERTURB_=$(MALLOC_PERTURB_) LD_LIBRARY_PATH=../src \ + ../src/sbchooser --explain \ + -d db.msft2023 \ + --no-system-dbx \ + --first-sig-only \ + > "$@" + +test.sbchooser.first.sig.only.explain: + $(quiet)echo testing sbchooser explanation with only the first signature + $(quiet)$(MAKE) $(makequiet) test.sbchooser.first.sig.only.explain.result + $(quiet)if ! cmp test.sbchooser.first.sig.only.explain.goal.txt test.sbchooser.first.sig.only.explain.result ; then \ + diff -U 200 test.sbchooser.first.sig.only.explain.goal.txt test.sbchooser.first.sig.only.explain.result ; \ + exit 1 ; \ + fi + $(quiet)cmp test.sbchooser.first.sig.only.explain.goal.txt test.sbchooser.first.sig.only.explain.result + $(quiet)rm -f test.sbchooser.first.sig.only.explain.result + $(quiet)echo passed + +test.sbchooser.padded.secdir.explain.result: + $(quiet)ls -1 shim-16.1-6.x64.onesig.efi shim-16.1-6.x64.onesig.efi \ + | sort -R | MALLOC_PERTURB_=$(MALLOC_PERTURB_) LD_LIBRARY_PATH=../src \ + ../src/sbchooser --explain \ + -d db.msft2011 \ + --no-system-dbx \ + > "$@" + +test.sbchooser.padded.secdir.explain: + $(quiet)echo testing sbchooser explanation with a padded security directory + $(quiet)$(MAKE) $(makequiet) test.sbchooser.padded.secdir.explain.result + $(quiet)if ! cmp test.sbchooser.padded.secdir.explain.goal.txt test.sbchooser.padded.secdir.explain.result ; then \ + diff -U 200 test.sbchooser.padded.secdir.explain.goal.txt test.sbchooser.padded.secdir.explain.result ; \ + exit 1 ; \ + fi + $(quiet)cmp test.sbchooser.padded.secdir.explain.goal.txt test.sbchooser.padded.secdir.explain.result + $(quiet)rm -f test.sbchooser.padded.secdir.explain.result + $(quiet)echo passed + .PHONY: all clean $(TESTS) # vim:ft=make diff --git a/tests/db.msft2011 b/tests/db.msft2011 new file mode 100644 index 00000000..37325b00 Binary files /dev/null and b/tests/db.msft2011 differ diff --git a/tests/db.msft2023 b/tests/db.msft2023 new file mode 100644 index 00000000..7147e633 Binary files /dev/null and b/tests/db.msft2023 differ diff --git a/tests/db.shim-13-0.2.fedora.x64.sha256 b/tests/db.shim-13-0.2.fedora.x64.sha256 new file mode 100644 index 00000000..1ca98fcc Binary files /dev/null and b/tests/db.shim-13-0.2.fedora.x64.sha256 differ diff --git a/tests/db.shim-13-0.2.fedora.x64.sha384 b/tests/db.shim-13-0.2.fedora.x64.sha384 new file mode 100644 index 00000000..23057ba5 Binary files /dev/null and b/tests/db.shim-13-0.2.fedora.x64.sha384 differ diff --git a/tests/db.shim-13-0.2.fedora.x64.sha512 b/tests/db.shim-13-0.2.fedora.x64.sha512 new file mode 100644 index 00000000..dff3e244 Binary files /dev/null and b/tests/db.shim-13-0.2.fedora.x64.sha512 differ diff --git a/tests/db.shim-15-7.el7_2.x64.sha256 b/tests/db.shim-15-7.el7_2.x64.sha256 new file mode 100644 index 00000000..b84cfef5 Binary files /dev/null and b/tests/db.shim-15-7.el7_2.x64.sha256 differ diff --git a/tests/db.shim-15-7.el7_2.x64.sha384 b/tests/db.shim-15-7.el7_2.x64.sha384 new file mode 100644 index 00000000..faf6c6de Binary files /dev/null and b/tests/db.shim-15-7.el7_2.x64.sha384 differ diff --git a/tests/db.shim-15-7.el7_2.x64.sha512 b/tests/db.shim-15-7.el7_2.x64.sha512 new file mode 100644 index 00000000..a9f8ab40 Binary files /dev/null and b/tests/db.shim-15-7.el7_2.x64.sha512 differ diff --git a/tests/shim-13-0.2.fedora.x64.nosigs.efi b/tests/shim-13-0.2.fedora.x64.nosigs.efi new file mode 100644 index 00000000..80c268db Binary files /dev/null and b/tests/shim-13-0.2.fedora.x64.nosigs.efi differ diff --git a/tests/shim-15-7.el7_2.x64.msft2011.efi b/tests/shim-15-7.el7_2.x64.msft2011.efi new file mode 100644 index 00000000..8c94c7a6 Binary files /dev/null and b/tests/shim-15-7.el7_2.x64.msft2011.efi differ diff --git a/tests/shim-15-7.el7_2.x64.nosigs.efi b/tests/shim-15-7.el7_2.x64.nosigs.efi new file mode 100644 index 00000000..52a5c16f Binary files /dev/null and b/tests/shim-15-7.el7_2.x64.nosigs.efi differ diff --git a/tests/shim-16.1-4.el10.x64.msft2011.efi b/tests/shim-16.1-4.el10.x64.msft2011.efi new file mode 100644 index 00000000..38b04309 Binary files /dev/null and b/tests/shim-16.1-4.el10.x64.msft2011.efi differ diff --git a/tests/shim-16.1-4.el10.x64.msft2011.msft2023.efi b/tests/shim-16.1-4.el10.x64.msft2011.msft2023.efi new file mode 100644 index 00000000..62b5e7bd Binary files /dev/null and b/tests/shim-16.1-4.el10.x64.msft2011.msft2023.efi differ diff --git a/tests/shim-16.1-4.el10.x64.msft2023.efi b/tests/shim-16.1-4.el10.x64.msft2023.efi new file mode 100644 index 00000000..56b1be69 Binary files /dev/null and b/tests/shim-16.1-4.el10.x64.msft2023.efi differ diff --git a/tests/shim-16.1-6.x64.onesig.efi b/tests/shim-16.1-6.x64.onesig.efi new file mode 100755 index 00000000..2fd23172 Binary files /dev/null and b/tests/shim-16.1-6.x64.onesig.efi differ diff --git a/tests/test.sbchooser.db.vs.dbx.explain.goal.txt b/tests/test.sbchooser.db.vs.dbx.explain.goal.txt new file mode 100644 index 00000000..9c188a99 --- /dev/null +++ b/tests/test.sbchooser.db.vs.dbx.explain.goal.txt @@ -0,0 +1,4 @@ +shim-16.1-4.el10.x64.msft2011.msft2023.efi is trusted because cert "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft UEFI CA 2023 signer" is trusted by "/C=US/O=Microsoft Corporation/CN=Microsoft UEFI CA 2023" in db +shim-16.1-4.el10.x64.msft2011.msft2023.efi is trusted because cert "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft UEFI CA 2023 signer" is trusted by "/C=US/O=Microsoft Corporation/CN=Microsoft UEFI CA 2023" in db +shim-15-7.el7_2.x64.msft2011.efi is not trusted because cert "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Windows UEFI Driver Publisher" is revoked by "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation UEFI CA 2011" in dbx +shim-15-7.el7_2.x64.msft2011.efi is not trusted because cert "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Windows UEFI Driver Publisher" is revoked by "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation UEFI CA 2011" in dbx diff --git a/tests/test.sbchooser.db.vs.dbx.goal.txt b/tests/test.sbchooser.db.vs.dbx.goal.txt new file mode 100644 index 00000000..9869ed5a --- /dev/null +++ b/tests/test.sbchooser.db.vs.dbx.goal.txt @@ -0,0 +1,2 @@ +shim-16.1-4.el10.x64.msft2011.msft2023.efi +shim-16.1-4.el10.x64.msft2011.msft2023.efi diff --git a/tests/test.sbchooser.first.sig.only.explain.goal.txt b/tests/test.sbchooser.first.sig.only.explain.goal.txt new file mode 100644 index 00000000..4bb79a49 --- /dev/null +++ b/tests/test.sbchooser.first.sig.only.explain.goal.txt @@ -0,0 +1,6 @@ +shim-16.1-4.el10.x64.msft2023.efi is trusted because cert "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft UEFI CA 2023 signer" is trusted by "/C=US/O=Microsoft Corporation/CN=Microsoft UEFI CA 2023" in db +shim-16.1-4.el10.x64.msft2023.efi is trusted because cert "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft UEFI CA 2023 signer" is trusted by "/C=US/O=Microsoft Corporation/CN=Microsoft UEFI CA 2023" in db +shim-16.1-4.el10.x64.msft2011.msft2023.efi is not trusted because no certs or hashes trust it +shim-16.1-4.el10.x64.msft2011.msft2023.efi is not trusted because no certs or hashes trust it +shim-16.1-4.el10.x64.msft2011.efi is not trusted because no certs or hashes trust it +shim-16.1-4.el10.x64.msft2011.efi is not trusted because no certs or hashes trust it diff --git a/tests/test.sbchooser.first.sig.only.goal.txt b/tests/test.sbchooser.first.sig.only.goal.txt new file mode 100644 index 00000000..92ee1c2e --- /dev/null +++ b/tests/test.sbchooser.first.sig.only.goal.txt @@ -0,0 +1,2 @@ +shim-16.1-4.el10.x64.msft2023.efi +shim-16.1-4.el10.x64.msft2023.efi diff --git a/tests/test.sbchooser.identical.secbits.explain.goal.txt b/tests/test.sbchooser.identical.secbits.explain.goal.txt new file mode 100644 index 00000000..11398dd9 --- /dev/null +++ b/tests/test.sbchooser.identical.secbits.explain.goal.txt @@ -0,0 +1,6 @@ +shim-16.1-4.el10.x64.msft2011.msft2023.efi is trusted because cert "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft UEFI CA 2023 signer" is trusted by "/C=US/O=Microsoft Corporation/CN=Microsoft UEFI CA 2023" in db +shim-16.1-4.el10.x64.msft2011.msft2023.efi is trusted because cert "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft UEFI CA 2023 signer" is trusted by "/C=US/O=Microsoft Corporation/CN=Microsoft UEFI CA 2023" in db +shim-16.1-4.el10.x64.msft2023.efi is trusted because cert "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft UEFI CA 2023 signer" is trusted by "/C=US/O=Microsoft Corporation/CN=Microsoft UEFI CA 2023" in db +shim-16.1-4.el10.x64.msft2023.efi is trusted because cert "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft UEFI CA 2023 signer" is trusted by "/C=US/O=Microsoft Corporation/CN=Microsoft UEFI CA 2023" in db +shim-16.1-4.el10.x64.msft2011.efi is not trusted because cert "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Windows UEFI Driver Publisher" is revoked by "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation UEFI CA 2011" in dbx +shim-16.1-4.el10.x64.msft2011.efi is not trusted because cert "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Windows UEFI Driver Publisher" is revoked by "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation UEFI CA 2011" in dbx diff --git a/tests/test.sbchooser.identical.secbits.goal.txt b/tests/test.sbchooser.identical.secbits.goal.txt new file mode 100644 index 00000000..51b21f83 --- /dev/null +++ b/tests/test.sbchooser.identical.secbits.goal.txt @@ -0,0 +1,4 @@ +shim-16.1-4.el10.x64.msft2011.msft2023.efi +shim-16.1-4.el10.x64.msft2011.msft2023.efi +shim-16.1-4.el10.x64.msft2023.efi +shim-16.1-4.el10.x64.msft2023.efi diff --git a/tests/test.sbchooser.padded.secdir.explain.goal.txt b/tests/test.sbchooser.padded.secdir.explain.goal.txt new file mode 100644 index 00000000..f7cb2214 --- /dev/null +++ b/tests/test.sbchooser.padded.secdir.explain.goal.txt @@ -0,0 +1,2 @@ +shim-16.1-6.x64.onesig.efi is trusted because cert "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Windows UEFI Driver Publisher" is trusted by "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation UEFI CA 2011" in db +shim-16.1-6.x64.onesig.efi is trusted because cert "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Windows UEFI Driver Publisher" is trusted by "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation UEFI CA 2011" in db diff --git a/tests/test.sbchooser.sha512.vs.db.explain.goal.txt b/tests/test.sbchooser.sha512.vs.db.explain.goal.txt new file mode 100644 index 00000000..8b296907 --- /dev/null +++ b/tests/test.sbchooser.sha512.vs.db.explain.goal.txt @@ -0,0 +1,4 @@ +shim-13-0.2.fedora.x64.nosigs.efi is trusted by hash 3a6bb1f335451278bd3b794630c806c83da7b29bae66a161e5ab78b75571d674d50a15b54735ec288b953c8d7696ffdf2f7ef0bcb5c4e74c8cb2ca4b6a5e2c98 in db +shim-13-0.2.fedora.x64.nosigs.efi is trusted by hash 3a6bb1f335451278bd3b794630c806c83da7b29bae66a161e5ab78b75571d674d50a15b54735ec288b953c8d7696ffdf2f7ef0bcb5c4e74c8cb2ca4b6a5e2c98 in db +shim-15-7.el7_2.x64.msft2011.efi is trusted because cert "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Windows UEFI Driver Publisher" is trusted by "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation UEFI CA 2011" in db +shim-15-7.el7_2.x64.msft2011.efi is trusted because cert "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Windows UEFI Driver Publisher" is trusted by "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation UEFI CA 2011" in db diff --git a/tests/test.sbchooser.sha512.vs.db.goal.txt b/tests/test.sbchooser.sha512.vs.db.goal.txt new file mode 100644 index 00000000..8eb8ba22 --- /dev/null +++ b/tests/test.sbchooser.sha512.vs.db.goal.txt @@ -0,0 +1,4 @@ +shim-13-0.2.fedora.x64.nosigs.efi +shim-13-0.2.fedora.x64.nosigs.efi +shim-15-7.el7_2.x64.msft2011.efi +shim-15-7.el7_2.x64.msft2011.efi diff --git a/tests/test.sbchooser.sha512.vs.sha256.explain.goal.txt b/tests/test.sbchooser.sha512.vs.sha256.explain.goal.txt new file mode 100644 index 00000000..d4154db3 --- /dev/null +++ b/tests/test.sbchooser.sha512.vs.sha256.explain.goal.txt @@ -0,0 +1,4 @@ +shim-15-7.el7_2.x64.nosigs.efi is trusted by hash 5c1872d1556e9e39f821d25682e3f3db0545a1e47db2ac6e6d93d2a687be536b4b3a56fa11395ff096b33348cfb15801160008188f207a5a39bdb69e65464f1d in db +shim-15-7.el7_2.x64.nosigs.efi is trusted by hash 5c1872d1556e9e39f821d25682e3f3db0545a1e47db2ac6e6d93d2a687be536b4b3a56fa11395ff096b33348cfb15801160008188f207a5a39bdb69e65464f1d in db +shim-13-0.2.fedora.x64.nosigs.efi is trusted by hash 9d4fc6b73c11868c839649f4a777a81ce8093676e5be1a84e567b5074c972956 in db +shim-13-0.2.fedora.x64.nosigs.efi is trusted by hash 9d4fc6b73c11868c839649f4a777a81ce8093676e5be1a84e567b5074c972956 in db diff --git a/tests/test.sbchooser.sha512.vs.sha256.goal.txt b/tests/test.sbchooser.sha512.vs.sha256.goal.txt new file mode 100644 index 00000000..ef3d94a0 --- /dev/null +++ b/tests/test.sbchooser.sha512.vs.sha256.goal.txt @@ -0,0 +1,4 @@ +shim-15-7.el7_2.x64.nosigs.efi +shim-15-7.el7_2.x64.nosigs.efi +shim-13-0.2.fedora.x64.nosigs.efi +shim-13-0.2.fedora.x64.nosigs.efi diff --git a/tests/test.sbchooser.sha512.vs.sha256.vs.dbx.explain.goal.txt b/tests/test.sbchooser.sha512.vs.sha256.vs.dbx.explain.goal.txt new file mode 100644 index 00000000..38e3c4e5 --- /dev/null +++ b/tests/test.sbchooser.sha512.vs.sha256.vs.dbx.explain.goal.txt @@ -0,0 +1,4 @@ +shim-13-0.2.fedora.x64.nosigs.efi is revoked by hash 9d4fc6b73c11868c839649f4a777a81ce8093676e5be1a84e567b5074c972956 in dbx +shim-13-0.2.fedora.x64.nosigs.efi is revoked by hash 9d4fc6b73c11868c839649f4a777a81ce8093676e5be1a84e567b5074c972956 in dbx +shim-15-7.el7_2.x64.nosigs.efi is trusted by hash 5c1872d1556e9e39f821d25682e3f3db0545a1e47db2ac6e6d93d2a687be536b4b3a56fa11395ff096b33348cfb15801160008188f207a5a39bdb69e65464f1d in db +shim-15-7.el7_2.x64.nosigs.efi is trusted by hash 5c1872d1556e9e39f821d25682e3f3db0545a1e47db2ac6e6d93d2a687be536b4b3a56fa11395ff096b33348cfb15801160008188f207a5a39bdb69e65464f1d in db diff --git a/tests/test.sbchooser.sha512.vs.sha256.vs.dbx.goal.txt b/tests/test.sbchooser.sha512.vs.sha256.vs.dbx.goal.txt new file mode 100644 index 00000000..d6063484 --- /dev/null +++ b/tests/test.sbchooser.sha512.vs.sha256.vs.dbx.goal.txt @@ -0,0 +1,2 @@ +shim-15-7.el7_2.x64.nosigs.efi +shim-15-7.el7_2.x64.nosigs.efi diff --git a/tests/test.sbchooser.sha512.vs.untrusted.explain.goal.txt b/tests/test.sbchooser.sha512.vs.untrusted.explain.goal.txt new file mode 100644 index 00000000..550e7245 --- /dev/null +++ b/tests/test.sbchooser.sha512.vs.untrusted.explain.goal.txt @@ -0,0 +1,4 @@ +shim-15-7.el7_2.x64.nosigs.efi is trusted by hash 5c1872d1556e9e39f821d25682e3f3db0545a1e47db2ac6e6d93d2a687be536b4b3a56fa11395ff096b33348cfb15801160008188f207a5a39bdb69e65464f1d in db +shim-15-7.el7_2.x64.nosigs.efi is trusted by hash 5c1872d1556e9e39f821d25682e3f3db0545a1e47db2ac6e6d93d2a687be536b4b3a56fa11395ff096b33348cfb15801160008188f207a5a39bdb69e65464f1d in db +shim-13-0.2.fedora.x64.nosigs.efi is not trusted because no certs or hashes trust it +shim-13-0.2.fedora.x64.nosigs.efi is not trusted because no certs or hashes trust it diff --git a/tests/test.sbchooser.sha512.vs.untrusted.goal.txt b/tests/test.sbchooser.sha512.vs.untrusted.goal.txt new file mode 100644 index 00000000..d6063484 --- /dev/null +++ b/tests/test.sbchooser.sha512.vs.untrusted.goal.txt @@ -0,0 +1,2 @@ +shim-15-7.el7_2.x64.nosigs.efi +shim-15-7.el7_2.x64.nosigs.efi