Blob Blame History Raw
From 81583146602bba96728fa7544c8e856b32c22ee4 Mon Sep 17 00:00:00 2001
From: Peter Jones <pjones@redhat.com>
Date: Tue, 25 Apr 2017 17:01:13 -0400
Subject: [PATCH 20/29] try to say why something fails

Signed-off-by: Peter Jones <pjones@redhat.com>
---
 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 <pjones@redhat.com>
  */
 
+#include <err.h>
 #include <fcntl.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -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