u-boot/lib/efi_loader/efi_signature.c
Ilias Apalodimas bdcc0a9594 efi_loader: fix uefi secure boot with intermediate certs
The general rule of accepting or rejecting an image is
 1. Is the sha256 of the image in dbx
 2. Is the image signed with a certificate that's found in db and
    not in dbx
 3. The image carries a cert which is signed by a cert in db (and
    not in dbx) and the image can be verified against the former
 4. Is the sha256 of the image in db

For example SHIM is signed by "CN=Microsoft Windows UEFI Driver Publisher",
which is issued by "CN=Microsoft Corporation UEFI CA 2011", which in it's
turn is issued by "CN=Microsoft Corporation Third Party Marketplace Root".
The latter is a self-signed CA certificate and with our current implementation
allows shim to execute if we insert it in db.

However it's the CA cert in the middle of the chain which usually ends up
in the system's db.  pkcs7_verify_one() might or might not return the root
certificate for a given chain.  But when verifying executables in UEFI,  the
trust anchor can be in the middle of the chain, as long as that certificate
is present in db.  Currently we only allow this check on self-signed
certificates,  so let's remove that check and allow all certs to try a
match an entry in db.

Open questions:
- Does this break any aspect of variable authentication since
  efi_signature_verify() is used on those as well?

Signed-off-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
2022-02-26 07:37:00 +01:00

793 lines
20 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2018 Patrick Wildt <patrick@blueri.se>
* Copyright (c) 2019 Linaro Limited, Author: AKASHI Takahiro
*/
#include <common.h>
#include <charset.h>
#include <efi_loader.h>
#include <efi_variable.h>
#include <image.h>
#include <hexdump.h>
#include <malloc.h>
#include <crypto/pkcs7.h>
#include <crypto/pkcs7_parser.h>
#include <crypto/public_key.h>
#include <linux/compat.h>
#include <linux/oid_registry.h>
#include <u-boot/hash-checksum.h>
#include <u-boot/rsa.h>
#include <u-boot/sha256.h>
const efi_guid_t efi_guid_sha256 = EFI_CERT_SHA256_GUID;
const efi_guid_t efi_guid_cert_rsa2048 = EFI_CERT_RSA2048_GUID;
const efi_guid_t efi_guid_cert_x509 = EFI_CERT_X509_GUID;
const efi_guid_t efi_guid_cert_x509_sha256 = EFI_CERT_X509_SHA256_GUID;
const efi_guid_t efi_guid_cert_type_pkcs7 = EFI_CERT_TYPE_PKCS7_GUID;
static u8 pkcs7_hdr[] = {
/* SEQUENCE */
0x30, 0x82, 0x05, 0xc7,
/* OID: pkcs7-signedData */
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02,
/* Context Structured? */
0xa0, 0x82, 0x05, 0xb8,
};
/**
* efi_parse_pkcs7_header - parse a signature in payload
* @buf: Pointer to payload's value
* @buflen: Length of @buf
* @tmpbuf: Pointer to temporary buffer
*
* Parse a signature embedded in payload's value and instantiate
* a pkcs7_message structure. Since pkcs7_parse_message() accepts only
* pkcs7's signedData, some header needed be prepended for correctly
* parsing authentication data
* A temporary buffer will be allocated if needed, and it should be
* kept valid during the authentication because some data in the buffer
* will be referenced by efi_signature_verify().
*
* Return: Pointer to pkcs7_message structure on success, NULL on error
*/
struct pkcs7_message *efi_parse_pkcs7_header(const void *buf,
size_t buflen,
u8 **tmpbuf)
{
u8 *ebuf;
size_t ebuflen, len;
struct pkcs7_message *msg;
/*
* This is the best assumption to check if the binary is
* already in a form of pkcs7's signedData.
*/
if (buflen > sizeof(pkcs7_hdr) &&
!memcmp(&((u8 *)buf)[4], &pkcs7_hdr[4], 11)) {
msg = pkcs7_parse_message(buf, buflen);
if (IS_ERR(msg))
return NULL;
return msg;
}
/*
* Otherwise, we should add a dummy prefix sequence for pkcs7
* message parser to be able to process.
* NOTE: EDK2 also uses similar hack in WrapPkcs7Data()
* in CryptoPkg/Library/BaseCryptLib/Pk/CryptPkcs7VerifyCommon.c
* TODO:
* The header should be composed in a more refined manner.
*/
EFI_PRINT("Makeshift prefix added to authentication data\n");
ebuflen = sizeof(pkcs7_hdr) + buflen;
if (ebuflen <= 0x7f) {
EFI_PRINT("Data is too short\n");
return NULL;
}
ebuf = malloc(ebuflen);
if (!ebuf) {
EFI_PRINT("Out of memory\n");
return NULL;
}
memcpy(ebuf, pkcs7_hdr, sizeof(pkcs7_hdr));
memcpy(ebuf + sizeof(pkcs7_hdr), buf, buflen);
len = ebuflen - 4;
ebuf[2] = (len >> 8) & 0xff;
ebuf[3] = len & 0xff;
len = ebuflen - 0x13;
ebuf[0x11] = (len >> 8) & 0xff;
ebuf[0x12] = len & 0xff;
msg = pkcs7_parse_message(ebuf, ebuflen);
if (IS_ERR(msg)) {
free(ebuf);
return NULL;
}
*tmpbuf = ebuf;
return msg;
}
/**
* efi_hash_regions - calculate a hash value
* @regs: Array of regions
* @count: Number of regions
* @hash: Pointer to a pointer to buffer holding a hash value
* @size: Size of buffer to be returned
*
* Calculate a sha256 value of @regs and return a value in @hash.
*
* Return: true on success, false on error
*/
static bool efi_hash_regions(struct image_region *regs, int count,
void **hash, size_t *size)
{
if (!*hash) {
*hash = calloc(1, SHA256_SUM_LEN);
if (!*hash) {
EFI_PRINT("Out of memory\n");
return false;
}
}
if (size)
*size = SHA256_SUM_LEN;
hash_calculate("sha256", regs, count, *hash);
#ifdef DEBUG
EFI_PRINT("hash calculated:\n");
print_hex_dump(" ", DUMP_PREFIX_OFFSET, 16, 1,
*hash, SHA256_SUM_LEN, false);
#endif
return true;
}
/**
* hash_algo_supported - check if the requested hash algorithm is supported
* @guid: guid of the algorithm
*
* Return: true if supported false otherwise
*/
static bool hash_algo_supported(const efi_guid_t guid)
{
int i;
const efi_guid_t unsupported_hashes[] = {
EFI_CERT_SHA1_GUID,
EFI_CERT_SHA224_GUID,
EFI_CERT_SHA384_GUID,
EFI_CERT_SHA512_GUID,
};
for (i = 0; i < ARRAY_SIZE(unsupported_hashes); i++) {
if (!guidcmp(&unsupported_hashes[i], &guid))
return false;
}
return true;
}
/**
* efi_signature_lookup_digest - search for an image's digest in sigdb
* @regs: List of regions to be authenticated
* @db: Signature database for trusted certificates
* @dbx Caller needs to set this to true if he is searching dbx
*
* A message digest of image pointed to by @regs is calculated and
* its hash value is compared to entries in signature database pointed
* to by @db.
*
* Return: true if found, false if not
*/
bool efi_signature_lookup_digest(struct efi_image_regions *regs,
struct efi_signature_store *db,
bool dbx)
{
struct efi_signature_store *siglist;
struct efi_sig_data *sig_data;
void *hash = NULL;
size_t size = 0;
bool found = false;
bool hash_done = false;
EFI_PRINT("%s: Enter, %p, %p\n", __func__, regs, db);
if (!regs || !db || !db->sig_data_list)
goto out;
for (siglist = db; siglist; siglist = siglist->next) {
/*
* if the hash algorithm is unsupported and we get an entry in
* dbx reject the image
*/
if (dbx && !hash_algo_supported(siglist->sig_type)) {
found = true;
continue;
};
/*
* Only support sha256 for now, that's what
* hash-to-efi-sig-list produces
*/
if (guidcmp(&siglist->sig_type, &efi_guid_sha256))
continue;
if (!hash_done &&
!efi_hash_regions(regs->reg, regs->num, &hash, &size)) {
EFI_PRINT("Digesting an image failed\n");
break;
}
hash_done = true;
for (sig_data = siglist->sig_data_list; sig_data;
sig_data = sig_data->next) {
#ifdef DEBUG
EFI_PRINT("Msg digest in database:\n");
print_hex_dump(" ", DUMP_PREFIX_OFFSET, 16, 1,
sig_data->data, sig_data->size, false);
#endif
if (sig_data->size == size &&
!memcmp(sig_data->data, hash, size)) {
found = true;
free(hash);
goto out;
}
}
free(hash);
hash = NULL;
}
out:
EFI_PRINT("%s: Exit, found: %d\n", __func__, found);
return found;
}
/**
* efi_lookup_certificate - find a certificate within db
* @msg: Signature
* @db: Signature database
*
* Search signature database pointed to by @db and find a certificate
* pointed to by @cert.
*
* Return: true if found, false otherwise.
*/
static bool efi_lookup_certificate(struct x509_certificate *cert,
struct efi_signature_store *db)
{
struct efi_signature_store *siglist;
struct efi_sig_data *sig_data;
struct image_region reg[1];
void *hash = NULL, *hash_tmp = NULL;
size_t size = 0;
bool found = false;
EFI_PRINT("%s: Enter, %p, %p\n", __func__, cert, db);
if (!cert || !db || !db->sig_data_list)
goto out;
/*
* TODO: identify a certificate using sha256 digest
* Is there any better way?
*/
/* calculate hash of TBSCertificate */
reg[0].data = cert->tbs;
reg[0].size = cert->tbs_size;
if (!efi_hash_regions(reg, 1, &hash, &size))
goto out;
EFI_PRINT("%s: searching for %s\n", __func__, cert->subject);
for (siglist = db; siglist; siglist = siglist->next) {
/* only with x509 certificate */
if (guidcmp(&siglist->sig_type, &efi_guid_cert_x509))
continue;
for (sig_data = siglist->sig_data_list; sig_data;
sig_data = sig_data->next) {
struct x509_certificate *cert_tmp;
cert_tmp = x509_cert_parse(sig_data->data,
sig_data->size);
if (IS_ERR_OR_NULL(cert_tmp))
continue;
EFI_PRINT("%s: against %s\n", __func__,
cert_tmp->subject);
reg[0].data = cert_tmp->tbs;
reg[0].size = cert_tmp->tbs_size;
if (!efi_hash_regions(reg, 1, &hash_tmp, NULL))
goto out;
x509_free_certificate(cert_tmp);
if (!memcmp(hash, hash_tmp, size)) {
found = true;
goto out;
}
}
}
out:
free(hash);
free(hash_tmp);
EFI_PRINT("%s: Exit, found: %d\n", __func__, found);
return found;
}
/**
* efi_verify_certificate - verify certificate's signature with database
* @signer: Certificate
* @db: Signature database
* @root: Certificate to verify @signer
*
* Determine if certificate pointed to by @signer may be verified
* by one of certificates in signature database pointed to by @db.
*
* Return: true if certificate is verified, false otherwise.
*/
static bool efi_verify_certificate(struct x509_certificate *signer,
struct efi_signature_store *db,
struct x509_certificate **root)
{
struct efi_signature_store *siglist;
struct efi_sig_data *sig_data;
struct x509_certificate *cert;
bool verified = false;
int ret;
EFI_PRINT("%s: Enter, %p, %p\n", __func__, signer, db);
if (!signer || !db || !db->sig_data_list)
goto out;
for (siglist = db; siglist; siglist = siglist->next) {
/* only with x509 certificate */
if (guidcmp(&siglist->sig_type, &efi_guid_cert_x509))
continue;
for (sig_data = siglist->sig_data_list; sig_data;
sig_data = sig_data->next) {
cert = x509_cert_parse(sig_data->data, sig_data->size);
if (IS_ERR_OR_NULL(cert)) {
EFI_PRINT("Cannot parse x509 certificate\n");
continue;
}
ret = public_key_verify_signature(cert->pub,
signer->sig);
if (!ret) {
verified = true;
if (root)
*root = cert;
else
x509_free_certificate(cert);
goto out;
}
x509_free_certificate(cert);
}
}
out:
EFI_PRINT("%s: Exit, verified: %d\n", __func__, verified);
return verified;
}
/**
* efi_signature_check_revocation - check revocation with dbx
* @sinfo: Signer's info
* @cert: x509 certificate
* @dbx: Revocation signature database
*
* Search revocation signature database pointed to by @dbx and find
* an entry matching to certificate pointed to by @cert.
*
* While this entry contains revocation time, we don't support timestamp
* protocol at this time and any image will be unconditionally revoked
* when this match occurs.
*
* Return: true if check passed (not found), false otherwise.
*/
static bool efi_signature_check_revocation(struct pkcs7_signed_info *sinfo,
struct x509_certificate *cert,
struct efi_signature_store *dbx)
{
struct efi_signature_store *siglist;
struct efi_sig_data *sig_data;
struct image_region reg[1];
void *hash = NULL;
size_t size = 0;
time64_t revoc_time;
bool revoked = false;
EFI_PRINT("%s: Enter, %p, %p, %p\n", __func__, sinfo, cert, dbx);
if (!sinfo || !cert || !dbx || !dbx->sig_data_list)
goto out;
EFI_PRINT("Checking revocation against %s\n", cert->subject);
for (siglist = dbx; siglist; siglist = siglist->next) {
if (guidcmp(&siglist->sig_type, &efi_guid_cert_x509_sha256))
continue;
/* calculate hash of TBSCertificate */
reg[0].data = cert->tbs;
reg[0].size = cert->tbs_size;
if (!efi_hash_regions(reg, 1, &hash, &size))
goto out;
for (sig_data = siglist->sig_data_list; sig_data;
sig_data = sig_data->next) {
/*
* struct efi_cert_x509_sha256 {
* u8 tbs_hash[256/8];
* time64_t revocation_time;
* };
*/
#ifdef DEBUG
if (sig_data->size >= size) {
EFI_PRINT("hash in db:\n");
print_hex_dump(" ", DUMP_PREFIX_OFFSET,
16, 1,
sig_data->data, size, false);
}
#endif
if ((sig_data->size < size + sizeof(time64_t)) ||
memcmp(sig_data->data, hash, size))
continue;
memcpy(&revoc_time, sig_data->data + size,
sizeof(revoc_time));
EFI_PRINT("revocation time: 0x%llx\n", revoc_time);
/*
* TODO: compare signing timestamp in sinfo
* with revocation time
*/
revoked = true;
free(hash);
goto out;
}
free(hash);
hash = NULL;
}
out:
EFI_PRINT("%s: Exit, revoked: %d\n", __func__, revoked);
return !revoked;
}
/*
* efi_signature_verify - verify signatures with db and dbx
* @regs: List of regions to be authenticated
* @msg: Signature
* @db: Signature database for trusted certificates
* @dbx: Revocation signature database
*
* All the signature pointed to by @msg against image pointed to by @regs
* will be verified by signature database pointed to by @db and @dbx.
*
* Return: true if verification for all signatures passed, false otherwise
*/
bool efi_signature_verify(struct efi_image_regions *regs,
struct pkcs7_message *msg,
struct efi_signature_store *db,
struct efi_signature_store *dbx)
{
struct pkcs7_signed_info *sinfo;
struct x509_certificate *signer, *root;
bool verified = false;
int ret;
EFI_PRINT("%s: Enter, %p, %p, %p, %p\n", __func__, regs, msg, db, dbx);
if (!regs || !msg || !db || !db->sig_data_list)
goto out;
for (sinfo = msg->signed_infos; sinfo; sinfo = sinfo->next) {
EFI_PRINT("Signed Info: digest algo: %s, pkey algo: %s\n",
sinfo->sig->hash_algo, sinfo->sig->pkey_algo);
/*
* only for authenticated variable.
*
* If this function is called for image,
* hash calculation will be done in
* pkcs7_verify_one().
*/
if (!msg->data &&
!efi_hash_regions(regs->reg, regs->num,
(void **)&sinfo->sig->digest, NULL)) {
EFI_PRINT("Digesting an image failed\n");
goto out;
}
EFI_PRINT("Verifying certificate chain\n");
signer = NULL;
ret = pkcs7_verify_one(msg, sinfo, &signer);
if (ret == -ENOPKG)
continue;
if (ret < 0 || !signer)
goto out;
if (sinfo->blacklisted)
goto out;
EFI_PRINT("Verifying last certificate in chain\n");
if (efi_lookup_certificate(signer, db))
if (efi_signature_check_revocation(sinfo, signer, dbx))
break;
if (!signer->self_signed &&
efi_verify_certificate(signer, db, &root)) {
bool check;
check = efi_signature_check_revocation(sinfo, root,
dbx);
x509_free_certificate(root);
if (check)
break;
}
EFI_PRINT("Certificate chain didn't reach trusted CA\n");
}
if (sinfo)
verified = true;
out:
EFI_PRINT("%s: Exit, verified: %d\n", __func__, verified);
return verified;
}
/**
* efi_signature_check_signers - check revocation against all signers with dbx
* @msg: Signature
* @dbx: Revocation signature database
*
* Determine if none of signers' certificates in @msg are revoked
* by signature database pointed to by @dbx.
*
* Return: true if all signers passed, false otherwise.
*/
bool efi_signature_check_signers(struct pkcs7_message *msg,
struct efi_signature_store *dbx)
{
struct pkcs7_signed_info *sinfo;
bool revoked = false;
EFI_PRINT("%s: Enter, %p, %p\n", __func__, msg, dbx);
if (!msg || !dbx)
goto out;
for (sinfo = msg->signed_infos; sinfo; sinfo = sinfo->next) {
if (sinfo->signer &&
!efi_signature_check_revocation(sinfo, sinfo->signer,
dbx)) {
revoked = true;
break;
}
}
out:
EFI_PRINT("%s: Exit, revoked: %d\n", __func__, revoked);
return !revoked;
}
/**
* efi_sigstore_free - free signature store
* @sigstore: Pointer to signature store structure
*
* Feee all the memories held in signature store and itself,
* which were allocated by efi_sigstore_parse_sigdb().
*/
void efi_sigstore_free(struct efi_signature_store *sigstore)
{
struct efi_signature_store *sigstore_next;
struct efi_sig_data *sig_data, *sig_data_next;
while (sigstore) {
sigstore_next = sigstore->next;
sig_data = sigstore->sig_data_list;
while (sig_data) {
sig_data_next = sig_data->next;
free(sig_data->data);
free(sig_data);
sig_data = sig_data_next;
}
free(sigstore);
sigstore = sigstore_next;
}
}
/**
* efi_sigstore_parse_siglist - parse a signature list
* @name: Pointer to signature list
*
* Parse signature list and instantiate a signature store structure.
* Signature database is a simple concatenation of one or more
* signature list(s).
*
* Return: Pointer to signature store on success, NULL on error
*/
static struct efi_signature_store *
efi_sigstore_parse_siglist(struct efi_signature_list *esl)
{
struct efi_signature_store *siglist = NULL;
struct efi_sig_data *sig_data, *sig_data_next;
struct efi_signature_data *esd;
size_t left;
/*
* UEFI specification defines certificate types:
* for non-signed images,
* EFI_CERT_SHA256_GUID
* EFI_CERT_RSA2048_GUID
* EFI_CERT_RSA2048_SHA256_GUID
* EFI_CERT_SHA1_GUID
* EFI_CERT_RSA2048_SHA_GUID
* EFI_CERT_SHA224_GUID
* EFI_CERT_SHA384_GUID
* EFI_CERT_SHA512_GUID
*
* for signed images,
* EFI_CERT_X509_GUID
* NOTE: Each certificate will normally be in a separate
* EFI_SIGNATURE_LIST as the size may vary depending on
* its algo's.
*
* for timestamp revocation of certificate,
* EFI_CERT_X509_SHA512_GUID
* EFI_CERT_X509_SHA256_GUID
* EFI_CERT_X509_SHA384_GUID
*/
if (esl->signature_list_size
<= (sizeof(*esl) + esl->signature_header_size)) {
EFI_PRINT("Siglist in wrong format\n");
return NULL;
}
/* Create a head */
siglist = calloc(sizeof(*siglist), 1);
if (!siglist) {
EFI_PRINT("Out of memory\n");
goto err;
}
memcpy(&siglist->sig_type, &esl->signature_type, sizeof(efi_guid_t));
/* Go through the list */
sig_data_next = NULL;
left = esl->signature_list_size
- (sizeof(*esl) + esl->signature_header_size);
esd = (struct efi_signature_data *)
((u8 *)esl + sizeof(*esl) + esl->signature_header_size);
while (left > 0) {
/* Signature must exist if there is remaining data. */
if (left < esl->signature_size) {
EFI_PRINT("Certificate is too small\n");
goto err;
}
sig_data = calloc(esl->signature_size
- sizeof(esd->signature_owner), 1);
if (!sig_data) {
EFI_PRINT("Out of memory\n");
goto err;
}
/* Append signature data */
memcpy(&sig_data->owner, &esd->signature_owner,
sizeof(efi_guid_t));
sig_data->size = esl->signature_size
- sizeof(esd->signature_owner);
sig_data->data = malloc(sig_data->size);
if (!sig_data->data) {
EFI_PRINT("Out of memory\n");
goto err;
}
memcpy(sig_data->data, esd->signature_data, sig_data->size);
sig_data->next = sig_data_next;
sig_data_next = sig_data;
/* Next */
esd = (struct efi_signature_data *)
((u8 *)esd + esl->signature_size);
left -= esl->signature_size;
}
siglist->sig_data_list = sig_data_next;
return siglist;
err:
efi_sigstore_free(siglist);
return NULL;
}
/**
* efi_sigstore_parse_sigdb - parse the signature list and populate
* the signature store
*
* @sig_list: Pointer to the signature list
* @size: Size of the signature list
*
* Parse the efi signature list and instantiate a signature store
* structure.
*
* Return: Pointer to signature store on success, NULL on error
*/
struct efi_signature_store *efi_build_signature_store(void *sig_list,
efi_uintn_t size)
{
struct efi_signature_list *esl;
struct efi_signature_store *sigstore = NULL, *siglist;
esl = sig_list;
while (size > 0) {
/* List must exist if there is remaining data. */
if (size < sizeof(*esl)) {
EFI_PRINT("Signature list in wrong format\n");
goto err;
}
if (size < esl->signature_list_size) {
EFI_PRINT("Signature list in wrong format\n");
goto err;
}
/* Parse a single siglist. */
siglist = efi_sigstore_parse_siglist(esl);
if (!siglist) {
EFI_PRINT("Parsing of signature list of failed\n");
goto err;
}
/* Append siglist */
siglist->next = sigstore;
sigstore = siglist;
/* Next */
size -= esl->signature_list_size;
esl = (void *)esl + esl->signature_list_size;
}
free(sig_list);
return sigstore;
err:
efi_sigstore_free(sigstore);
free(sig_list);
return NULL;
}
/**
* efi_sigstore_parse_sigdb - parse a signature database variable
* @name: Variable's name
*
* Read in a value of signature database variable pointed to by
* @name, parse it and instantiate a signature store structure.
*
* Return: Pointer to signature store on success, NULL on error
*/
struct efi_signature_store *efi_sigstore_parse_sigdb(u16 *name)
{
const efi_guid_t *vendor;
void *db;
efi_uintn_t db_size;
vendor = efi_auth_var_get_guid(name);
db = efi_get_var(name, vendor, &db_size);
if (!db) {
EFI_PRINT("variable, %ls, not found\n", name);
return calloc(sizeof(struct efi_signature_store), 1);
}
return efi_build_signature_store(db, db_size);
}