diff --git a/lib/libsecureboot/h/libsecureboot.h b/lib/libsecureboot/h/libsecureboot.h index 79b5cc46ee97..200f8bdb763f 100644 --- a/lib/libsecureboot/h/libsecureboot.h +++ b/lib/libsecureboot/h/libsecureboot.h @@ -1,94 +1,97 @@ /*- * 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_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/h/verify_file.h b/lib/libsecureboot/h/verify_file.h index 844b8266f42c..88d758b27af4 100644 --- a/lib/libsecureboot/h/verify_file.h +++ b/lib/libsecureboot/h/verify_file.h @@ -1,59 +1,66 @@ /*- * 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 _VERIFY_FILE_H_ #define _VERIFY_FILE_H_ #define VE_GUESS -1 /* let verify_file work it out */ #define VE_TRY 0 /* we don't mind if unverified */ #define VE_WANT 1 /* we want this verified */ #define VE_MUST 2 /* this must be verified */ #define VE_NOT_CHECKED -42 #define VE_VERIFIED 1 /* all good */ #define VE_UNVERIFIED_OK 0 /* not verified but that's ok */ #define VE_NOT_VERIFYING 2 /* we are not verifying */ +/* suitable buf size for hash_string */ +#ifndef SHA_DIGEST_LENGTH +# define SHA_DIGEST_LENGTH 20 +#endif + struct stat; int verify_prep(int, const char *, off_t, struct stat *, const char *); void ve_debug_set(int); char *ve_error_get(void); void ve_efi_init(void); int ve_status_get(int); int load_manifest(const char *, const char *, const char *, struct stat *); int pass_manifest(const char *, const char *); int pass_manifest_export_envs(void); +void verify_report(const char *, int, int, struct stat *); int verify_file(int, const char *, off_t, int, const char *); void verify_pcr_export(void); +int hash_string(char *s, size_t n, char *buf, size_t bufsz); +int is_verified(struct stat *); +void add_verify_status(struct stat *, int); struct vectx; struct vectx* vectx_open(int, const char *, off_t, struct stat *, int *, const char *); ssize_t vectx_read(struct vectx *, void *, size_t); off_t vectx_lseek(struct vectx *, off_t, int); int vectx_close(struct vectx *, int, const char *); #endif /* _VERIFY_FILE_H_ */ diff --git a/lib/libsecureboot/openpgp/opgp_sig.c b/lib/libsecureboot/openpgp/opgp_sig.c index c16d8974e8a3..fcf4a708c4c3 100644 --- a/lib/libsecureboot/openpgp/opgp_sig.c +++ b/lib/libsecureboot/openpgp/opgp_sig.c @@ -1,483 +1,483 @@ /*- * 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. */ /* * RCSid: * from: signer.c,v 1.10 2018/03/23 01:14:30 sjg * * @(#) Copyright (c) 2012 Simon J. Gerraty * * This file is provided in the hope that it will * be of use. There is absolutely NO WARRANTY. * Permission to copy, redistribute or otherwise * use this file is hereby granted provided that * the above copyright notice and this notice are * left intact. * * Please send copies of changes and bug-fixes to: * sjg@crufty.net */ #include __FBSDID("$FreeBSD$"); #include "../libsecureboot-priv.h" #ifdef _STANDALONE #define warnx printf #else #include #include #include #include #include #include #include #include #endif #include "decode.h" #include "packet.h" #ifdef USE_BEARSSL #define get_error_string ve_error_get void initialize (void) { openpgp_trust_init(); } #else #include /** * @brief intialize OpenSSL */ void initialize(void) { static int once; if (once) return); once = 1; //CRYPTO_malloc_init(); ERR_load_crypto_strings(); OpenSSL_add_all_algorithms(); } /** * @brief * last error from OpenSSL as a string */ char * get_error_string(void) { initialize(); return (ERR_error_string(ERR_get_error(), NULL)); } #endif /** * @brief decode a signature packet * * We only support RSA * * @sa rfc4880:5.2 */ ssize_t decode_sig(int tag, unsigned char **pptr, size_t len, OpenPGP_sig *sig) { unsigned char *ptr; unsigned char *pgpbytes; unsigned char *sp; int version; int hcount = 0; int ucount = 0; int stag = 0; int n; n = tag; /* avoid unused */ /* * We need to keep a reference to the packet bytes * as these form part of the signature data. * * @sa rfc4880:5.2.4 */ pgpbytes = ptr = *pptr; version = *ptr++; if (version == 3) { ptr++; sig->pgpbytes = malloc(5); if (!sig->pgpbytes) return (-1); memcpy(sig->pgpbytes, ptr, 5); sig->pgpbytes_len = 5; sig->sig_type = *ptr++; ptr += 4; sig->key_id = octets2hex(ptr, 8); ptr += 8; sig->sig_alg = *ptr++; sig->hash_alg = *ptr++; } else if (version == 4) { sig->sig_type = *ptr++; sig->sig_alg = *ptr++; sig->hash_alg = *ptr++; hcount = octets2i(ptr, 2); ptr += 2; sig->pgpbytes_len = (size_t)hcount + 6; sig->pgpbytes = malloc(sig->pgpbytes_len + 6); if (!sig->pgpbytes) return (-1); memcpy(sig->pgpbytes, pgpbytes, sig->pgpbytes_len); sp = &sig->pgpbytes[sig->pgpbytes_len]; *sp++ = 4; *sp++ = 255; memcpy(sp, i2octets(4, (int)sig->pgpbytes_len), 4); sig->pgpbytes_len += 6; while (hcount > 0) { sp = decode_subpacket(&ptr, &stag, &n); hcount -= n; /* can check stag to see if we care */ } ucount = octets2i(ptr, 2); ptr += 2; while (ucount > 0) { sp = decode_subpacket(&ptr, &stag, &n); ucount -= n; /* can check stag to see if we care */ if (stag == 16) { free(sig->key_id); sig->key_id = octets2hex(sp, 8); } } } else return (-1); ptr += 2; /* skip hash16 */ if (sig->sig_alg == 1) { /* RSA */ sig->sig = decode_mpi(&ptr, &sig->sig_len); } /* we are done */ return ((ssize_t)len); } /** * @brief map OpenPGP hash algorithm id's to name * * @sa rfc4880:9.4 */ static struct hash_alg_map { int halg; const char *hname; } hash_algs[] = { {1, "md5"}, {2, "sha1"}, {8, "sha256"}, {9, "sha384"}, {10, "sha512"}, {11, "sha224"}, {0, NULL}, }; static const char * get_hname(int hash_alg) { struct hash_alg_map *hmp; for (hmp = hash_algs; hmp->halg > 0; hmp++) { if (hmp->halg == hash_alg) return (hmp->hname); } return (NULL); } /* lifted from signer.c */ /** * @brief verify a digest * * The public key, digest name, file and signature data. * * @return 1 on success 0 on failure, -1 on error */ #ifndef USE_BEARSSL static int verify_digest (EVP_PKEY *pkey, const char *digest, unsigned char *mdata, size_t mlen, unsigned char *sdata, size_t slen) { EVP_MD_CTX ctx; const EVP_MD *md = NULL; EVP_PKEY_CTX *pctx = NULL; int rc = 0; int i = -1; initialize(); md = EVP_get_digestbyname(digest); EVP_DigestInit(&ctx, md); pctx = EVP_PKEY_CTX_new(pkey, NULL); if (!pctx) goto fail; if (EVP_PKEY_verify_init(pctx) <= 0) goto fail; if (EVP_PKEY_CTX_set_signature_md(pctx, ctx.digest) <= 0) goto fail; i = EVP_PKEY_verify(pctx, sdata, slen, mdata, mlen); if (i >= 0) rc = i; fail: EVP_PKEY_CTX_free(pctx); return (rc); } #endif /** * @brief verify OpenPGP signed file * * * @param[in] filename * used to determine the signature name * * @param[in] fdata * content of filename * * @param[in] fbytes * of fdata * * @param[in] sdata * content of signature * * @param[in] sbytes * of sdata * * @param[in] flags * * @return 0 on success */ int openpgp_verify(const char *filename, unsigned char *fdata, size_t fbytes, unsigned char *sdata, size_t sbytes, int flags) { OpenPGP_key *key; OpenPGP_sig *sig; #ifdef USE_BEARSSL const br_hash_class *md; br_hash_compat_context mctx; const unsigned char *hash_oid; #else const EVP_MD *md = NULL; EVP_MD_CTX mctx; #endif unsigned char mdata[64]; unsigned char *ptr; unsigned char *ddata = NULL; const char *hname; size_t mlen; int rc = -1; initialize(); sig = NEW(OpenPGP_sig); if (!sdata || !sig) { warnx("cannot verify %s", filename); goto oops; } if (!(sdata[0] & OPENPGP_TAG_ISTAG)) sdata = ddata = dearmor((char *)sdata, sbytes, &sbytes); ptr = sdata; rc = decode_packet(2, &ptr, sbytes, (decoder_t)decode_sig, sig); DEBUG_PRINTF(2, ("rc=%d keyID=%s\n", rc, sig->key_id ? sig->key_id : "?")); if (rc == 0 && sig->key_id) { key = load_key_id(sig->key_id); if (!key) { warnx("cannot find key-id: %s", sig->key_id); rc = -1; } else if (!(hname = get_hname(sig->hash_alg))) { warnx("unsupported hash algorithm: %d", sig->hash_alg); rc = -1; } else { /* * Hash fdata according to the OpenPGP recipe * * @sa rfc4880:5.2.4 */ #ifdef USE_BEARSSL switch (sig->hash_alg) { /* see hash_algs above */ case 2: /* sha1 */ md = &br_sha1_vtable; mlen = br_sha1_SIZE; hash_oid = BR_HASH_OID_SHA1; break; case 8: /* sha256 */ md = &br_sha256_vtable; mlen = br_sha256_SIZE; hash_oid = BR_HASH_OID_SHA256; break; default: warnx("unsupported hash algorithm: %s", hname); goto oops; } md->init(&mctx.vtable); md->update(&mctx.vtable, fdata, fbytes); md->update(&mctx.vtable, sig->pgpbytes, sig->pgpbytes_len); md->out(&mctx.vtable, mdata); rc = verify_rsa_digest(key->key, hash_oid, mdata, mlen, sig->sig, sig->sig_len); #else md = EVP_get_digestbyname(hname); EVP_DigestInit(&mctx, md); EVP_DigestUpdate(&mctx, fdata, fbytes); EVP_DigestUpdate(&mctx, sig->pgpbytes, sig->pgpbytes_len); mlen = sizeof(mdata); EVP_DigestFinal(&mctx,mdata,(unsigned int *)&mlen); rc = verify_digest(key->key, hname, mdata, mlen, sig->sig, sig->sig_len); #endif if (rc > 0) { - if ((flags & 1)) + if ((flags & VEF_VERBOSE)) printf("Verified %s signed by %s\n", filename, key->user ? key->user->name : "someone"); rc = 0; /* success */ } else if (rc == 0) { printf("Unverified %s: %s\n", filename, get_error_string()); rc = 1; } else { printf("Unverified %s\n", filename); } } } else { warnx("cannot decode signature for %s", filename); rc = -1; } oops: free(ddata); free(sig); return (rc); } #ifndef _STANDALONE /** * @brief list of extensions we handle * * ".asc" is preferred as it works seamlessly with openpgp */ static const char *sig_exts[] = { ".asc", ".pgp", ".psig", NULL, }; /** * @brief verify OpenPGP signed file * * * @param[in] filename * used to determine the signature name * * @param[in] fdata * content of filename * * @param[in] nbytes * of fdata * * @return */ int openpgp_verify_file(const char *filename, unsigned char *fdata, size_t nbytes) { char pbuf[MAXPATHLEN]; unsigned char *sdata; const char *sname = NULL; const char **ep; size_t sz; int n; for (ep = sig_exts; *ep; ep++) { n = snprintf(pbuf, sizeof(pbuf), "%s%s", filename, *ep); if (n >= (int)sizeof(pbuf)) { warnx("cannot form signature name for %s", filename); return (-1); } if (access(pbuf, R_OK) == 0) { sname = pbuf; break; } } if (!sname) { warnx("cannot find signature for %s", filename); return (-1); } sdata = read_file(sname, &sz); - return (openpgp_verify(filename, fdata, nbytes, sdata, sz, 1)); + return (openpgp_verify(filename, fdata, nbytes, sdata, sz, VerifyFlags)); } #endif /** * @brief verify OpenPGP signature * * @return content of signed file */ unsigned char * verify_asc(const char *sigfile, int flags) { char pbuf[MAXPATHLEN]; char *cp; size_t n; unsigned char *fdata, *sdata; size_t fbytes, sbytes; if ((sdata = read_file(sigfile, &sbytes))) { n = strlcpy(pbuf, sigfile, sizeof(pbuf)); if ((cp = strrchr(pbuf, '.'))) *cp = '\0'; if ((fdata = read_file(pbuf, &fbytes))) { if (openpgp_verify(pbuf, fdata, fbytes, sdata, sbytes, flags)) { free(fdata); fdata = NULL; } } } else fdata = NULL; free(sdata); return (fdata); } diff --git a/lib/libsecureboot/readfile.c b/lib/libsecureboot/readfile.c index ff9f9f6f6d60..550c9419c159 100644 --- a/lib/libsecureboot/readfile.c +++ b/lib/libsecureboot/readfile.c @@ -1,72 +1,82 @@ /*- * 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$"); #include unsigned char * read_fd(int fd, size_t len) { int m, n, x; unsigned char *buf; buf = malloc(len + 1); - for (x = 0, m = len; m > 0; ) { - n = read(fd, &buf[x], m); - if (n < 0) - break; - if (n > 0) { - m -= n; - x += n; + if (buf != NULL) { + for (x = 0, m = len; m > 0; ) { + n = read(fd, &buf[x], m); + if (n < 0) + break; + if (n > 0) { + m -= n; + x += n; + } } + if (m == 0) { + buf[len] = '\0'; + return (buf); + } + free(buf); } - if (m == 0) { - buf[len] = '\0'; - return (buf); - } - free(buf); return (NULL); } unsigned char * read_file(const char *path, size_t *len) { struct stat st; unsigned char *ucp; int fd; if (len) *len = 0; if ((fd = open(path, O_RDONLY)) < 0) return (NULL); fstat(fd, &st); ucp = read_fd(fd, st.st_size); close(fd); - if (len != NULL && ucp != NULL) - *len = st.st_size; + if (ucp != NULL) { + if (len != NULL) + *len = st.st_size; + } +#ifdef _STANDALONE + else + printf("%s: out of memory! %lu\n", __func__, + (unsigned long)len); +#endif + return (ucp); } diff --git a/lib/libsecureboot/tests/tvo.c b/lib/libsecureboot/tests/tvo.c index 879d87f128f1..5ea471faa0ba 100644 --- a/lib/libsecureboot/tests/tvo.c +++ b/lib/libsecureboot/tests/tvo.c @@ -1,198 +1,215 @@ /* * 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$"); #include "../libsecureboot-priv.h" #include #include #include +/* keep clang quiet */ +extern char *Destdir; +extern size_t DestdirLen; +extern char *Skip; +extern time_t ve_utc; + size_t DestdirLen; char *Destdir; char *Skip; int main(int argc, char *argv[]) { int n; int fd; int c; int Vflag; + int vflag; char *cp; char *prefix; - char *destdir; Destdir = NULL; DestdirLen = 0; prefix = NULL; Skip = NULL; n = ve_trust_init(); - printf("Trust %d\n", n); Vflag = 0; + vflag = 0; - while ((c = getopt(argc, argv, "D:dp:s:T:V")) != -1) { + while ((c = getopt(argc, argv, "D:dp:s:T:u:Vv")) != -1) { switch (c) { case 'D': Destdir = optarg; DestdirLen = strlen(optarg); break; case 'd': DebugVe++; break; case 'p': prefix = optarg; break; case 's': Skip = optarg; break; case 'T': n = ve_trust_add(optarg); printf("Local trust %s: %d\n", optarg, n); break; case 'V': Vflag = 1; break; + case 'v': + vflag = 1; + break; + case 'u': + ve_utc = (time_t)atoi(optarg); + break; default: errx(1, "unknown option: -%c", c); break; } } + if (!vflag) { + printf("Trust %d\n", n); #ifdef VE_PCR_SUPPORT - ve_pcr_updating_set(1); + ve_pcr_updating_set(1); #endif - ve_self_tests(); - + ve_self_tests(); + } for ( ; optind < argc; optind++) { if (Vflag) { /* * Simulate what loader does. * verify_file should "just work" */ fd = open(argv[optind], O_RDONLY); if (fd > 0) { /* * See if verify_file is happy */ int x; x = verify_file(fd, argv[optind], 0, VE_GUESS, __func__); printf("verify_file(%s) = %d\n", argv[optind], x); close(fd); } continue; } #ifdef VE_OPENPGP_SUPPORT if (strstr(argv[optind], "asc")) { cp = (char *)verify_asc(argv[optind], 1); if (cp) { printf("Verified: %s: %.28s...\n", argv[optind], cp); - fingerprint_info_add(argv[optind], - prefix, Skip, cp, NULL); + if (!vflag) + fingerprint_info_add(argv[optind], + prefix, Skip, cp, NULL); } else { fprintf(stderr, "%s: %s\n", argv[optind], ve_error_get()); } } else #endif if (strstr(argv[optind], "sig")) { cp = (char *)verify_sig(argv[optind], 1); if (cp) { printf("Verified: %s: %.28s...\n", argv[optind], cp); - fingerprint_info_add(argv[optind], - prefix, Skip, cp, NULL); + if (!vflag) + fingerprint_info_add(argv[optind], + prefix, Skip, cp, NULL); } else { fprintf(stderr, "%s: %s\n", argv[optind], ve_error_get()); } } else if (strstr(argv[optind], "manifest")) { cp = (char *)read_file(argv[optind], NULL); if (cp) { fingerprint_info_add(argv[optind], prefix, Skip, cp, NULL); } } else { fd = verify_open(argv[optind], O_RDONLY); printf("verify_open(%s) = %d %s\n", argv[optind], fd, (fd < 0) ? ve_error_get() : ""); if (fd > 0) { /* * Check that vectx_* can also verify the file. */ void *vp; char buf[BUFSIZ]; struct stat st; int error; - size_t off, n; + off_t off; + size_t nb; fstat(fd, &st); lseek(fd, 0, SEEK_SET); off = st.st_size % 512; vp = vectx_open(fd, argv[optind], off, &st, &error, __func__); if (!vp) { printf("vectx_open(%s) failed: %d %s\n", argv[optind], error, ve_error_get()); } else { off = vectx_lseek(vp, (st.st_size % 1024), SEEK_SET); /* we can seek backwards! */ off = vectx_lseek(vp, off/2, SEEK_SET); if (off < st.st_size) { - n = vectx_read(vp, buf, + nb = vectx_read(vp, buf, sizeof(buf)); - if (n > 0) - off += n; + if (nb > 0) + off += nb; } off = vectx_lseek(vp, 0, SEEK_END); /* repeating that should be harmless */ off = vectx_lseek(vp, 0, SEEK_END); error = vectx_close(vp, VE_MUST, __func__); if (error) { printf("vectx_close(%s) == %d %s\n", argv[optind], error, ve_error_get()); } else { printf("vectx_close: Verified: %s\n", argv[optind]); } } close(fd); } } } #ifdef VE_PCR_SUPPORT verify_pcr_export(); printf("pcr=%s\n", getenv("loader.ve.pcr")); #endif return (0); } diff --git a/lib/libsecureboot/vectx.c b/lib/libsecureboot/vectx.c index 3e5277d935f0..45a15ddece4b 100644 --- a/lib/libsecureboot/vectx.c +++ b/lib/libsecureboot/vectx.c @@ -1,372 +1,406 @@ /*- * 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$"); #ifndef _STANDALONE /* Avoid unwanted userlandish components */ #define _KERNEL #include #undef _KERNEL #endif +#ifdef VECTX_DEBUG +static int vectx_debug = VECTX_DEBUG; +# define DEBUG_PRINTF(n, x) if (vectx_debug >= n) printf x +#endif + #include "libsecureboot-priv.h" #include /** * @file vectx.c * @brief api to verify file while reading * * This API allows the hash of a file to be computed as it is read. * Key to this is seeking by reading. * * On close an indication of the verification result is returned. */ struct vectx { br_hash_compat_context vec_ctx; /* hash ctx */ const br_hash_class *vec_md; /* hash method */ const char *vec_path; /* path we are verifying */ const char *vec_want; /* hash value we want */ off_t vec_off; /* current offset */ off_t vec_hashed; /* where we have hashed to */ - size_t vec_size; /* size of path */ + off_t vec_size; /* size of path */ size_t vec_hashsz; /* size of hash */ int vec_fd; /* file descriptor */ int vec_status; /* verification status */ + int vec_closing; /* we are closing */ }; /** * @brief * verify an open file as we read it * * If the file has no fingerprint to match, we will still return a * verification context containing little more than the file * descriptor, and an error code in @c error. * * @param[in] fd * open descriptor * * @param[in] path * pathname to open * * @param[in] off * current offset * * @param[in] stp * pointer to struct stat * * @param[out] error * @li 0 all is good * @li ENOMEM out of memory * @li VE_FINGERPRINT_NONE no entry found * @li VE_FINGERPRINT_UNKNOWN no fingerprint in entry * * @return ctx or NULL on error. * NULL is only returned for non-files or out-of-memory. */ struct vectx * vectx_open(int fd, const char *path, off_t off, struct stat *stp, int *error, const char *caller) { struct vectx *ctx; struct stat st; size_t hashsz; char *cp; int rc; if (!stp) stp = &st; rc = verify_prep(fd, path, off, stp, __func__); DEBUG_PRINTF(2, ("vectx_open: caller=%s,fd=%d,name='%s',prep_rc=%d\n", caller, fd, path, rc)); switch (rc) { case VE_FINGERPRINT_NONE: case VE_FINGERPRINT_UNKNOWN: case VE_FINGERPRINT_WRONG: *error = rc; return (NULL); } ctx = malloc(sizeof(struct vectx)); if (!ctx) goto enomem; ctx->vec_fd = fd; ctx->vec_path = path; ctx->vec_size = stp->st_size; ctx->vec_off = 0; ctx->vec_hashed = 0; ctx->vec_want = NULL; ctx->vec_status = 0; ctx->vec_hashsz = hashsz = 0; + ctx->vec_closing = 0; if (rc == 0) { /* we are not verifying this */ *error = 0; return (ctx); } cp = fingerprint_info_lookup(fd, path); if (!cp) { ctx->vec_status = VE_FINGERPRINT_NONE; ve_error_set("%s: no entry", path); } else { if (strncmp(cp, "no_hash", 7) == 0) { ctx->vec_status = VE_FINGERPRINT_IGNORE; hashsz = 0; } else if (strncmp(cp, "sha256=", 7) == 0) { ctx->vec_md = &br_sha256_vtable; hashsz = br_sha256_SIZE; cp += 7; #ifdef VE_SHA1_SUPPORT } else if (strncmp(cp, "sha1=", 5) == 0) { ctx->vec_md = &br_sha1_vtable; hashsz = br_sha1_SIZE; cp += 5; #endif #ifdef VE_SHA384_SUPPORT } else if (strncmp(cp, "sha384=", 7) == 0) { ctx->vec_md = &br_sha384_vtable; hashsz = br_sha384_SIZE; cp += 7; #endif #ifdef VE_SHA512_SUPPORT } else if (strncmp(cp, "sha512=", 7) == 0) { ctx->vec_md = &br_sha512_vtable; hashsz = br_sha512_SIZE; cp += 7; #endif } else { ctx->vec_status = VE_FINGERPRINT_UNKNOWN; ve_error_set("%s: no supported fingerprint", path); } } *error = ctx->vec_status; ctx->vec_hashsz = hashsz; ctx->vec_want = cp; if (hashsz > 0) { ctx->vec_md->init(&ctx->vec_ctx.vtable); if (off > 0) { lseek(fd, 0, SEEK_SET); vectx_lseek(ctx, off, SEEK_SET); } } DEBUG_PRINTF(2, ("vectx_open: caller=%s,name='%s',hashsz=%lu,status=%d\n", caller, path, (unsigned long)ctx->vec_hashsz, ctx->vec_status)); return (ctx); enomem: /* unlikely */ *error = ENOMEM; free(ctx); return (NULL); } /** * @brief * read bytes from file and update hash * * It is critical that all file I/O comes through here. * We keep track of current offset. * We also track what offset we have hashed to, * so we won't replay data if we seek backwards. * * @param[in] pctx * pointer to ctx * * @param[in] buf * * @param[in] nbytes * * @return bytes read or error. */ ssize_t vectx_read(struct vectx *ctx, void *buf, size_t nbytes) { unsigned char *bp = buf; int d; int n; int delta; int x; size_t off; if (ctx->vec_hashsz == 0) /* nothing to do */ return (read(ctx->vec_fd, buf, nbytes)); off = 0; do { /* * Do this in reasonable chunks so * we don't timeout if doing tftp */ x = nbytes - off; x = MIN(PAGE_SIZE, x); d = n = read(ctx->vec_fd, &bp[off], x); + if (ctx->vec_closing && n < x) { + DEBUG_PRINTF(3, + ("%s: read %d off=%ld hashed=%ld size=%ld\n", + __func__, n, (long)ctx->vec_off, + (long)ctx->vec_hashed, (long)ctx->vec_size)); + } if (n < 0) { return (n); } if (d > 0) { /* we may have seeked backwards! */ delta = ctx->vec_hashed - ctx->vec_off; if (delta > 0) { x = MIN(delta, d); off += x; d -= x; ctx->vec_off += x; } if (d > 0) { + if (ctx->vec_closing && d < PAGE_SIZE) { + DEBUG_PRINTF(3, + ("%s: update %ld + %d\n", + __func__, + (long)ctx->vec_hashed, d)); + } ctx->vec_md->update(&ctx->vec_ctx.vtable, &bp[off], d); off += d; ctx->vec_off += d; ctx->vec_hashed += d; } } } while (n > 0 && off < nbytes); return (off); } /** * @brief * vectx equivalent of lseek * * When seeking forwards we actually call vectx_read * to reach the desired offset. * * We support seeking backwards. * * @param[in] pctx * pointer to ctx * * @param[in] off * desired offset * * @param[in] whence * We try to convert whence to ``SEEK_SET``. * We do not support ``SEEK_DATA`` or ``SEEK_HOLE``. * * @return offset or error. */ off_t vectx_lseek(struct vectx *ctx, off_t off, int whence) { unsigned char buf[PAGE_SIZE]; size_t delta; ssize_t n; if (ctx->vec_hashsz == 0) /* nothing to do */ return (lseek(ctx->vec_fd, off, whence)); /* * Convert whence to SEEK_SET */ + DEBUG_PRINTF(3, + ("%s(%s, %ld, %d)\n", __func__, ctx->vec_path, (long)off, whence)); if (whence == SEEK_END && off <= 0) { + if (ctx->vec_closing && ctx->vec_hashed < ctx->vec_size) { + DEBUG_PRINTF(3, ("%s: SEEK_END %ld\n", + __func__, + (long)(ctx->vec_size - ctx->vec_hashed))); + } whence = SEEK_SET; off += ctx->vec_size; } else if (whence == SEEK_CUR) { whence = SEEK_SET; off += ctx->vec_off; } if (whence != SEEK_SET || - (size_t)off > ctx->vec_size) { - printf("ERROR: %s: unsupported operation: whence=%d off=%lld -> %lld\n", - __func__, whence, (long long)ctx->vec_off, (long long)off); + off > ctx->vec_size) { + printf("ERROR: %s: unsupported operation: whence=%d off=%ld -> %ld\n", + __func__, whence, (long)ctx->vec_off, (long)off); return (-1); } if (off < ctx->vec_hashed) { +#ifdef _STANDALONE + struct open_file *f = fd2open_file(ctx->vec_fd); + + if (f != NULL && + strncmp(f->f_ops->fs_name, "tftp", 4) == 0) { + /* we cannot rewind if we've hashed much of the file */ + if (ctx->vec_hashed > ctx->vec_size / 5) + return (-1); /* refuse! */ + } +#endif /* seeking backwards! just do it */ ctx->vec_off = lseek(ctx->vec_fd, off, whence); return (ctx->vec_off); } n = 0; do { delta = off - ctx->vec_off; if (delta > 0) { delta = MIN(PAGE_SIZE, delta); n = vectx_read(ctx, buf, delta); if (n < 0) return (n); } } while (ctx->vec_off < off && n > 0); return (ctx->vec_off); } /** * @brief * check that hashes match and cleanup * * We have finished reading file, compare the hash with what * we wanted. * * Be sure to call this before closing the file, since we may * need to seek to the end to ensure hashing is complete. * * @param[in] pctx * pointer to ctx * * @return 0 or an error. */ int vectx_close(struct vectx *ctx, int severity, const char *caller) { int rc; + ctx->vec_closing = 1; if (ctx->vec_hashsz == 0) { rc = ctx->vec_status; } else { #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 /* make sure we have hashed it all */ vectx_lseek(ctx, 0, SEEK_END); rc = ve_check_hash(&ctx->vec_ctx, ctx->vec_md, ctx->vec_path, ctx->vec_want, ctx->vec_hashsz); } DEBUG_PRINTF(2, ("vectx_close: caller=%s,name='%s',rc=%d,severity=%d\n", caller,ctx->vec_path, rc, severity)); + verify_report(ctx->vec_path, severity, rc, NULL); if (rc == VE_FINGERPRINT_WRONG) { - printf("Unverified: %s\n", ve_error_get()); #if !defined(UNIT_TEST) && !defined(DEBUG_VECTX) /* we are generally called with VE_MUST */ if (severity > VE_WANT) panic("cannot continue"); #endif - } else if (severity > VE_WANT) { - printf("%serified %s\n", (rc <= 0) ? "Unv" : "V", - ctx->vec_path); } free(ctx); return ((rc < 0) ? rc : 0); } diff --git a/lib/libsecureboot/veopen.c b/lib/libsecureboot/veopen.c index da6291504c4c..b428fa4b13a3 100644 --- a/lib/libsecureboot/veopen.c +++ b/lib/libsecureboot/veopen.c @@ -1,463 +1,470 @@ /*- * 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$"); #include #include "libsecureboot-priv.h" struct fingerprint_info { char *fi_prefix; /**< manifest entries relative to */ char *fi_skip; /**< manifest entries prefixed with */ const char *fi_data; /**< manifest data */ size_t fi_prefix_len; /**< length of prefix */ size_t fi_skip_len; /**< length of skip */ dev_t fi_dev; /**< device id */ LIST_ENTRY(fingerprint_info) entries; }; static LIST_HEAD(, fingerprint_info) fi_list; static void fingerprint_info_init(void) { static int once; if (once) return; LIST_INIT(&fi_list); once = 1; } /** * @brief * add manifest data to list * * list is kept sorted by longest prefix. * * @param[in] prefix * path that all manifest entries are resolved via * * @param[in] skip * optional prefix within manifest entries which should be skipped * * @param[in] data * manifest data */ void fingerprint_info_add(const char *filename, const char *prefix, const char *skip, const char *data, struct stat *stp) { struct fingerprint_info *fip, *nfip, *lfip; char *cp; int n; fingerprint_info_init(); nfip = malloc(sizeof(struct fingerprint_info)); + if (nfip == NULL) { +#ifdef _STANDALONE + printf("%s: out of memory! %lu\n", __func__, + (unsigned long)sizeof(struct fingerprint_info)); +#endif + return; + } if (prefix) { nfip->fi_prefix = strdup(prefix); } else { if (!filename) { free(nfip); return; } nfip->fi_prefix = strdup(filename); cp = strrchr(nfip->fi_prefix, '/'); if (cp == nfip->fi_prefix) { cp[1] = '\0'; } else if (cp) { *cp = '\0'; } else { free(nfip->fi_prefix); free(nfip); return; } } /* collapse any trailing ..[/] */ n = 0; while ((cp = strrchr(nfip->fi_prefix, '/')) > nfip->fi_prefix) { if (cp[1] == '\0') { /* trailing "/" */ *cp = '\0'; continue; } if (strcmp(&cp[1], "..") == 0) { n++; *cp = '\0'; continue; } if (n > 0) { n--; *cp = '\0'; } if (n == 0) break; } + nfip->fi_dev = stp->st_dev; #ifdef UNIT_TEST nfip->fi_dev = 0; -#else - nfip->fi_dev = stp->st_dev; #endif nfip->fi_data = data; nfip->fi_prefix_len = strlen(nfip->fi_prefix); if (skip) { nfip->fi_skip_len = strlen(skip); if (nfip->fi_skip_len) nfip->fi_skip = strdup(skip); else nfip->fi_skip = NULL; } else { nfip->fi_skip = NULL; nfip->fi_skip_len = 0; } if (LIST_EMPTY(&fi_list)) { LIST_INSERT_HEAD(&fi_list, nfip, entries); DEBUG_PRINTF(4, ("inserted %zu %s at head\n", nfip->fi_prefix_len, nfip->fi_prefix)); return; } LIST_FOREACH(fip, &fi_list, entries) { if (nfip->fi_prefix_len >= fip->fi_prefix_len) { LIST_INSERT_BEFORE(fip, nfip, entries); DEBUG_PRINTF(4, ("inserted %zu %s before %zu %s\n", nfip->fi_prefix_len, nfip->fi_prefix, fip->fi_prefix_len, fip->fi_prefix)); return; } lfip = fip; } LIST_INSERT_AFTER(lfip, nfip, entries); DEBUG_PRINTF(4, ("inserted %zu %s after %zu %s\n", nfip->fi_prefix_len, nfip->fi_prefix, lfip->fi_prefix_len, lfip->fi_prefix)); } #ifdef MANIFEST_SKIP_MAYBE /* * Deal with old incompatible boot/manifest * if fp[-1] is '/' and start of entry matches * MANIFEST_SKIP_MAYBE, we want it. */ static char * maybe_skip(char *fp, struct fingerprint_info *fip, size_t *nplenp) { char *tp; tp = fp - sizeof(MANIFEST_SKIP_MAYBE); if (tp >= fip->fi_data) { DEBUG_PRINTF(3, ("maybe: %.48s\n", tp)); if ((tp == fip->fi_data || tp[-1] == '\n') && strncmp(tp, MANIFEST_SKIP_MAYBE, sizeof(MANIFEST_SKIP_MAYBE) - 1) == 0) { fp = tp; *nplenp += sizeof(MANIFEST_SKIP_MAYBE); } } return (fp); } #endif char * fingerprint_info_lookup(int fd, const char *path) { char pbuf[MAXPATHLEN+1]; char nbuf[MAXPATHLEN+1]; struct stat st; struct fingerprint_info *fip; char *cp, *ep, *fp, *np; const char *prefix; size_t n, plen, nlen, nplen; dev_t dev = 0; fingerprint_info_init(); n = strlcpy(pbuf, path, sizeof(pbuf)); if (n >= sizeof(pbuf)) return (NULL); -#ifndef UNIT_TEST if (fstat(fd, &st) == 0) dev = st.st_dev; +#ifdef UNIT_TEST + dev = 0; #endif /* * get the first entry - it will have longest prefix * so we can can work out how to initially split path */ fip = LIST_FIRST(&fi_list); if (!fip) return (NULL); prefix = pbuf; ep = NULL; cp = &pbuf[fip->fi_prefix_len]; do { if (ep) { *ep = '/'; cp -= 2; if (cp < pbuf) break; } nlen = plen = 0; /* keep gcc quiet */ if (cp > pbuf) { for ( ; cp >= pbuf && *cp != '/'; cp--) ; /* nothing */ if (cp > pbuf) { ep = cp++; *ep = '\0'; } else { cp = pbuf; } if (ep) { plen = ep - pbuf; nlen = n - plen - 1; } } if (cp == pbuf) { prefix = "/"; plen = 1; if (*cp == '/') { nlen = n - 1; cp++; } else nlen = n; ep = NULL; } DEBUG_PRINTF(2, ("looking for %s %zu %s\n", prefix, plen, cp)); LIST_FOREACH(fip, &fi_list, entries) { DEBUG_PRINTF(4, ("at %zu %s\n", fip->fi_prefix_len, fip->fi_prefix)); if (fip->fi_prefix_len < plen) { DEBUG_PRINTF(3, ("skipping prefix=%s %zu %zu\n", fip->fi_prefix, fip->fi_prefix_len, plen)); break; } if (fip->fi_prefix_len == plen) { if (fip->fi_dev != 0 && fip->fi_dev != dev) { DEBUG_PRINTF(3, ( "skipping dev=%ld != %ld\n", (long)fip->fi_dev, (long)dev)); continue; } if (strcmp(prefix, fip->fi_prefix)) { DEBUG_PRINTF(3, ( "skipping prefix=%s\n", fip->fi_prefix)); continue; } DEBUG_PRINTF(3, ("checking prefix=%s\n", fip->fi_prefix)); if (fip->fi_skip_len) { np = nbuf; nplen = snprintf(nbuf, sizeof(nbuf), "%s/%s", fip->fi_skip, cp); nplen = MIN(nplen, sizeof(nbuf) - 1); } else { np = cp; nplen = nlen; } DEBUG_PRINTF(3, ("lookup: '%s'\n", np)); if (!(fp = strstr(fip->fi_data, np))) continue; #ifdef MANIFEST_SKIP_MAYBE if (fip->fi_skip_len == 0 && fp > fip->fi_data && fp[-1] == '/') { fp = maybe_skip(fp, fip, &nplen); } #endif /* * when we find a match: * fp[nplen] will be space and * fp will be fip->fi_data or * fp[-1] will be \n */ if (!((fp == fip->fi_data || fp[-1] == '\n') && fp[nplen] == ' ')) { do { fp++; fp = strstr(fp, np); if (fp) { #ifdef MANIFEST_SKIP_MAYBE if (fip->fi_skip_len == 0 && fp > fip->fi_data && fp[-1] == '/') { fp = maybe_skip(fp, fip, &nplen); } #endif DEBUG_PRINTF(3, ("fp[-1]=%#x fp[%zu]=%#x fp=%.78s\n", fp[-1], nplen, fp[nplen], fp)); } } while (fp != NULL && !(fp[-1] == '\n' && fp[nplen] == ' ')); if (!fp) continue; } DEBUG_PRINTF(2, ("found %.78s\n", fp)); /* we have a match! */ for (cp = &fp[nplen]; *cp == ' '; cp++) ; /* nothing */ return (cp); } else { DEBUG_PRINTF(3, ("Ignoring prefix=%s\n", fip->fi_prefix)); } } } while (cp > &pbuf[1]); return (NULL); } static int verify_fingerprint(int fd, const char *path, const char *cp, off_t off) { unsigned char buf[PAGE_SIZE]; const br_hash_class *md; br_hash_compat_context mctx; size_t hlen; int n; if (strncmp(cp, "no_hash", 7) == 0) { return (VE_FINGERPRINT_IGNORE); } else if (strncmp(cp, "sha256=", 7) == 0) { md = &br_sha256_vtable; hlen = br_sha256_SIZE; cp += 7; #ifdef VE_SHA1_SUPPORT } else if (strncmp(cp, "sha1=", 5) == 0) { md = &br_sha1_vtable; hlen = br_sha1_SIZE; cp += 5; #endif #ifdef VE_SHA384_SUPPORT } else if (strncmp(cp, "sha384=", 7) == 0) { md = &br_sha384_vtable; hlen = br_sha384_SIZE; cp += 7; #endif #ifdef VE_SHA512_SUPPORT } else if (strncmp(cp, "sha512=", 7) == 0) { md = &br_sha512_vtable; hlen = br_sha512_SIZE; cp += 7; #endif } else { ve_error_set("%s: no supported fingerprint", path); return (VE_FINGERPRINT_UNKNOWN); } md->init(&mctx.vtable); if (off) lseek(fd, 0, SEEK_SET); do { n = read(fd, buf, sizeof(buf)); if (n < 0) return (n); if (n > 0) md->update(&mctx.vtable, buf, n); } while (n > 0); lseek(fd, off, SEEK_SET); return (ve_check_hash(&mctx, md, path, cp, hlen)); } /** * @brief * verify an open file * * @param[in] fd * open descriptor * * @param[in] path * pathname to open * * @param[in] off * current offset * * @return 0, VE_FINGERPRINT_OK or VE_FINGERPRINT_NONE, VE_FINGERPRINT_WRONG */ int verify_fd(int fd, const char *path, off_t off, struct stat *stp) { struct stat st; char *cp; int rc; if (!stp) { if (fstat(fd, &st) == 0) stp = &st; } if (stp && !S_ISREG(stp->st_mode)) return (0); /* not relevant */ cp = fingerprint_info_lookup(fd, path); if (!cp) { ve_error_set("%s: no entry", path); return (VE_FINGERPRINT_NONE); } rc = verify_fingerprint(fd, path, cp, off); switch (rc) { case VE_FINGERPRINT_OK: case VE_FINGERPRINT_IGNORE: case VE_FINGERPRINT_UNKNOWN: return (rc); default: return (VE_FINGERPRINT_WRONG); } } /** * @brief * open a file if it can be verified * * @param[in] path * pathname to open * * @param[in] flags * flags for open * * @return fd or VE_FINGERPRINT_NONE, VE_FINGERPRINT_WRONG */ int verify_open(const char *path, int flags) { int fd; int rc; if ((fd = open(path, flags)) >= 0) { if ((rc = verify_fd(fd, path, 0, NULL)) < 0) { close(fd); fd = rc; } } return (fd); } diff --git a/lib/libsecureboot/verify_file.c b/lib/libsecureboot/verify_file.c index 22f3f06b0eda..a6d4410a5724 100644 --- a/lib/libsecureboot/verify_file.c +++ b/lib/libsecureboot/verify_file.c @@ -1,561 +1,673 @@ /*- * Copyright (c) 2017-2020, 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 #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 *Destdir; extern size_t DestdirLen; 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. */ #ifndef SOPEN_MAX -#define SOPEN_MAX 64 +#define SOPEN_MAX 64 #endif static int ve_status[SOPEN_MAX+1]; static int ve_status_state; struct verify_status; -struct verify_status *verified_files = NULL; +static struct verify_status *verified_files = NULL; static int loaded_manifests = 0; /* have we loaded anything? */ +enum { + VE_VERBOSE_SILENT, /* only report errors */ + VE_VERBOSE_UNVERIFIED, /* all unverified files */ + VE_VERBOSE_MUST, /* report VE_MUST */ + VE_VERBOSE_ALL, /* report all */ + VE_VERBOSE_DEBUG, /* extra noise */ +}; + +#ifndef VE_VERBOSE_DEFAULT +# define VE_VERBOSE_DEFAULT VE_VERBOSE_MUST +#endif +static int Verbose = VE_VERBOSE_DEFAULT; + #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; if (stp->st_ino > 0) { 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; + if (vsp) { + 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); + content = (char *)verify_signed(name, VerifyFlags); if (content) { #ifdef UNIT_TEST if (DestdirLen > 0 && strncmp(name, Destdir, DestdirLen) == 0) { name += DestdirLen; if (prefix && strncmp(prefix, Destdir, DestdirLen) == 0) prefix += DestdirLen; } #endif 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 = VE_VERIFIED; } 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); + if (*tp[0] == '.') { + /* skip /../ */ + if (prefix[0] == '\0' || prefix[1] == '\0') + continue; + } 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 */ + /* + * Some files like *.conf and *.hints may be unsigned, + * a *.tgz is expected to have its own signed manifest. + */ if ((cp = strrchr(filename, '.'))) { if (strcmp(cp, ".conf") == 0 || strcmp(cp, ".cookie") == 0 || - strcmp(cp, ".hints") == 0) + strcmp(cp, ".hints") == 0 || + strcmp(cp, ".tgz") == 0) return (VE_TRY); if (strcmp(cp, ".4th") == 0 || strcmp(cp, ".lua") == 0 || strcmp(cp, ".rc") == 0) return (VE_MUST); } return (VE_WANT); } static int Verifying = -1; /* 0 if not verifying */ static void verify_tweak(int fd, off_t off, struct stat *stp, - char *tweak, int *accept_no_fp, - int *verbose) + char *tweak, int *accept_no_fp) { 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 */ + Verbose = VE_VERBOSE_ALL; /* 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; + Verbose = VE_VERBOSE_ALL; } else if (strcmp(tweak, "quiet") == 0) { - *verbose = 0; + Verbose = VE_VERBOSE_UNVERIFIED; + VerifyFlags = 0; + } else if (strcmp(tweak, "silent") == 0) { + Verbose = VE_VERBOSE_SILENT; + VerifyFlags = 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 report verification status + * + * @param[in] path + * path we attempted to verify + * + * @param[in] severity + * indicator of how to handle case of missing fingerprint + * + * @param[in] status + * result of verification + * 0 not a file to be verified, > 0 success, < 0 error + * + * @param[in] stp + * pointer to struct stat, used in extra info to be output + * + * The output is dictated by combinations of the above and the setting + * of Verbose: + * + * VE_VERBOSE_SILENT + * report only failure to verify if severity is VE_WANT or higher. + * + * VE_VERBOSE_UNVERIFIED + * report any unverified file. + * + * VE_VERBOSE_MUST + * report verified only if severity is VE_MUST or higher. + * + * VE_VERBOSE_ALL + * report all verified files. + * + * VE_VERBOSE_DEBUG + * if stp is not NULL report dev,inode for path + */ +void +verify_report(const char *path, int severity, int status, struct stat *stp) +{ + if (status < 0 || status == VE_FINGERPRINT_IGNORE) { + if (Verbose >= VE_VERBOSE_UNVERIFIED || severity > VE_TRY || + status <= VE_FINGERPRINT_WRONG) { + if (Verbose == VE_VERBOSE_DEBUG && stp != NULL) + printf("Unverified %s %llu,%llu\n", + ve_error_get(), + (long long)stp->st_dev, + (long long)stp->st_ino); + else + printf("Unverified %s\n", ve_error_get()); + } + } else if (status > 0 && Verbose >= VE_VERBOSE_MUST) { + if (severity >= VE_MUST || Verbose >= VE_VERBOSE_ALL) { + if (Verbose == VE_VERBOSE_DEBUG && stp != NULL) + printf("Unverified %s %llu,%llu\n", + path, + (long long)stp->st_dev, + (long long)stp->st_ino); + else + printf("Verified %s\n", path); + } + } +} + + /** * @brief prepare to verify an open file * * @param[in] fd * open descriptor * * @param[in] filename * path we opened and will use to lookup fingerprint * * @param[in] stp * stat pointer so we can check file type */ int verify_prep(int fd, const char *filename, off_t off, struct stat *stp, const char *caller) { int rc; if (Verifying < 0) { Verifying = ve_trust_init(); -#ifndef UNIT_TEST - ve_debug_set(getenv_int("VE_DEBUG_LEVEL", VE_DEBUG_LEVEL)); -#endif /* 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 || fd < 0) return (0); if (stp) { if (fstat(fd, stp) < 0 || !S_ISREG(stp->st_mode)) return (0); } DEBUG_PRINTF(2, - ("verify_prep: caller=%s,fd=%d,name='%s',off=%lld,dev=%lld,ino=%lld\n", + ("verify_prep: caller=%s,fd=%d,name='%s',off=%lld,dev=%lld,ino=%llu\n", caller, fd, filename, (long long)off, (long long)stp->st_dev, - (long long)stp->st_ino)); + (unsigned long long)stp->st_ino)); rc = is_verified(stp); DEBUG_PRINTF(4,("verify_prep: is_verified()->%d\n", rc)); if (rc == VE_NOT_CHECKED) { rc = find_manifest(filename); } else { ve_status_set(fd, rc); } return (rc); } /** * @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, const char *caller) { - static int once; + static int check_verbose = 1; static int accept_no_fp = ACCEPT_NO_FP_DEFAULT; - static int verbose = VE_VERBOSE_DEFAULT; struct stat st; char *cp; int rc; + if (check_verbose) { + check_verbose = 0; + Verbose = getenv_int("VE_VERBOSE", VE_VERBOSE_DEFAULT); + VerifyFlags = getenv_int("VE_VERIFY_FLAGS", VEF_VERBOSE); +#ifndef UNIT_TEST + ve_debug_set(getenv_int("VE_DEBUG_LEVEL", VE_DEBUG_LEVEL)); +#endif + } + rc = verify_prep(fd, filename, off, &st, caller); if (!rc) return (0); - if (!once) { - once++; - verbose = getenv_int("VE_VERBOSE", VE_VERBOSE_DEFAULT); - } - 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 #ifdef UNIT_TEST if (DestdirLen > 0 && strncmp(filename, Destdir, DestdirLen) == 0) { filename += DestdirLen; } #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 - } + rc = verify_fd(fd, filename, off, &st); + verify_report(filename, severity, rc, &st); + if (rc >= 0) { 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); + &accept_no_fp); } } } 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); + + /* recheck debug/verbose level next time we are called */ + if (rc == VE_UNVERIFIED_OK) { + check_verbose = 1; + } } #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 *hinfo; 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); DEBUG_PRINTF(1, ("%s: setenv(loader.ve.pcr, %s\n", __func__, hex)); hinfo = ve_pcr_hashed_get(1); if (hinfo) { setenv("loader.ve.hashed", hinfo, 1); DEBUG_PRINTF(1, ("%s: setenv(loader.ve.hashed, %s\n", __func__, hinfo)); if ((hlen = strlen(hinfo)) > KENV_MVALLEN) { /* * bump kenv_mvallen * roundup to multiple of KENV_MVALLEN */ char mvallen[16]; hlen += KENV_MVALLEN - (hlen % KENV_MVALLEN); if (snprintf(mvallen, sizeof(mvallen), - "%d", (int) hlen) < sizeof(mvallen)) + "%d", (int) hlen) < (int)sizeof(mvallen)) setenv("kenv_mvallen", mvallen, 1); } free(hinfo); } } } #endif } + +/* + * For tftp and http we need to hash pathname + * to be able to fake stat(2) data. + */ +int +hash_string(char *s, size_t n, char *buf, size_t bufsz) +{ + br_hash_compat_context mctx; + const br_hash_class *md; + + switch (bufsz) { + case br_sha1_SIZE: + md = &br_sha1_vtable; + break; + case br_sha256_SIZE: + md = &br_sha256_vtable; + break; + default: + if (bufsz < br_sha1_SIZE) + return -1; + md = &br_sha1_vtable; + bufsz = br_sha1_SIZE; + break; + } + if (n == 0) + n = strlen(s); + md->init(&mctx.vtable); + md->update(&mctx.vtable, s, n); + md->out(&mctx.vtable, buf); + return bufsz; +} + + diff --git a/lib/libsecureboot/vets.c b/lib/libsecureboot/vets.c index 3a82592ea699..dd347a0a8c13 100644 --- a/lib/libsecureboot/vets.c +++ b/lib/libsecureboot/vets.c @@ -1,1023 +1,1128 @@ /*- * 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; } 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 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, + 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; +#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 */ +#endif + 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 _STANDALONE +#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) { - ve_error_set("Validation failed, err = %d", 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 & 1) != 0) { + } 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); }