barebox's EFI loader verifies no signatures: efi_image_authenticate() accepts every image. Add efi_authenticode_verify() as the verifier for signed EFI images: compute the Authenticode digest over the regions efi_image_parse() collects, check it against the SpcIndirectDataContent of the PKCS#7 signature, check the messageDigest attribute against the digest of that content, and verify the signature over the attributes with the keys of a barebox keyring. The following commits use it.
barebox has no ASN.1 decoder: keys are converted from certificates at build time, and FIT signatures carry none. The PKCS#7 structure is walked as plain DER for the fields needed, with every length checked against what remains; the certificates it carries are skipped. Supported are one signer, SHA-256 and RSA. As for FIT images, trust comes from the keyring, not from X.509 chains or db/dbx. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Johannes Schneider <[email protected]> --- efi/loader/Kconfig | 16 ++ efi/loader/Makefile | 1 + efi/loader/authenticode.c | 435 ++++++++++++++++++++++++++++++ include/efi/loader/authenticode.h | 9 + 4 files changed, 461 insertions(+) create mode 100644 efi/loader/authenticode.c create mode 100644 include/efi/loader/authenticode.h diff --git a/efi/loader/Kconfig b/efi/loader/Kconfig index 5692e54ebe..4099da0689 100644 --- a/efi/loader/Kconfig +++ b/efi/loader/Kconfig @@ -24,6 +24,22 @@ config EFI_LOADER_DEBUG_SUPPORT config EFI_LOADER_SECURE_BOOT bool +config EFI_LOADER_AUTHENTICODE + bool "Verify Authenticode signatures of booted EFI images" + depends on CRYPTO_BUILTIN_KEYS && HAVE_DIGEST_SHA256 + select CRYPTO_RSA + help + Verify the Authenticode (PKCS#7, SHA-256, RSA) signature of an EFI + image booted with bootm against the keys compiled into the "efi" + keyring (CONFIG_CRYPTO_PUBLIC_KEYS, keyring=efi). With signed images + forced, an EFI image then boots only if one of those keys verifies + it, the same way a FIT image must carry a valid signature. + + X.509 certificates in the signature are not evaluated: trust is + anchored in the keyring. barebox does not report UEFI Secure Boot + to the payload, so a UKI keeps taking its command line from + barebox. + menu "UEFI services" config EFI_LOADER_GET_TIME diff --git a/efi/loader/Makefile b/efi/loader/Makefile index 24850e87b1..775014dc22 100644 --- a/efi/loader/Makefile +++ b/efi/loader/Makefile @@ -14,6 +14,7 @@ obj-y += boot.o obj-y += runtime.o obj-y += setup.o obj-y += watchdog.o +obj-$(CONFIG_EFI_LOADER_AUTHENTICODE) += authenticode.o obj-y += loadopts.o obj-y += efi_var_common.o obj-y += efi_variable.o diff --git a/efi/loader/authenticode.c b/efi/loader/authenticode.c new file mode 100644 index 0000000000..36e5a46fc6 --- /dev/null +++ b/efi/loader/authenticode.c @@ -0,0 +1,435 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Authenticode verification of PE images against barebox built-in keys: + * one signer, SHA-256 and RSA, the certificates in the signature are ignored + */ + +#define pr_fmt(fmt) "efi-loader: authenticode: " fmt + +#include <common.h> +#include <digest.h> +#include <crypto/sha.h> +#include <malloc.h> +#include <crypto/public_key.h> +#include <efi/loader/pe.h> +#include <efi/loader/authenticode.h> +#include <efi/error.h> +#include <pe.h> + +struct der { + const u8 *p; + const u8 *end; +}; + +struct der_elem { + u8 tag; + const u8 *start; /* tag byte */ + const u8 *val; + size_t len; + size_t total; /* tag + length + value */ +}; + +#define DER_INTEGER 0x02 +#define DER_OCTET 0x04 +#define DER_OID 0x06 +#define DER_SEQ 0x30 +#define DER_SET 0x31 +#define DER_CTX0 0xa0 +#define DER_CTX1 0xa1 + +static const u8 oid_signed_data[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02 }; +static const u8 oid_spc_indirect_data[] = { + 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x01, 0x04 +}; +static const u8 oid_sha256[] = { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 }; +static const u8 oid_content_type[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x03 }; +static const u8 oid_message_digest[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x04 }; + +static int der_next(struct der *d, struct der_elem *e) +{ + const u8 *p = d->p; + size_t len, n; + + if (d->end - p < 2) + return -EBADMSG; + + e->start = p; + e->tag = *p++; + if ((e->tag & 0x1f) == 0x1f) + return -EBADMSG; + + len = *p++; + if (len & 0x80) { + n = len & 0x7f; + if (!n || n > 4 || d->end - p < n) + return -EBADMSG; + len = 0; + while (n--) + len = (len << 8) | *p++; + } + + if (d->end - p < len) + return -EBADMSG; + + e->val = p; + e->len = len; + e->total = p + len - e->start; + d->p = p + len; + + return 0; +} + +static int der_expect(struct der *d, u8 tag, struct der_elem *e) +{ + int ret = der_next(d, e); + + if (ret) + return ret; + + return e->tag == tag ? 0 : -EBADMSG; +} + +static struct der der_enter(const struct der_elem *e) +{ + return (struct der) { .p = e->val, .end = e->val + e->len }; +} + +static bool der_oid_is(const struct der_elem *e, const u8 *oid, size_t len) +{ + return e->tag == DER_OID && e->len == len && !memcmp(e->val, oid, len); +} + +/* AlgorithmIdentifier ::= SEQUENCE { OID, parameters OPTIONAL } */ +static int der_expect_sha256(struct der *d) +{ + struct der_elem seq, oid; + struct der in; + int ret; + + ret = der_expect(d, DER_SEQ, &seq); + if (ret) + return ret; + + in = der_enter(&seq); + ret = der_expect(&in, DER_OID, &oid); + if (ret) + return ret; + + return der_oid_is(&oid, oid_sha256, sizeof(oid_sha256)) ? 0 : -EOPNOTSUPP; +} + +struct authenticode { + const u8 *pe_digest; /* SpcIndirectDataContent.messageDigest */ + const u8 *spc; /* SpcIndirectDataContent content octets */ + size_t spc_len; + const u8 *attrs; /* [0] IMPLICIT authenticatedAttributes */ + size_t attrs_len; + const u8 *attr_digest; /* messageDigest attribute value */ + bool attr_content_type_ok; + const u8 *sig; + size_t sig_len; +}; + +static int authenticode_parse_attrs(struct authenticode *a, + const struct der_elem *attrs) +{ + struct der in = der_enter(attrs); + struct der_elem attr, oid, set, val; + struct der ain, sin; + int ret; + + while (in.p < in.end) { + ret = der_expect(&in, DER_SEQ, &attr); + if (ret) + return ret; + + ain = der_enter(&attr); + ret = der_expect(&ain, DER_OID, &oid); + if (ret) + return ret; + ret = der_expect(&ain, DER_SET, &set); + if (ret) + return ret; + sin = der_enter(&set); + + if (der_oid_is(&oid, oid_message_digest, sizeof(oid_message_digest))) { + ret = der_expect(&sin, DER_OCTET, &val); + if (ret || val.len != SHA256_DIGEST_SIZE) + return -EBADMSG; + a->attr_digest = val.val; + } else if (der_oid_is(&oid, oid_content_type, sizeof(oid_content_type))) { + ret = der_expect(&sin, DER_OID, &val); + if (ret) + return ret; + a->attr_content_type_ok = + der_oid_is(&val, oid_spc_indirect_data, + sizeof(oid_spc_indirect_data)); + } + } + + return a->attr_digest ? 0 : -EBADMSG; +} + +static int authenticode_parse(struct authenticode *a, const void *buf, size_t len) +{ + struct der d = { .p = buf, .end = (const u8 *)buf + len }; + struct der_elem e, ci, sd, spc, dinfo, si; + struct der in, sdin, ciin, spcin, dinin, siin; + int ret; + + /* ContentInfo ::= SEQUENCE { contentType, [0] EXPLICIT content } */ + ret = der_expect(&d, DER_SEQ, &ci); + if (ret) + return ret; + in = der_enter(&ci); + ret = der_expect(&in, DER_OID, &e); + if (ret) + return ret; + if (!der_oid_is(&e, oid_signed_data, sizeof(oid_signed_data))) + return -EBADMSG; + ret = der_expect(&in, DER_CTX0, &e); + if (ret) + return ret; + in = der_enter(&e); + + /* SignedData ::= SEQUENCE { version, digestAlgorithms, contentInfo, ... } */ + ret = der_expect(&in, DER_SEQ, &sd); + if (ret) + return ret; + sdin = der_enter(&sd); + ret = der_expect(&sdin, DER_INTEGER, &e); + if (ret) + return ret; + ret = der_expect(&sdin, DER_SET, &e); + if (ret) + return ret; + + /* contentInfo: SPC_INDIRECT_DATA carrying the PE image digest */ + ret = der_expect(&sdin, DER_SEQ, &e); + if (ret) + return ret; + ciin = der_enter(&e); + ret = der_expect(&ciin, DER_OID, &e); + if (ret) + return ret; + if (!der_oid_is(&e, oid_spc_indirect_data, sizeof(oid_spc_indirect_data))) + return -EBADMSG; + ret = der_expect(&ciin, DER_CTX0, &e); + if (ret) + return ret; + ciin = der_enter(&e); + ret = der_expect(&ciin, DER_SEQ, &spc); + if (ret) + return ret; + a->spc = spc.val; + a->spc_len = spc.len; + + spcin = der_enter(&spc); + ret = der_expect(&spcin, DER_SEQ, &e); /* SpcAttributeTypeAndOptionalValue */ + if (ret) + return ret; + ret = der_expect(&spcin, DER_SEQ, &dinfo); /* DigestInfo */ + if (ret) + return ret; + dinin = der_enter(&dinfo); + ret = der_expect_sha256(&dinin); + if (ret) + return ret; + ret = der_expect(&dinin, DER_OCTET, &e); + if (ret || e.len != SHA256_DIGEST_SIZE) + return -EBADMSG; + a->pe_digest = e.val; + + /* skip optional certificates [0] and crls [1] */ + do { + ret = der_next(&sdin, &e); + if (ret) + return ret; + } while (e.tag == DER_CTX0 || e.tag == DER_CTX1); + + if (e.tag != DER_SET) + return -EBADMSG; + + /* first SignerInfo only */ + in = der_enter(&e); + ret = der_expect(&in, DER_SEQ, &si); + if (ret) + return ret; + siin = der_enter(&si); + ret = der_expect(&siin, DER_INTEGER, &e); + if (ret) + return ret; + ret = der_expect(&siin, DER_SEQ, &e); /* issuerAndSerialNumber */ + if (ret) + return ret; + ret = der_expect_sha256(&siin); + if (ret) + return ret; + + ret = der_expect(&siin, DER_CTX0, &e); + if (ret) + return ret; + a->attrs = e.start; + a->attrs_len = e.total; + ret = authenticode_parse_attrs(a, &e); + if (ret) + return ret; + + ret = der_expect(&siin, DER_SEQ, &e); /* digestEncryptionAlgorithm */ + if (ret) + return ret; + ret = der_expect(&siin, DER_OCTET, &e); + if (ret) + return ret; + a->sig = e.val; + a->sig_len = e.len; + + return 0; +} + +static int sha256_regions(const struct efi_image_regions *regs, u8 *out) +{ + struct digest *d = digest_alloc_by_algo(HASH_ALGO_SHA256); + int i, ret; + + if (!d) + return -EOPNOTSUPP; + + ret = digest_init(d); + for (i = 0; !ret && i < regs->num; i++) + ret = digest_update(d, regs->reg[i].data, regs->reg[i].size); + if (!ret) + ret = digest_final(d, out); + + digest_free(d); + return ret; +} + +static int sha256_buf(const void *buf, size_t len, u8 *out) +{ + struct digest *d = digest_alloc_by_algo(HASH_ALGO_SHA256); + int ret; + + if (!d) + return -EOPNOTSUPP; + + ret = digest_digest(d, buf, len, out); + digest_free(d); + return ret; +} + +/* The signature covers the attributes DER-encoded as SET OF, not as [0] */ +static int sha256_attrs(const struct authenticode *a, u8 *out) +{ + struct digest *d = digest_alloc_by_algo(HASH_ALGO_SHA256); + const u8 set_tag = DER_SET; + int ret; + + if (!d) + return -EOPNOTSUPP; + + ret = digest_init(d); + if (!ret) + ret = digest_update(d, &set_tag, 1); + if (!ret) + ret = digest_update(d, a->attrs + 1, a->attrs_len - 1); + if (!ret) + ret = digest_final(d, out); + + digest_free(d); + return ret; +} + +/** + * efi_authenticode_verify() - verify a PE image's Authenticode signature + * @efi: PE image + * @len: exact size of the image, see efi_pe_file_size() + * @keyring: barebox keyring holding the trusted keys + * + * Return: 0 if the image is signed by a key in @keyring, negative error code + * otherwise. + */ +int efi_authenticode_verify(void *efi, size_t len, const char *keyring) +{ + struct efi_image_regions *regs = NULL; + const struct public_key *key; + struct authenticode a = {}; + WIN_CERTIFICATE *wincert; + size_t auth_len; + u8 pe_hash[SHA256_DIGEST_SIZE], hash[SHA256_DIGEST_SIZE]; + const struct keyring *kr; + int ret; + + if (!efi_image_parse(efi, len, ®s, &wincert, &auth_len)) + return -EBADMSG; + + if (!wincert) { + pr_err("image is not signed\n"); + ret = -ENOKEY; + goto out; + } + + if (wincert->dwLength > auth_len || wincert->dwLength <= sizeof(*wincert) || + wincert->wRevision != WIN_CERT_REVISION_2_0 || + wincert->wCertificateType != WIN_CERT_TYPE_PKCS_SIGNED_DATA) { + pr_err("unsupported certificate table entry\n"); + ret = -EBADMSG; + goto out; + } + + ret = authenticode_parse(&a, wincert + 1, wincert->dwLength - sizeof(*wincert)); + if (ret) { + pr_err("cannot parse signature: %pe\n", ERR_PTR(ret)); + goto out; + } + + if (!a.attr_content_type_ok) { + pr_err("signed content is not SpcIndirectDataContent\n"); + ret = -EBADMSG; + goto out; + } + + ret = sha256_regions(regs, pe_hash); + if (ret) + goto out; + if (memcmp(pe_hash, a.pe_digest, sizeof(pe_hash))) { + pr_err("image digest mismatch\n"); + ret = -EBADMSG; + goto out; + } + + ret = sha256_buf(a.spc, a.spc_len, hash); + if (ret) + goto out; + if (memcmp(hash, a.attr_digest, sizeof(hash))) { + pr_err("signed attributes do not match the content\n"); + ret = -EBADMSG; + goto out; + } + + ret = sha256_attrs(&a, hash); + if (ret) + goto out; + + kr = keyring_find(keyring); + if (!kr) { + pr_err("keyring '%s' not registered\n", keyring); + ret = -ENOKEY; + goto out; + } + + ret = -ENOKEY; + for_each_key_in_keyring(key, kr) { + if (!public_key_verify(key, a.sig, a.sig_len, hash, HASH_ALGO_SHA256)) { + pr_info("verified with key '%s'\n", key->key_name_hint ?: "?"); + ret = 0; + break; + } + } + + if (ret) + pr_err("no key in keyring '%s' verifies the signature\n", keyring); +out: + free(regs); + return ret; +} diff --git a/include/efi/loader/authenticode.h b/include/efi/loader/authenticode.h new file mode 100644 index 0000000000..24c46ca7cf --- /dev/null +++ b/include/efi/loader/authenticode.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef __EFI_LOADER_AUTHENTICODE_H +#define __EFI_LOADER_AUTHENTICODE_H + +#include <linux/types.h> + +int efi_authenticode_verify(void *efi, size_t len, const char *keyring); + +#endif -- 2.43.0
