Heinrich, On Sat, May 30, 2020 at 09:01:53AM +0200, Heinrich Schuchardt wrote: > On 5/29/20 8:41 AM, AKASHI Takahiro wrote: > > A signed image may have multiple signatures in > > - each WIN_CERTIFICATE in authenticode, and/or > > - each SignerInfo in pkcs7 SignedData (of WIN_CERTIFICATE) > > > > In the initial implementation of efi_image_authenticate(), the criteria > > of verification check for multiple signatures case is a bit ambiguous > > and it may cause inconsistent result. > > > > With this patch, we will make sure that verification check in > > efi_image_authenticate() should pass against all the signatures. > > The only exception would be > > - the case where a digest algorithm used in signature is not supported by > > U-Boot, or > > - the case where parsing some portion of authenticode has failed > > In those cases, we don't know how the signature be handled and should > > just ignore them. > > > > Please note that, due to this change, efi_signature_verify_with_sigdb()'s > > function prototype will be modified, taking "dbx" as well as "db" > > instead of outputing a "certificate." If "dbx" is null, the behavior would > > be the exact same as before. > > The function's name will be changed to efi_signature_verify() once > > current efi_signature_verify() has gone due to further improvement > > in intermedaite certificates support. > > > > Signed-off-by: AKASHI Takahiro <takahiro.aka...@linaro.org> > > --- > > include/efi_loader.h | 10 +- > > lib/efi_loader/efi_image_loader.c | 37 +++-- > > lib/efi_loader/efi_signature.c | 266 ++++++++++++++---------------- > > 3 files changed, 146 insertions(+), 167 deletions(-) > > > > diff --git a/include/efi_loader.h b/include/efi_loader.h > > index 9533df26dc9e..2cbd52e273d4 100644 > > --- a/include/efi_loader.h > > +++ b/include/efi_loader.h > > @@ -761,14 +761,12 @@ struct efi_signature_store { > > struct x509_certificate; > > struct pkcs7_message; > > > > -bool efi_signature_verify_cert(struct x509_certificate *cert, > > - struct efi_signature_store *dbx); > > -bool efi_signature_verify_signers(struct pkcs7_message *msg, > > - struct efi_signature_store *dbx); > > bool efi_signature_verify_with_sigdb(struct efi_image_regions *regs, > > struct pkcs7_message *msg, > > - struct efi_signature_store *db, > > - struct x509_certificate **cert); > > + struct efi_signature_store *db, > > + struct efi_signature_store *dbx); > > +bool efi_signature_check_signers(struct pkcs7_message *msg, > > + struct efi_signature_store *dbx); > > > > efi_status_t efi_image_region_add(struct efi_image_regions *regs, > > const void *start, const void *end, > > diff --git a/lib/efi_loader/efi_image_loader.c > > b/lib/efi_loader/efi_image_loader.c > > index 5cdaa93519e7..33ffb43f3886 100644 > > --- a/lib/efi_loader/efi_image_loader.c > > +++ b/lib/efi_loader/efi_image_loader.c > > @@ -492,11 +492,12 @@ static bool efi_image_authenticate(void *efi, size_t > > efi_size) > > size_t wincerts_len; > > struct pkcs7_message *msg = NULL; > > struct efi_signature_store *db = NULL, *dbx = NULL; > > - struct x509_certificate *cert = NULL; > > void *new_efi = NULL, *auth, *wincerts_end; > > size_t new_efi_size, auth_size; > > bool ret = false; > > > > + debug("%s: Enter, %d\n", __func__, ret); > > + > > if (!efi_secure_boot_enabled()) > > return true; > > > > @@ -542,7 +543,17 @@ static bool efi_image_authenticate(void *efi, size_t > > efi_size) > > goto err; > > } > > > > - /* go through WIN_CERTIFICATE list */ > > + /* > > + * go through WIN_CERTIFICATE list > > + * NOTE: > > + * We may have multiple signatures either as WIN_CERTIFICATE's > > + * in PE header, or as pkcs7 SignerInfo's in SignedData. > > + * So the verification policy here is: > > + * - Success if, at least, one of signatures is verified > > + * - unless > > + * any of signatures is rejected explicitly, or > > If one signature is good and the others are rejected (e.g. due to the > revocation list), why won't you be happy with the one good signature? Is > this a requirement in the UEFI spec?
Actually, I don't know the right answer. UEFI specification doesn't say anything here and as far as I correctly understand EDK2 code, it behaves in the same way. Since there is no conformance test available (in UEFI SCT), it is difficult to confirm that. # FYI, I have been requesting such test cases in SCT since last October: https://bugzilla.tianocore.org/show_bug.cgi?id=2230 I also think that applying more strict rules and then easing them later would be much easier and safer than the reverse(?). Thanks, -Takahiro Akashi > Best regards > > Heinrich > > > + * none of digest algorithms are supported > > + */ > > for (wincert = wincerts, wincerts_end = (void *)wincerts + wincerts_len; > > (void *)wincert < wincerts_end; > > wincert = (void *)wincert + ALIGN(wincert->dwLength, 8)) { > > @@ -596,37 +607,27 @@ static bool efi_image_authenticate(void *efi, size_t > > efi_size) > > goto err; > > } > > > > - if (!efi_signature_verify_signers(msg, dbx)) { > > - debug("Signer was rejected by \"dbx\"\n"); > > + if (!efi_signature_check_signers(msg, dbx)) { > > + debug("Signer(s) in \"dbx\"\n"); > > goto err; > > - } else { > > - ret = true; > > } > > > > /* try white-list */ > > - if (!efi_signature_verify_with_sigdb(regs, msg, db, &cert)) { > > - debug("Verifying signature with \"db\" failed\n"); > > + if (!efi_signature_verify_with_sigdb(regs, msg, db, dbx)) { > > + debug("Signature was not verified by \"db\"\n"); > > goto err; > > - } else { > > - ret = true; > > - } > > - > > - if (!efi_signature_verify_cert(cert, dbx)) { > > - debug("Certificate was rejected by \"dbx\"\n"); > > - goto err; > > - } else { > > - ret = true; > > } > > } > > + ret = true; > > > > err: > > - x509_free_certificate(cert); > > efi_sigstore_free(db); > > efi_sigstore_free(dbx); > > pkcs7_free_message(msg); > > free(regs); > > free(new_efi); > > > > + debug("%s: Exit, %d\n", __func__, ret); > > return ret; > > } > > #else > > diff --git a/lib/efi_loader/efi_signature.c b/lib/efi_loader/efi_signature.c > > index 00e442783059..ab5687040a38 100644 > > --- a/lib/efi_loader/efi_signature.c > > +++ b/lib/efi_loader/efi_signature.c > > @@ -300,27 +300,111 @@ out: > > } > > > > /** > > - * efi_signature_verify_with_sigdb - verify a signature with db > > + * 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, 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; > > + > > + debug("%s: Enter, %p, %p, %p\n", __func__, sinfo, cert, dbx); > > + > > + if (!sinfo || !cert || !dbx || !dbx->sig_data_list) > > + goto out; > > + > > + debug("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) { > > + debug("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)); > > + debug("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: > > + debug("%s: Exit, revoked: %d\n", __func__, revoked); > > + return !revoked; > > +} > > + > > +/** > > + * efi_signature_verify_with_sigdb - verify signatures with db and dbx > > * @regs: List of regions to be authenticated > > * @msg: Signature > > * @db: Signature database for trusted certificates > > - * @cert: x509 certificate that verifies this signature > > + * @dbx: Revocation signature database > > * > > - * Signature pointed to by @msg against image pointed to by @regs > > - * is verified by signature database pointed to by @db. > > + * 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 signature is verified, false if not > > + * Return: true if verification for all signatures passed, false otherwise > > */ > > bool efi_signature_verify_with_sigdb(struct efi_image_regions *regs, > > struct pkcs7_message *msg, > > struct efi_signature_store *db, > > - struct x509_certificate **cert) > > + struct efi_signature_store *dbx) > > { > > struct pkcs7_signed_info *info; > > struct efi_signature_store *siglist; > > + struct x509_certificate *cert; > > bool verified = false; > > > > - debug("%s: Enter, %p, %p, %p, %p\n", __func__, regs, msg, db, cert); > > + debug("%s: Enter, %p, %p, %p, %p\n", __func__, regs, msg, db, dbx); > > > > if (!db) > > goto out; > > @@ -333,7 +417,7 @@ bool efi_signature_verify_with_sigdb(struct > > efi_image_regions *regs, > > debug("%s: Verify unsigned image with db\n", __func__); > > for (siglist = db; siglist; siglist = siglist->next) > > if (efi_signature_verify_with_list(regs, NULL, NULL, > > - siglist, cert)) { > > + siglist, &cert)) { > > verified = true; > > goto out; > > } > > @@ -349,12 +433,21 @@ bool efi_signature_verify_with_sigdb(struct > > efi_image_regions *regs, > > > > for (siglist = db; siglist; siglist = siglist->next) { > > if (efi_signature_verify_with_list(regs, msg, info, > > - siglist, cert)) { > > - verified = true; > > - goto out; > > - } > > + siglist, &cert)) > > + break; > > } > > + if (!siglist) { > > + debug("No valid certificate in \"db\"\n"); > > + goto out; > > + } > > + > > + if (!dbx || efi_signature_check_revocation(info, cert, dbx)) > > + continue; > > + > > + debug("Certificate in \"dbx\"\n"); > > + goto out; > > } > > + verified = true; > > > > out: > > debug("%s: Exit, verified: %d\n", __func__, verified); > > @@ -362,150 +455,37 @@ out: > > } > > > > /** > > - * efi_search_siglist - search signature list for a certificate > > - * @cert: x509 certificate > > - * @siglist: Signature list > > - * @revoc_time: Pointer to buffer for revocation time > > - * > > - * Search signature list pointed to by @siglist and find a certificate > > - * pointed to by @cert. > > - * If found, revocation time that is specified in signature database is > > - * returned in @revoc_time. > > - * > > - * Return: true if certificate is found, false if not > > - */ > > -static bool efi_search_siglist(struct x509_certificate *cert, > > - struct efi_signature_store *siglist, > > - time64_t *revoc_time) > > -{ > > - struct image_region reg[1]; > > - void *hash = NULL, *msg = NULL; > > - struct efi_sig_data *sig_data; > > - bool found = false; > > - > > - /* can be null */ > > - if (!siglist->sig_data_list) > > - return false; > > - > > - if (guidcmp(&siglist->sig_type, &efi_guid_cert_x509_sha256)) { > > - /* TODO: other hash algos */ > > - debug("Certificate's digest type is not supported: %pUl\n", > > - &siglist->sig_type); > > - goto out; > > - } > > - > > - /* calculate hash of TBSCertificate */ > > - msg = calloc(1, SHA256_SUM_LEN); > > - if (!msg) { > > - debug("Out of memory\n"); > > - goto out; > > - } > > - > > - hash = calloc(1, SHA256_SUM_LEN); > > - if (!hash) { > > - debug("Out of memory\n"); > > - goto out; > > - } > > - > > - reg[0].data = cert->tbs; > > - reg[0].size = cert->tbs_size; > > - hash_calculate("sha256", reg, 1, msg); > > - > > - /* go through signature list */ > > - 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; > > - * }; > > - */ > > - if ((sig_data->size >= SHA256_SUM_LEN + sizeof(time64_t)) && > > - !memcmp(sig_data->data, msg, SHA256_SUM_LEN)) { > > - memcpy(revoc_time, sig_data->data + SHA256_SUM_LEN, > > - sizeof(*revoc_time)); > > - debug("revocation time: 0x%llx\n", *revoc_time); > > - found = true; > > - goto out; > > - } > > - } > > - > > -out: > > - free(hash); > > - free(msg); > > - > > - return found; > > -} > > - > > -/** > > - * efi_signature_verify_cert - verify a certificate with dbx > > - * @cert: x509 certificate > > - * @dbx: Signature database > > - * > > - * Search signature database pointed to by @dbx and find a certificate > > - * pointed to by @cert. > > - * This function is expected to be used against "dbx". > > - * > > - * Return: true if a certificate is not rejected, false otherwise. > > - */ > > -bool efi_signature_verify_cert(struct x509_certificate *cert, > > - struct efi_signature_store *dbx) > > -{ > > - struct efi_signature_store *siglist; > > - time64_t revoc_time; > > - bool found = false; > > - > > - debug("%s: Enter, %p, %p\n", __func__, dbx, cert); > > - > > - if (!cert) > > - return false; > > - > > - for (siglist = dbx; siglist; siglist = siglist->next) { > > - if (efi_search_siglist(cert, siglist, &revoc_time)) { > > - /* TODO */ > > - /* compare signing time with revocation time */ > > - > > - found = true; > > - break; > > - } > > - } > > - > > - debug("%s: Exit, verified: %d\n", __func__, !found); > > - return !found; > > -} > > - > > -/** > > - * efi_signature_verify_signers - verify signers' certificates with dbx > > + * efi_signature_check_signers - check revocation against all signers with > > dbx > > * @msg: Signature > > - * @dbx: Signature database > > + * @dbx: Revocation signature database > > * > > - * Determine if any of signers' certificates in @msg may be verified > > - * by any of certificates in signature database pointed to by @dbx. > > - * This function is expected to be used against "dbx". > > + * Determine if none of signers' certificates in @msg are revoked > > + * by signature database pointed to by @dbx. > > * > > - * Return: true if none of certificates is rejected, false otherwise. > > + * Return: true if all signers passed, false otherwise. > > */ > > -bool efi_signature_verify_signers(struct pkcs7_message *msg, > > - struct efi_signature_store *dbx) > > +bool efi_signature_check_signers(struct pkcs7_message *msg, > > + struct efi_signature_store *dbx) > > { > > - struct pkcs7_signed_info *info; > > - bool found = false; > > + struct pkcs7_signed_info *sinfo; > > + bool revoked = false; > > > > debug("%s: Enter, %p, %p\n", __func__, msg, dbx); > > > > - if (!msg) > > + if (!msg || !dbx) > > goto out; > > > > - for (info = msg->signed_infos; info; info = info->next) { > > - if (info->signer && > > - !efi_signature_verify_cert(info->signer, dbx)) { > > - found = true; > > - 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: > > - debug("%s: Exit, verified: %d\n", __func__, !found); > > - return !found; > > + debug("%s: Exit, revoked: %d\n", __func__, revoked); > > + return !revoked; > > } > > > > /** > > >