diff options
Diffstat (limited to 'lib/libsecureboot/vets.c')
-rw-r--r-- | lib/libsecureboot/vets.c | 1156 |
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); +} |