diff --git a/lib/libsecureboot/Makefile b/lib/libsecureboot/Makefile new file mode 100644 index 000000000000..24f3d2bc5855 --- /dev/null +++ b/lib/libsecureboot/Makefile @@ -0,0 +1,11 @@ +# $FreeBSD$ + +.include + +LIB= secureboot + +.include "Makefile.inc" + +INCS= h/libsecureboot.h + +.include diff --git a/lib/libsecureboot/Makefile.depend b/lib/libsecureboot/Makefile.depend new file mode 100644 index 000000000000..6cfaab1c3644 --- /dev/null +++ b/lib/libsecureboot/Makefile.depend @@ -0,0 +1,17 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + gnu/lib/csu \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + + +.include + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/lib/libsecureboot/Makefile.depend.host b/lib/libsecureboot/Makefile.depend.host new file mode 100644 index 000000000000..c6441c263f4a --- /dev/null +++ b/lib/libsecureboot/Makefile.depend.host @@ -0,0 +1,12 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + lib/libstand \ + + +.include + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/lib/libsecureboot/Makefile.inc b/lib/libsecureboot/Makefile.inc new file mode 100644 index 000000000000..1042fe898ace --- /dev/null +++ b/lib/libsecureboot/Makefile.inc @@ -0,0 +1,133 @@ +# $FreeBSD$ + +.if empty(BEARSSL) +.include "../libbearssl/Makefile.inc" +.endif + +.if !target(_${__this}_) +_${__this}_: + +libsecureboot_src:= ${.PARSEDIR} + +CFLAGS+= -I${libsecureboot_src}/h + +.PATH: ${.PARSEDIR} + +SRCS+= \ + readfile.c \ + brf.c \ + vesigned.c \ + vets.c + +.if ${.CURDIR:M*libsecureboot*} != "" +SRCS+= veta.c +.endif + +CFLAGS+= ${XCFLAGS.${.TARGET:T:R}:U} + +# we use a couple of files from ${BEARSSL}/tools +BRSSL_CFLAGS+= -I${BEARSSL}/tools +BRSSL_SRCS+= \ + ${BEARSSL}/tools/xmem.c \ + ${BEARSSL}/tools/vector.c + +# we do not need/want nested objdirs +OBJS_SRCS_FILTER = T R + +SRCS+= ${BRSSL_SRCS} + + +# extract the last cert from a chain (should be rootCA) +_LAST_PEM_USE: .USE + sed "1,`grep -n .-END ${.ALLSRC:M*.pem} | tail -2 | head -1 | sed 's,:.*,,'`d" ${.ALLSRC:M*.pem} > ${.TARGET} + +# extract 2nd last cert from chain - we use this for self-test +_2ndLAST_PEM_USE: .USE + sed -n "`grep -n .-BEGIN ${.ALLSRC:M*.pem} | tail -2 | \ + sed 's,:.*,,' | xargs | (read a b; echo $$a,$$(($$b - 1)))`p" ${.ALLSRC:M*.pem} > ${.TARGET} + +# list of hashes we support +VE_HASH_LIST?= SHA256 + +# list of signatures we support +# some people don't trust ECDSA +VE_SIGNATURE_LIST?= RSA + +# this list controls our search for signatures so will not be sorted +# note: for X509 signatures we assume we can replace the trailing +# "sig" with "certs" to find the certificate chain +# eg. for manifest.esig we use manifest.ecerts +VE_SIGNATURE_EXT_LIST?= sig + +# needs to be yes for FIPS 140-2 compliance +VE_SELF_TESTS?= no + +# rules to populate the [tv]*.pem files we use to generate ta.h +# and can add/alter VE_*_LIST as desired. +.-include "local.trust.mk" + +# this is what we use as our trust anchor +CFLAGS+= -I. -DTRUST_ANCHOR_STR=ta_PEM + +.if ${VE_SELF_TESTS} != "no" +XCFLAGS.vets+= -DVERIFY_CERTS_STR=vc_PEM +.endif + +# clean these up +VE_HASH_LIST:= ${VE_HASH_LIST:tu:O:u} +VE_SIGNATURE_LIST:= ${VE_SIGNATURE_LIST:tu:O:u} + +# define what we are supporting +CFLAGS+= ${VE_HASH_LIST:@H@-DVE_$H_SUPPORT@} \ + ${VE_SIGNATURE_LIST:@S@-DVE_$S_SUPPORT@} + +.if ${VE_SIGNATURE_LIST:MOPENPGP} != "" +.include "openpgp/Makefile.inc" +.endif + +.if ${VE_SELF_TESTS} != "no" +# The input used for hash KATs +VE_HASH_KAT_STR?= vc_PEM + +XCFLAGS.vets+= -DVE_HASH_KAT_STR=${VE_HASH_KAT_STR} +.endif + +# Generate ta.h containing one or more PEM encoded trust anchors in ta_PEM. +# +# If we are doing self-tests, we define another arrary vc_PEM +# containing certificates that we can verify for each trust anchor. +# This is typically a subordinate CA cert. +# Finally we generate a hash of vc_PEM using each supported hash method +# to use as a Known Answer Test (needed for FIPS 140-2) +# +vets.o vets.po vets.pico: ta.h +ta.h: ${.ALLTARGETS:M[tv]*pem:O:u} + @( echo '/* Autogenerated - DO NOT EDIT!!! */'; echo; \ + cat ${.ALLSRC:N*crl*:Mt*.pem} /dev/null | \ + file2c -sx 'static const char ta_PEM[] = {' '};'; \ + echo "${.newline}${VE_HASH_LIST:@H@static char vh_$H[] = \"`cat ${.ALLSRC:N*crl*:Mv*.pem} | ${$H:U${H:tl}}`\";${.newline}@}"; ) > ${.TARGET} +.if ${VE_SELF_TESTS} != "no" + ( cat ${.ALLSRC:N*crl*:Mv*.pem} /dev/null | \ + file2c -sx 'static const char vc_PEM[] = {' '};'; echo ) >> ${.TARGET} +.endif +.if !empty(BUILD_UTC_FILE) + echo '#define BUILD_UTC ${${STAT:Ustat} -f %m ${BUILD_UTC_FILE}:L:sh}' >> ${.TARGET} ${.OODATE:MNOMETA_CMP} +.endif + +# This header records our preference for signature extensions. +vesigned.o vesigned.po vesigned.pico: vse.h +vse.h: + @( echo '/* Autogenerated - DO NOT EDIT!!! */'; echo; \ + echo "static const char *signature_exts[] = {"; \ + echo '${VE_SIGNATURE_EXT_LIST:@e@"$e",${.newline}@}'; \ + echo 'NULL };' ) > ${.TARGET} + + +.for s in ${BRSSL_SRCS} brf.c vets.c veta.c +.ifdef BRSSL_SED +$s: brssl.h +.endif +XCFLAGS.${s:R}+= ${BRSSL_CFLAGS} +.endfor + +.endif diff --git a/lib/libsecureboot/Makefile.libsa.inc b/lib/libsecureboot/Makefile.libsa.inc new file mode 100644 index 000000000000..2456484a89c7 --- /dev/null +++ b/lib/libsecureboot/Makefile.libsa.inc @@ -0,0 +1,40 @@ +# $FreeBSD$ + +BRSSL_CFLAGS+= -DNO_STDIO + +.include "Makefile.inc" + +# for "measured boot" +# loader puts the equivalent of TPM's PCR register into kenv +# this is not as good but *way* simpler than talking to TPM +CFLAGS+= -DVE_PCR_SUPPORT + +# sources that only apply to libsa +SRCS+= \ + vectx.c \ + veopen.c \ + vepcr.c \ + verify_file.c \ + +# this is the list of paths (relative to a file +# that we need to verify) used to find a signed manifest. +# the signature extensions in VE_SIGNATURE_EXT_LIST +# will be applied to each. +VE_MANIFEST_LIST?= manifest ../manifest + +verify_file.o: manifests.h +manifests.h: + @( echo '/* Autogenerated - DO NOT EDIT!!! */'; echo; \ + echo "static const char *manifest_names[] = {"; \ + echo '${VE_MANIFEST_LIST:@m@"$m",${.newline}@}'; \ + echo 'NULL };' ) > ${.TARGET} + +XCFLAGS.verify_file+= \ + -DVE_DEBUG_LEVEL=${VE_DEBUG_LEVEL:U0} \ + -DVE_VERBOSE_DEFAULT=${VE_VERBOSE_DEFAULT:U0} \ + +.if !empty(MANIFEST_SKIP_ALWAYS) +XCFLAGS.verify_file+= -DMANIFEST_SKIP_ALWAYS=\"${MANIFEST_SKIP_ALWAYS}\" +.elif !empty(MANIFEST_SKIP) +XCFLAGS.verify_file+= -DMANIFEST_SKIP=\"${MANIFEST_SKIP}\" +.endif diff --git a/lib/libsecureboot/README.rst b/lib/libsecureboot/README.rst new file mode 100644 index 000000000000..8e6f7580faaf --- /dev/null +++ b/lib/libsecureboot/README.rst @@ -0,0 +1,134 @@ +libsecureboot +************* + +This library depends one way or another on verifying digital signatures. +To do that, the necessary trust anchors need to be available. + +The simplest (and most attractive for an embedded system) is to +capture them in this library. + +The makefile ``local.trust.mk`` is responsible for doing that. +The file provided is just an example and depends on the environment +here at Juniper. + +Within Juniper we use signing servers, which apart from signing things +provide access to the necessary trust anchors. +That signing server is freely available - see +http://www.crufty.net/sjg/docs/signing-server.htm + +X.509 certificates chains offer a lot of flexibility over time and are +a great solution for an embedded vendor like Juniper or even +FreeBSD.org, but are probably overkill for personal or small site use. + +Setting up a CA for this is rather involved so I'll just provide a +link below to suitable tutorial below. + +Using OpenPGP is much simpler. + + +OpenPGP +======== + +This is very simple to setup and use. + +An RSA key pair can be generated with:: + + GNUPGHOME=$PWD/.gnupg gpg --openpgp \ + --quick-generate-key --batch --passphrase '' "keyname" RSA + +The use of ``GNUPGHOME=$PWD/.gnupg`` just avoids messing with personal +keyrings. +We can list the resulting key:: + + GNUPGHOME=$PWD/.gnupg gpg --openpgp --list-keys + + gpg: WARNING: unsafe permissions on homedir + '/h/sjg/openpgp/.gnupg' + gpg: Warning: using insecure memory! + /h/sjg/openpgp/.gnupg/pubring.kbx + --------------------------------- + pub rsa2048 2018-03-26 [SC] [expires: 2020-03-25] + AB39B111E40DD019E0E7C171ACA72B4719FD2523 + uid [ultimate] OpenPGPtest + +The ``keyID`` we want later will be the last 8 octets +(``ACA72B4719FD2523``) +This is what we will use for looking up the key. + +We can then export the private and public keys:: + + GNUPGHOME=$PWD/.gnupg gpg --openpgp \ + --export --armor > ACA72B4719FD2523.pub.asc + GNUPGHOME=$PWD/.gnupg gpg --openpgp \ + --export-secret-keys --armor > ACA72B4719FD2523.sec.asc + +The public key ``ACA72B4719FD2523.pub.asc`` is what we want to +embed in this library. +If you look at the ``ta_asc.h`` target in ``openpgp/Makefile.inc`` +we want the trust anchor in a file named ``t*.asc`` +eg. ``ta_openpgp.asc``. + +The ``ta_asc.h`` target will capture all such ``t*.asc`` into that +header. + +Signatures +---------- + +We expect ascii armored (``.asc``) detached signatures. +Eg. signature for ``manifest`` would be in ``manifest.asc`` + +We only support version 4 signatures using RSA (the default for ``gpg``). + + +OpenSSL +======== + +The basic idea here is to setup a private CA. + +There are lots of good tutorials on available on this topic; +just google *setup openssl ca*. +A good example is https://jamielinux.com/docs/openssl-certificate-authority/ + +All we need for this library is a copy of the PEM encoded root CA +certificate (trust anchor). This is expected to be in a file named +``t*.pem`` eg. ``ta_rsa.pem``. + +The ``ta.h`` target in ``Makefile.inc`` will combine all such +``t*.pem`` files into that header. + +Signatures +---------- + +For Junos we currently use EC DSA signatures with file extension +``.esig`` so the signature for ``manifest`` would be ``manifest.esig`` + +This was the first signature method we used with the remote signing +servers and it ends up being a signature of a hash. +Ie. client sends a hash which during signing gets hashed again. +So for Junos we define VE_ECDSA_HASH_AGAIN which causes ``verify_ec`` +to hash again. + +Otherwise our EC DSA and RSA signatures are the default used by +OpenSSL - an original design goal was that a customer could verify our +signatures using nothing but an ``openssl`` binary. + + +Self tests +========== + +If you want the ``loader`` to perform self-test of a given signature +verification method on startup (a must for FIPS 140-2 certification) +you need to provide a suitable file signed by each supported trust +anchor. + +These should be stored in files with names that start with ``v`` and +have the same extension as the corresponding trust anchor. +Eg. for ``ta_openpgp.asc`` we use ``vc_openpgp.asc`` +and for ``ta_rsa.pem`` we use ``vc_rsa.pem``. + +Note for the X.509 case we simply extract the 2nd last certificate +from the relevant chain - which is sure to be a valid certificate +signed by the corresponding trust anchor. + +-------------------- +$FreeBSD$ diff --git a/lib/libsecureboot/brf.c b/lib/libsecureboot/brf.c new file mode 100644 index 000000000000..9e3af4a7c089 --- /dev/null +++ b/lib/libsecureboot/brf.c @@ -0,0 +1,403 @@ +// The functions here are derrived from BearSSL/tools/*.c +// When that is refactored suitably we can use them directly. +/* + * Copyright (c) 2016 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +__FBSDID("$FreeBSD$"); + +#define NEED_BRSSL_H +#include "libsecureboot-priv.h" +#include + + +static int +is_ign(int c) +{ + if (c == 0) { + return (0); + } + if (c <= 32 || c == '-' || c == '_' || c == '.' + || c == '/' || c == '+' || c == ':') + { + return (1); + } + return (0); +} + +/* + * Get next non-ignored character, normalised: + * ASCII letters are converted to lowercase + * control characters, space, '-', '_', '.', '/', '+' and ':' are ignored + * A terminating zero is returned as 0. + */ +static int +next_char(const char **ps, const char *limit) +{ + for (;;) { + int c; + + if (*ps == limit) { + return (0); + } + c = *(*ps) ++; + if (c == 0) { + return (0); + } + if (c >= 'A' && c <= 'Z') { + c += 'a' - 'A'; + } + if (!is_ign(c)) { + return (c); + } + } +} + +/* + * Partial string equality comparison, with normalisation. + */ +static int +eqstr_chunk(const char *s1, size_t s1_len, const char *s2, size_t s2_len) +{ + const char *lim1, *lim2; + + lim1 = s1 + s1_len; + lim2 = s2 + s2_len; + for (;;) { + int c1, c2; + + c1 = next_char(&s1, lim1); + c2 = next_char(&s2, lim2); + if (c1 != c2) { + return (0); + } + if (c1 == 0) { + return (1); + } + } +} + +/* see brssl.h */ +int +eqstr(const char *s1, const char *s2) +{ + return (eqstr_chunk(s1, strlen(s1), s2, strlen(s2))); +} + +int +looks_like_DER(const unsigned char *buf, size_t len) +{ + int fb; + size_t dlen; + + if (len < 2) { + return (0); + } + if (*buf ++ != 0x30) { + return (0); + } + fb = *buf ++; + len -= 2; + if (fb < 0x80) { + return ((size_t)fb == len); + } else if (fb == 0x80) { + return (0); + } else { + fb -= 0x80; + if (len < (size_t)fb + 2) { + return (0); + } + len -= (size_t)fb; + dlen = 0; + while (fb -- > 0) { + if (dlen > (len >> 8)) { + return (0); + } + dlen = (dlen << 8) + (size_t)*buf ++; + } + return (dlen == len); + } +} + +static void +vblob_append(void *cc, const void *data, size_t len) +{ + bvector *bv; + + bv = cc; + VEC_ADDMANY(*bv, data, len); +} + +void +free_pem_object_contents(pem_object *po) +{ + if (po != NULL) { + xfree(po->name); + xfree(po->data); + } +} + +pem_object * +decode_pem(const void *src, size_t len, size_t *num) +{ + VECTOR(pem_object) pem_list = VEC_INIT; + br_pem_decoder_context pc; + pem_object po, *pos; + const unsigned char *buf; + bvector bv = VEC_INIT; + int inobj; + int extra_nl; + + *num = 0; + br_pem_decoder_init(&pc); + buf = src; + inobj = 0; + po.name = NULL; + po.data = NULL; + po.data_len = 0; + extra_nl = 1; + while (len > 0) { + size_t tlen; + + tlen = br_pem_decoder_push(&pc, buf, len); + buf += tlen; + len -= tlen; + switch (br_pem_decoder_event(&pc)) { + + case BR_PEM_BEGIN_OBJ: + po.name = xstrdup(br_pem_decoder_name(&pc)); + br_pem_decoder_setdest(&pc, vblob_append, &bv); + inobj = 1; + break; + + case BR_PEM_END_OBJ: + if (inobj) { + po.data = VEC_TOARRAY(bv); + po.data_len = VEC_LEN(bv); + VEC_ADD(pem_list, po); + VEC_CLEAR(bv); + po.name = NULL; + po.data = NULL; + po.data_len = 0; + inobj = 0; + } + break; + + case BR_PEM_ERROR: + xfree(po.name); + VEC_CLEAR(bv); + ve_error_set("ERROR: invalid PEM encoding"); + VEC_CLEAREXT(pem_list, &free_pem_object_contents); + return (NULL); + } + + /* + * We add an extra newline at the end, in order to + * support PEM files that lack the newline on their last + * line (this is somwehat invalid, but PEM format is not + * standardised and such files do exist in the wild, so + * we'd better accept them). + */ + if (len == 0 && extra_nl) { + extra_nl = 0; + buf = (const unsigned char *)"\n"; + len = 1; + } + } + if (inobj) { + ve_error_set("ERROR: unfinished PEM object"); + xfree(po.name); + VEC_CLEAR(bv); + VEC_CLEAREXT(pem_list, &free_pem_object_contents); + return (NULL); + } + + *num = VEC_LEN(pem_list); + VEC_ADD(pem_list, po); + pos = VEC_TOARRAY(pem_list); + VEC_CLEAR(pem_list); + return (pos); +} + +br_x509_certificate * +parse_certificates(unsigned char *buf, size_t len, size_t *num) +{ + VECTOR(br_x509_certificate) cert_list = VEC_INIT; + pem_object *pos; + size_t u, num_pos; + br_x509_certificate *xcs; + br_x509_certificate dummy; + + *num = 0; + + /* + * Check for a DER-encoded certificate. + */ + if (looks_like_DER(buf, len)) { + xcs = xmalloc(2 * sizeof *xcs); + xcs[0].data = buf; + xcs[0].data_len = len; + xcs[1].data = NULL; + xcs[1].data_len = 0; + *num = 1; + return (xcs); + } + + pos = decode_pem(buf, len, &num_pos); + if (pos == NULL) { + return (NULL); + } + for (u = 0; u < num_pos; u ++) { + if (eqstr(pos[u].name, "CERTIFICATE") + || eqstr(pos[u].name, "X509 CERTIFICATE")) + { + br_x509_certificate xc; + + xc.data = pos[u].data; + xc.data_len = pos[u].data_len; + pos[u].data = NULL; + VEC_ADD(cert_list, xc); + } + } + for (u = 0; u < num_pos; u ++) { + free_pem_object_contents(&pos[u]); + } + xfree(pos); + + if (VEC_LEN(cert_list) == 0) { + return (NULL); + } + *num = VEC_LEN(cert_list); + dummy.data = NULL; + dummy.data_len = 0; + VEC_ADD(cert_list, dummy); + xcs = VEC_TOARRAY(cert_list); + VEC_CLEAR(cert_list); + return (xcs); +} + +br_x509_certificate * +read_certificates(const char *fname, size_t *num) +{ + br_x509_certificate *xcs; + unsigned char *buf; + size_t len; + + *num = 0; + + /* + * TODO: reading the whole file is crude; we could parse them + * in a streamed fashion. But it does not matter much in practice. + */ + buf = read_file(fname, &len); + if (buf == NULL) { + return (NULL); + } + xcs = parse_certificates(buf, len, num); + if (xcs == NULL) { + ve_error_set("ERROR: no certificate in file '%s'\n", fname); + } + xfree(buf); + return (xcs); +} + +/* see brssl.h */ +void +free_certificates(br_x509_certificate *certs, size_t num) +{ + size_t u; + + for (u = 0; u < num; u ++) { + xfree(certs[u].data); + } + xfree(certs); +} + + +static void +dn_append(void *ctx, const void *buf, size_t len) +{ + VEC_ADDMANY(*(bvector *)ctx, buf, len); +} + +int +certificate_to_trust_anchor_inner(br_x509_trust_anchor *ta, + br_x509_certificate *xc) +{ + br_x509_decoder_context dc; + bvector vdn = VEC_INIT; + br_x509_pkey *pk; + + br_x509_decoder_init(&dc, dn_append, &vdn); + br_x509_decoder_push(&dc, xc->data, xc->data_len); + pk = br_x509_decoder_get_pkey(&dc); + if (pk == NULL) { + ve_error_set("ERROR: CA decoding failed with error %d\n", + br_x509_decoder_last_error(&dc)); + VEC_CLEAR(vdn); + return (-1); + } + ta->dn.data = VEC_TOARRAY(vdn); + ta->dn.len = VEC_LEN(vdn); + VEC_CLEAR(vdn); + ta->flags = 0; + if (br_x509_decoder_isCA(&dc)) { + ta->flags |= BR_X509_TA_CA; + } + switch (pk->key_type) { + case BR_KEYTYPE_RSA: + ta->pkey.key_type = BR_KEYTYPE_RSA; + ta->pkey.key.rsa.n = xblobdup(pk->key.rsa.n, pk->key.rsa.nlen); + ta->pkey.key.rsa.nlen = pk->key.rsa.nlen; + ta->pkey.key.rsa.e = xblobdup(pk->key.rsa.e, pk->key.rsa.elen); + ta->pkey.key.rsa.elen = pk->key.rsa.elen; + break; + case BR_KEYTYPE_EC: + ta->pkey.key_type = BR_KEYTYPE_EC; + ta->pkey.key.ec.curve = pk->key.ec.curve; + ta->pkey.key.ec.q = xblobdup(pk->key.ec.q, pk->key.ec.qlen); + ta->pkey.key.ec.qlen = pk->key.ec.qlen; + break; + default: + ve_error_set("ERROR: unsupported public key type in CA\n"); + xfree(ta->dn.data); + return (-1); + } + return (0); +} + +/* see brssl.h */ +void +free_ta_contents(br_x509_trust_anchor *ta) +{ + xfree(ta->dn.data); + switch (ta->pkey.key_type) { + case BR_KEYTYPE_RSA: + xfree(ta->pkey.key.rsa.n); + xfree(ta->pkey.key.rsa.e); + break; + case BR_KEYTYPE_EC: + xfree(ta->pkey.key.ec.q); + break; + } +} diff --git a/lib/libsecureboot/h/libsecureboot.h b/lib/libsecureboot/h/libsecureboot.h new file mode 100644 index 000000000000..d0ec327b866a --- /dev/null +++ b/lib/libsecureboot/h/libsecureboot.h @@ -0,0 +1,94 @@ +/*- + * 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 + +#ifndef NEED_BRSSL_H +unsigned char * read_file(const char *, size_t *); +#endif + +extern int DebugVe; + +#define DEBUG_PRINTF(n, x) if (DebugVe >= n) printf x + +int ve_trust_init(void); +int ve_trust_add(const char *); +void ve_debug_set(int); +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 *); + +int ve_check_hash(br_hash_compat_context *, const br_hash_class *, + const char *, const char *, size_t); + +struct vectx; +struct vectx* vectx_open(int, const char *, off_t, struct stat *, int *); +ssize_t vectx_read(struct vectx *, void *, size_t); +off_t vectx_lseek(struct vectx *, off_t, int); +int vectx_close(struct vectx *); + +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(unsigned char *, size_t); +ssize_t ve_pcr_get(unsigned char *, size_t); + +/* flags for verify_{asc,sig,signed} */ +#define VEF_VERBOSE 1 + +#define VE_FINGERPRINT_OK 1 +/* 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 new file mode 100644 index 000000000000..3f8e6138cbc8 --- /dev/null +++ b/lib/libsecureboot/h/verify_file.h @@ -0,0 +1,47 @@ +/*- + * 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_VERIFIED 1 /* all good */ +#define VE_UNVERIFIED_OK 0 /* not verified but that's ok */ +#define VE_NOT_VERIFYING 2 /* we are not verifying */ + +struct stat; + +void ve_debug_set(int); +int ve_status_get(int); +int load_manifest(const char *, const char *, const char *, struct stat *); +int verify_file(int, const char *, off_t, int); +void verify_pcr_export(void); + +#endif /* _VERIFY_FILE_H_ */ diff --git a/lib/libsecureboot/libsecureboot-priv.h b/lib/libsecureboot/libsecureboot-priv.h new file mode 100644 index 000000000000..0618f05f15b1 --- /dev/null +++ b/lib/libsecureboot/libsecureboot-priv.h @@ -0,0 +1,48 @@ +/*- + * Copyright (c) 2017, 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_PRIV_H_ +#define _LIBSECUREBOOT_PRIV_H_ + +/* public api */ +#include "libsecureboot.h" + +size_t ve_trust_anchors_add(br_x509_certificate *, size_t); +char *fingerprint_info_lookup(int, const char *); + +br_x509_certificate * parse_certificates(unsigned char *, size_t, size_t *); +int certificate_to_trust_anchor_inner(br_x509_trust_anchor *, + br_x509_certificate *); + +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); + +int openpgp_self_tests(void); + +#endif /* _LIBSECUREBOOT_PRIV_H_ */ diff --git a/lib/libsecureboot/local.trust.mk b/lib/libsecureboot/local.trust.mk new file mode 100644 index 000000000000..f26a4ec0a9be --- /dev/null +++ b/lib/libsecureboot/local.trust.mk @@ -0,0 +1,114 @@ +# $FreeBSD$ + +# Consider this file an example. +# +# For Junos this is how we obtain trust anchor .pems +# the signing server (http://www.crufty.net/sjg/blog/signing-server.htm) +# for each key will provide the appropriate certificate chain on request + +# force these for Junos +MANIFEST_SKIP_ALWAYS= boot +VE_HASH_LIST= \ + SHA1 \ + SHA256 \ + SHA384 + +VE_SIGNATURE_LIST= \ + ECDSA + +VE_SIGNATURE_EXT_LIST= \ + esig + +VE_SELF_TESTS= yes + +.if ${MACHINE} == "host" && ${.CURDIR:T} == "tests" +# for testing +VE_HASH_LIST+= \ + SHA512 + +VE_SIGNATURE_LIST+= \ + RSA \ + DEPRECATED_RSA_SHA1 + +VE_SIGNATURE_EXT_LIST+= \ + sig +.endif + +SIGNER ?= ${SB_TOOLS_PATH:U/volume/buildtools/bin}/sign.py + +.if exists(${SIGNER}) +SIGN_HOST ?= ${SB_SITE:Usvl}-junos-signer.juniper.net +ECDSA_PORT:= ${133%y:L:gmtime} +SIGN_ECDSA= ${PYTHON} ${SIGNER} -u ${SIGN_HOST}:${ECDSA_PORT} -h sha256 +RSA2_PORT:= ${163%y:L:gmtime} +SIGN_RSA2= ${PYTHON} ${SIGNER} -u ${SIGN_HOST}:${RSA2_PORT} -h sha256 + +.if !empty(OPENPGP_SIGN_URL) +VE_SIGNATURE_LIST+= OPENPGP +VE_SIGNATURE_EXT_LIST+= asc + +SIGN_OPENPGP= ${PYTHON} ${SIGNER:H}/openpgp-sign.py -a -u ${OPENPGP_SIGN_URL} + +ta_openpgp.asc: + ${SIGN_OPENPGP} -C ${.TARGET} + +ta.h: ta_openpgp.asc + +.if ${VE_SELF_TESTS} != "no" +# for self test +vc_openpgp.asc: ta_openpgp.asc + ${SIGN_OPENPGP} ${.ALLSRC:M*.asc} + mv ta_openpgp.asc.asc ${.TARGET} + +ta.h: vc_openpgp.asc +.endif +.endif + +rcerts.pem: + ${SIGN_RSA2} -C ${.TARGET} + +ecerts.pem: + ${SIGN_ECDSA} -C ${.TARGET} + +.if ${VE_SIGNATURE_LIST:tu:MECDSA} != "" +# the last cert in the chain is the one we want +ta_ec.pem: ecerts.pem _LAST_PEM_USE + +.if ${VE_SELF_TESTS} != "no" +# these are for verification self test +vc_ec.pem: ecerts.pem _2ndLAST_PEM_USE +.endif +.endif + +.if ${VE_SIGNATURE_LIST:tu:MRSA} != "" +ta_rsa.pem: rcerts.pem _LAST_PEM_USE +.if ${VE_SELF_TESTS} != "no" +vc_rsa.pem: rcerts.pem _2ndLAST_PEM_USE +.endif +.endif + +# we take the mtime of this as our baseline time +BUILD_UTC_FILE= ecerts.pem +#VE_DEBUG_LEVEL=3 +#VE_VERBOSE_DEFAULT=1 + +.else +# you need to provide t*.pem or t*.asc files for each trust anchor +.if empty(TRUST_ANCHORS) +TRUST_ANCHORS!= cd ${.CURDIR} && 'ls' -1 *.pem t*.asc 2> /dev/null +.endif +.if empty(TRUST_ANCHORS) +.error Need TRUST_ANCHORS see ${.CURDIR}/README.rst +.endif +.if ${TRUST_ANCHORS:T:Mt*.pem} != "" +ta.h: ${TRUST_ANCHORS:M*.pem} +.endif +.if ${TRUST_ANCHORS:T:Mt*.asc} != "" +VE_SIGNATURE_LIST+= OPENPGP +VE_SIGNATURE_EXT_LIST+= asc +ta_asc.h: ${TRUST_ANCHORS:M*.asc} +.endif +# we take the mtime of this as our baseline time +BUILD_UTC_FILE?= ${TRUST_ANCHORS:[1]} +.endif + diff --git a/lib/libsecureboot/openpgp/Makefile.inc b/lib/libsecureboot/openpgp/Makefile.inc new file mode 100644 index 000000000000..a1b234271852 --- /dev/null +++ b/lib/libsecureboot/openpgp/Makefile.inc @@ -0,0 +1,48 @@ +# $FreeBSD$ + +# decode OpenPGP signatures per rfc4880 +.PATH: ${.PARSEDIR} + +CFLAGS+= -DUSE_BEARSSL + +BRSSL_SRCS+= dearmor.c +SRCS+= \ + decode.c \ + opgp_key.c \ + opgp_sig.c + +opgp_key.o opgp_key.po opgp_key.pico: ta_asc.h + +# Generate ta_asc.h containing one or more OpenPGP trust anchors. +# +# Since each trust anchor must be processed individually, +# we create ta_ASC as a list of pointers to them. +# +# If we are doing self-tests, we define another arrary vc_ASC +# containing pointers to a signature of each trust anchor. +# It is assumed that these v*.asc files are named similarly to +# the appropriate t*.asc so that the relative order of vc_ASC +# entries matches ta_ASC. +# +ta_asc.h: ${.ALLTARGETS:M[tv]*.asc:O:u} +.if ${VE_SIGNATURE_LIST:MOPENPGP} != "" + @( echo '/* Autogenerated - DO NOT EDIT!!! */'; echo; \ + echo "#define HAVE_TA_ASC 1"; \ + set -- ${.ALLSRC:Mt*.asc:@f@$f ${f:T:R}@}; \ + while test $$# -ge 2; do \ + file2c -sx "static const char $$2[] = {" ', 0x00 };' < $$1; \ + shift 2; \ + done; \ + echo 'static const char *ta_ASC[] = { ${.ALLSRC:Mt*.asc:T:R:ts,}, NULL };'; \ + echo; ) > ${.TARGET} +.if ${VE_SELF_TESTS} != "no" + @( echo "#define HAVE_VC_ASC 1"; \ + set -- ${.ALLSRC:Mv*.asc:@f@$f ${f:T:R}@}; \ + while test $$# -ge 2; do \ + file2c -sx "static const char $$2[] = {" ', 0x00 };' < $$1; \ + shift 2; \ + done; \ + echo 'static const char *vc_ASC[] = { ${.ALLSRC:Mv*.asc:T:R:ts,}, NULL };'; \ + echo; ) >> ${.TARGET} +.endif +.endif diff --git a/lib/libsecureboot/openpgp/dearmor.c b/lib/libsecureboot/openpgp/dearmor.c new file mode 100644 index 000000000000..3d53b54474dc --- /dev/null +++ b/lib/libsecureboot/openpgp/dearmor.c @@ -0,0 +1,144 @@ +/*- + * 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$"); + +#define NEED_BRSSL_H +#include +#include + +#include "decode.h" + +/** + * @brief decode ascii armor + * + * once we get rid of the trailing checksum + * we can treat as PEM. + * + * @sa rfc4880:6.2 + */ +unsigned char * +dearmor(char *pem, size_t nbytes, size_t *len) +{ +#ifdef USE_BEARSSL + pem_object *po; + size_t npo; +#else + BIO *bp; + char *name = NULL; + char *header = NULL; +#endif + unsigned char *data = NULL; + char *cp; + char *ep; + + /* we need to remove the Armor tail */ + if ((cp = strstr((char *)pem, "\n=")) && + (ep = strstr(cp, "\n---"))) { + memmove(cp, ep, nbytes - (size_t)(ep - pem)); + nbytes -= (size_t)(ep - cp); + pem[nbytes] = '\0'; + } +#ifdef USE_BEARSSL + /* we also need to remove any headers */ + if ((cp = strstr((char *)pem, "---\n")) && + (ep = strstr(cp, "\n\n"))) { + cp += 4; + ep += 2; + memmove(cp, ep, nbytes - (size_t)(ep - pem)); + nbytes -= (size_t)(ep - cp); + pem[nbytes] = '\0'; + } + if ((po = decode_pem(pem, nbytes, &npo))) { + data = po->data; + *len = po->data_len; + } +#else + if ((bp = BIO_new_mem_buf(pem, (int)nbytes))) { + long llen = (long)nbytes; + + if (!PEM_read_bio(bp, &name, &header, &data, &llen)) + data = NULL; + BIO_free(bp); + *len = (size_t)llen; + } +#endif + return (data); +} + +#ifdef MAIN_DEARMOR +#include +#include +#include + +/* + * Mostly a unit test. + */ +int +main(int argc, char *argv[]) +{ + const char *infile, *outfile; + unsigned char *data; + size_t n, x; + int fd; + int o; + + infile = outfile = NULL; + while ((o = getopt(argc, argv, "i:o:")) != -1) { + switch (o) { + case 'i': + infile = optarg; + break; + case 'o': + outfile = optarg; + break; + default: + errx(1, "unknown option: -%c", o); + } + } + if (!infile) + errx(1, "need -i infile"); + if (outfile) { + if ((fd = open(outfile, O_WRONLY|O_CREAT|O_TRUNC)) < 0) + err(1, "cannot open %s", outfile); + } else { + fd = 1; /* stdout */ + } + data = read_file(infile, &n); + if (!(data[0] & OPENPGP_TAG_ISTAG)) + data = dearmor(data, n, &n); + for (x = 0; x < n; ) { + o = write(fd, &data[x], (n - x)); + if (o < 0) + err(1, "cannot write"); + x += o; + } + if (fd != 1) + close(fd); + free(data); + return (0); +} +#endif diff --git a/lib/libsecureboot/openpgp/decode.c b/lib/libsecureboot/openpgp/decode.c new file mode 100644 index 000000000000..fd6a19ad1bea --- /dev/null +++ b/lib/libsecureboot/openpgp/decode.c @@ -0,0 +1,304 @@ +/*- + * Copyright (c) 2018, Juniper Networks, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include + +#include "decode.h" + +char * +octets2hex(unsigned char *ptr, size_t n) +{ + char *hex; + char *cp; + size_t i; + + hex = malloc(2 * n + 1); + if (hex != NULL) { + for (i = 0, cp = hex; i < n; i++) { + snprintf(&cp[i*2], 3, "%02X", ptr[i]); + } + } + return (hex); +} + +unsigned char * +i2octets(int n, size_t i) +{ + static unsigned char o[16]; + int x, j; + + if (n > 15) + return (NULL); + for (j = 0, x = n - 1; x >= 0; x--, j++) { + o[j] = (unsigned char)((i & (0xff << x * 8)) >> x * 8); + } + return (o); +} + +int +octets2i(unsigned char *ptr, size_t n) +{ + size_t i; + int val; + + for (val = i = 0; i < n; i++) { + val |= (*ptr++ << ((n - i - 1) * 8)); + } + return (val); +} + +/** + * @brief decode packet tag + * + * Also indicate if new/old and in the later case + * the length type + * + * @sa rfc4880:4.2 + */ +int +decode_tag(unsigned char *ptr, int *isnew, int *ltype) +{ + int tag; + + if (!ptr || !isnew || !ltype) + return (-1); + tag = *ptr; + + if (!(tag & OPENPGP_TAG_ISTAG)) + return (-1); /* we are lost! */ + *isnew = tag & OPENPGP_TAG_ISNEW; + if (*isnew) { + *ltype = -1; /* irrelevant */ + tag &= OPENPGP_TAG_NEW_MASK; + } else { + *ltype = tag & OPENPGP_TAG_OLD_TYPE; + tag = (tag & OPENPGP_TAG_OLD_MASK) >> 2; + } + return (tag); +} + +/** + * @brief return packet length + * + * @sa rfc4880:4.2.2 + */ +static int +decode_new_len(unsigned char **pptr) +{ + unsigned char *ptr; + int len = -1; + + if (pptr == NULL) + return (-1); + ptr = *pptr; + + if (!(*ptr < 224 || *ptr == 255)) + return (-1); /* not supported */ + + if (*ptr < 192) + len = *ptr++; + else if (*ptr < 224) { + len = ((*ptr - 192) << 8) + *(ptr+1) + 192; + ptr++; + } else if (*ptr == 255) { + len = (*ptr++ << 24); + len |= (*ptr++ << 16); + len |= (*ptr++ < 8); + len |= *ptr++; + } + + *pptr = ptr; + return (len); +} + +/** + * @brief return packet length + * + * @sa rfc4880:4.2.1 + */ +static int +decode_len(unsigned char **pptr, int ltype) +{ + unsigned char *ptr; + int len; + + if (ltype < 0) + return (decode_new_len(pptr)); + + if (pptr == NULL) + return (-1); + + ptr = *pptr; + + switch (ltype) { + case 0: + len = *ptr++; + break; + case 1: + len = (*ptr++ << 8); + len |= *ptr++; + break; + case 2: + len = *ptr++ << 24; + len |= *ptr++ << 16; + len |= *ptr++ << 8; + len |= *ptr++; + break; + case 3: + default: + /* Not supported */ + len = -1; + } + + *pptr = ptr; + return (len); +} + +/** + * @brief return pointer and length of an mpi + * + * @sa rfc4880:3.2 + */ +unsigned char * +decode_mpi(unsigned char **pptr, size_t *sz) +{ + unsigned char *data; + unsigned char *ptr; + size_t mlen; + + if (pptr == NULL || sz == NULL) + return (NULL); + + ptr = *pptr; + + mlen = (size_t)(*ptr++ << 8); + mlen |= (size_t)*ptr++; /* number of bits */ + mlen = (mlen + 7) / 8; /* number of bytes */ + *sz = mlen; + data = ptr; + ptr += mlen; + *pptr = ptr; + return (data); +} + +/** + * @brief return an OpenSSL BIGNUM from mpi + * + * @sa rfc4880:3.2 + */ +#ifdef USE_BEARSSL +unsigned char * +mpi2bn(unsigned char **pptr, size_t *sz) +{ + return (decode_mpi(pptr, sz)); +} +#else +BIGNUM * +mpi2bn(unsigned char **pptr) +{ + BIGNUM *bn = NULL; + unsigned char *ptr; + int mlen; + + if (pptr == NULL) + return (NULL); + + ptr = *pptr; + + mlen = (*ptr++ << 8); + mlen |= *ptr++; /* number of bits */ + mlen = (mlen + 7) / 8; /* number of bytes */ + bn = BN_bin2bn(ptr, mlen, NULL); + ptr += mlen; + *pptr = ptr; + + return (bn); +} +#endif + +/** + * @brief decode a packet + * + * If want is set, check that the packet tag matches + * if all good, call the provided decoder with its arg + * + * @return count of unconsumed data + * + * @sa rfc4880:4.2 + */ +int +decode_packet(int want, unsigned char **pptr, size_t nbytes, + decoder_t decoder, void *decoder_arg) +{ + int tag; + unsigned char *ptr; + unsigned char *nptr; + int isnew, ltype; + int len; + int hlen; + int rc = 0; + + nptr = ptr = *pptr; + + tag = decode_tag(ptr, &isnew, <ype); + + if (want > 0 && tag != want) + return (-1); + ptr++; + + len = rc = decode_len(&ptr, ltype); + hlen = (int)(ptr - nptr); + nptr = ptr + len; /* consume it */ + + if (decoder) + rc = decoder(tag, &ptr, len, decoder_arg); + *pptr = nptr; + nbytes -= (size_t)(hlen + len); + if (rc < 0) + return (rc); /* error */ + return ((int)nbytes); /* unconsumed data */ +} + +/** + * @brief decode a sub packet + * + * @sa rfc4880:5.2.3.1 + */ +unsigned char * +decode_subpacket(unsigned char **pptr, int *stag, int *sz) +{ + unsigned char *ptr; + int len; + + ptr = *pptr; + len = decode_len(&ptr, -1); + *sz = (int)(len + ptr - *pptr); + *pptr = ptr + len; + *stag = *ptr++; + return (ptr); +} diff --git a/lib/libsecureboot/openpgp/decode.h b/lib/libsecureboot/openpgp/decode.h new file mode 100644 index 000000000000..8da39c9cf14b --- /dev/null +++ b/lib/libsecureboot/openpgp/decode.h @@ -0,0 +1,57 @@ +/*- + * Copyright (c) 2018, Juniper Networks, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * $FreeBSD$ + */ + +#ifdef USE_BEARSSL +unsigned char * mpi2bn(unsigned char **pptr, size_t *sz); +#else +# include +# include +# include + +BIGNUM * mpi2bn(unsigned char **pptr); +#endif + +#define NEW(x) calloc(1,sizeof(x)) + +#define OPENPGP_TAG_ISTAG 0200 +#define OPENPGP_TAG_ISNEW 0100 +#define OPENPGP_TAG_NEW_MASK 0077 +#define OPENPGP_TAG_OLD_MASK 0074 +#define OPENPGP_TAG_OLD_TYPE 0003 + +typedef int (*decoder_t)(int, unsigned char **, int, void *); + +unsigned char * i2octets(int n, size_t i); +int octets2i(unsigned char *ptr, size_t n); +char * octets2hex(unsigned char *ptr, size_t n); +int decode_tag(unsigned char *ptr, int *isnew, int *ltype); +unsigned char * decode_mpi(unsigned char **pptr, size_t *sz); +unsigned char * dearmor(char *pem, size_t nbytes, size_t *len); +int decode_packet(int want, unsigned char **pptr, size_t nbytes, + decoder_t decoder, void *decoder_arg); +unsigned char * decode_subpacket(unsigned char **pptr, int *stag, int *sz); diff --git a/lib/libsecureboot/openpgp/opgp_key.c b/lib/libsecureboot/openpgp/opgp_key.c new file mode 100644 index 000000000000..1a8e06b1d677 --- /dev/null +++ b/lib/libsecureboot/openpgp/opgp_key.c @@ -0,0 +1,352 @@ +/*- + * Copyright (c) 2018, Juniper Networks, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include "../libsecureboot-priv.h" + +#include "decode.h" +#include "packet.h" + +/** + * @brief decode user-id packet + * + * This is trivial + * + * @sa rfc4880:5.11 + */ +ssize_t +decode_user(int tag, unsigned char **pptr, size_t len, OpenPGP_user *user) +{ + char *cp; + + if (tag == 13) { + user->id = malloc(len + 1); + strncpy(user->id, (char *)*pptr, len); + user->id[len] = '\0'; + user->name = user->id; + cp = strchr(user->id, '<'); + if (cp > user->id) { + user->id = strdup(user->id); + cp[-1] = '\0'; + } + } + *pptr += len; + return ((ssize_t)len); +} + +/** + * @brief decode a key packet + * + * We only really support v4 and RSA + * + * @sa rfc4880:5.5.1.1 + */ +ssize_t +decode_key(int tag, unsigned char **pptr, size_t len, OpenPGP_key *key) +{ + unsigned char *ptr; + int version; +#ifdef USE_BEARSSL + br_sha1_context mctx; + unsigned char mdata[br_sha512_SIZE]; + size_t mlen; +#else + RSA *rsa = NULL; + const EVP_MD *md = NULL; + EVP_MD_CTX mctx; + unsigned char mdata[EVP_MAX_MD_SIZE]; + unsigned int mlen; +#endif + + if (tag != 6) + return (-1); + + key->key = NULL; + ptr = *pptr; + version = *ptr; + if (version == 4) { /* all we support really */ + /* comput key fingerprint and id @sa rfc4880:12.2 */ + mdata[0] = 0x99; /* rfc4880: 12.2.a.1 */ + mdata[1] = (len >> 8) & 0xff; + mdata[2] = len & 0xff; + +#ifdef USE_BEARSSL + br_sha1_init(&mctx); + br_sha1_update(&mctx, mdata, 3); + br_sha1_update(&mctx, ptr, len); + br_sha1_out(&mctx, mdata); + mlen = br_sha1_SIZE; +#else + md = EVP_get_digestbyname("sha1"); + EVP_DigestInit(&mctx, md); + EVP_DigestUpdate(&mctx, mdata, 3); + EVP_DigestUpdate(&mctx, ptr, len); + mlen = (unsigned int)sizeof(mdata); + EVP_DigestFinal(&mctx, mdata, &mlen); +#endif + key->id = octets2hex(&mdata[mlen - 8], 8); + } + ptr += 1; /* done with version */ + ptr += 4; /* skip ctime */ + if (version == 3) + ptr += 2; /* valid days */ + key->sig_alg = *ptr++; + if (key->sig_alg == 1) { /* RSA */ +#ifdef USE_BEARSSL + key->key = NEW(br_rsa_public_key); + if (!key->key) + goto oops; + key->key->n = mpi2bn(&ptr, &key->key->nlen); + key->key->e = mpi2bn(&ptr, &key->key->elen); +#else + rsa = RSA_new(); + if (!rsa) + goto oops; + rsa->n = mpi2bn(&ptr); + rsa->e = mpi2bn(&ptr); + key->key = EVP_PKEY_new(); + if (!key->key || !rsa->n || !rsa->e) { + goto oops; + } + if (!EVP_PKEY_set1_RSA(key->key, rsa)) + goto oops; +#endif + } + /* we are done */ + return ((ssize_t)len); +oops: +#ifdef USE_BEARSSL + free(key->key); + key->key = NULL; +#else + if (rsa) + RSA_free(rsa); + if (key->key) { + EVP_PKEY_free(key->key); + key->key = NULL; + } +#endif + return (-1); +} + +static OpenPGP_key * +load_key_buf(unsigned char *buf, size_t nbytes) +{ + unsigned char *data = NULL; + unsigned char *ptr; + ssize_t rc; + int tag; + OpenPGP_key *key; + + if (!buf) + return (NULL); + + initialize(); + + if (!(buf[0] & OPENPGP_TAG_ISTAG)) { + data = dearmor((char *)buf, nbytes, &nbytes); + ptr = data; + } else + ptr = buf; + key = NEW(OpenPGP_key); + if (key) { + rc = decode_packet(0, &ptr, nbytes, (decoder_t)decode_key, + key); + if (rc < 0) { + free(key); + key = NULL; + } else if (rc > 8) { + int isnew, ltype; + + tag = decode_tag(ptr, &isnew, <ype); + if (tag == 13) { + key->user = NEW(OpenPGP_user); + rc = decode_packet(0, &ptr, (size_t)rc, + (decoder_t)decode_user, key->user); + } + } + } + free(data); + return (key); +} + +static LIST_HEAD(, OpenPGP_key_) trust_list; + +/** + * @brief add a key to our list + */ +void +openpgp_trust_add(OpenPGP_key *key) +{ + static int once = 0; + + if (!once) { + once = 1; + + LIST_INIT(&trust_list); + } + if (key) + LIST_INSERT_HEAD(&trust_list, key, entries); +} + +/** + * @brief if keyID is in our list return the key + * + * @return key or NULL + */ +OpenPGP_key * +openpgp_trust_get(const char *keyID) +{ + OpenPGP_key *key; + + openpgp_trust_add(NULL); /* initialize if needed */ + + LIST_FOREACH(key, &trust_list, entries) { + if (strcmp(key->id, keyID) == 0) + return (key); + } + return (NULL); +} + +/** + * @brief load a key from file + */ +OpenPGP_key * +load_key_file(const char *kfile) +{ + unsigned char *data = NULL; + size_t n; + OpenPGP_key *key; + + data = read_file(kfile, &n); + key = load_key_buf(data, n); + free(data); + openpgp_trust_add(key); + return (key); +} + +#include + +#ifndef _STANDALONE +/* we can lookup keyID in filesystem */ + +static const char *trust_store[] = { + "/var/db/trust", + "/etc/db/trust", + NULL, +}; + +/** + * @brief lookup key id in trust store + * + */ +static OpenPGP_key * +load_trusted_key_id(const char *keyID) +{ + char kfile[MAXPATHLEN]; + const char **tp; + size_t n; + + for (tp = trust_store; *tp; tp++) { + n = (size_t)snprintf(kfile, sizeof(kfile), "%s/%s", *tp, keyID); + if (n >= sizeof(kfile)) + return (NULL); + if (access(kfile, R_OK) == 0) { + return (load_key_file(kfile)); + } + } + return (NULL); +} +#endif + +/** + * @brief return key if trusted + */ +OpenPGP_key * +load_key_id(const char *keyID) +{ + static int once = 0; + OpenPGP_key *key; + + if (!once) { +#ifdef HAVE_TA_ASC + const char **tp; + char *cp; + size_t n; + + for (tp = ta_ASC; *tp; tp++) { + if ((cp = strdup(*tp))) { + n = strlen(cp); + key = load_key_buf((unsigned char *)cp, n); + free(cp); + openpgp_trust_add(key); + } + } +#endif + once = 1; + } + key = openpgp_trust_get(keyID); +#ifndef _STANDALONE + if (!key) + key = load_trusted_key_id(keyID); +#endif + return (key); +} + +/** + * @brief test that we can verify a signature + * + * Unlike X.509 certificates, we only support RSA keys + * so we stop after first successful signature verification + * (which should also be the first attempt ;-) + */ +int +openpgp_self_tests(void) +{ + static int rc = -1; /* remember result */ +#ifdef HAVE_VC_ASC + const char **vp, **tp; + char *fdata, *sdata = NULL; + size_t fbytes, sbytes; + + for (tp = ta_ASC, vp = vc_ASC; *tp && *vp && rc; tp++, vp++) { + if ((fdata = strdup(*tp)) && + (sdata = strdup(*vp))) { + fbytes = strlen(fdata); + sbytes = strlen(sdata); + rc = openpgp_verify("ta_ASC", + (unsigned char *)fdata, fbytes, + (unsigned char *)sdata, sbytes, 0); + printf("Testing verify OpenPGP signature:\t\t%s\n", + rc ? "Failed" : "Passed"); + } + free(fdata); + free(sdata); + } +#endif + return (rc); +} diff --git a/lib/libsecureboot/openpgp/opgp_sig.c b/lib/libsecureboot/openpgp/opgp_sig.c new file mode 100644 index 000000000000..804e2c83ce45 --- /dev/null +++ b/lib/libsecureboot/openpgp/opgp_sig.c @@ -0,0 +1,484 @@ +/*- + * 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) +{ +#ifdef _STANDALONE + ve_trust_init(); +#endif +} + +#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); + 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)) + 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)); +} +#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/openpgp/packet.h b/lib/libsecureboot/openpgp/packet.h new file mode 100644 index 000000000000..28ac3c9b40fe --- /dev/null +++ b/lib/libsecureboot/openpgp/packet.h @@ -0,0 +1,82 @@ +/*- + * Copyright (c) 2018, Juniper Networks, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * $FreeBSD$ + */ + +#include + +/* + * Structs to represent what we need + */ + +typedef struct OpenPGP_user { + char *id; + char *name; +} OpenPGP_user; + +struct OpenPGP_key_ { + char *id; + int sig_alg; + OpenPGP_user *user; +#ifdef USE_BEARSSL + br_rsa_public_key *key; +#else + EVP_PKEY *key; +#endif + LIST_ENTRY(OpenPGP_key_) entries; +}; + +typedef struct OpenPGP_key_ OpenPGP_key; + +typedef struct OpenPGP_sig { + char *key_id; + int sig_type; + int sig_alg; + int hash_alg; + unsigned char *pgpbytes; + size_t pgpbytes_len; + unsigned char *sig; + size_t sig_len; +} OpenPGP_sig; + +void openpgp_trust_add(OpenPGP_key *key); +OpenPGP_key * openpgp_trust_get(const char *keyID); +OpenPGP_key * load_key_file(const char *kfile); +OpenPGP_key * load_key_id(const char *keyID); +void initialize(void); +char * get_error_string(void); +int openpgp_verify(const char *filename, unsigned char *fdata, size_t fbytes, + unsigned char *sdata, size_t sbytes, int flags); +int openpgp_verify_file(const char *filename, unsigned char *fdata, + size_t nbytes); + +/* packet decoders */ +#define DECODER_DECL(x) \ + ssize_t decode_##x(int, unsigned char **, size_t, OpenPGP_##x *) + +DECODER_DECL(user); +DECODER_DECL(key); +DECODER_DECL(sig); diff --git a/lib/libsecureboot/readfile.c b/lib/libsecureboot/readfile.c new file mode 100644 index 000000000000..f632922143ce --- /dev/null +++ b/lib/libsecureboot/readfile.c @@ -0,0 +1,61 @@ +/*- + * 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_file(const char *path, size_t *len) +{ + int fd, m, n, x; + struct stat st; + unsigned char *buf; + + if (len) + *len = 0; + if ((fd = open(path, O_RDONLY)) < 0) + return (NULL); + fstat(fd, &st); + if (len) + *len = st.st_size; + buf = malloc(st.st_size + 1); + for (x = 0, m = st.st_size; m > 0; ) { + n = read(fd, &buf[x], m); + if (n < 0) + break; + if (n > 0) { + m -= n; + x += n; + } + } + close(fd); + if (m == 0) { + buf[st.st_size] = '\0'; + return (buf); + } + free(buf); + return (NULL); +} diff --git a/lib/libsecureboot/tests/Makefile b/lib/libsecureboot/tests/Makefile new file mode 100644 index 000000000000..0cc32562d36b --- /dev/null +++ b/lib/libsecureboot/tests/Makefile @@ -0,0 +1,18 @@ +# $FreeBSD$ + +PROG= tvo + +SRCS+= tvo.c +CFLAGS+= -DUNIT_TEST -g -O0 + +LIBADD+= bearssl +MAN= +NO_SHARED= + +# we want to test verify_file api too +# which requires a kludge or two +.include "../Makefile.libsa.inc" +BRSSL_CFLAGS := ${BRSSL_CFLAGS:N-DNO_STDIO} +XCFLAGS.verify_file += -DSOPEN_MAX=64 + +.include diff --git a/lib/libsecureboot/tests/Makefile.depend.host b/lib/libsecureboot/tests/Makefile.depend.host new file mode 100644 index 000000000000..772c1c8d1c6b --- /dev/null +++ b/lib/libsecureboot/tests/Makefile.depend.host @@ -0,0 +1,12 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + lib/libbearssl \ + + +.include + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/lib/libsecureboot/tests/tvo.c b/lib/libsecureboot/tests/tvo.c new file mode 100644 index 000000000000..9ad327801016 --- /dev/null +++ b/lib/libsecureboot/tests/tvo.c @@ -0,0 +1,181 @@ +/* + * 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 + +char *Skip; + +int +main(int argc, char *argv[]) +{ + int n; + int fd; + int c; + int Vflag; + char *cp; + char *prefix; + + prefix = NULL; + Skip = NULL; + + n = ve_trust_init(); + printf("Trust %d\n", n); + Vflag = 0; + + while ((c = getopt(argc, argv, "dp:s:T:V")) != -1) { + switch (c) { + 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; + default: + errx(1, "unknown option: -%c", c); + break; + } + } + + 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); + 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); + } 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); + } 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; + + fstat(fd, &st); + lseek(fd, 0, SEEK_SET); + off = st.st_size % 512; + vp = vectx_open(fd, argv[optind], off, + &st, &error); + 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); + + if (off < st.st_size) { + n = vectx_read(vp, buf, + sizeof(buf)); + if (n > 0) + off += n; + } + off = vectx_lseek(vp, 0, SEEK_END); + /* repeating that should be harmless */ + off = vectx_lseek(vp, 0, SEEK_END); + error = vectx_close(vp); + 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); + } + } + } + return (0); +} + diff --git a/lib/libsecureboot/vectx.c b/lib/libsecureboot/vectx.c new file mode 100644 index 000000000000..879cecac188d --- /dev/null +++ b/lib/libsecureboot/vectx.c @@ -0,0 +1,291 @@ +/*- + * 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 + +#include "libsecureboot-priv.h" + +/** + * @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 */ + size_t vec_size; /* size of path */ + size_t vec_hashsz; /* size of hash */ + int vec_fd; /* file descriptor */ + int vec_status; /* verification status */ +}; + +/** + * @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) +{ + struct vectx *ctx; + struct stat st; + size_t hashsz; + char *cp; + + if (!stp) { + if (fstat(fd, &st) == 0) + stp = &st; + } + + /* we *should* only get called for files */ + if (stp && !S_ISREG(stp->st_mode)) { + *error = 0; + 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_want = NULL; + ctx->vec_status = 0; + hashsz = 0; + + 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, "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; + ctx->vec_md->init(&ctx->vec_ctx.vtable); + + if (hashsz > 0 && off > 0) { + lseek(fd, 0, SEEK_SET); + vectx_lseek(ctx, off, SEEK_SET); + } + 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. + * + * @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 n; + size_t off; + + if (ctx->vec_hashsz == 0) /* nothing to do */ + return (read(ctx->vec_fd, buf, nbytes)); + + off = 0; + do { + n = read(ctx->vec_fd, &bp[off], nbytes - off); + if (n < 0) + return (n); + if (n > 0) { + ctx->vec_md->update(&ctx->vec_ctx.vtable, &bp[off], n); + off += n; + ctx->vec_off += n; + } + } while (n > 0 && off < nbytes); + return (off); +} + +/** + * @brief + * vectx equivalent of lseek + * + * We do not actually, seek, but call vectx_read + * to reach the desired offset. + * + * We do not support seeking backwards. + * + * @param[in] pctx + * pointer to ctx + * + * @param[in] off + * desired offset + * + * @param[in] whence + * + * @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)); + + /* + * Try to convert whence to SEEK_SET + * but we cannot support seeking backwards! + * Nor beyond end of file. + */ + if (whence == SEEK_END && off <= 0) { + whence = SEEK_SET; + off += ctx->vec_size; + } else if (whence == SEEK_CUR && off >= 0) { + whence = SEEK_SET; + off += ctx->vec_off; + } + if (whence != SEEK_SET || off < ctx->vec_off || + (size_t)off > ctx->vec_size) { + printf("ERROR: %s: unsupported operation\n", __func__); + return (-1); + } + 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. + * + * @param[in] pctx + * pointer to ctx + * + * @return 0 or an error. + */ +int +vectx_close(struct vectx *ctx) +{ + int rc; + + if (ctx->vec_hashsz == 0) { + rc = ctx->vec_status; + } else { + rc = ve_check_hash(&ctx->vec_ctx, ctx->vec_md, + ctx->vec_path, ctx->vec_want, ctx->vec_hashsz); + } + free(ctx); + return ((rc < 0) ? rc : 0); +} diff --git a/lib/libsecureboot/veopen.c b/lib/libsecureboot/veopen.c new file mode 100644 index 000000000000..b9da7e4479d8 --- /dev/null +++ b/lib/libsecureboot/veopen.c @@ -0,0 +1,458 @@ +/*- + * 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 (prefix) { + nfip->fi_prefix = strdup(prefix); + } else { + if (!filename) { + free(nfip); + return; + } + nfip->fi_prefix = strdup(filename); + cp = strrchr(nfip->fi_prefix, '/'); + if (cp) + *cp = '\0'; + else { + free(nfip->fi_prefix); + free(nfip); + return; + } + } + /* collapse any trailing ..[/] */ + n = 0; + while ((cp = strrchr(nfip->fi_prefix, '/')) != NULL) { + 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; + } +#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; +#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, "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_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/vepcr.c b/lib/libsecureboot/vepcr.c new file mode 100644 index 000000000000..0a3dc1fa12eb --- /dev/null +++ b/lib/libsecureboot/vepcr.c @@ -0,0 +1,84 @@ +/*- + * Copyright (c) 2018, Juniper Networks, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +__FBSDID("$FreeBSD$"); + +#include "libsecureboot-priv.h" + +/* + * To support measured boot without putting a ton + * of extra code in the loader, we just maintain + * a hash of all the hashes we (attempt to) verify. + * The loader can export this for kernel or rc script + * to feed to a TPM pcr register - hence the name ve_pcr. + * + * NOTE: in the current standard the TPM pcr register size is for SHA1, + * the fact that we provide a SHA256 hash should not matter + * as long as we are consistent - it can be truncated or hashed + * before feeding to TPM. + */ + +static const br_hash_class *pcr_md = NULL; +static br_hash_compat_context pcr_ctx; +static size_t pcr_hlen = 0; + +/** + * @brief initialize pcr context + * + * Real TPM registers only hold a SHA1 hash + * but we use SHA256 + */ +void +ve_pcr_init(void) +{ + pcr_hlen = br_sha256_SIZE; + pcr_md = &br_sha256_vtable; + pcr_md->init(&pcr_ctx.vtable); +} + +/** + * @brief update pcr context + */ +void +ve_pcr_update(unsigned char *data, size_t dlen) +{ + if (pcr_md) + pcr_md->update(&pcr_ctx.vtable, data, dlen); +} + +/** + * @brief get pcr result + */ +ssize_t +ve_pcr_get(unsigned char *buf, size_t sz) +{ + if (!pcr_md) + return (-1); + if (sz < pcr_hlen) + return (-1); + pcr_md->out(&pcr_ctx.vtable, buf); + return (pcr_hlen); +} + diff --git a/lib/libsecureboot/verify_file.c b/lib/libsecureboot/verify_file.c new file mode 100644 index 000000000000..8bde42090755 --- /dev/null +++ b/lib/libsecureboot/verify_file.c @@ -0,0 +1,421 @@ +/*- + * Copyright (c) 2017-2018, Juniper Networks, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Routines to verify files loaded. + */ +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +#include "libsecureboot.h" +#include +#include + +#define VE_NOT_CHECKED -42 + +#ifdef UNIT_TEST +# include +# define panic warn +/* + * define MANIFEST_SKIP to Skip - in tests/tvo.c so that + * tvo can control the value we use in find_manifest() + */ +extern char *Skip; +# undef MANIFEST_SKIP +# define MANIFEST_SKIP Skip +# undef VE_DEBUG_LEVEL +#endif + +/* + * We sometimes need to know if input is verified or not. + * The extra slot is for tracking most recently opened. + */ +static int ve_status[SOPEN_MAX+1]; +static int ve_status_state; +struct verify_status; +struct verify_status *verified_files = NULL; +static int loaded_manifests = 0; /* have we loaded anything? */ + +#define VE_STATUS_NONE 1 +#define VE_STATUS_VALID 2 + +/** + * @brief set ve status for fd + */ +static void +ve_status_set(int fd, int ves) +{ + if (fd >= 0 && fd < SOPEN_MAX) { + ve_status[fd] = ves; + ve_status_state = VE_STATUS_VALID; + } + ve_status[SOPEN_MAX] = ves; +} + +/** + * @brief get ve status of fd + * + * What we return depends on ve_status_state. + * + * @return + * @li ve_status[fd] if ve_status_state is valid + * @li ve_status[SOPEN_MAX] if ve_status_state is none + * @li VE_NOT_CHECKED if ve_status_state uninitialized + */ +int +ve_status_get(int fd) +{ + if (!ve_status_state) { + return (VE_NOT_CHECKED); + } + if (ve_status_state == VE_STATUS_VALID && + fd >= 0 && fd < SOPEN_MAX) + return (ve_status[fd]); + return (ve_status[SOPEN_MAX]); /* most recent */ +} + +/** + * @brief track verify status + * + * occasionally loader will make multiple calls + * for the same file, we need only check it once. + */ +struct verify_status { + dev_t vs_dev; + ino_t vs_ino; + int vs_status; + struct verify_status *vs_next; +}; + +static int +is_verified(struct stat *stp) +{ + struct verify_status *vsp; + + for (vsp = verified_files; vsp != NULL; vsp = vsp->vs_next) { + if (stp->st_dev == vsp->vs_dev && + stp->st_ino == vsp->vs_ino) + return (vsp->vs_status); + } + return (VE_NOT_CHECKED); +} + +/* most recent first, since most likely to see repeated calls. */ +static void +add_verify_status(struct stat *stp, int status) +{ + struct verify_status *vsp; + + vsp = malloc(sizeof(struct verify_status)); + vsp->vs_next = verified_files; + vsp->vs_dev = stp->st_dev; + vsp->vs_ino = stp->st_ino; + vsp->vs_status = status; + verified_files = vsp; +} + + +/** + * @brief + * load specified manifest if verified + */ +int +load_manifest(const char *name, const char *prefix, + const char *skip, struct stat *stp) +{ + struct stat st; + size_t n; + int rc; + char *content; + + rc = VE_FINGERPRINT_NONE; + n = strlen(name); + if (n > 4) { + if (!stp) { + stp = &st; + if (stat(name, &st) < 0 || !S_ISREG(st.st_mode)) + return (rc); + } + rc = is_verified(stp); + if (rc != VE_NOT_CHECKED) { + return (rc); + } + /* loader has no sense of time */ + ve_utc_set(stp->st_mtime); + content = (char *)verify_signed(name, VEF_VERBOSE); + if (content) { + fingerprint_info_add(name, prefix, skip, content, stp); + add_verify_status(stp, VE_VERIFIED); + loaded_manifests = 1; /* we are verifying! */ + DEBUG_PRINTF(3, ("loaded: %s %s %s\n", + name, prefix, skip)); + rc = 0; + } else { + rc = VE_FINGERPRINT_WRONG; + add_verify_status(stp, rc); /* remember */ + } + } + return (rc); +} + +static int +find_manifest(const char *name) +{ + struct stat st; + char buf[MAXPATHLEN]; + char *prefix; + char *skip; + const char **tp; + int rc; + + strncpy(buf, name, MAXPATHLEN - 1); + if (!(prefix = strrchr(buf, '/'))) + return (-1); + *prefix = '\0'; + prefix = strdup(buf); + rc = VE_FINGERPRINT_NONE; + for (tp = manifest_names; *tp; tp++) { + snprintf(buf, sizeof(buf), "%s/%s", prefix, *tp); + DEBUG_PRINTF(5, ("looking for %s\n", buf)); + if (stat(buf, &st) == 0 && st.st_size > 0) { +#ifdef MANIFEST_SKIP_ALWAYS /* very unlikely */ + skip = MANIFEST_SKIP_ALWAYS; +#else +#ifdef MANIFEST_SKIP /* rare */ + if (*tp[0] == '.') { + skip = MANIFEST_SKIP; + } else +#endif + skip = NULL; +#endif + rc = load_manifest(buf, skip ? prefix : NULL, + skip, &st); + break; + } + } + free(prefix); + return (rc); +} + + +#ifdef LOADER_VERIEXEC_TESTING +# define ACCEPT_NO_FP_DEFAULT VE_MUST + 1 +#else +# define ACCEPT_NO_FP_DEFAULT VE_MUST +#endif +#ifndef VE_VERBOSE_DEFAULT +# define VE_VERBOSE_DEFAULT 0 +#endif + +static int +severity_guess(const char *filename) +{ + const char *cp; + + /* Some files like *.conf and *.hints may be unsigned */ + if ((cp = strrchr(filename, '.'))) { + if (strcmp(cp, ".conf") == 0 || + strcmp(cp, ".cookie") == 0 || + strcmp(cp, ".hints") == 0) + return (VE_TRY); + } + return (VE_WANT); +} + +static void +verify_tweak(char *tweak, int *accept_no_fp, int *verbose, int *verifying) +{ + if (strcmp(tweak, "off") == 0) { + *verifying = 0; + } else if (strcmp(tweak, "strict") == 0) { + /* anything caller wants verified must be */ + *accept_no_fp = VE_WANT; + *verbose = 1; /* warn of anything unverified */ + /* treat self test failure as fatal */ + if (!ve_self_tests()) { + panic("verify self tests failed"); + } + } else if (strcmp(tweak, "modules") == 0) { + /* modules/kernel must be verified */ + *accept_no_fp = VE_MUST; + } else if (strcmp(tweak, "try") == 0) { + /* best effort: always accept no fp */ + *accept_no_fp = VE_MUST + 1; + } else if (strcmp(tweak, "verbose") == 0) { + *verbose = 1; + } else if (strcmp(tweak, "quiet") == 0) { + *verbose = 0; + } +} + +/** + * @brief verify an open file + * + * @param[in] fd + * open descriptor + * + * @param[in] filename + * path we opened and will use to lookup fingerprint + * + * @param[in] off + * current offset in fd, must be restored on return + * + * @param[in] severity + * indicator of how to handle case of missing fingerprint + * + * We look for a signed manifest relative to the filename + * just opened and verify/load it if needed. + * + * We then use verify_fd() in libve to actually verify that hash for + * open file. If it returns < 0 we look at the severity arg to decide + * what to do about it. + * + * If verify_fd() returns VE_FINGERPRINT_NONE we accept it if severity + * is < accept_no_fp. + * + * @return >= 0 on success < 0 on failure + */ +int +verify_file(int fd, const char *filename, off_t off, int severity) +{ + static int verifying = -1; + static int accept_no_fp = ACCEPT_NO_FP_DEFAULT; + static int verbose = VE_VERBOSE_DEFAULT; + struct stat st; + char *cp; + int rc; + + if (verifying < 0) { + verifying = ve_trust_init(); +#ifdef VE_DEBUG_LEVEL + ve_debug_set(VE_DEBUG_LEVEL); +#endif + /* 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(); + } + if (!verifying) + return (0); + + if (fd < 0 || fstat(fd, &st) < 0 || !S_ISREG(st.st_mode)) + return (0); + + DEBUG_PRINTF(3, ("fd=%d,name='%s',off=%lld,dev=%lld,ino=%lld\n", + fd, filename, (long long)off, (long long)st.st_dev, + (long long)st.st_ino)); + + + rc = is_verified(&st); + if (rc != VE_NOT_CHECKED) { + ve_status_set(fd, rc); + return (rc); + } + rc = find_manifest(filename); + if (rc != VE_FINGERPRINT_WRONG && loaded_manifests) { + if (severity <= VE_GUESS) + severity = severity_guess(filename); + if ((rc = verify_fd(fd, filename, off, &st)) >= 0) { + if (verbose || severity > VE_WANT) { +#if defined(VE_DEBUG_LEVEL) && VE_DEBUG_LEVEL > 0 + printf("Verified %s %llu,%llu\n", filename, + st.st_dev, st.st_ino); +#else + printf("Verified %s\n", filename); +#endif + } + if (severity < VE_MUST) { /* not a kernel or module */ + + if ((cp = strrchr(filename, '/'))) { + cp++; + if (strncmp(cp, "loader.ve.", 10) == 0) { + cp += 10; + verify_tweak(cp, + &accept_no_fp, &verbose, + &verifying); + } + } + } + add_verify_status(&st, rc); + ve_status_set(fd, rc); + return (rc); + } + + if (severity || verbose) + printf("Unverified: %s\n", ve_error_get()); + if (rc == VE_FINGERPRINT_UNKNOWN && severity < VE_MUST) + rc = VE_UNVERIFIED_OK; + else if (rc == VE_FINGERPRINT_NONE && severity < accept_no_fp) + rc = VE_UNVERIFIED_OK; + + add_verify_status(&st, rc); + } +#ifdef LOADER_VERIEXEC_TESTING + else if (rc != VE_FINGERPRINT_WRONG) { + /* + * We have not loaded any manifest and + * not because of verication failure. + * Most likely reason is we have none. + * Allow boot to proceed if we are just testing. + */ + return (VE_UNVERIFIED_OK); + } +#endif + if (rc == VE_FINGERPRINT_WRONG && severity > accept_no_fp) + panic("cannot continue"); + ve_status_set(fd, rc); + return (rc); +} + +/** + * @brief get hex string for pcr value and export + * + * In case we are doing measured boot, provide + * value of the "pcr" data we have accumulated. + */ +void +verify_pcr_export(void) +{ +#ifdef VE_PCR_SUPPORT + char hexbuf[br_sha256_SIZE * 2 + 2]; + unsigned char hbuf[br_sha256_SIZE]; + char *hex; + ssize_t hlen; + + hlen = ve_pcr_get(hbuf, sizeof(hbuf)); + if (hlen > 0) { + hex = hexdigest(hexbuf, sizeof(hexbuf), hbuf, hlen); + if (hex) { + hex[hlen*2] = '\0'; /* clobber newline */ + setenv("loader.ve.pcr", hex, 1); + } + } +#endif +} diff --git a/lib/libsecureboot/vesigned.c b/lib/libsecureboot/vesigned.c new file mode 100644 index 000000000000..47195f82494a --- /dev/null +++ b/lib/libsecureboot/vesigned.c @@ -0,0 +1,61 @@ +/*- + * Copyright (c) 2018, Juniper Networks, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +__FBSDID("$FreeBSD$"); + +#include + +#include + +/** + * @brief + * verify signed file + * + * We look for a signature using the extensions + * recorded in signature_exts. + * If we find a match we pass it to a suitable verify method. + * + * @return content of verified file or NULL on error. + */ +unsigned char * +verify_signed(const char *filename, int flags) +{ + struct stat st; + char buf[MAXPATHLEN]; + const char **se; + + for (se = signature_exts; *se; se++) { + snprintf(buf, sizeof(buf), "%s.%s", filename, *se); + if (stat(buf, &st) < 0 || !S_ISREG(st.st_mode)) + continue; + DEBUG_PRINTF(5, ("verify_signed: %s\n", buf)); +#ifdef VE_OPENPGP_SUPPORT + if (strncmp(*se, "asc", 3) == 0) + return (verify_asc(buf, flags)); +#endif + return (verify_sig(buf, flags)); + } + return (NULL); +} diff --git a/lib/libsecureboot/veta.c b/lib/libsecureboot/veta.c new file mode 100644 index 000000000000..f052f41792bf --- /dev/null +++ b/lib/libsecureboot/veta.c @@ -0,0 +1,111 @@ +/*- + * 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$"); + +/** + * @file veta.c - add to trust anchors + * + */ + +#define NEED_BRSSL_H +#include "libsecureboot-priv.h" +#include +#include +#include + +#ifdef VE_OPENPGP_SUPPORT +#include "openpgp/packet.h" +#endif + +/** + * @brief add trust anchors from a file + * + * The file might contain X.509 certs + * or OpenPGP public key + */ +static size_t +trust_file_add(const char *trust) +{ + br_x509_certificate *xcs; + size_t num; + + xcs = read_certificates(trust, &num); + if (xcs) { + num = ve_trust_anchors_add(xcs, num); + } +#ifdef VE_OPENPGP_SUPPORT + else if (load_key_file(trust)) { + num = 1; + } +#endif + return (num); +} + +/** + * @brief add trust anchors from a directory + * + * Pass each file in directory to trust_file_add + */ +static size_t +trust_dir_add(const char *trust) +{ + char fbuf[MAXPATHLEN]; + DIR *dh; + struct dirent *de; + struct stat st; + ssize_t sz; + size_t num; + + if (!(dh = opendir(trust))) + return (0); + for (num = 0, de = readdir(dh); de; de = readdir(dh)) { + if (de->d_name[0] == '.') + continue; + sz = snprintf(fbuf, sizeof(fbuf), "%s/%s", trust, de->d_name); + if (sz >= (ssize_t)sizeof(fbuf)) + continue; + if (stat(fbuf, &st) < 0 || S_ISDIR(st.st_mode)) + continue; + num += trust_file_add(fbuf); + } + closedir(dh); + return (num); +} + +/** + * @brief add trust anchors + */ +int +ve_trust_add(const char *trust) +{ + struct stat st; + + if (stat(trust, &st) < 0) + return (-1); + if (S_ISDIR(st.st_mode)) + return (trust_dir_add(trust)); + return (trust_file_add(trust)); +} diff --git a/lib/libsecureboot/vets.c b/lib/libsecureboot/vets.c new file mode 100644 index 000000000000..db96fe965a5c --- /dev/null +++ b/lib/libsecureboot/vets.c @@ -0,0 +1,700 @@ +/*- + * Copyright (c) 2017-2018, Juniper Networks, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +__FBSDID("$FreeBSD$"); + +/** + * @file vets.c - trust store + * @brief verify signatures + * + * We leverage code from BearSSL www.bearssl.org + */ + +#include +#include +#define NEED_BRSSL_H +#include "libsecureboot-priv.h" +#include +#include + +#ifndef TRUST_ANCHOR_STR +# define TRUST_ANCHOR_STR ta_PEM +#endif + +#define SECONDS_PER_DAY 86400 +#define X509_DAYS_TO_UTC0 719528 + +int DebugVe = 0; + +typedef VECTOR(br_x509_certificate) cert_list; + +static anchor_list trust_anchors = VEC_INIT; + +void +ve_debug_set(int n) +{ + DebugVe = n; +} + +static char ebuf[512]; + +char * +ve_error_get(void) +{ + return (ebuf); +} + +int +ve_error_set(const char *fmt, ...) +{ + int rc; + va_list ap; + + va_start(ap, fmt); + ebuf[0] = '\0'; + rc = 0; + if (fmt) { +#ifdef STAND_H + vsprintf(ebuf, fmt, ap); /* no vsnprintf in libstand */ + ebuf[sizeof(ebuf) - 1] = '\0'; + rc = strlen(ebuf); +#else + rc = vsnprintf(ebuf, sizeof(ebuf), fmt, ap); +#endif + } + va_end(ap); + return (rc); +} + +/* this is the time we use for verifying certs */ +static time_t ve_utc = 0; + +/** + * @brief + * set ve_utc used for certificate verification + * + * @param[in] utc + * time - ignored unless greater than current value. + */ +void +ve_utc_set(time_t utc) +{ + if (utc > ve_utc) { + DEBUG_PRINTF(2, ("Set ve_utc=%jd\n", (intmax_t)utc)); + ve_utc = utc; + } +} + +static void +free_cert_contents(br_x509_certificate *xc) +{ + xfree(xc->data); +} + +/** + * @brief + * add certs to our trust store + */ +size_t +ve_trust_anchors_add(br_x509_certificate *xcs, size_t num) +{ + 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(trust_anchors, ta); + } + return (u); +} + +/** + * @brief + * initialize our trust_anchors from ta_PEM + */ +int +ve_trust_init(void) +{ + br_x509_certificate *xcs; + static int once = -1; + size_t num; + + if (once >= 0) + return (once); + once = 0; + + ve_utc_set(time(NULL)); +#ifdef BUILD_UTC + ve_utc_set(BUILD_UTC); /* just in case */ +#endif + ve_error_set(NULL); /* make sure it is empty */ +#ifdef VE_PCR_SUPPORT + ve_pcr_init(); +#endif + +#ifdef TRUST_ANCHOR_STR + xcs = parse_certificates(__DECONST(unsigned char *, TRUST_ANCHOR_STR), + sizeof(TRUST_ANCHOR_STR), &num); + if (xcs == NULL) + return (0); + num = ve_trust_anchors_add(xcs, num); + once = (int) num; +#else + num = 0; +#endif + return (num); +} + +/** + * 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) +{ + 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(trust_anchors))); + + br_x509_minimal_init(&mc, &br_sha256_vtable, + &VEC_ELT(trust_anchors, 0), + VEC_LEN(trust_anchors)); +#ifdef VE_ECDSA_SUPPORT + br_x509_minimal_set_ecdsa(&mc, + &br_ec_prime_i31, &br_ecdsa_i31_vrfy_asn1); +#endif +#ifdef VE_RSA_SUPPORT + br_x509_minimal_set_rsa(&mc, &br_rsa_i31_pkcs1_vrfy); +#endif +#if defined(UNIT_TEST) && defined(VE_DEPRECATED_RSA_SHA1_SUPPORT) + /* This is deprecated! do not enable unless you absoultely have to */ + br_x509_minimal_set_hash(&mc, br_sha1_ID, &br_sha1_vtable); +#endif + br_x509_minimal_set_hash(&mc, br_sha256_ID, &br_sha256_vtable); +#ifdef VE_SHA384_SUPPORT + br_x509_minimal_set_hash(&mc, br_sha384_ID, &br_sha384_vtable); +#endif +#ifdef VE_SHA512_SUPPORT + br_x509_minimal_set_hash(&mc, br_sha512_ID, &br_sha512_vtable); +#endif + br_x509_minimal_set_name_elements(&mc, elts, num_elts); + +#ifdef _STANDALONE + /* + * Clock is probably bogus so we use ve_utc. + */ + mc.days = (ve_utc / SECONDS_PER_DAY) + X509_DAYS_TO_UTC0; + mc.seconds = (ve_utc % SECONDS_PER_DAY); +#endif + + mc.vtable->start_chain(&mc.vtable, NULL); + for (u = 0; u < VEC_LEN(chain); u ++) { + xc = &VEC_ELT(chain, u); + mc.vtable->start_cert(&mc.vtable, xc->data_len); + mc.vtable->append(&mc.vtable, xc->data, xc->data_len); + mc.vtable->end_cert(&mc.vtable); + switch (mc.err) { + case 0: + case BR_ERR_X509_OK: + case BR_ERR_X509_EXPIRED: + break; + default: + printf("u=%zu mc.err=%d\n", u, mc.err); + break; + } + } + err = mc.vtable->end_chain(&mc.vtable); + pk = NULL; + if (err) { + ve_error_set("Validation failed, err = %d", err); + } else { + tpk = mc.vtable->get_pkey(&mc.vtable, &usages); + if (tpk != NULL) { + pk = xpkeydup(tpk); + } + } + VEC_CLEAREXT(chain, &free_cert_contents); + return (pk); +} + +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; + + ve_trust_init(); + xcs = read_certificates(certs, &num); + if (xcs == NULL) { + ve_error_set("cannot read certificates\n"); + return (NULL); + } + pk = verify_signer_xcs(xcs, num, elts, num_elts); + xfree(xcs); + return (pk); +} + +/** + * we need a hex digest including trailing newline below + */ +char * +hexdigest(char *buf, size_t bufsz, unsigned char *foo, size_t foo_len) +{ + char const hex2ascii[] = "0123456789abcdef"; + size_t i; + + /* every binary byte is 2 chars in hex + newline + null */ + if (bufsz < (2 * foo_len) + 2) + return (NULL); + + for (i = 0; i < foo_len; i++) { + buf[i * 2] = hex2ascii[foo[i] >> 4]; + buf[i * 2 + 1] = hex2ascii[foo[i] & 0x0f]; + } + + buf[i * 2] = 0x0A; /* we also want a newline */ + buf[i * 2 + 1] = '\0'; + + return (buf); +} + +/** + * @brief + * verify file against sigfile using pk + * + * When we generated the signature in sigfile, + * we hashed (sha256) file, and sent that to signing server + * which hashed (sha256) that hash. + * + * To verify we need to replicate that result. + * + * @param[in] pk + * br_x509_pkey + * + * @paramp[in] file + * file to be verified + * + * @param[in] sigfile + * signature (PEM encoded) + * + * @return NULL on error, otherwise content of file. + */ +#ifdef VE_ECDSA_SUPPORT +static unsigned char * +verify_ec(br_x509_pkey *pk, const char *file, const char *sigfile) +{ + char hexbuf[br_sha512_SIZE * 2 + 2]; + unsigned char rhbuf[br_sha512_SIZE]; + char *hex; + br_sha256_context ctx; + unsigned char *fcp, *scp; + size_t flen, slen, plen; + pem_object *po; + const br_ec_impl *ec; + br_ecdsa_vrfy vrfy; + + if ((fcp = read_file(file, &flen)) == NULL) + return (NULL); + if ((scp = read_file(sigfile, &slen)) == NULL) { + free(fcp); + return (NULL); + } + if ((po = decode_pem(scp, slen, &plen)) == NULL) { + free(fcp); + free(scp); + return (NULL); + } + br_sha256_init(&ctx); + br_sha256_update(&ctx, fcp, flen); + br_sha256_out(&ctx, rhbuf); + 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); + } + ec = br_ec_get_default(); + vrfy = br_ecdsa_vrfy_asn1_get_default(); + if (!vrfy(ec, rhbuf, br_sha256_SIZE, &pk->key.ec, po->data, + po->data_len)) { + free(fcp); + fcp = NULL; + } + free(scp); + return (fcp); +} +#endif + +#if defined(VE_RSA_SUPPORT) || defined(VE_OPENPGP_SUPPORT) +/** + * @brief verify an rsa digest + * + * @return 0 on failure + */ +int +verify_rsa_digest (br_rsa_public_key *pkey, + const unsigned char *hash_oid, + unsigned char *mdata, size_t mlen, + unsigned char *sdata, size_t slen) +{ + br_rsa_pkcs1_vrfy vrfy; + unsigned char vhbuf[br_sha512_SIZE]; + + vrfy = br_rsa_pkcs1_vrfy_get_default(); + + if (!vrfy(sdata, slen, hash_oid, mlen, pkey, vhbuf) || + memcmp(vhbuf, mdata, mlen) != 0) { + return (0); /* fail */ + } + return (1); /* ok */ +} +#endif + +/** + * @brief + * verify file against sigfile using pk + * + * When we generated the signature in sigfile, + * we hashed (sha256) file, and sent that to signing server + * which hashed (sha256) that hash. + * + * Or (deprecated) we simply used sha1 hash directly. + * + * To verify we need to replicate that result. + * + * @param[in] pk + * br_x509_pkey + * + * @paramp[in] file + * file to be verified + * + * @param[in] sigfile + * signature (PEM encoded) + * + * @return NULL on error, otherwise content of file. + */ +#ifdef VE_RSA_SUPPORT +static unsigned char * +verify_rsa(br_x509_pkey *pk, const char *file, const char *sigfile) +{ + unsigned char rhbuf[br_sha512_SIZE]; + const unsigned char *hash_oid; + const br_hash_class *md; + br_hash_compat_context mctx; + unsigned char *fcp, *scp; + size_t flen, slen, plen, hlen; + pem_object *po; + + if ((fcp = read_file(file, &flen)) == NULL) + return (NULL); + if ((scp = read_file(sigfile, &slen)) == NULL) { + free(fcp); + return (NULL); + } + if ((po = decode_pem(scp, slen, &plen)) == NULL) { + free(fcp); + free(scp); + return (NULL); + } + + switch (po->data_len) { +#if defined(UNIT_TEST) && defined(VE_DEPRECATED_RSA_SHA1_SUPPORT) + case 256: + // this is our old deprecated sig method + md = &br_sha1_vtable; + hlen = br_sha1_SIZE; + hash_oid = BR_HASH_OID_SHA1; + break; +#endif + default: + md = &br_sha256_vtable; + hlen = br_sha256_SIZE; + hash_oid = BR_HASH_OID_SHA256; + break; + } + md->init(&mctx.vtable); + md->update(&mctx.vtable, fcp, flen); + md->out(&mctx.vtable, rhbuf); + if (!verify_rsa_digest(&pk->key.rsa, hash_oid, + rhbuf, hlen, po->data, po->data_len)) { + free(fcp); + fcp = NULL; + } + free(scp); + return (fcp); +} +#endif + +/** + * @brief + * verify a signature and return content of signed file + * + * @param[in] sigfile + * file containing signature + * we derrive path of signed file and certificate change from + * this. + * + * @param[in] flags + * only bit 1 significant so far + * + * @return NULL on error otherwise content of signed file + */ +unsigned char * +verify_sig(const char *sigfile, int flags) +{ + br_x509_pkey *pk; + br_name_element cn; + char cn_buf[80]; + unsigned char cn_oid[4]; + char pbuf[MAXPATHLEN]; + char *cp; + unsigned char *ucp; + size_t n; + + DEBUG_PRINTF(5, ("verify_sig: %s\n", sigfile)); + n = strlcpy(pbuf, sigfile, sizeof(pbuf)); + if (n > (sizeof(pbuf) - 5) || strcmp(&sigfile[n - 3], "sig") != 0) + return (NULL); + cp = strcpy(&pbuf[n - 3], "certs"); + /* + * We want the commonName field + * the OID we want is 2,5,4,3 - but DER encoded + */ + cn_oid[0] = 3; + cn_oid[1] = 0x55; + cn_oid[2] = 4; + cn_oid[3] = 3; + cn.oid = cn_oid; + cn.buf = cn_buf; + cn.len = sizeof(cn_buf); + + pk = verify_signer(pbuf, &cn, 1); + if (!pk) { + printf("cannot verify: %s: %s\n", pbuf, ve_error_get()); + return (NULL); + } + for (; cp > pbuf; cp--) { + if (*cp == '.') { + *cp = '\0'; + break; + } + } + switch (pk->key_type) { +#ifdef VE_ECDSA_SUPPORT + case BR_KEYTYPE_EC: + ucp = verify_ec(pk, pbuf, sigfile); + break; +#endif +#ifdef VE_RSA_SUPPORT + case BR_KEYTYPE_RSA: + ucp = verify_rsa(pk, pbuf, sigfile); + break; +#endif + default: + ucp = NULL; /* not supported */ + } + xfreepkey(pk); + if (!ucp) { + printf("Unverified %s (%s)\n", pbuf, + cn.status ? cn_buf : "unknown"); + } else if ((flags & 1) != 0) { + printf("Verified %s signed by %s\n", pbuf, + cn.status ? cn_buf : "someone we trust"); + } + return (ucp); +} + + +/** + * @brief verify hash matches + * + * We have finished hashing a file, + * see if we got the desired result. + * + * @param[in] ctx + * pointer to hash context + * + * @param[in] md + * pointer to hash class + * + * @param[in] path + * name of the file we are checking + * + * @param[in] want + * the expected result + * + * @param[in] hlen + * size of hash output + * + * @return 0 on success + */ +int +ve_check_hash(br_hash_compat_context *ctx, const br_hash_class *md, + const char *path, const char *want, size_t hlen) +{ + char hexbuf[br_sha512_SIZE * 2 + 2]; + unsigned char hbuf[br_sha512_SIZE]; + char *hex; + int rc; + int n; + + md->out(&ctx->vtable, hbuf); +#ifdef VE_PCR_SUPPORT + ve_pcr_update(hbuf, hlen); +#endif + hex = hexdigest(hexbuf, sizeof(hexbuf), hbuf, hlen); + if (!hex) + return (VE_FINGERPRINT_WRONG); + n = 2*hlen; + if ((rc = strncmp(hex, want, n))) { + ve_error_set("%s: %.*s != %.*s", path, n, hex, n, want); + rc = VE_FINGERPRINT_WRONG; + } + return (rc ? rc : VE_FINGERPRINT_OK); +} + +#ifdef VE_HASH_KAT_STR +static int +test_hash(const br_hash_class *md, size_t hlen, + const char *hname, const char *s, size_t slen, const char *want) +{ + br_hash_compat_context mctx; + + md->init(&mctx.vtable); + md->update(&mctx.vtable, s, slen); + return (ve_check_hash(&mctx, md, hname, want, hlen) != VE_FINGERPRINT_OK); +} + +#endif + +#define ve_test_hash(n, N) \ + printf("Testing hash: " #n "\t\t\t\t%s\n", \ + test_hash(&br_ ## n ## _vtable, br_ ## n ## _SIZE, #n, \ + VE_HASH_KAT_STR, sizeof(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) + return (0); + /* + * 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)) != NULL) { + 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); +#else + printf("No X.509 self tests\n"); +#endif /* VERIFY_CERTS_STR */ +#ifdef VE_OPENPGP_SUPPORT + if (!openpgp_self_tests()) + once++; +#endif + return (once); +}