Index: lib/libsecureboot/Makefile.inc =================================================================== --- lib/libsecureboot/Makefile.inc +++ lib/libsecureboot/Makefile.inc @@ -80,6 +80,9 @@ # this is what we use as our trust anchor CFLAGS+= -I. -DTRUST_ANCHOR_STR=ta_PEM +# Forbidden anchors +CFLAGS+= -I. -DFORBIDDEN_ANCHORS_STR=fa_PEM + .if ${VE_SELF_TESTS} != "no" XCFLAGS.vets+= -DVERIFY_CERTS_STR=vc_PEM .endif @@ -126,7 +129,8 @@ # you can of course set BUILD_UTC to any value you like BUILD_UTC?= ${${STAT:Ustat} -f %m ${BUILD_UTC_FILE}:L:sh} -# Generate ta.h containing one or more PEM encoded trust anchors in ta_PEM. +# Generate ta.h and fa.h containing one or more PEM encoded trust anchors in +# ta_PEM and forbidden anchors in fa_PEM. # # If we are doing self-tests, we define another arrary vc_PEM # containing certificates that we can verify for each trust anchor. @@ -135,9 +139,10 @@ # using each supported hash method # to use as a Known Answer Test (needed for FIPS 140-2) # +FA_PEM_LIST ?= ${.ALLSRC:N*crl*:Mf*.pem} TA_PEM_LIST ?= ${.ALLSRC:N*crl*:Mt*.pem} VC_PEM_LIST ?= ${.ALLSRC:N*crl*:Mv*.pem} -vets.o vets.po vets.pico: ta.h +vets.o vets.po vets.pico: ta.h fa.h ta.h: @( echo '/* Autogenerated - DO NOT EDIT!!! */'; echo; \ cat ${TA_PEM_LIST:O:u} /dev/null | \ @@ -148,6 +153,11 @@ file2c -sx 'static const char vc_PEM[] = {' '};'; echo ) >> ${.TARGET} .endif echo '#define BUILD_UTC ${BUILD_UTC}' >> ${.TARGET} ${.OODATE:MNOMETA_CMP} +fa.h: + @( echo '/* Autogenerated - DO NOT EDIT!!! */'; \ + echo; \ + cat ${FA_PEM_LIST:O:u} /dev/null | file2c -sx 'static const char fa_PEM[] = {' '};'; \ + ) > ${.TARGET} # This header records our preference for signature extensions. vesigned.o vesigned.po vesigned.pico: vse.h Index: lib/libsecureboot/local.trust.mk =================================================================== --- lib/libsecureboot/local.trust.mk +++ lib/libsecureboot/local.trust.mk @@ -109,7 +109,7 @@ TRUST_ANCHORS!= cd ${.CURDIR} && 'ls' -1 *.pem t*.asc 2> /dev/null .endif .if empty(TRUST_ANCHORS) && ${MK_LOADER_EFI_SECUREBOOT} != "yes" -.error Need TRUST_ANCHORS see ${.CURDIR}/README.rst +.warning May need TRUST_ANCHORS see ${.CURDIR}/README.rst .endif .if ${TRUST_ANCHORS:T:Mt*.pem} != "" ta.h: ${TRUST_ANCHORS:M*.pem} @@ -119,6 +119,15 @@ VE_SIGNATURE_EXT_LIST+= asc ta_asc.h: ${TRUST_ANCHORS:M*.asc} .endif + +# Forbidden anchors are found in: f*.pem +.if empty(FORBIDDEN_ANCHORS) +FORBIDDEN_ANCHORS!= cd ${.CURDIR} && 'ls' -1 f*.pem 2> /dev/null +.endif +.if ${FORBIDDEN_ANCHORS:T:Mf*.pem} != "" +fa.h: ${FORBIDDEN_ANCHORS:Mf*.pem} +.endif + # we take the mtime of this as our baseline time BUILD_UTC_FILE?= ${TRUST_ANCHORS:[1]} .endif Index: lib/libsecureboot/vets.c =================================================================== --- lib/libsecureboot/vets.c +++ lib/libsecureboot/vets.c @@ -37,11 +37,15 @@ #define NEED_BRSSL_H #include "libsecureboot-priv.h" #include +#include #include #ifndef TRUST_ANCHOR_STR # define TRUST_ANCHOR_STR ta_PEM #endif +#ifndef FORBIDDEN_ANCHORS_STR +# define FORBIDDEN_ANCHORS_STR fa_PEM +#endif #define SECONDS_PER_DAY 86400 #define SECONDS_PER_YEAR 365 * SECONDS_PER_DAY @@ -300,6 +304,9 @@ br_x509_certificate *xcs; size_t num; + if (len == 0) + return (0); + num = 0; xcs = parse_certificates(buf, len, &num); if (xcs != NULL) { @@ -366,6 +373,10 @@ sizeof(TRUST_ANCHOR_STR)); #endif once = (int) VEC_LEN(trust_anchors); +#ifdef FORBIDDEN_ANCHORS_STR + ve_trust_anchors_revoke(__DECONST(unsigned char *, FORBIDDEN_ANCHORS_STR), + sizeof(FORBIDDEN_ANCHORS_STR)); +#endif #ifdef VE_OPENPGP_SUPPORT once += openpgp_trust_init(); #endif Index: sbin/veriexec/Makefile =================================================================== --- sbin/veriexec/Makefile +++ sbin/veriexec/Makefile @@ -9,6 +9,9 @@ LIBADD+= veriexec secureboot bearssl +# Packed anchors support +LIBADD+= archive lzma + NO_SHARED= .include Index: sbin/veriexec/veriexec.c =================================================================== --- sbin/veriexec/veriexec.c +++ sbin/veriexec/veriexec.c @@ -25,14 +25,20 @@ #include __FBSDID("$FreeBSD$"); -#include -#include -#include -#include +#include +#include +#include #include -#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include #include "veriexec.h" @@ -43,6 +49,192 @@ const char *Cdir = NULL; +/* Packed Anchors: a TAR archive compressed with xz and containing two PEM files: trust.pem and forbidden.pem. */ +#define DEFAULT_PACKED_ANCHORS_PATH "/etc/veriexec/anchors.txz" +#define TRUST_ANCHORS_FILENAME "trust.pem" +#define FORBIDDEN_ANCHORS_FILENAME "forbidden.pem" + +static struct archive * +open_packed_anchors(int fd) +{ + struct archive *a; + int ares; + + a = archive_read_new(); + if (a == NULL) { + warnx("archive_read_new: %s", archive_error_string(a)); + return NULL; + } + + ares = archive_read_support_format_tar(a); + if (ares == ARCHIVE_FATAL) { + warnx("archive_read_support_format_tar: %s", archive_error_string(a)); + goto fail; + } + + ares = archive_read_support_filter_xz(a); + if (ares == ARCHIVE_FATAL) { + warnx("archive_read_support_filter_xz: %s", archive_error_string(a)); + goto fail; + } + + ares = archive_read_open_fd(a, fd, 2048 /* block_size */); + if (ares == ARCHIVE_FATAL) { + warnx("archive_read_open_fd: %s", archive_error_string(a)); + goto fail; + } + + return a; + + fail: + ares = archive_read_free(a); + if (ares != ARCHIVE_OK) + warnx("archive_read_free: %s", archive_error_string(a)); + return NULL; +} + +static void +close_packed_anchors(struct archive *a) +{ + if (archive_read_free(a) != ARCHIVE_OK) + warnx("archive_read_free: %s", archive_error_string(a)); +} + +/* Read a bunch of PEMs from the current archive entry and add them to the corresponding store. + * If trust is true, certificates are added to the trust anchors. Else, they're added to the forbidden anchors. + * If provided count is set to the number of certificate successfully added to the store. + */ +static bool +load_pem(struct archive *a, int64_t pem_size, bool trust, int *count) +{ + la_ssize_t data_size; + int ares, c; + unsigned char *pem = NULL; + + pem = malloc(pem_size); + if (pem == NULL) { + warn("malloc"); + return false; + } + data_size = archive_read_data(a, pem, pem_size); + switch (data_size) { + case ARCHIVE_FATAL: warnx("archive_read_data: %s", archive_error_string(a)); free(pem); return false; + case ARCHIVE_RETRY: warnx("archive_read_data: unexpected retry request"); free(pem); return false; + case ARCHIVE_WARN: warnx("archive_read_data: %s", archive_error_string(a)); break; + } + assert(data_size == pem_size); + + if (trust) + c = ve_trust_anchors_add_buf(pem, pem_size); + else + c = ve_trust_anchors_revoke(pem, pem_size); + free(pem); + if ( c <= 0 ) { + warnx("failed to load %s anchors", (trust ? "trust" : "forbidden")); + return false; + } + if (count != NULL) + *count = c; + return true; +} + +static bool +load_packed_anchors(const char *path, bool fail_if_missing) +{ + struct archive *a = NULL; + int ares; + int fd = -1; + int trust_count = -1; + int forbidden_count = -1; + + if (Verbose >= 1) + warnx("anchors: pack: %s", path); + fd = open(path, O_RDONLY | O_VERIFY); + if (fd == -1) { + if (errno == ENOENT && !fail_if_missing) + return true; + + warn("open: %s", path); + goto fail; + } + a = open_packed_anchors(fd); + if (a == NULL) + goto fail; + + while (trust_count < 1 || forbidden_count < 0) { /* Look for trust.pem and forbidden.pem. */ + struct archive_entry *ent; + const char *pathname; + unsigned char *pem; + int64_t pem_size; + la_ssize_t data_size; + + ares = archive_read_next_header(a, &ent); + if (ares == ARCHIVE_EOF) + break; + switch (ares) { + case ARCHIVE_FATAL: warnx("archive_read_next_header: %s", archive_error_string(a)); goto fail; + case ARCHIVE_RETRY: warnx("archive_read_next_header: must retry"); continue; + case ARCHIVE_WARN: warnx("archive_read_next_header: %s", archive_error_string(a)); break; + } + + /* Ignore entry if: + * - not one of the expected filenames, + * - the corresponding file was already found, + * - not a regular file, + * - or TRUST_ANCHORS_FILENAME's file size is 0. + */ + pem_size = archive_entry_size(ent); + pathname = archive_entry_pathname(ent); /* FIXME Handle encoding? */ + if (pathname == NULL) { + warnx("archive_entry_pathname: error"); + goto fail; + } else if (archive_entry_filetype(ent) != AE_IFREG) { + warnx("unexpected file type: %s", pathname); + continue; + } else if ((strcmp(pathname, TRUST_ANCHORS_FILENAME) != 0 || trust_count >= 1) && + (strcmp(pathname, FORBIDDEN_ANCHORS_FILENAME) != 0 || forbidden_count >= 0)) { + warnx("unexpected/redundant file (will be discarded): %s", pathname); + continue; + } else if (pem_size == 0) { + if (strcmp(pathname, FORBIDDEN_ANCHORS_FILENAME) == 0) { /* The empty list of forbidden anchors is valid. */ + forbidden_count = 0; + if (Verbose >= 1) + warnx("empty forbidden anchors file: %s", pathname); + } + else + warnx("unexpected empty file: %s", pathname); + continue; + } + assert(pem_size >= 1); + + /* Dispatch PEMs to the corresponding store. */ + if (strcmp(pathname, TRUST_ANCHORS_FILENAME) == 0) { + if (!load_pem(a, pem_size, true /* trust */, &trust_count)) + goto fail; + } else { /* pathname == FORBIDDEN_ANCHORS_FILENAME */ + if (!load_pem(a, pem_size, false /* trust */, &forbidden_count)) + goto fail; + } + } + if (Verbose >= 1) { + if (trust_count == -1) + warnx("anchors: missing trust anchors"); + else + warnx("anchors: %d trust anchors", trust_count); + if (forbidden_count == -1) + warnx("anchors: missing forbidden anchors"); + else + warnx("anchors: %d forbidden anchors", forbidden_count); + } + + fail: + if (a != NULL) + close_packed_anchors(a); + if (fd != -1) + close(fd); + return (trust_count >= 1 && forbidden_count >= 0); +} + static int veriexec_load(const char *manifest) { @@ -67,11 +259,17 @@ unsigned long ctl; int c; int x; + const char *pa_path = DEFAULT_PACKED_ANCHORS_PATH; + bool explicit_anchor_path = false; dev_fd = open(_PATH_DEV_VERIEXEC, O_WRONLY, 0); - while ((c = getopt(argc, argv, "C:i:xvz:")) != -1) { + while ((c = getopt(argc, argv, "a:C:i:xvz:")) != -1) { switch (c) { + case 'a': + pa_path = optarg; + explicit_anchor_path = true; + break; case 'C': Cdir = optarg; break; @@ -160,6 +358,8 @@ } } openlog(getprogname(), LOG_PID, LOG_AUTH); + if (!load_packed_anchors(pa_path, explicit_anchor_path)) + errx(EX_OSFILE, "cannot load packed anchors"); if (ve_trust_init() < 1) errx(EX_OSFILE, "cannot initialize trust store"); #ifdef VERIEXEC_GETVERSION