aboutsummaryrefslogtreecommitdiff
path: root/lib/libsecureboot/vets.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libsecureboot/vets.c')
-rw-r--r--lib/libsecureboot/vets.c1156
1 files changed, 1156 insertions, 0 deletions
diff --git a/lib/libsecureboot/vets.c b/lib/libsecureboot/vets.c
new file mode 100644
index 000000000000..67d27d567485
--- /dev/null
+++ b/lib/libsecureboot/vets.c
@@ -0,0 +1,1156 @@
+/*-
+ * Copyright (c) 2017-2018, Juniper Networks, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <sys/cdefs.h>
+/**
+ * @file vets.c - trust store
+ * @brief verify signatures
+ *
+ * We leverage code from BearSSL www.bearssl.org
+ */
+
+#include <sys/time.h>
+#include <stdarg.h>
+#define NEED_BRSSL_H
+#include "libsecureboot-priv.h"
+#include <brssl.h>
+#include <ta.h>
+
+#ifndef TRUST_ANCHOR_STR
+# define TRUST_ANCHOR_STR ta_PEM
+#endif
+
+#define EPOCH_YEAR 1970
+#define AVG_SECONDS_PER_YEAR 31556952L
+#define SECONDS_PER_DAY 86400
+#define SECONDS_PER_YEAR 365 * SECONDS_PER_DAY
+#ifndef VE_UTC_MAX_JUMP
+# define VE_UTC_MAX_JUMP 20 * SECONDS_PER_YEAR
+#endif
+#define X509_DAYS_TO_UTC0 719528
+
+int DebugVe = 0;
+
+#ifndef VE_VERIFY_FLAGS
+# define VE_VERIFY_FLAGS VEF_VERBOSE
+#endif
+int VerifyFlags = VE_VERIFY_FLAGS;
+
+typedef VECTOR(br_x509_certificate) cert_list;
+typedef VECTOR(hash_data) digest_list;
+
+static anchor_list trust_anchors = VEC_INIT;
+static anchor_list forbidden_anchors = VEC_INIT;
+static digest_list forbidden_digests = VEC_INIT;
+
+static int anchor_verbose = 0;
+
+void
+ve_anchor_verbose_set(int n)
+{
+ anchor_verbose = n;
+}
+
+int
+ve_anchor_verbose_get(void)
+{
+ return (anchor_verbose);
+}
+
+void
+ve_debug_set(int n)
+{
+ DebugVe = n;
+}
+
+/*
+ * For embedded systems (and boot loaders)
+ * we do not want to enforce certificate validity post install.
+ * It is generally unacceptible for infrastructure to stop working
+ * just because it has not been updated recently.
+ */
+static int enforce_validity = 0;
+
+void
+ve_enforce_validity_set(int i)
+{
+ enforce_validity = i;
+}
+
+static char ebuf[512];
+
+char *
+ve_error_get(void)
+{
+ return (ebuf);
+}
+
+int
+ve_error_set(const char *fmt, ...)
+{
+ int rc;
+ va_list ap;
+
+ va_start(ap, fmt);
+ ebuf[0] = '\0';
+ rc = 0;
+ if (fmt) {
+#ifdef STAND_H
+ vsprintf(ebuf, fmt, ap); /* no vsnprintf in libstand */
+ ebuf[sizeof(ebuf) - 1] = '\0';
+ rc = strlen(ebuf);
+#else
+ rc = vsnprintf(ebuf, sizeof(ebuf), fmt, ap);
+#endif
+ }
+ va_end(ap);
+ return (rc);
+}
+
+#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
+
+/*
+ * The *approximate* date.
+ *
+ * When certificate verification fails for being
+ * expired or not yet valid, it helps to indicate
+ * our current date.
+ * Since libsa lacks strftime and gmtime,
+ * this simple implementation suffices.
+ */
+static const char *
+gdate(char *buf, size_t bufsz, time_t clock)
+{
+ int days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+ int year, y, m, d;
+
+ y = clock / AVG_SECONDS_PER_YEAR;
+ year = EPOCH_YEAR + y;
+ for (y = EPOCH_YEAR; y < year; y++) {
+ clock -= SECONDS_PER_YEAR;
+ if (isleap(y))
+ clock -= SECONDS_PER_DAY;
+ }
+ d = clock / SECONDS_PER_DAY;
+ for (m = 0; d > 1 && m < 12; m++) {
+ if (d > days[m]) {
+ d -= days[m];
+ if (m == 1 && d > 0 && isleap(year))
+ d--;
+ } else
+ break;
+ }
+ d++;
+ if (d > days[m]) {
+ d = 1;
+ m++;
+ if (m >= 12) {
+ year++;
+ m = 0;
+ }
+ }
+ (void)snprintf(buf, bufsz, "%04d-%02d-%02d", year, m+1, d);
+ return(buf);
+}
+
+/* this is the time we use for verifying certs */
+#ifdef UNIT_TEST
+extern time_t ve_utc;
+time_t ve_utc = 0;
+#else
+static time_t ve_utc = 0;
+#endif
+
+/**
+ * @brief
+ * set ve_utc used for certificate verification
+ *
+ * @param[in] utc
+ * time - ignored unless greater than current value
+ * and not a leap of 20 years or more.
+ */
+void
+ve_utc_set(time_t utc)
+{
+ if (utc > ve_utc &&
+ (ve_utc == 0 || (utc - ve_utc) < VE_UTC_MAX_JUMP)) {
+ DEBUG_PRINTF(2, ("Set ve_utc=%jd\n", (intmax_t)utc));
+ ve_utc = utc;
+ }
+}
+
+#ifdef VERIFY_CERTS_STR
+static void
+free_cert_contents(br_x509_certificate *xc)
+{
+ xfree(xc->data);
+}
+#endif
+
+/*
+ * a bit of a dance to get commonName from a certificate
+ */
+static char *
+x509_cn_get(br_x509_certificate *xc, char *buf, size_t len)
+{
+ br_x509_minimal_context mc;
+ br_name_element cn;
+ unsigned char cn_oid[4];
+ int err;
+
+ if (buf == NULL)
+ return (buf);
+ /*
+ * We want the commonName field
+ * the OID we want is 2,5,4,3 - but DER encoded
+ */
+ cn_oid[0] = 3;
+ cn_oid[1] = 0x55;
+ cn_oid[2] = 4;
+ cn_oid[3] = 3;
+ cn.oid = cn_oid;
+ cn.buf = buf;
+ cn.len = len;
+ cn.buf[0] = '\0';
+
+ br_x509_minimal_init(&mc, &br_sha256_vtable, NULL, 0);
+ br_x509_minimal_set_name_elements(&mc, &cn, 1);
+ /* the below actually does the work - updates cn.status */
+ mc.vtable->start_chain(&mc.vtable, NULL);
+ mc.vtable->start_cert(&mc.vtable, xc->data_len);
+ mc.vtable->append(&mc.vtable, xc->data, xc->data_len);
+ mc.vtable->end_cert(&mc.vtable);
+ /* we don't actually care about cert status - just its name */
+ err = mc.vtable->end_chain(&mc.vtable);
+ (void)err; /* keep compiler quiet */
+
+ if (cn.status <= 0)
+ buf = NULL;
+ return (buf);
+}
+
+/* ASN parsing related defines */
+#define ASN1_PRIMITIVE_TAG 0x1F
+#define ASN1_INF_LENGTH 0x80
+#define ASN1_LENGTH_MASK 0x7F
+
+/*
+ * Get TBS part of certificate.
+ * Since BearSSL doesn't provide any API to do this,
+ * it has to be implemented here.
+ */
+static void*
+X509_to_tbs(unsigned char* cert, size_t* output_size)
+{
+ unsigned char *result;
+ size_t tbs_size;
+ int size, i;
+
+ if (cert == NULL)
+ return (NULL);
+
+ /* Strip two sequences to get to the TBS section */
+ for (i = 0; i < 2; i++) {
+ /*
+ * XXX: We don't need to support extended tags since
+ * they should not be present in certificates.
+ */
+ if ((*cert & ASN1_PRIMITIVE_TAG) == ASN1_PRIMITIVE_TAG)
+ return (NULL);
+
+ cert++;
+
+ if (*cert == ASN1_INF_LENGTH)
+ return (NULL);
+
+ size = *cert & ASN1_LENGTH_MASK;
+ tbs_size = 0;
+
+ /* Size can either be stored on a single or multiple bytes */
+ if (*cert & (ASN1_LENGTH_MASK + 1)) {
+ cert++;
+ while (*cert == 0 && size > 0) {
+ cert++;
+ size--;
+ }
+ while (size-- > 0) {
+ tbs_size <<= 8;
+ tbs_size |= *(cert++);
+ }
+ }
+ if (i == 0)
+ result = cert;
+ }
+ tbs_size += (cert - result);
+
+ if (output_size != NULL)
+ *output_size = tbs_size;
+
+ return (result);
+}
+
+void
+ve_forbidden_digest_add(hash_data *digest, size_t num)
+{
+ while (num--)
+ VEC_ADD(forbidden_digests, digest[num]);
+}
+
+static size_t
+ve_anchors_add(br_x509_certificate *xcs, size_t num, anchor_list *anchors,
+ const char *anchors_name)
+{
+ br_x509_trust_anchor ta;
+ size_t u;
+
+ for (u = 0; u < num; u++) {
+ if (certificate_to_trust_anchor_inner(&ta, &xcs[u]) < 0) {
+ break;
+ }
+ VEC_ADD(*anchors, ta);
+ if (anchor_verbose && anchors_name) {
+ char buf[64];
+ char *cp;
+
+ cp = x509_cn_get(&xcs[u], buf, sizeof(buf));
+ if (cp) {
+ printf("x509_anchor(%s) %s\n", cp, anchors_name);
+ }
+ }
+ }
+ return (u);
+}
+
+/**
+ * @brief
+ * add certs to our trust store
+ */
+size_t
+ve_trust_anchors_add(br_x509_certificate *xcs, size_t num)
+{
+ return (ve_anchors_add(xcs, num, &trust_anchors, "trusted"));
+}
+
+size_t
+ve_forbidden_anchors_add(br_x509_certificate *xcs, size_t num)
+{
+ return (ve_anchors_add(xcs, num, &forbidden_anchors, "forbidden"));
+}
+
+
+/**
+ * @brief add trust anchors in buf
+ *
+ * Assume buf contains x509 certificates, but if not and
+ * we support OpenPGP try adding as that.
+ *
+ * @return number of anchors added
+ */
+size_t
+ve_trust_anchors_add_buf(unsigned char *buf, size_t len)
+{
+ br_x509_certificate *xcs;
+ size_t num;
+
+ num = 0;
+ if (len > 0) {
+ xcs = parse_certificates(buf, len, &num);
+ if (xcs != NULL) {
+ num = ve_trust_anchors_add(xcs, num);
+#ifdef VE_OPENPGP_SUPPORT
+ } else {
+ num = openpgp_trust_add_buf(buf, len);
+#endif
+ }
+ }
+ return (num);
+}
+
+/**
+ * @brief revoke trust anchors in buf
+ *
+ * Assume buf contains x509 certificates, but if not and
+ * we support OpenPGP try revoking keyId
+ *
+ * @return number of anchors revoked
+ */
+size_t
+ve_trust_anchors_revoke(unsigned char *buf, size_t len)
+{
+ br_x509_certificate *xcs;
+ size_t num;
+
+ num = 0;
+ if (len > 0) {
+ xcs = parse_certificates(buf, len, &num);
+ if (xcs != NULL) {
+ num = ve_forbidden_anchors_add(xcs, num);
+#ifdef VE_OPENPGP_SUPPORT
+ } else {
+ if (buf[len - 1] == '\n')
+ buf[len - 1] = '\0';
+ num = openpgp_trust_revoke((char *)buf);
+#endif
+ }
+ }
+ return (num);
+}
+
+/**
+ * @brief
+ * initialize our trust_anchors from ta_PEM
+ */
+int
+ve_trust_init(void)
+{
+ static int once = -1;
+
+ if (once >= 0)
+ return (once);
+ once = 0; /* to be sure */
+#ifdef BUILD_UTC
+ ve_utc_set(BUILD_UTC); /* ensure sanity */
+#endif
+ ve_utc_set(time(NULL));
+ ve_error_set(NULL); /* make sure it is empty */
+#ifdef VE_PCR_SUPPORT
+ ve_pcr_init();
+#endif
+
+#ifdef TRUST_ANCHOR_STR
+ if (TRUST_ANCHOR_STR != NULL && strlen(TRUST_ANCHOR_STR) != 0ul)
+ ve_trust_anchors_add_buf(__DECONST(unsigned char *,
+ TRUST_ANCHOR_STR), sizeof(TRUST_ANCHOR_STR));
+#endif
+ once = (int) VEC_LEN(trust_anchors);
+#ifdef VE_OPENPGP_SUPPORT
+ once += openpgp_trust_init();
+#endif
+ return (once);
+}
+
+#ifdef HAVE_BR_X509_TIME_CHECK
+static int
+verify_time_cb(void *tctx __unused,
+ uint32_t not_before_days, uint32_t not_before_seconds,
+ uint32_t not_after_days, uint32_t not_after_seconds)
+{
+ time_t not_before;
+ time_t not_after;
+ int rc;
+#ifdef UNIT_TEST
+ char date[12], nb_date[12], na_date[12];
+#endif
+
+ if (enforce_validity) {
+ not_before = ((not_before_days - X509_DAYS_TO_UTC0) * SECONDS_PER_DAY) + not_before_seconds;
+ not_after = ((not_after_days - X509_DAYS_TO_UTC0) * SECONDS_PER_DAY) + not_after_seconds;
+ if (ve_utc < not_before)
+ rc = -1;
+ else if (ve_utc > not_after)
+ rc = 1;
+ else
+ rc = 0;
+#ifdef UNIT_TEST
+ printf("notBefore %s notAfter %s date %s rc %d\n",
+ gdate(nb_date, sizeof(nb_date), not_before),
+ gdate(na_date, sizeof(na_date), not_after),
+ gdate(date, sizeof(date), ve_utc), rc);
+#endif
+ } else
+ rc = 0; /* don't fail */
+ return rc;
+}
+#endif
+
+/**
+ * if we can verify the certificate chain in "certs",
+ * return the public key and if "xcp" is !NULL the associated
+ * certificate
+ */
+static br_x509_pkey *
+verify_signer_xcs(br_x509_certificate *xcs,
+ size_t num,
+ br_name_element *elts, size_t num_elts,
+ anchor_list *anchors)
+{
+ br_x509_minimal_context mc;
+ br_x509_certificate *xc;
+ size_t u;
+ cert_list chain = VEC_INIT;
+ const br_x509_pkey *tpk;
+ br_x509_pkey *pk;
+ unsigned int usages;
+ int err;
+
+ DEBUG_PRINTF(5, ("verify_signer: %zu certs in chain\n", num));
+ VEC_ADDMANY(chain, xcs, num);
+ if (VEC_LEN(chain) == 0) {
+ ve_error_set("ERROR: no/invalid certificate chain\n");
+ return (NULL);
+ }
+
+ DEBUG_PRINTF(5, ("verify_signer: %zu trust anchors\n",
+ VEC_LEN(*anchors)));
+
+ br_x509_minimal_init(&mc, &br_sha256_vtable,
+ &VEC_ELT(*anchors, 0),
+ VEC_LEN(*anchors));
+#ifdef VE_ECDSA_SUPPORT
+ br_x509_minimal_set_ecdsa(&mc,
+ &br_ec_prime_i31, &br_ecdsa_i31_vrfy_asn1);
+#endif
+#ifdef VE_RSA_SUPPORT
+ br_x509_minimal_set_rsa(&mc, &br_rsa_i31_pkcs1_vrfy);
+#endif
+#if defined(UNIT_TEST) && defined(VE_DEPRECATED_RSA_SHA1_SUPPORT)
+ /* This is deprecated! do not enable unless you absolutely have to */
+ br_x509_minimal_set_hash(&mc, br_sha1_ID, &br_sha1_vtable);
+#endif
+ br_x509_minimal_set_hash(&mc, br_sha256_ID, &br_sha256_vtable);
+#ifdef VE_SHA384_SUPPORT
+ br_x509_minimal_set_hash(&mc, br_sha384_ID, &br_sha384_vtable);
+#endif
+#ifdef VE_SHA512_SUPPORT
+ br_x509_minimal_set_hash(&mc, br_sha512_ID, &br_sha512_vtable);
+#endif
+ br_x509_minimal_set_name_elements(&mc, elts, num_elts);
+
+#ifdef HAVE_BR_X509_TIME_CHECK
+ br_x509_minimal_set_time_callback(&mc, NULL, verify_time_cb);
+#else
+#if defined(_STANDALONE) || defined(UNIT_TEST)
+ /*
+ * Clock is probably bogus so we use ve_utc.
+ */
+ mc.days = (ve_utc / SECONDS_PER_DAY) + X509_DAYS_TO_UTC0;
+ mc.seconds = (ve_utc % SECONDS_PER_DAY);
+#endif
+#endif
+ mc.vtable->start_chain(&mc.vtable, NULL);
+ for (u = 0; u < VEC_LEN(chain); u ++) {
+ xc = &VEC_ELT(chain, u);
+ mc.vtable->start_cert(&mc.vtable, xc->data_len);
+ mc.vtable->append(&mc.vtable, xc->data, xc->data_len);
+ mc.vtable->end_cert(&mc.vtable);
+ switch (mc.err) {
+ case 0:
+ case BR_ERR_X509_OK:
+ case BR_ERR_X509_EXPIRED:
+ break;
+ default:
+ printf("u=%zu mc.err=%d\n", u, mc.err);
+ break;
+ }
+ }
+ err = mc.vtable->end_chain(&mc.vtable);
+ pk = NULL;
+ if (err) {
+ char date[12];
+
+ switch (err) {
+ case 54:
+ ve_error_set("Validation failed, certificate not valid as of %s",
+ gdate(date, sizeof(date), ve_utc));
+ break;
+ default: {
+ const char *err_desc = NULL;
+ const char *err_name = find_error_name(err, &err_desc);
+
+ if (err_name == NULL)
+ ve_error_set("Validation failed, err = %d",
+ err);
+ else
+ ve_error_set("Validation failed, %s (%s)",
+ err_desc, err_name);
+ break; }
+ }
+ } else {
+ tpk = mc.vtable->get_pkey(&mc.vtable, &usages);
+ if (tpk != NULL) {
+ pk = xpkeydup(tpk);
+ }
+ }
+ VEC_CLEAR(chain);
+ return (pk);
+}
+
+/*
+ * Check if digest of one of the certificates from verified chain
+ * is present in the forbidden database.
+ * Since UEFI allows to store three types of digests
+ * all of them have to be checked separately.
+ */
+static int
+check_forbidden_digests(br_x509_certificate *xcs, size_t num)
+{
+ unsigned char sha256_digest[br_sha256_SIZE];
+ unsigned char sha384_digest[br_sha384_SIZE];
+ unsigned char sha512_digest[br_sha512_SIZE];
+ void *tbs;
+ hash_data *digest;
+ br_hash_compat_context ctx;
+ const br_hash_class *md;
+ size_t tbs_len, i;
+ int have_sha256, have_sha384, have_sha512;
+
+ if (VEC_LEN(forbidden_digests) == 0)
+ return (0);
+
+ /*
+ * Iterate through certificates, extract their To-Be-Signed section,
+ * and compare its digest against the ones in the forbidden database.
+ */
+ while (num--) {
+ tbs = X509_to_tbs(xcs[num].data, &tbs_len);
+ if (tbs == NULL) {
+ printf("Failed to obtain TBS part of certificate\n");
+ return (1);
+ }
+ have_sha256 = have_sha384 = have_sha512 = 0;
+
+ for (i = 0; i < VEC_LEN(forbidden_digests); i++) {
+ digest = &VEC_ELT(forbidden_digests, i);
+ switch (digest->hash_size) {
+ case br_sha256_SIZE:
+ if (!have_sha256) {
+ have_sha256 = 1;
+ md = &br_sha256_vtable;
+ md->init(&ctx.vtable);
+ md->update(&ctx.vtable, tbs, tbs_len);
+ md->out(&ctx.vtable, sha256_digest);
+ }
+ if (!memcmp(sha256_digest,
+ digest->data,
+ br_sha256_SIZE))
+ return (1);
+
+ break;
+ case br_sha384_SIZE:
+ if (!have_sha384) {
+ have_sha384 = 1;
+ md = &br_sha384_vtable;
+ md->init(&ctx.vtable);
+ md->update(&ctx.vtable, tbs, tbs_len);
+ md->out(&ctx.vtable, sha384_digest);
+ }
+ if (!memcmp(sha384_digest,
+ digest->data,
+ br_sha384_SIZE))
+ return (1);
+
+ break;
+ case br_sha512_SIZE:
+ if (!have_sha512) {
+ have_sha512 = 1;
+ md = &br_sha512_vtable;
+ md->init(&ctx.vtable);
+ md->update(&ctx.vtable, tbs, tbs_len);
+ md->out(&ctx.vtable, sha512_digest);
+ }
+ if (!memcmp(sha512_digest,
+ digest->data,
+ br_sha512_SIZE))
+ return (1);
+
+ break;
+ }
+ }
+ }
+
+ return (0);
+}
+
+static br_x509_pkey *
+verify_signer(const char *certs,
+ br_name_element *elts, size_t num_elts)
+{
+ br_x509_certificate *xcs;
+ br_x509_pkey *pk;
+ size_t num;
+
+ pk = NULL;
+
+ ve_trust_init();
+ xcs = read_certificates(certs, &num);
+ if (xcs == NULL) {
+ ve_error_set("cannot read certificates\n");
+ return (NULL);
+ }
+
+ /*
+ * Check if either
+ * 1. There is a direct match between cert from forbidden_anchors
+ * and a cert from chain.
+ * 2. CA that signed the chain is found in forbidden_anchors.
+ */
+ if (VEC_LEN(forbidden_anchors) > 0)
+ pk = verify_signer_xcs(xcs, num, elts, num_elts, &forbidden_anchors);
+ if (pk != NULL) {
+ ve_error_set("Certificate is on forbidden list\n");
+ xfreepkey(pk);
+ pk = NULL;
+ goto out;
+ }
+
+ pk = verify_signer_xcs(xcs, num, elts, num_elts, &trust_anchors);
+ if (pk == NULL)
+ goto out;
+
+ /*
+ * Check if hash of tbs part of any certificate in chain
+ * is on the forbidden list.
+ */
+ if (check_forbidden_digests(xcs, num)) {
+ ve_error_set("Certificate hash is on forbidden list\n");
+ xfreepkey(pk);
+ pk = NULL;
+ }
+out:
+ free_certificates(xcs, num);
+ return (pk);
+}
+
+/**
+ * we need a hex digest including trailing newline below
+ */
+char *
+hexdigest(char *buf, size_t bufsz, unsigned char *foo, size_t foo_len)
+{
+ char const hex2ascii[] = "0123456789abcdef";
+ size_t i;
+
+ /* every binary byte is 2 chars in hex + newline + null */
+ if (bufsz < (2 * foo_len) + 2)
+ return (NULL);
+
+ for (i = 0; i < foo_len; i++) {
+ buf[i * 2] = hex2ascii[foo[i] >> 4];
+ buf[i * 2 + 1] = hex2ascii[foo[i] & 0x0f];
+ }
+
+ buf[i * 2] = 0x0A; /* we also want a newline */
+ buf[i * 2 + 1] = '\0';
+
+ return (buf);
+}
+
+/**
+ * @brief
+ * verify file against sigfile using pk
+ *
+ * When we generated the signature in sigfile,
+ * we hashed (sha256) file, and sent that to signing server
+ * which hashed (sha256) that hash.
+ *
+ * To verify we need to replicate that result.
+ *
+ * @param[in] pk
+ * br_x509_pkey
+ *
+ * @paramp[in] file
+ * file to be verified
+ *
+ * @param[in] sigfile
+ * signature (PEM encoded)
+ *
+ * @return NULL on error, otherwise content of file.
+ */
+#ifdef VE_ECDSA_SUPPORT
+static unsigned char *
+verify_ec(br_x509_pkey *pk, const char *file, const char *sigfile)
+{
+#ifdef VE_ECDSA_HASH_AGAIN
+ char *hex, hexbuf[br_sha512_SIZE * 2 + 2];
+#endif
+ unsigned char rhbuf[br_sha512_SIZE];
+ br_sha256_context ctx;
+ unsigned char *fcp, *scp;
+ size_t flen, slen, plen;
+ pem_object *po;
+ const br_ec_impl *ec;
+ br_ecdsa_vrfy vrfy;
+
+ if ((fcp = read_file(file, &flen)) == NULL)
+ return (NULL);
+ if ((scp = read_file(sigfile, &slen)) == NULL) {
+ free(fcp);
+ return (NULL);
+ }
+ if ((po = decode_pem(scp, slen, &plen)) == NULL) {
+ free(fcp);
+ free(scp);
+ return (NULL);
+ }
+ br_sha256_init(&ctx);
+ br_sha256_update(&ctx, fcp, flen);
+ br_sha256_out(&ctx, rhbuf);
+#ifdef VE_ECDSA_HASH_AGAIN
+ hex = hexdigest(hexbuf, sizeof(hexbuf), rhbuf, br_sha256_SIZE);
+ /* now hash that */
+ if (hex) {
+ br_sha256_init(&ctx);
+ br_sha256_update(&ctx, hex, strlen(hex));
+ br_sha256_out(&ctx, rhbuf);
+ }
+#endif
+ ec = br_ec_get_default();
+ vrfy = br_ecdsa_vrfy_asn1_get_default();
+ if (!vrfy(ec, rhbuf, br_sha256_SIZE, &pk->key.ec, po->data,
+ po->data_len)) {
+ free(fcp);
+ fcp = NULL;
+ }
+ free(scp);
+ return (fcp);
+}
+#endif
+
+#if defined(VE_RSA_SUPPORT) || defined(VE_OPENPGP_SUPPORT)
+/**
+ * @brief verify an rsa digest
+ *
+ * @return 0 on failure
+ */
+int
+verify_rsa_digest (br_rsa_public_key *pkey,
+ const unsigned char *hash_oid,
+ unsigned char *mdata, size_t mlen,
+ unsigned char *sdata, size_t slen)
+{
+ br_rsa_pkcs1_vrfy vrfy;
+ unsigned char vhbuf[br_sha512_SIZE];
+
+ vrfy = br_rsa_pkcs1_vrfy_get_default();
+
+ if (!vrfy(sdata, slen, hash_oid, mlen, pkey, vhbuf) ||
+ memcmp(vhbuf, mdata, mlen) != 0) {
+ return (0); /* fail */
+ }
+ return (1); /* ok */
+}
+#endif
+
+/**
+ * @brief
+ * verify file against sigfile using pk
+ *
+ * When we generated the signature in sigfile,
+ * we hashed (sha256) file, and sent that to signing server
+ * which hashed (sha256) that hash.
+ *
+ * Or (deprecated) we simply used sha1 hash directly.
+ *
+ * To verify we need to replicate that result.
+ *
+ * @param[in] pk
+ * br_x509_pkey
+ *
+ * @paramp[in] file
+ * file to be verified
+ *
+ * @param[in] sigfile
+ * signature (PEM encoded)
+ *
+ * @return NULL on error, otherwise content of file.
+ */
+#ifdef VE_RSA_SUPPORT
+static unsigned char *
+verify_rsa(br_x509_pkey *pk, const char *file, const char *sigfile)
+{
+ unsigned char rhbuf[br_sha512_SIZE];
+ const unsigned char *hash_oid;
+ const br_hash_class *md;
+ br_hash_compat_context mctx;
+ unsigned char *fcp, *scp;
+ size_t flen, slen, plen, hlen;
+ pem_object *po;
+
+ if ((fcp = read_file(file, &flen)) == NULL)
+ return (NULL);
+ if ((scp = read_file(sigfile, &slen)) == NULL) {
+ free(fcp);
+ return (NULL);
+ }
+ if ((po = decode_pem(scp, slen, &plen)) == NULL) {
+ free(fcp);
+ free(scp);
+ return (NULL);
+ }
+
+ switch (po->data_len) {
+#if defined(UNIT_TEST) && defined(VE_DEPRECATED_RSA_SHA1_SUPPORT)
+ case 256:
+ // this is our old deprecated sig method
+ md = &br_sha1_vtable;
+ hlen = br_sha1_SIZE;
+ hash_oid = BR_HASH_OID_SHA1;
+ break;
+#endif
+ default:
+ md = &br_sha256_vtable;
+ hlen = br_sha256_SIZE;
+ hash_oid = BR_HASH_OID_SHA256;
+ break;
+ }
+ md->init(&mctx.vtable);
+ md->update(&mctx.vtable, fcp, flen);
+ md->out(&mctx.vtable, rhbuf);
+ if (!verify_rsa_digest(&pk->key.rsa, hash_oid,
+ rhbuf, hlen, po->data, po->data_len)) {
+ free(fcp);
+ fcp = NULL;
+ }
+ free(scp);
+ return (fcp);
+}
+#endif
+
+/**
+ * @brief
+ * verify a signature and return content of signed file
+ *
+ * @param[in] sigfile
+ * file containing signature
+ * we derrive path of signed file and certificate change from
+ * this.
+ *
+ * @param[in] flags
+ * only bit 1 significant so far
+ *
+ * @return NULL on error otherwise content of signed file
+ */
+unsigned char *
+verify_sig(const char *sigfile, int flags)
+{
+ br_x509_pkey *pk;
+ br_name_element cn;
+ char cn_buf[80];
+ unsigned char cn_oid[4];
+ char pbuf[MAXPATHLEN];
+ char *cp;
+ unsigned char *ucp;
+ size_t n;
+
+ DEBUG_PRINTF(5, ("verify_sig: %s\n", sigfile));
+ n = strlcpy(pbuf, sigfile, sizeof(pbuf));
+ if (n > (sizeof(pbuf) - 5) || strcmp(&sigfile[n - 3], "sig") != 0)
+ return (NULL);
+ cp = strcpy(&pbuf[n - 3], "certs");
+ /*
+ * We want the commonName field
+ * the OID we want is 2,5,4,3 - but DER encoded
+ */
+ cn_oid[0] = 3;
+ cn_oid[1] = 0x55;
+ cn_oid[2] = 4;
+ cn_oid[3] = 3;
+ cn.oid = cn_oid;
+ cn.buf = cn_buf;
+ cn.len = sizeof(cn_buf);
+
+ pk = verify_signer(pbuf, &cn, 1);
+ if (!pk) {
+ printf("cannot verify: %s: %s\n", pbuf, ve_error_get());
+ return (NULL);
+ }
+ for (; cp > pbuf; cp--) {
+ if (*cp == '.') {
+ *cp = '\0';
+ break;
+ }
+ }
+ switch (pk->key_type) {
+#ifdef VE_ECDSA_SUPPORT
+ case BR_KEYTYPE_EC:
+ ucp = verify_ec(pk, pbuf, sigfile);
+ break;
+#endif
+#ifdef VE_RSA_SUPPORT
+ case BR_KEYTYPE_RSA:
+ ucp = verify_rsa(pk, pbuf, sigfile);
+ break;
+#endif
+ default:
+ ucp = NULL; /* not supported */
+ }
+ xfreepkey(pk);
+ if (!ucp) {
+ printf("Unverified %s (%s)\n", pbuf,
+ cn.status ? cn_buf : "unknown");
+ } else if ((flags & VEF_VERBOSE) != 0) {
+ printf("Verified %s signed by %s\n", pbuf,
+ cn.status ? cn_buf : "someone we trust");
+ }
+ return (ucp);
+}
+
+
+/**
+ * @brief verify hash matches
+ *
+ * We have finished hashing a file,
+ * see if we got the desired result.
+ *
+ * @param[in] ctx
+ * pointer to hash context
+ *
+ * @param[in] md
+ * pointer to hash class
+ *
+ * @param[in] path
+ * name of the file we are checking
+ *
+ * @param[in] want
+ * the expected result
+ *
+ * @param[in] hlen
+ * size of hash output
+ *
+ * @return 0 on success
+ */
+int
+ve_check_hash(br_hash_compat_context *ctx, const br_hash_class *md,
+ const char *path, const char *want, size_t hlen)
+{
+ char hexbuf[br_sha512_SIZE * 2 + 2];
+ unsigned char hbuf[br_sha512_SIZE];
+ char *hex;
+ int rc;
+ int n;
+
+ md->out(&ctx->vtable, hbuf);
+#ifdef VE_PCR_SUPPORT
+ ve_pcr_update(path, hbuf, hlen);
+#endif
+ hex = hexdigest(hexbuf, sizeof(hexbuf), hbuf, hlen);
+ if (!hex)
+ return (VE_FINGERPRINT_WRONG);
+ n = 2*hlen;
+ if ((rc = strncmp(hex, want, n))) {
+ ve_error_set("%s: %.*s != %.*s", path, n, hex, n, want);
+ rc = VE_FINGERPRINT_WRONG;
+ }
+ return (rc ? rc : VE_FINGERPRINT_OK);
+}
+
+#ifdef VE_HASH_KAT_STR
+static int
+test_hash(const br_hash_class *md, size_t hlen,
+ const char *hname, const char *s, size_t slen, const char *want)
+{
+ br_hash_compat_context mctx;
+
+ md->init(&mctx.vtable);
+ md->update(&mctx.vtable, s, slen);
+ return (ve_check_hash(&mctx, md, hname, want, hlen) != VE_FINGERPRINT_OK);
+}
+
+#endif
+
+#define ve_test_hash(n, N) \
+ printf("Testing hash: " #n "\t\t\t\t%s\n", \
+ test_hash(&br_ ## n ## _vtable, br_ ## n ## _SIZE, #n, \
+ VE_HASH_KAT_STR, VE_HASH_KAT_STRLEN(VE_HASH_KAT_STR), \
+ vh_ ## N) ? "Failed" : "Passed")
+
+/**
+ * @brief
+ * run self tests on hash and signature verification
+ *
+ * Test that the hash methods (SHA1 and SHA256) work.
+ * Test that we can verify a certificate for each supported
+ * Root CA.
+ *
+ * @return cached result.
+ */
+int
+ve_self_tests(void)
+{
+ static int once = -1;
+#ifdef VERIFY_CERTS_STR
+ br_x509_certificate *xcs;
+ br_x509_pkey *pk;
+ br_name_element cn;
+ char cn_buf[80];
+ unsigned char cn_oid[4];
+ size_t num;
+ size_t u;
+#endif
+
+ if (once >= 0)
+ return (once);
+ once = 0;
+
+ DEBUG_PRINTF(5, ("Self tests...\n"));
+#ifdef VE_HASH_KAT_STR
+#ifdef VE_SHA1_SUPPORT
+ ve_test_hash(sha1, SHA1);
+#endif
+#ifdef VE_SHA256_SUPPORT
+ ve_test_hash(sha256, SHA256);
+#endif
+#ifdef VE_SHA384_SUPPORT
+ ve_test_hash(sha384, SHA384);
+#endif
+#ifdef VE_SHA512_SUPPORT
+ ve_test_hash(sha512, SHA512);
+#endif
+#endif
+#ifdef VERIFY_CERTS_STR
+ xcs = parse_certificates(__DECONST(unsigned char *, VERIFY_CERTS_STR),
+ sizeof(VERIFY_CERTS_STR), &num);
+ if (xcs != NULL) {
+ /*
+ * We want the commonName field
+ * the OID we want is 2,5,4,3 - but DER encoded
+ */
+ cn_oid[0] = 3;
+ cn_oid[1] = 0x55;
+ cn_oid[2] = 4;
+ cn_oid[3] = 3;
+ cn.oid = cn_oid;
+ cn.buf = cn_buf;
+
+ for (u = 0; u < num; u ++) {
+ cn.len = sizeof(cn_buf);
+ if ((pk = verify_signer_xcs(&xcs[u], 1, &cn, 1, &trust_anchors)) != NULL) {
+ free_cert_contents(&xcs[u]);
+ once++;
+ printf("Testing verify certificate: %s\tPassed\n",
+ cn.status ? cn_buf : "");
+ xfreepkey(pk);
+ }
+ }
+ if (!once)
+ printf("Testing verify certificate:\t\t\tFailed\n");
+ xfree(xcs);
+ }
+#endif /* VERIFY_CERTS_STR */
+#ifdef VE_OPENPGP_SUPPORT
+ if (!openpgp_self_tests())
+ once++;
+#endif
+ return (once);
+}