// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (c) 2018 Patrick Wildt * Copyright (c) 2019 Linaro Limited, Author: AKASHI Takahiro */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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: %pUs\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) { 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); }