diff --git a/lib/libsecureboot/Makefile.depend.host b/lib/libsecureboot/Makefile.depend.host index c6441c263f4a..f80275d86ab1 100644 --- a/lib/libsecureboot/Makefile.depend.host +++ b/lib/libsecureboot/Makefile.depend.host @@ -1,12 +1,11 @@ # $FreeBSD$ # Autogenerated - do NOT edit! DIRDEPS = \ - lib/libstand \ .include .if ${DEP_RELDIR} == ${_DEP_RELDIR} # local dependencies - needed for -jN in clean tree .endif diff --git a/lib/libsecureboot/h/libsecureboot.h b/lib/libsecureboot/h/libsecureboot.h index 200f8bdb763f..f07988a8206e 100644 --- a/lib/libsecureboot/h/libsecureboot.h +++ b/lib/libsecureboot/h/libsecureboot.h @@ -1,97 +1,98 @@ /*- * 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. */ /* * $FreeBSD$ */ #ifndef _LIBSECUREBOOT_H_ #define _LIBSECUREBOOT_H_ #include #ifdef _STANDALONE #include #else #include #include #include #include #include #include #endif #include unsigned char * read_fd(int, size_t); #ifndef NEED_BRSSL_H unsigned char * read_file(const char *, size_t *); #endif extern int DebugVe; extern int VerifyFlags; #ifndef DEBUG_PRINTF #define DEBUG_PRINTF(n, x) if (DebugVe >= n) printf x #endif int ve_trust_init(void); size_t ve_trust_anchors_add_buf(unsigned char *, size_t); size_t ve_trust_anchors_revoke(unsigned char *, size_t); int ve_trust_add(const char *); void ve_debug_set(int); +void ve_enforce_validity_set(int); void ve_anchor_verbose_set(int); int ve_anchor_verbose_get(void); void ve_utc_set(time_t utc); char *ve_error_get(void); int ve_error_set(const char *, ...) __printflike(1,2); int ve_self_tests(void); void fingerprint_info_add(const char *, const char *, const char *, const char *, struct stat *); char * hexdigest(char *, size_t, unsigned char *, size_t); int verify_fd(int, const char *, off_t, struct stat *); int verify_open(const char *, int); unsigned char *verify_signed(const char *, int); unsigned char *verify_sig(const char *, int); unsigned char *verify_asc(const char *, int); /* OpenPGP */ void ve_pcr_init(void); void ve_pcr_update(const char *, unsigned char *, size_t); ssize_t ve_pcr_get(unsigned char *, size_t); int ve_pcr_updating_get(void); void ve_pcr_updating_set(int); char * ve_pcr_hashed_get(int); /* flags for verify_{asc,sig,signed} */ #define VEF_VERBOSE 1 #define VE_FINGERPRINT_OK 1 #define VE_FINGERPRINT_IGNORE 2 /* errors from verify_fd */ #define VE_FINGERPRINT_NONE -2 #define VE_FINGERPRINT_WRONG -3 #define VE_FINGERPRINT_UNKNOWN -4 /* may not be an error */ #endif /* _LIBSECUREBOOT_H_ */ diff --git a/lib/libsecureboot/vets.c b/lib/libsecureboot/vets.c index b06ab346f716..94475e7cad36 100644 --- a/lib/libsecureboot/vets.c +++ b/lib/libsecureboot/vets.c @@ -1,1129 +1,1143 @@ /*- * 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 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; } } 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, 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 */ #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 - 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; + 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 -#if defined(_STANDALONE) - rc = 0; /* don't fail */ + 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 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 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: ve_error_set("Validation failed, err = %d", err); 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); } diff --git a/sbin/veriexec/veriexec.8 b/sbin/veriexec/veriexec.8 index 161406ae6de2..d191f5175074 100644 --- a/sbin/veriexec/veriexec.8 +++ b/sbin/veriexec/veriexec.8 @@ -1,159 +1,165 @@ .\"- .\" Copyright (c) 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. .\" .\" $FreeBSD$ .\" -.Dd February 14, 2022 +.Dd July 8, 2022 .Dt VERIEXEC 8 .Os .Sh NAME .Nm veriexec .Nd manipulate state of mac_veriexec .Sh SYNOPSIS .Nm .Op Fl v .Op Fl C Ar directory +.Op Fl S .Pa manifest .Nm .Fl z Ar state .Nm .Fl i Ar state .Nm .Fl x .Ar file ... .Sh DESCRIPTION .Nm is a utility to query or manipulate the state of .Xr mac_veriexec 4 . .Pp The first form is for loading a .Pa manifest . .Nm first verifies a digital signature of the .Ar manifest and if successful, parses it and feeds its content to kernel. +The +.Fl S +flag indicates that certificate validity should be checked. +Without this, a valid signature with an expired certificate +will still be accepted. .Pp The second form with .Fl z is used to modify the .Ar state , and with .Fl i to query the current .Ar state . .Pp The final form with .Fl x is used to test whether .Ar file is verified or not. This requires .Xr mac_veriexec 4 to be in the .Ql active or .Ql enforce state. .Pp The possible states are: .Bl -tag -width enforce .It Ar loaded set automatically when first .Pa manifest has been loaded. .It Ar active .Xr mac_veriexec 4 will begin checking files. This state can only be entered from the .Ar loaded state. .It Ar enforce .Xr mac_veriexec 4 will fail attempts to .Xr exec 2 or .Xr open 2 files with .Dv O_VERIFY unless verified. .It Ar locked prevent loading of any more manifests. .El .Pp When setting or querying the state, it is sufficient to provide a unique prefix of the desired state. So .Fl i .Ar a or .Fl z .Ar e are sufficient, but .Fl i .Ar loc is the minimum required to avoid confusion with .Ar loaded . .Sh MANIFESTS The manifest contains a mapping of relative pathnames to fingerprints with optional flags. For example: .Bd -literal -offset indent sbin/veriexec sha256=f22136...c0ff71 no_ptrace usr/bin/python sha256=5944d9...876525 indirect sbin/somedaemon sha256=77fc2f...63f5687 label=mod1/val1,mod2/val2 .Ed The supported flags are: .Bl -tag -width indirect .It Ql indirect the executable cannot be run directly, but can be used as an interpreter for example via: .Bd -literal -offset indent #!/usr/bin/python .Ed .It Ql no_ptrace do not allow running executable under a debugger. Useful for any application critical to the security state of system. .El .Pp The .Ql label argument allows associating a .Xr maclabel 7 with the executable. Neither .Nm nor .Xr mac_veriexec 4 (if it supports labels) pay any attention to the content of the label they are provided for the use of other .Xr mac 4 modules. .Sh HISTORY The Verified Exec system first appeared in .Nx . This utility derrives from the one found in Junos. The key difference is the requirement that manifest files be digitally signed. diff --git a/sbin/veriexec/veriexec.c b/sbin/veriexec/veriexec.c index aff514b1cac5..0162eeda5347 100644 --- a/sbin/veriexec/veriexec.c +++ b/sbin/veriexec/veriexec.c @@ -1,269 +1,273 @@ /*- * Copyright (c) 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$"); #include #include #include #include #include #include #include #include #include #include "veriexec.h" /* Globals that are shared with manifest_parser.c */ int dev_fd = -1; int ForceFlags = 0; int Verbose = 0; int VeriexecVersion = 0; const char *Cdir = NULL; /*! * @brief Print help message describing program's usage * @param void * @return always returns code 0 */ static int veriexec_usage() { printf("%s", "Usage:\tveriexec [-h] [-i state] [-C] [-xv state|verbosity] [path]\n"); return (0); } /*! * @brief Load a veriexec manifest * @param manifest Pointer to the location of the manifest file * @retval the error code returned from the parser */ static int veriexec_load(const char *manifest) { unsigned char *content; int rc; content = verify_signed(manifest, VEF_VERBOSE); if (!content) errx(EX_USAGE, "cannot verify %s", manifest); if (manifest_open(manifest, (const char *)content)) { rc = yyparse(); } else { err(EX_NOINPUT, "cannot load %s", manifest); } free(content); return (rc); } /*! * @brief Get the veriexec state for the supplied argument * @param arg_text String containing the argument to be processed * @retval The veriexec state number for the specified argument */ static uint32_t veriexec_state_query(const char *arg_text) { uint32_t state = 0; unsigned long len; len = strlen(arg_text); if (strncmp(arg_text, "active", len) == 0) state |= VERIEXEC_STATE_ACTIVE; else if (strncmp(arg_text, "enforce", len) == 0) state |= VERIEXEC_STATE_ENFORCE; if (strncmp(arg_text, "loaded", len) == 0) state |= VERIEXEC_STATE_LOADED; if (strncmp(arg_text, "locked", len) == 0) state |= VERIEXEC_STATE_LOCKED; if (state == 0 || __bitcount(state) > 1) errx(EX_USAGE, "Unknown state \'%s\'", arg_text); return (state); } /*! * @brief Get the veriexec command state for the supplied argument * @param arg_text String containing the argument to be processed * @retval The veriexec command state for the specified argument */ static uint32_t veriexec_state_modify(const char *arg_text) { uint32_t state = 0; unsigned long len; len = strlen(arg_text); if (strncmp(arg_text, "active", len) == 0) state = VERIEXEC_ACTIVE; else if (strncmp(arg_text, "enforce", len) == 0) state = VERIEXEC_ENFORCE; else if (strncmp(arg_text, "getstate", len) == 0) state = VERIEXEC_GETSTATE; else if (strncmp(arg_text, "lock", len) == 0) state = VERIEXEC_LOCK; else errx(EX_USAGE, "Unknown command \'%s\'", arg_text); return (state); } int main(int argc, char *argv[]) { long long converted_int; uint32_t state; char c; int x; if (argc < 2) return (veriexec_usage()); dev_fd = open(_PATH_DEV_VERIEXEC, O_WRONLY, 0); - while ((c = getopt(argc, argv, "hC:i:xvz:")) != -1) { + while ((c = getopt(argc, argv, "hC:i:Sxvz:")) != -1) { switch (c) { case 'h': /* Print usage info */ return (veriexec_usage()); case 'C': /* Get the provided directory argument */ Cdir = optarg; break; case 'i': /* Query the current state */ if (dev_fd < 0) { err(EX_UNAVAILABLE, "cannot open veriexec"); } if (ioctl(dev_fd, VERIEXEC_GETSTATE, &x)) { err(EX_UNAVAILABLE, "Cannot get veriexec state"); } state = veriexec_state_query(optarg); exit((x & state) == 0); break; + case 'S': + /* Strictly enforce certificate validity */ + ve_enforce_validity_set(1); + break; case 'v': /* Increase the verbosity */ Verbose++; break; case 'x': /* Check veriexec paths */ /* * -x says all other args are paths to check. */ for (x = EX_OK; optind < argc; optind++) { if (veriexec_check_path(argv[optind])) { warn("%s", argv[optind]); x = 2; } } exit(x); break; case 'z': /* Modify the state */ if (strncmp(optarg, "debug", strlen(optarg)) == 0) { const char *error; if (optind >= argc) errx(EX_USAGE, "Missing mac_veriexec verbosity level \'N\', veriexec -z debug N, where N is \'off\' or the value 0 or greater"); if (strncmp(argv[optind], "off", strlen(argv[optind])) == 0) { state = VERIEXEC_DEBUG_OFF; x = 0; } else { state = VERIEXEC_DEBUG_ON; converted_int = strtonum(argv[optind], 0, INT_MAX, &error); if (error != NULL) errx(EX_USAGE, "Conversion error for argument \'%s\' : %s", argv[optind], error); x = (int) converted_int; if (x == 0) state = VERIEXEC_DEBUG_OFF; } } else state = veriexec_state_modify(optarg); if (dev_fd < 0) err(EX_UNAVAILABLE, "Cannot open veriexec"); if (ioctl(dev_fd, state, &x)) err(EX_UNAVAILABLE, "Cannot %s veriexec", optarg); if (state == VERIEXEC_DEBUG_ON || state == VERIEXEC_DEBUG_OFF) printf("mac_veriexec debug verbosity level: %d\n", x); else if (state == VERIEXEC_GETSTATE) printf("Veriexec state (octal) : %#o\n", x); exit(EX_OK); break; default: /* Missing argument, print usage info.*/ veriexec_usage(); exit(EX_USAGE); break; } } if (Verbose) printf("Verbosity level : %d\n", Verbose); if (dev_fd < 0) err(EX_UNAVAILABLE, "Cannot open veriexec"); openlog(getprogname(), LOG_PID, LOG_AUTH); if (ve_trust_init() < 1) errx(EX_OSFILE, "cannot initialize trust store"); #ifdef VERIEXEC_GETVERSION if (ioctl(dev_fd, VERIEXEC_GETVERSION, &VeriexecVersion)) { VeriexecVersion = 0; /* unknown */ } #endif for (; optind < argc; optind++) { if (veriexec_load(argv[optind])) { err(EX_DATAERR, "cannot load %s", argv[optind]); } } exit(EX_OK); }