420 lines
11 KiB
Diff
420 lines
11 KiB
Diff
|
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
|
||
|
|