From 81583146602bba96728fa7544c8e856b32c22ee4 Mon Sep 17 00:00:00 2001 From: Peter Jones Date: Tue, 25 Apr 2017 17:01:13 -0400 Subject: [PATCH 20/29] try to say why something fails Signed-off-by: Peter Jones --- src/certdb.c | 15 ++- src/certdb.h | 2 +- src/pesigcheck.c | 244 ++++++++++++++++++++++++++++++++++++++++++----- src/pesigcheck_context.h | 1 + 4 files changed, 233 insertions(+), 29 deletions(-) diff --git a/src/certdb.c b/src/certdb.c index 1078a8a..fae80af 100644 --- a/src/certdb.c +++ b/src/certdb.c @@ -205,7 +205,7 @@ typedef db_status (*checkfn)(pesigcheck_context *ctx, SECItem *sig, static db_status check_db(db_specifier which, pesigcheck_context *ctx, checkfn check, - void *data, ssize_t datalen) + void *data, ssize_t datalen, SECItem *match) { SECItem pkcs7sig, sig; dblist *dbl = which == DB ? ctx->db : ctx->dbx; @@ -241,8 +241,12 @@ check_db(db_specifier which, pesigcheck_context *ctx, checkfn check, found = check(ctx, &sig, &certlist->SignatureType, &pkcs7sig); - if (found == FOUND) + if (found == FOUND) { + if (match) + memcpy(match, &sig, + sizeof(sig)); return FOUND; + } cert = (EFI_SIGNATURE_DATA *)((uint8_t *)cert + certlist->SignatureSize); } @@ -280,7 +284,7 @@ check_hash(pesigcheck_context *ctx, SECItem *sig, efi_guid_t *sigtype, db_status check_db_hash(db_specifier which, pesigcheck_context *ctx) { - return check_db(which, ctx, check_hash, NULL, 0); + return check_db(which, ctx, check_hash, NULL, 0, NULL); } static void @@ -459,7 +463,8 @@ out: } db_status -check_db_cert(db_specifier which, pesigcheck_context *ctx, void *data, ssize_t datalen) +check_db_cert(db_specifier which, pesigcheck_context *ctx, + void *data, ssize_t datalen, SECItem *match) { - return check_db(which, ctx, check_cert, data, datalen); + return check_db(which, ctx, check_cert, data, datalen, match); } diff --git a/src/certdb.h b/src/certdb.h index ccf3c87..8402299 100644 --- a/src/certdb.h +++ b/src/certdb.h @@ -43,7 +43,7 @@ typedef struct { extern db_status check_db_hash(db_specifier which, pesigcheck_context *ctx); extern db_status check_db_cert(db_specifier which, pesigcheck_context *ctx, - void *data, ssize_t datalen); + void *data, ssize_t datalen, SECItem *match); extern void init_cert_db(pesigcheck_context *ctx, int use_system_dbs); extern int add_cert_db(pesigcheck_context *ctx, const char *filename); diff --git a/src/pesigcheck.c b/src/pesigcheck.c index d7be542..c8e1086 100644 --- a/src/pesigcheck.c +++ b/src/pesigcheck.c @@ -17,7 +17,9 @@ * Author(s): Peter Jones */ +#include #include +#include #include #include #include @@ -88,7 +90,8 @@ check_inputs(pesigcheck_context *ctx) } static int -cert_matches_digest(pesigcheck_context *ctx, void *data, ssize_t datalen) +cert_matches_digest(pesigcheck_context *ctx, void *data, ssize_t datalen, + SECItem *digest_out) { SECItem sig, *pe_digest, *content; uint8_t *digest; @@ -109,6 +112,12 @@ cert_matches_digest(pesigcheck_context *ctx, void *data, ssize_t datalen) pe_digest = ctx->cms_ctx->digests[0].pe_digest; content = cinfo->content.signedData->contentInfo.content.data; digest = content->data + content->len - pe_digest->len; + if (digest_out) { + digest_out->data = malloc(pe_digest->len); + digest_out->len = pe_digest->len; + digest_out->type = pe_digest->type; + memcpy(digest_out->data, digest, pe_digest->len); + } if (memcmp(pe_digest->data, digest, pe_digest->len) != 0) goto out; @@ -120,22 +129,149 @@ out: return ret; } +struct reason { + enum { + WHITELISTED = 0, + INVALID = 1, + BLACKLISTED = 2, + NO_WHITELIST = 3, + } reason; + enum { + NONE = 0, + DIGEST = 1, + SIGNATURE = 2, + } type; + union { + struct { + SECItem digest; + }; + struct { + SECItem sig; + SECItem db_cert; + }; + }; +}; + +static void +print_digest(SECItem *digest) +{ + char buf[digest->len * 2 + 2]; + + for (unsigned int i = 0; i < digest->len; i++) + snprintf(buf + i * 2, digest->len * 2, "%02x", + digest->data[i]); + buf[digest->len * 2] = '\0'; + printf("%s\n", buf); +} + +static void +print_certificate(SECItem *cert) +{ + printf("put a breakpoint at %s:%d\n", __FILE__, __LINE__); + printf("cert: %p\n", cert); +} + +static void +print_signatures(SECItem *database_cert, SECItem *signature) +{ + printf("put a breakpoint at %s:%d\n", __FILE__, __LINE__); + print_certificate(database_cert); + print_certificate(signature); +} + +static void +print_reason(struct reason *reason) +{ + switch (reason->reason) { + case WHITELISTED: + printf("Whitelist entry: "); + if (reason->type == DIGEST) + print_digest(&reason->digest); + else if (reason->type == SIGNATURE) + print_signatures(&reason->sig, &reason->db_cert); + else + errx(1, "Unknown data type %d\n", reason->type); + break; + case INVALID: + if (reason->type == DIGEST) { + printf("Invalid digest: "); + print_digest(&reason->digest); + } else if (reason->type == SIGNATURE) { + printf("Invalid signature: "); + print_signatures(&reason->sig, &reason->db_cert); + } else { + errx(1, "Unknown data type %d\n", reason->type); + } + break; + case BLACKLISTED: + if (reason->type == DIGEST) { + printf("Invalid digest: "); + print_digest(&reason->digest); + } else if (reason->type == SIGNATURE) { + printf("Invalid signature: "); + print_signatures(&reason->sig, &reason->db_cert); + } else { + errx(1, "Unknown data type %d\n", reason->type); + } + break; + case NO_WHITELIST: + if (reason->type == NONE) + printf("No matching whitelist entry.\n"); + else + errx(1, "Invalid data type %d\n", reason->type); + break; + default: + errx(1, "Unknown reason type %d\n", reason->reason); + break; + } +} + +static void +get_digest(pesigcheck_context *ctx, SECItem *digest) +{ + struct cms_context *cms = ctx->cms_ctx; + struct digest *cms_digest = &cms->digests[cms->selected_digest]; + + memcpy(digest, cms_digest->pe_digest, sizeof (*digest)); +} + static int -check_signature(pesigcheck_context *ctx) +check_signature(pesigcheck_context *ctx, int *nreasons, + struct reason **reasons) { - int has_valid_cert = 0; - int has_invalid_cert = 0; + bool has_valid_cert = false; + bool is_invalid = false; + struct reason *reasonps = NULL, *reason; + int num_reasons = 16; + int nreason = 0; int rc = 0; + int ret = -1; cert_iter iter; + reasonps = calloc(sizeof(struct reason), 512); + if (!reasonps) + err(1, "check_signature"); + generate_digest(ctx->cms_ctx, ctx->inpe, 1); - if (check_db_hash(DBX, ctx) == FOUND) - return -1; + if (check_db_hash(DBX, ctx) == FOUND) { + reason = &reasonps[nreason]; + reason->reason = BLACKLISTED; + reason->type = DIGEST; + get_digest(ctx, &reason->digest); + reason += 1; + is_invalid = true; + } - if (check_db_hash(DB, ctx) == FOUND) - has_valid_cert = 1; + if (check_db_hash(DB, ctx) == FOUND) { + reason = &reasonps[nreason]; + reason->reason = WHITELISTED; + reason->type = DIGEST; + get_digest(ctx, &reason->digest); + nreason += 1; + has_valid_cert = true; + } rc = cert_iter_init(&iter, ctx->inpe); if (rc < 0) @@ -145,32 +281,81 @@ check_signature(pesigcheck_context *ctx) ssize_t datalen; while (1) { + /* + * Make sure we always have enough for this iteration of the + * loop, plus one "NO_WHITELIST" entry at the end. + */ + if (nreason >= num_reasons - 4) { + struct reason *new_reasons; + + num_reasons += 16; + + new_reasons = calloc(sizeof(struct reason), num_reasons); + if (!new_reasons) + err(1, "check_signature"); + reasonps = new_reasons; + } + rc = next_cert(&iter, &data, &datalen); if (rc <= 0) break; - if (cert_matches_digest(ctx, data, datalen) < 0) { - has_invalid_cert = 1; - break; + reason = &reasonps[nreason]; + if (cert_matches_digest(ctx, data, datalen, + &reason->digest) < 0) { + reason->reason = INVALID; + reason->type = DIGEST; + nreason += 1; + is_invalid = true; } - if (check_db_cert(DBX, ctx, data, datalen) == FOUND) { - has_invalid_cert = 1; - break; + reason = &reasonps[nreason]; + if (check_db_cert(DBX, ctx, data, datalen, + &reason->db_cert) == FOUND) { + reason->reason = INVALID; + reason->type = SIGNATURE; + reason->sig.data = data; + reason->sig.len = datalen; + reason->type = siBuffer; + nreason += 1; + is_invalid = true; } - if (check_db_cert(DB, ctx, data, datalen) == FOUND) - has_valid_cert = 1; + reason = &reasonps[nreason]; + if (check_db_cert(DB, ctx, data, datalen, + &reason->db_cert) == FOUND) { + reason->reason = WHITELISTED; + reason->type = SIGNATURE; + reason->sig.data = data; + reason->sig.len = datalen; + reason->type = siBuffer; + nreason += 1; + has_valid_cert = true; + } } err: - if (has_invalid_cert) - return -1; + if (has_valid_cert != true) { + if (is_invalid != true) { + reason = &reasonps[nreason]; + reason->reason = NO_WHITELIST; + reason->type = NONE; + nreason += 1; + } + is_invalid = true; + } - if (has_valid_cert) - return 0; + if (is_invalid == false) + ret = 0; - return -1; + if (nreasons && reasons) { + *nreasons = nreason; + *reasons = reasonps; + } else { + free(reasonps); + } + + return ret; } void @@ -204,6 +389,9 @@ main(int argc, char *argv[]) pesigcheck_context ctx, *ctxp = &ctx; + struct reason *reasons = NULL; + int nreasons = 0; + char *dbfile = NULL; char *dbxfile = NULL; char *certfile = NULL; @@ -242,6 +430,12 @@ main(int argc, char *argv[]) .arg = &ctx.quiet, .val = 1, .descrip = "return only; no text output." }, + {.longName = "verbose", + .shortName = 'v', + .argInfo = POPT_BIT_SET, + .arg = &ctx.verbose, + .val = 1, + .descrip = "print reasons for success and failure." }, {.longName = "no-system-db", .shortName = 'n', .argInfo = POPT_ARG_INT, @@ -308,12 +502,16 @@ main(int argc, char *argv[]) exit(1); } - rc = check_signature(ctxp); + rc = check_signature(ctxp, &nreasons, &reasons); - close_input(ctxp); + if (!ctx.quiet && ctx.verbose) { + for (int i = 0; i < nreasons; i++) + print_reason(&reasons[i]); + } if (!ctx.quiet) printf("pesigcheck: \"%s\" is %s.\n", ctx.infile, rc >= 0 ? "valid" : "invalid"); + close_input(ctxp); pesigcheck_context_fini(&ctx); NSS_Shutdown(); diff --git a/src/pesigcheck_context.h b/src/pesigcheck_context.h index 7b5cc89..aec415e 100644 --- a/src/pesigcheck_context.h +++ b/src/pesigcheck_context.h @@ -61,6 +61,7 @@ typedef struct pesigcheck_context { Pe *inpe; int quiet; + int verbose; hashlist *hashes; -- 2.13.4