Index: head/lib/libsecureboot/verify_file.c =================================================================== --- head/lib/libsecureboot/verify_file.c (revision 354037) +++ head/lib/libsecureboot/verify_file.c (revision 354038) @@ -1,454 +1,475 @@ /*- * 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. */ /* * Routines to verify files loaded. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include "libsecureboot.h" #include #include #ifdef UNIT_TEST # include # define panic warn /* * define MANIFEST_SKIP to Skip - in tests/tvo.c so that * tvo can control the value we use in find_manifest() */ extern char *Skip; # undef MANIFEST_SKIP # define MANIFEST_SKIP Skip # undef VE_DEBUG_LEVEL #endif /* * We sometimes need to know if input is verified or not. * The extra slot is for tracking most recently opened. */ static int ve_status[SOPEN_MAX+1]; static int ve_status_state; struct verify_status; struct verify_status *verified_files = NULL; static int loaded_manifests = 0; /* have we loaded anything? */ #define VE_STATUS_NONE 1 #define VE_STATUS_VALID 2 /** * @brief set ve status for fd */ static void ve_status_set(int fd, int ves) { if (fd >= 0 && fd < SOPEN_MAX) { ve_status[fd] = ves; ve_status_state = VE_STATUS_VALID; } ve_status[SOPEN_MAX] = ves; } /** * @brief get ve status of fd * * What we return depends on ve_status_state. * * @return * @li ve_status[fd] if ve_status_state is valid * @li ve_status[SOPEN_MAX] if ve_status_state is none * @li VE_NOT_CHECKED if ve_status_state uninitialized */ int ve_status_get(int fd) { if (!ve_status_state) { return (VE_NOT_CHECKED); } if (ve_status_state == VE_STATUS_VALID && fd >= 0 && fd < SOPEN_MAX) return (ve_status[fd]); return (ve_status[SOPEN_MAX]); /* most recent */ } /** * @brief track verify status * * occasionally loader will make multiple calls * for the same file, we need only check it once. */ struct verify_status { dev_t vs_dev; ino_t vs_ino; int vs_status; struct verify_status *vs_next; }; int is_verified(struct stat *stp) { struct verify_status *vsp; for (vsp = verified_files; vsp != NULL; vsp = vsp->vs_next) { if (stp->st_dev == vsp->vs_dev && stp->st_ino == vsp->vs_ino) return (vsp->vs_status); } return (VE_NOT_CHECKED); } /* most recent first, since most likely to see repeated calls. */ void add_verify_status(struct stat *stp, int status) { struct verify_status *vsp; vsp = malloc(sizeof(struct verify_status)); vsp->vs_next = verified_files; vsp->vs_dev = stp->st_dev; vsp->vs_ino = stp->st_ino; vsp->vs_status = status; verified_files = vsp; } /** * @brief * load specified manifest if verified */ int load_manifest(const char *name, const char *prefix, const char *skip, struct stat *stp) { struct stat st; size_t n; int rc; char *content; rc = VE_FINGERPRINT_NONE; n = strlen(name); if (n > 4) { if (!stp) { stp = &st; if (stat(name, &st) < 0 || !S_ISREG(st.st_mode)) return (rc); } rc = is_verified(stp); if (rc != VE_NOT_CHECKED) { return (rc); } /* loader has no sense of time */ ve_utc_set(stp->st_mtime); content = (char *)verify_signed(name, VEF_VERBOSE); if (content) { fingerprint_info_add(name, prefix, skip, content, stp); add_verify_status(stp, VE_VERIFIED); loaded_manifests = 1; /* we are verifying! */ DEBUG_PRINTF(3, ("loaded: %s %s %s\n", name, prefix, skip)); rc = 0; } else { rc = VE_FINGERPRINT_WRONG; add_verify_status(stp, rc); /* remember */ } } return (rc); } static int find_manifest(const char *name) { struct stat st; char buf[MAXPATHLEN]; char *prefix; char *skip; const char **tp; int rc; strncpy(buf, name, MAXPATHLEN - 1); if (!(prefix = strrchr(buf, '/'))) return (-1); *prefix = '\0'; prefix = strdup(buf); rc = VE_FINGERPRINT_NONE; for (tp = manifest_names; *tp; tp++) { snprintf(buf, sizeof(buf), "%s/%s", prefix, *tp); DEBUG_PRINTF(5, ("looking for %s\n", buf)); if (stat(buf, &st) == 0 && st.st_size > 0) { #ifdef MANIFEST_SKIP_ALWAYS /* very unlikely */ skip = MANIFEST_SKIP_ALWAYS; #else #ifdef MANIFEST_SKIP /* rare */ if (*tp[0] == '.') { skip = MANIFEST_SKIP; } else #endif skip = NULL; #endif rc = load_manifest(buf, skip ? prefix : NULL, skip, &st); break; } } free(prefix); return (rc); } #ifdef LOADER_VERIEXEC_TESTING # define ACCEPT_NO_FP_DEFAULT VE_MUST + 1 #else # define ACCEPT_NO_FP_DEFAULT VE_MUST #endif #ifndef VE_VERBOSE_DEFAULT # define VE_VERBOSE_DEFAULT 0 #endif static int severity_guess(const char *filename) { const char *cp; /* Some files like *.conf and *.hints may be unsigned */ if ((cp = strrchr(filename, '.'))) { if (strcmp(cp, ".conf") == 0 || strcmp(cp, ".cookie") == 0 || strcmp(cp, ".hints") == 0) return (VE_TRY); } return (VE_WANT); } static void verify_tweak(int fd, off_t off, struct stat *stp, char *tweak, int *accept_no_fp, int *verbose, int *verifying) { if (strcmp(tweak, "off") == 0) { *verifying = 0; } else if (strcmp(tweak, "strict") == 0) { /* anything caller wants verified must be */ *accept_no_fp = VE_WANT; *verbose = 1; /* warn of anything unverified */ /* treat self test failure as fatal */ if (!ve_self_tests()) { panic("verify self tests failed"); } } else if (strcmp(tweak, "modules") == 0) { /* modules/kernel must be verified */ *accept_no_fp = VE_MUST; } else if (strcmp(tweak, "try") == 0) { /* best effort: always accept no fp */ *accept_no_fp = VE_MUST + 1; } else if (strcmp(tweak, "verbose") == 0) { *verbose = 1; } else if (strcmp(tweak, "quiet") == 0) { *verbose = 0; } else if (strncmp(tweak, "trust", 5) == 0) { /* content is trust anchor to add or revoke */ unsigned char *ucp; size_t num; if (off > 0) lseek(fd, 0, SEEK_SET); ucp = read_fd(fd, stp->st_size); if (ucp == NULL) return; if (strstr(tweak, "revoke")) { num = ve_trust_anchors_revoke(ucp, stp->st_size); DEBUG_PRINTF(3, ("revoked %d trust anchors\n", (int) num)); } else { num = ve_trust_anchors_add_buf(ucp, stp->st_size); DEBUG_PRINTF(3, ("added %d trust anchors\n", (int) num)); } } } +#ifndef VE_DEBUG_LEVEL +# define VE_DEBUG_LEVEL 0 +#endif + +static int +getenv_int(const char *var, int def) +{ + const char *cp; + char *ep; + long val; + + val = def; + cp = getenv(var); + if (cp && *cp) { + val = strtol(cp, &ep, 0); + if ((ep && *ep) || val != (int)val) { + val = def; + } + } + return (int)val; +} + /** * @brief verify an open file * * @param[in] fd * open descriptor * * @param[in] filename * path we opened and will use to lookup fingerprint * * @param[in] off * current offset in fd, must be restored on return * * @param[in] severity * indicator of how to handle case of missing fingerprint * * We look for a signed manifest relative to the filename * just opened and verify/load it if needed. * * We then use verify_fd() in libve to actually verify that hash for * open file. If it returns < 0 we look at the severity arg to decide * what to do about it. * * If verify_fd() returns VE_FINGERPRINT_NONE we accept it if severity * is < accept_no_fp. * * @return >= 0 on success < 0 on failure */ int verify_file(int fd, const char *filename, off_t off, int severity) { static int verifying = -1; static int accept_no_fp = ACCEPT_NO_FP_DEFAULT; static int verbose = VE_VERBOSE_DEFAULT; struct stat st; char *cp; int rc; if (verifying < 0) { verifying = ve_trust_init(); -#ifdef VE_DEBUG_LEVEL - ve_debug_set(VE_DEBUG_LEVEL); -#endif + verbose = getenv_int("VE_VERBOSE", VE_VERBOSE_DEFAULT); + ve_debug_set(getenv_int("VE_DEBUG_LEVEL", VE_DEBUG_LEVEL)); /* initialize ve_status with default result */ rc = verifying ? VE_NOT_CHECKED : VE_NOT_VERIFYING; ve_status_set(0, rc); ve_status_state = VE_STATUS_NONE; if (verifying) { ve_self_tests(); ve_anchor_verbose_set(1); } } if (!verifying) return (0); if (fd < 0 || fstat(fd, &st) < 0 || !S_ISREG(st.st_mode)) return (0); DEBUG_PRINTF(3, ("fd=%d,name='%s',off=%lld,dev=%lld,ino=%lld\n", fd, filename, (long long)off, (long long)st.st_dev, (long long)st.st_ino)); rc = is_verified(&st); if (rc != VE_NOT_CHECKED) { ve_status_set(fd, rc); return (rc); } rc = find_manifest(filename); if (rc != VE_FINGERPRINT_WRONG && loaded_manifests) { if (severity <= VE_GUESS) severity = severity_guess(filename); #ifdef VE_PCR_SUPPORT /* * Only update pcr with things that must verify * these tend to be processed in a more deterministic * order, which makes our pseudo pcr more useful. */ ve_pcr_updating_set((severity == VE_MUST)); #endif if ((rc = verify_fd(fd, filename, off, &st)) >= 0) { if (verbose || severity > VE_WANT) { #if defined(VE_DEBUG_LEVEL) && VE_DEBUG_LEVEL > 0 printf("%serified %s %llu,%llu\n", (rc == VE_FINGERPRINT_IGNORE) ? "Unv" : "V", filename, (long long)st.st_dev, (long long)st.st_ino); #else printf("%serified %s\n", (rc == VE_FINGERPRINT_IGNORE) ? "Unv" : "V", filename); #endif } if (severity < VE_MUST) { /* not a kernel or module */ if ((cp = strrchr(filename, '/'))) { cp++; if (strncmp(cp, "loader.ve.", 10) == 0) { cp += 10; verify_tweak(fd, off, &st, cp, &accept_no_fp, &verbose, &verifying); } } } add_verify_status(&st, rc); ve_status_set(fd, rc); return (rc); } if (severity || verbose || rc == VE_FINGERPRINT_WRONG) printf("Unverified: %s\n", ve_error_get()); if (rc == VE_FINGERPRINT_UNKNOWN && severity < VE_MUST) rc = VE_UNVERIFIED_OK; else if (rc == VE_FINGERPRINT_NONE && severity < accept_no_fp) rc = VE_UNVERIFIED_OK; add_verify_status(&st, rc); } #ifdef LOADER_VERIEXEC_TESTING else if (rc != VE_FINGERPRINT_WRONG) { /* * We have not loaded any manifest and * not because of verication failure. * Most likely reason is we have none. * Allow boot to proceed if we are just testing. */ return (VE_UNVERIFIED_OK); } #endif if (rc == VE_FINGERPRINT_WRONG && severity > accept_no_fp) panic("cannot continue"); ve_status_set(fd, rc); return (rc); } /** * @brief get hex string for pcr value and export * * In case we are doing measured boot, provide * value of the "pcr" data we have accumulated. */ void verify_pcr_export(void) { #ifdef VE_PCR_SUPPORT char hexbuf[br_sha256_SIZE * 2 + 2]; unsigned char hbuf[br_sha256_SIZE]; char *hex; ssize_t hlen; hlen = ve_pcr_get(hbuf, sizeof(hbuf)); if (hlen > 0) { hex = hexdigest(hexbuf, sizeof(hexbuf), hbuf, hlen); if (hex) { hex[hlen*2] = '\0'; /* clobber newline */ setenv("loader.ve.pcr", hex, 1); } } #endif } Index: head/lib/libsecureboot/vets.c =================================================================== --- head/lib/libsecureboot/vets.c (revision 354037) +++ head/lib/libsecureboot/vets.c (revision 354038) @@ -1,1016 +1,1016 @@ /*- * 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 __FBSDID("$FreeBSD$"); /** * @file vets.c - trust store * @brief verify signatures * * We leverage code from BearSSL www.bearssl.org */ #include #include #define NEED_BRSSL_H #include "libsecureboot-priv.h" #include #include #ifndef TRUST_ANCHOR_STR # define TRUST_ANCHOR_STR ta_PEM #endif #define SECONDS_PER_DAY 86400 #define X509_DAYS_TO_UTC0 719528 int DebugVe = 0; 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; } 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); } /* this is the time we use for verifying certs */ static time_t ve_utc = 0; /** * @brief * set ve_utc used for certificate verification * * @param[in] utc * time - ignored unless greater than current value. */ void ve_utc_set(time_t utc) { if (utc > ve_utc) { DEBUG_PRINTF(2, ("Set ve_utc=%jd\n", (intmax_t)utc)); ve_utc = utc; } } static void free_cert_contents(br_x509_certificate *xc) { xfree(xc->data); } /* * 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' actually care about cert status - just its name */ err = mc.vtable->end_chain(&mc.vtable); if (!cn.status) 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, - char *anchors_name) + 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; 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; 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 */ ve_utc_set(time(NULL)); #ifdef BUILD_UTC ve_utc_set(BUILD_UTC); /* just in case */ #endif ve_error_set(NULL); /* make sure it is empty */ #ifdef VE_PCR_SUPPORT ve_pcr_init(); #endif #ifdef TRUST_ANCHOR_STR 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); } /** * 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 absoultely 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 _STANDALONE /* * 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 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) { ve_error_set("Validation failed, err = %d", err); } 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) { char hexbuf[br_sha512_SIZE * 2 + 2]; unsigned char rhbuf[br_sha512_SIZE]; char *hex; 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 & 1) != 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(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); }