diff options
Diffstat (limited to 'roms/u-boot/lib/efi_loader/efi_signature.c')
-rw-r--r-- | roms/u-boot/lib/efi_loader/efi_signature.c | 784 |
1 files changed, 784 insertions, 0 deletions
diff --git a/roms/u-boot/lib/efi_loader/efi_signature.c b/roms/u-boot/lib/efi_loader/efi_signature.c new file mode 100644 index 000000000..bdd09881f --- /dev/null +++ b/roms/u-boot/lib/efi_loader/efi_signature.c @@ -0,0 +1,784 @@ +// 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 <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; +} + +/** + * 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 + * + * 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) +{ + struct efi_signature_store *siglist; + struct efi_sig_data *sig_data; + void *hash = NULL; + size_t size = 0; + bool found = 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) { + /* TODO: support other hash algorithms */ + if (guidcmp(&siglist->sig_type, &efi_guid_sha256)) { + EFI_PRINT("Digest algorithm is not supported: %pUl\n", + &siglist->sig_type); + break; + } + + if (!efi_hash_regions(regs->reg, regs->num, &hash, &size)) { + EFI_PRINT("Digesting an image failed\n"); + break; + } + + 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 (signer->self_signed) { + if (efi_lookup_certificate(signer, db)) + if (efi_signature_check_revocation(sinfo, + signer, dbx)) + break; + } else if (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) +{ + struct efi_signature_store *sigstore = NULL; + const efi_guid_t *vendor; + void *db; + efi_uintn_t db_size; + efi_status_t ret; + + if (!u16_strcmp(name, L"PK") || !u16_strcmp(name, L"KEK")) { + vendor = &efi_global_variable_guid; + } else if (!u16_strcmp(name, L"db") || !u16_strcmp(name, L"dbx")) { + vendor = &efi_guid_image_security_database; + } else { + EFI_PRINT("unknown signature database, %ls\n", name); + return NULL; + } + + /* retrieve variable data */ + db_size = 0; + ret = EFI_CALL(efi_get_variable(name, vendor, NULL, &db_size, NULL)); + if (ret == EFI_NOT_FOUND) { + EFI_PRINT("variable, %ls, not found\n", name); + sigstore = calloc(sizeof(*sigstore), 1); + return sigstore; + } else if (ret != EFI_BUFFER_TOO_SMALL) { + EFI_PRINT("Getting variable, %ls, failed\n", name); + return NULL; + } + + db = malloc(db_size); + if (!db) { + EFI_PRINT("Out of memory\n"); + return NULL; + } + + ret = EFI_CALL(efi_get_variable(name, vendor, NULL, &db_size, db)); + if (ret != EFI_SUCCESS) { + EFI_PRINT("Getting variable, %ls, failed\n", name); + free(db); + return NULL; + } + + return efi_build_signature_store(db, db_size); +} |