diff options
Diffstat (limited to 'lib/libsecureboot')
36 files changed, 7299 insertions, 0 deletions
diff --git a/lib/libsecureboot/Makefile b/lib/libsecureboot/Makefile new file mode 100644 index 000000000000..85167e57abf2 --- /dev/null +++ b/lib/libsecureboot/Makefile @@ -0,0 +1,9 @@ +.include <src.opts.mk> + +LIB= secureboot + +.include "Makefile.inc" + +INCS= h/libsecureboot.h + +.include <bsd.lib.mk> diff --git a/lib/libsecureboot/Makefile.depend b/lib/libsecureboot/Makefile.depend new file mode 100644 index 000000000000..6ef78fac5cbf --- /dev/null +++ b/lib/libsecureboot/Makefile.depend @@ -0,0 +1,15 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/lib/libsecureboot/Makefile.depend.amd64 b/lib/libsecureboot/Makefile.depend.amd64 new file mode 100644 index 000000000000..6ef78fac5cbf --- /dev/null +++ b/lib/libsecureboot/Makefile.depend.amd64 @@ -0,0 +1,15 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + + +.include <dirdeps.mk> + +.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..11aba52f82cf --- /dev/null +++ b/lib/libsecureboot/Makefile.depend.host @@ -0,0 +1,10 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + + +.include <dirdeps.mk> + +.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..21ad019a0cb5 --- /dev/null +++ b/lib/libsecureboot/Makefile.inc @@ -0,0 +1,176 @@ +.if empty(BEARSSL) +.include "../libbearssl/Makefile.inc" +.endif + +.if !target(_${__this}_) +_${__this}_: + +libsecureboot_src:= ${.PARSEDIR} + +CFLAGS+= -I${libsecureboot_src}/h + +CFLAGS+= -DHAVE_BR_X509_TIME_CHECK + +.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 + +BRSSL_DEPS= \ + brf.c \ + vets.c \ + veta.c + +.if ${MK_LOADER_EFI_SECUREBOOT} != "no" +BRSSL_DEPS+= \ + efi_init.c \ + efi_variables.c +.endif + +# 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} + +# 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" + +# 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 + +CFLAGS+= -I. + +.if ${VE_SIGNATURE_EXT_LIST:M*sig} != "" +# this is what we use as our trust anchor +CFLAGS+= -DTRUST_ANCHOR_STR=ta_PEM + +.if ${VE_SELF_TESTS} != "no" +XCFLAGS.vets+= -DVERIFY_CERTS_STR=vc_PEM +.endif +.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 +# we use a string by default so it is independent of any other test +VE_HASH_KAT_STRLEN?= strlen +.if ${VE_HASH_KAT_STRLEN} == "strlen" +VE_HASH_KAT_STR?= self-tests-are-good +VE_HASH_KAT_STR_INPUT= echo -n +XCFLAGS.vets+= -DVE_HASH_KAT_STR=\"${VE_HASH_KAT_STR}\" +.else +VE_HASH_KAT_STR?= vc_PEM +VE_HASH_KAT_STR_INPUT= cat +VE_HASH_KAT_STRLEN= sizeof +XCFLAGS.vets+= -DVE_HASH_KAT_STR=${VE_HASH_KAT_STR} +.endif +XCFLAGS.vets+= -DVE_HASH_KAT_STRLEN=${VE_HASH_KAT_STRLEN} +.endif + +# this should be updated occassionally this is 2019-01-01Z +SOURCE_DATE_EPOCH?= 1546329600 +.if ${MK_REPRODUCIBLE_BUILD} == "yes" +BUILD_UTC?= ${SOURCE_DATE_EPOCH} +.endif +# BUILD_UTC provides a basis for the loader's notion of time +# By default we use the mtime of BUILD_UTC_FILE +.if empty(BUILD_UTC_FILE) +BUILD_UTC_FILE:= ${.PARSEDIR:tA}/${.PARSEFILE} +.endif +# you can of course set BUILD_UTC to any value you like +.if ${MAKE_VERSION} > 20230509 +BUILD_UTC?= ${BUILD_UTC_FILE:mtime} +.else +BUILD_UTC?= ${${STAT:Ustat} -L -f %m ${BUILD_UTC_FILE}:L:sh} +.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 VE_HASH_KAT_STR +# using each supported hash method +# to use as a Known Answer Test (needed for FIPS 140-2) +# +TA_PEM_LIST ?= ${.ALLSRC:N*crl*:Mt*.pem} +VC_PEM_LIST ?= ${.ALLSRC:N*crl*:Mv*.pem} +vets.o vets.po vets.pico: ta.h +ta.h: + @( echo '/* Autogenerated - DO NOT EDIT!!! */'; echo; \ + cat ${TA_PEM_LIST:O:u} /dev/null | \ + file2c -sx 'static const char ta_PEM[] = {' '};'; \ + echo "${.newline}${VE_HASH_LIST:O:u:@H@static char vh_$H[] = \"`${VE_HASH_KAT_STR_INPUT} ${VE_HASH_KAT_STR} | ${$H:U${H:tl}}`\";${.newline}@}"; ) > ${.TARGET} +.if ${VE_SELF_TESTS} != "no" + ( cat ${VC_PEM_LIST:O:u} /dev/null | \ + file2c -sx 'static const char vc_PEM[] = {' '};'; echo ) >> ${.TARGET} +.endif + echo '#define BUILD_UTC ${BUILD_UTC}' >> ${.TARGET} ${.OODATE:MNOMETA_CMP} + +# 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:O:u:@e@"$e",${.newline}@}'; \ + echo 'NULL };' ) > ${.TARGET} + + +.for s in ${BRSSL_SRCS} ${BRSSL_DEPS} +.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..7397d0fc1144 --- /dev/null +++ b/lib/libsecureboot/Makefile.libsa.inc @@ -0,0 +1,59 @@ +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 \ + +# Build library with support for the UEFI based authentication +.if ${MK_LOADER_EFI_SECUREBOOT} == "yes" +SRCS+= \ + efi/efi_variables.c \ + efi/efi_init.c + +# Add includes required by efi part +CFLAGS+= \ + -I${SRCTOP}/stand/efi/include \ + -I${SRCTOP}/lib/libsecureboot/efi/include \ + -I${SRCTOP}/stand/efi/include/${MACHINE} +.endif + +.if ${MK_LOADER_VERIEXEC_PASS_MANIFEST} == "yes" +SRCS+= \ + pass_manifest.c +.endif + +# 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} + +# only add these if set +XCFLAGS.verify_file+= \ + ${VE_DEBUG_LEVEL \ + VE_VERBOSE_DEFAULT \ + VE_VERIFY_FLAGS \ + :L:@v@${$v:S,^,-D$v=,}@} + +.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..f1d3c5679d35 --- /dev/null +++ b/lib/libsecureboot/README.rst @@ -0,0 +1,142 @@ +libsecureboot +************* + +This library depends one way or another on verifying detached 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 certificate 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.:: + + gpg -a --detach-sign manifest + +should produce the expected signature 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. + +Later I added a FakeHash class to the signing server so we could +generate signatures compatible with our previous RSA scheme and +others. + +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. + +-------------------- diff --git a/lib/libsecureboot/brf.c b/lib/libsecureboot/brf.c new file mode 100644 index 000000000000..fbd24f322bee --- /dev/null +++ b/lib/libsecureboot/brf.c @@ -0,0 +1,401 @@ +// The functions here are derrived from BearSSL/tools/*.c +// When that is refactored suitably we can use them directly. +/* + * Copyright (c) 2016 Thomas Pornin <pornin@bolet.org> + * + * 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 <sys/cdefs.h> +#define NEED_BRSSL_H +#include "libsecureboot-priv.h" +#include <brssl.h> + + +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/efi/efi_init.c b/lib/libsecureboot/efi/efi_init.c new file mode 100644 index 000000000000..1078d9bc24ca --- /dev/null +++ b/lib/libsecureboot/efi/efi_init.c @@ -0,0 +1,72 @@ +/*- + * Copyright (c) 2019 Stormshield. + * Copyright (c) 2019 Semihalf. + * + * 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 AUTHOR ``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 AUTHOR 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 <sys/cdefs.h> +#define NEED_BRSSL_H +#include "../libsecureboot-priv.h" +#include <brssl.h> + +void +ve_efi_init(void) +{ + br_x509_certificate *xcs; + hash_data *digests; + size_t num; + int result; + static int once = 0; + + if (once > 0) + return; + + once = 1; + + result = efi_secure_boot_enabled(); + if (result <= 0) + return; + + xcs = efi_get_trusted_certs(&num); + if (num > 0 && xcs != NULL) { + num = ve_trust_anchors_add(xcs, num); + free_certificates(xcs, num); + } + xcs = efi_get_forbidden_certs(&num); + if (num > 0 && xcs != NULL) { + num = ve_forbidden_anchors_add(xcs, num); + free_certificates(xcs, num); + } + digests = efi_get_forbidden_digests(&num); + if (num > 0 && digests != NULL) { + ve_forbidden_digest_add(digests, num); + /* + * Don't free the buffors for digests, + * since they are shallow copied. + */ + xfree(digests); + } + + return; +} diff --git a/lib/libsecureboot/efi/efi_variables.c b/lib/libsecureboot/efi/efi_variables.c new file mode 100644 index 000000000000..3c36bc66f788 --- /dev/null +++ b/lib/libsecureboot/efi/efi_variables.c @@ -0,0 +1,274 @@ +/*- + * Copyright (c) 2019 Stormshield. + * Copyright (c) 2019 Semihalf. + * + * 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 AUTHOR ``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 AUTHOR 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 <sys/cdefs.h> +#include <stand.h> +#include <string.h> + +#include <efi.h> +#include <efilib.h> +#include <Guid/ImageAuthentication.h> + +#define NEED_BRSSL_H +#include "../libsecureboot-priv.h" +#include <brssl.h> + +static EFI_GUID ImageSecurityDatabaseGUID = EFI_IMAGE_SECURITY_DATABASE_GUID; + +static EFI_GUID efiCertX509GUID = EFI_CERT_X509_GUID; +static EFI_GUID efiCertX509Sha256GUID = EFI_CERT_X509_SHA256_GUID; +static EFI_GUID efiCertX509Sha384GUID = EFI_CERT_X509_SHA384_GUID; +static EFI_GUID efiCertX509Sha5122UID = EFI_CERT_X509_SHA512_GUID; + +/* + * Check if Secure Boot is enabled in firmware. + * We evaluate two variables - Secure Boot and Setup Mode. + * Secure Boot is enforced only if the first one equals 1 and the other 0. + */ +int +efi_secure_boot_enabled(void) +{ + UINT8 SecureBoot; + UINT8 SetupMode; + size_t length; + EFI_STATUS status; + + length = sizeof(SecureBoot); + status = efi_global_getenv("SecureBoot", &SecureBoot, &length); + if (status != EFI_SUCCESS) { + if (status == EFI_NOT_FOUND) + return (0); + + printf("Failed to read \"SecureBoot\" variable\n"); + return (-efi_status_to_errno(status)); + } + + length = sizeof(SetupMode); + status = efi_global_getenv("SetupMode", &SetupMode, &length); + if (status != EFI_SUCCESS) + SetupMode = 0; + + printf(" SecureBoot: %d, SetupMode: %d\n", SecureBoot, SetupMode); + + return (SecureBoot == 1 && SetupMode == 0); +} + +/* + * Iterate through UEFI variable and extract X509 certificates from it. + * The EFI_* structures and related guids are defined in UEFI standard. + */ +static br_x509_certificate* +efi_get_certs(const char *name, size_t *count) +{ + br_x509_certificate *certs; + UINT8 *database; + EFI_SIGNATURE_LIST *list; + EFI_SIGNATURE_DATA *entry; + size_t db_size; + ssize_t cert_count; + EFI_STATUS status; + + database = NULL; + certs = NULL; + db_size = 0; + cert_count = 0; + + /* + * Read variable length and allocate memory for it + */ + status = efi_getenv(&ImageSecurityDatabaseGUID, name, database, &db_size); + if (status != EFI_BUFFER_TOO_SMALL) + return (NULL); + + database = malloc(db_size); + if (database == NULL) + return (NULL); + + status = efi_getenv(&ImageSecurityDatabaseGUID, name, database, &db_size); + if (status != EFI_SUCCESS) + goto fail; + + for (list = (EFI_SIGNATURE_LIST*) database; + db_size >= list->SignatureListSize && db_size > 0; + db_size -= list->SignatureListSize, + list = (EFI_SIGNATURE_LIST*) + ((UINT8*)list + list->SignatureListSize)) { + + /* We are only interested in entries containing X509 certs. */ + if (memcmp(&efiCertX509GUID, + &list->SignatureType, + sizeof(EFI_GUID)) != 0) { + continue; + } + + entry = (EFI_SIGNATURE_DATA*) + ((UINT8*)list + + sizeof(EFI_SIGNATURE_LIST) + + list->SignatureHeaderSize); + + certs = realloc(certs, + (cert_count + 1) * sizeof(br_x509_certificate)); + if (certs == NULL) { + cert_count = 0; + goto fail; + } + + certs[cert_count].data_len = list->SignatureSize - sizeof(EFI_GUID); + certs[cert_count].data = malloc(certs[cert_count].data_len); + if (certs[cert_count].data == NULL) + goto fail; + + memcpy(certs[cert_count].data, + entry->SignatureData, + certs[cert_count].data_len); + + cert_count++; + } + + *count = cert_count; + + xfree(database); + return (certs); + +fail: + free_certificates(certs, cert_count); + xfree(database); + return (NULL); + +} + +/* + * Extract digests from UEFI "dbx" variable. + * UEFI standard specifies three types of digest - sha256, sha386, sha512. + */ +hash_data* +efi_get_forbidden_digests(size_t *count) +{ + UINT8 *database; + hash_data *digests; + EFI_SIGNATURE_LIST *list; + EFI_SIGNATURE_DATA *entry; + size_t db_size, header_size, hash_size; + int digest_count, entry_count; + EFI_STATUS status; + + db_size = 0; + digest_count = 0; + database = NULL; + digests = NULL; + + status = efi_getenv(&ImageSecurityDatabaseGUID, "dbx", database, &db_size); + if (status != EFI_BUFFER_TOO_SMALL) + return (NULL); + + database = malloc(db_size); + if (database == NULL) + return (NULL); + + status = efi_getenv(&ImageSecurityDatabaseGUID, "dbx", database, &db_size); + if (status != EFI_SUCCESS) + goto fail; + + + for (list = (EFI_SIGNATURE_LIST*) database; + db_size >= list->SignatureListSize && db_size > 0; + db_size -= list->SignatureListSize, + list = (EFI_SIGNATURE_LIST*) + ((UINT8*)list + list->SignatureListSize)) { + + /* We are only interested in entries that contain digests. */ + if (memcmp(&efiCertX509Sha256GUID, &list->SignatureType, + sizeof(EFI_GUID)) == 0) { + hash_size = br_sha256_SIZE; + } else if (memcmp(&efiCertX509Sha384GUID, &list->SignatureType, + sizeof(EFI_GUID)) == 0) { + hash_size = br_sha384_SIZE; + } else if (memcmp(&efiCertX509Sha5122UID, &list->SignatureType, + sizeof(EFI_GUID)) == 0) { + hash_size = br_sha512_SIZE; + } else { + continue; + } + + /* + * A single entry can have multiple digests + * of the same type for some reason. + */ + header_size = sizeof(EFI_SIGNATURE_LIST) + list->SignatureHeaderSize; + + /* Calculate the number of entries basing on structure size */ + entry_count = list->SignatureListSize - header_size; + entry_count /= list->SignatureSize; + entry = (EFI_SIGNATURE_DATA*)((UINT8*)list + header_size); + while (entry_count-- > 0) { + digests = realloc(digests, + (digest_count + 1) * sizeof(hash_data)); + if (digests == NULL) { + digest_count = 0; + goto fail; + } + + digests[digest_count].data = malloc(hash_size); + if (digests[digest_count].data == NULL) + goto fail; + + memcpy(digests[digest_count].data, + entry->SignatureData, + hash_size); + digests[digest_count].hash_size = hash_size; + + entry = (EFI_SIGNATURE_DATA*)(entry + list->SignatureSize); + digest_count++; + } + } + xfree(database); + if (count != NULL) + *count = digest_count; + + return (digests); + +fail: + while (digest_count--) + xfree(digests[digest_count].data); + + xfree(database); + xfree(digests); + return (NULL); +} + +/* Copy x509 certificates from db */ +br_x509_certificate* +efi_get_trusted_certs(size_t *count) +{ + return (efi_get_certs("db", count)); +} + +/* Copy forbidden certificates from dbx */ +br_x509_certificate* +efi_get_forbidden_certs(size_t *count) +{ + return (efi_get_certs("dbx", count)); +} diff --git a/lib/libsecureboot/efi/include/Guid/GlobalVariable.h b/lib/libsecureboot/efi/include/Guid/GlobalVariable.h new file mode 100644 index 000000000000..3bd0a815fcf6 --- /dev/null +++ b/lib/libsecureboot/efi/include/Guid/GlobalVariable.h @@ -0,0 +1,192 @@ +/** @file + GUID for EFI (NVRAM) Variables. + Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR> + This program and the accompanying materials + are licensed and made available under the terms and conditions of the BSD License + which accompanies this distribution. The full text of the license may be found at + http://opensource.org/licenses/bsd-license.php + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + @par Revision Reference: + GUID defined in UEFI 2.1 +**/ + +#ifndef __GLOBAL_VARIABLE_GUID_H__ +#define __GLOBAL_VARIABLE_GUID_H__ + +#include <sys/cdefs.h> +#ifndef EFI_GLOBAL_VARIABLE +#define EFI_GLOBAL_VARIABLE \ + { \ + 0x8BE4DF61, 0x93CA, 0x11d2, {0xAA, 0x0D, 0x00, 0xE0, 0x98, 0x03, 0x2B, 0x8C } \ + } +#endif /* EFI_GLOBAL_VARIABLE */ + +extern EFI_GUID gEfiGlobalVariableGuid; + +// +// Follow UEFI 2.4 spec: +// To prevent name collisions with possible future globally defined variables, +// other internal firmware data variables that are not defined here must be +// saved with a unique VendorGuid other than EFI_GLOBAL_VARIABLE or +// any other GUID defined by the UEFI Specification. Implementations must +// only permit the creation of variables with a UEFI Specification-defined +// VendorGuid when these variables are documented in the UEFI Specification. +// +// Note: except the globally defined variables defined below, the spec also defines +// L"Boot####" - A boot load option. +// L"Driver####" - A driver load option. +// L"SysPrep####" - A System Prep application load option. +// L"Key####" - Describes hot key relationship with a Boot#### load option. +// The attribute for them is NV+BS+RT, #### is a printed hex value, and no 0x or h +// is included in the hex value. They can not be expressed as a #define like other globally +// defined variables, it is because we can not list the Boot0000, Boot0001, etc one by one. +// + +/// +/// The language codes that the firmware supports. This value is deprecated. +/// Its attribute is BS+RT. +/// +#define EFI_LANG_CODES_VARIABLE_NAME L"LangCodes" +/// +/// The language code that the system is configured for. This value is deprecated. +/// Its attribute is NV+BS+RT. +/// +#define EFI_LANG_VARIABLE_NAME L"Lang" +/// +/// The firmware's boot managers timeout, in seconds, before initiating the default boot selection. +/// Its attribute is NV+BS+RT. +/// +#define EFI_TIME_OUT_VARIABLE_NAME L"Timeout" +/// +/// The language codes that the firmware supports. +/// Its attribute is BS+RT. +/// +#define EFI_PLATFORM_LANG_CODES_VARIABLE_NAME L"PlatformLangCodes" +/// +/// The language code that the system is configured for. +/// Its attribute is NV+BS+RT. +/// +#define EFI_PLATFORM_LANG_VARIABLE_NAME L"PlatformLang" +/// +/// The device path of the default input/output/error output console. +/// Its attribute is NV+BS+RT. +/// +#define EFI_CON_IN_VARIABLE_NAME L"ConIn" +#define EFI_CON_OUT_VARIABLE_NAME L"ConOut" +#define EFI_ERR_OUT_VARIABLE_NAME L"ErrOut" +/// +/// The device path of all possible input/output/error output devices. +/// Its attribute is BS+RT. +/// +#define EFI_CON_IN_DEV_VARIABLE_NAME L"ConInDev" +#define EFI_CON_OUT_DEV_VARIABLE_NAME L"ConOutDev" +#define EFI_ERR_OUT_DEV_VARIABLE_NAME L"ErrOutDev" +/// +/// The ordered boot option load list. +/// Its attribute is NV+BS+RT. +/// +#define EFI_BOOT_ORDER_VARIABLE_NAME L"BootOrder" +/// +/// The boot option for the next boot only. +/// Its attribute is NV+BS+RT. +/// +#define EFI_BOOT_NEXT_VARIABLE_NAME L"BootNext" +/// +/// The boot option that was selected for the current boot. +/// Its attribute is BS+RT. +/// +#define EFI_BOOT_CURRENT_VARIABLE_NAME L"BootCurrent" +/// +/// The types of boot options supported by the boot manager. Should be treated as read-only. +/// Its attribute is BS+RT. +/// +#define EFI_BOOT_OPTION_SUPPORT_VARIABLE_NAME L"BootOptionSupport" +/// +/// The ordered driver load option list. +/// Its attribute is NV+BS+RT. +/// +#define EFI_DRIVER_ORDER_VARIABLE_NAME L"DriverOrder" +/// +/// The ordered System Prep Application load option list. +/// Its attribute is NV+BS+RT. +/// +#define EFI_SYS_PREP_ORDER_VARIABLE_NAME L"SysPrepOrder" +/// +/// Identifies the level of hardware error record persistence +/// support implemented by the platform. This variable is +/// only modified by firmware and is read-only to the OS. +/// Its attribute is NV+BS+RT. +/// +#define EFI_HW_ERR_REC_SUPPORT_VARIABLE_NAME L"HwErrRecSupport" +/// +/// Whether the system is operating in setup mode (1) or not (0). +/// All other values are reserved. Should be treated as read-only. +/// Its attribute is BS+RT. +/// +#define EFI_SETUP_MODE_NAME L"SetupMode" +/// +/// The Key Exchange Key Signature Database. +/// Its attribute is NV+BS+RT+AT. +/// +#define EFI_KEY_EXCHANGE_KEY_NAME L"KEK" +/// +/// The public Platform Key. +/// Its attribute is NV+BS+RT+AT. +/// +#define EFI_PLATFORM_KEY_NAME L"PK" +/// +/// Array of GUIDs representing the type of signatures supported +/// by the platform firmware. Should be treated as read-only. +/// Its attribute is BS+RT. +/// +#define EFI_SIGNATURE_SUPPORT_NAME L"SignatureSupport" +/// +/// Whether the platform firmware is operating in Secure boot mode (1) or not (0). +/// All other values are reserved. Should be treated as read-only. +/// Its attribute is BS+RT. +/// +#define EFI_SECURE_BOOT_MODE_NAME L"SecureBoot" +/// +/// The OEM's default Key Exchange Key Signature Database. Should be treated as read-only. +/// Its attribute is BS+RT. +/// +#define EFI_KEK_DEFAULT_VARIABLE_NAME L"KEKDefault" +/// +/// The OEM's default public Platform Key. Should be treated as read-only. +/// Its attribute is BS+RT. +/// +#define EFI_PK_DEFAULT_VARIABLE_NAME L"PKDefault" +/// +/// The OEM's default secure boot signature store. Should be treated as read-only. +/// Its attribute is BS+RT. +/// +#define EFI_DB_DEFAULT_VARIABLE_NAME L"dbDefault" +/// +/// The OEM's default secure boot blacklist signature store. Should be treated as read-only. +/// Its attribute is BS+RT. +/// +#define EFI_DBX_DEFAULT_VARIABLE_NAME L"dbxDefault" +/// +/// The OEM's default secure boot timestamp signature store. Should be treated as read-only. +/// Its attribute is BS+RT. +/// +#define EFI_DBT_DEFAULT_VARIABLE_NAME L"dbtDefault" +/// +/// Allows the firmware to indicate supported features and actions to the OS. +/// Its attribute is BS+RT. +/// +#define EFI_OS_INDICATIONS_SUPPORT_VARIABLE_NAME L"OsIndicationsSupported" +/// +/// Allows the OS to request the firmware to enable certain features and to take certain actions. +/// Its attribute is NV+BS+RT. +/// +#define EFI_OS_INDICATIONS_VARIABLE_NAME L"OsIndications" +/// +/// Whether the system is configured to use only vendor provided +/// keys or not. Should be treated as read-only. +/// Its attribute is BS+RT. +/// +#define EFI_VENDOR_KEYS_VARIABLE_NAME L"VendorKeys" + +#endif diff --git a/lib/libsecureboot/efi/include/Guid/ImageAuthentication.h b/lib/libsecureboot/efi/include/Guid/ImageAuthentication.h new file mode 100644 index 000000000000..b1fc1f54cce1 --- /dev/null +++ b/lib/libsecureboot/efi/include/Guid/ImageAuthentication.h @@ -0,0 +1,350 @@ +/** @file + Image signature database are defined for the signed image validation. + Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved.<BR> + This program and the accompanying materials + are licensed and made available under the terms and conditions of the BSD License + which accompanies this distribution. The full text of the license may be found at + http://opensource.org/licenses/bsd-license.php + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + @par Revision Reference: + GUIDs defined in UEFI 2.5 spec. +**/ + +#ifndef __IMAGE_AUTHTICATION_H__ +#define __IMAGE_AUTHTICATION_H__ + +#include <sys/cdefs.h> +#include <Guid/GlobalVariable.h> +#include <Protocol/Hash.h> + +#define EFI_IMAGE_SECURITY_DATABASE_GUID \ + { \ + 0xd719b2cb, 0x3d3a, 0x4596, { 0xa3, 0xbc, 0xda, 0xd0, 0xe, 0x67, 0x65, 0x6f } \ + } + +/// +/// Varialbe name with guid EFI_IMAGE_SECURITY_DATABASE_GUID +/// for the authorized signature database. +/// +#define EFI_IMAGE_SECURITY_DATABASE L"db" +/// +/// Varialbe name with guid EFI_IMAGE_SECURITY_DATABASE_GUID +/// for the forbidden signature database. +/// +#define EFI_IMAGE_SECURITY_DATABASE1 L"dbx" +/// +/// Variable name with guid EFI_IMAGE_SECURITY_DATABASE_GUID +/// for the timestamp signature database. +/// +#define EFI_IMAGE_SECURITY_DATABASE2 L"dbt" + +#define SECURE_BOOT_MODE_ENABLE 1 +#define SECURE_BOOT_MODE_DISABLE 0 + +#define SETUP_MODE 1 +#define USER_MODE 0 + +//*********************************************************************** +// Signature Database +//*********************************************************************** +/// +/// The format of a signature database. +/// +#pragma pack(1) + +typedef struct { + /// + /// An identifier which identifies the agent which added the signature to the list. + /// + EFI_GUID SignatureOwner; + /// + /// The format of the signature is defined by the SignatureType. + /// + UINT8 SignatureData[1]; +} EFI_SIGNATURE_DATA; + +typedef struct { + /// + /// Type of the signature. GUID signature types are defined in below. + /// + EFI_GUID SignatureType; + /// + /// Total size of the signature list, including this header. + /// + UINT32 SignatureListSize; + /// + /// Size of the signature header which precedes the array of signatures. + /// + UINT32 SignatureHeaderSize; + /// + /// Size of each signature. + /// + UINT32 SignatureSize; + /// + /// Header before the array of signatures. The format of this header is specified + /// by the SignatureType. + /// UINT8 SignatureHeader[SignatureHeaderSize]; + /// + /// An array of signatures. Each signature is SignatureSize bytes in length. + /// EFI_SIGNATURE_DATA Signatures[][SignatureSize]; + /// +} EFI_SIGNATURE_LIST; + +typedef struct { + /// + /// The SHA256 hash of an X.509 certificate's To-Be-Signed contents. + /// + EFI_SHA256_HASH ToBeSignedHash; + /// + /// The time that the certificate shall be considered to be revoked. + /// + EFI_TIME TimeOfRevocation; +} EFI_CERT_X509_SHA256; + +typedef struct { + /// + /// The SHA384 hash of an X.509 certificate's To-Be-Signed contents. + /// + EFI_SHA384_HASH ToBeSignedHash; + /// + /// The time that the certificate shall be considered to be revoked. + /// + EFI_TIME TimeOfRevocation; +} EFI_CERT_X509_SHA384; + +typedef struct { + /// + /// The SHA512 hash of an X.509 certificate's To-Be-Signed contents. + /// + EFI_SHA512_HASH ToBeSignedHash; + /// + /// The time that the certificate shall be considered to be revoked. + /// + EFI_TIME TimeOfRevocation; +} EFI_CERT_X509_SHA512; + +#pragma pack() + +/// +/// This identifies a signature containing a SHA-256 hash. The SignatureHeader size shall +/// always be 0. The SignatureSize shall always be 16 (size of SignatureOwner component) + +/// 32 bytes. +/// +#define EFI_CERT_SHA256_GUID \ + { \ + 0xc1c41626, 0x504c, 0x4092, {0xac, 0xa9, 0x41, 0xf9, 0x36, 0x93, 0x43, 0x28} \ + } + +/// +/// This identifies a signature containing an RSA-2048 key. The key (only the modulus +/// since the public key exponent is known to be 0x10001) shall be stored in big-endian +/// order. +/// The SignatureHeader size shall always be 0. The SignatureSize shall always be 16 (size +/// of SignatureOwner component) + 256 bytes. +/// +#define EFI_CERT_RSA2048_GUID \ + { \ + 0x3c5766e8, 0x269c, 0x4e34, {0xaa, 0x14, 0xed, 0x77, 0x6e, 0x85, 0xb3, 0xb6} \ + } + +/// +/// This identifies a signature containing a RSA-2048 signature of a SHA-256 hash. The +/// SignatureHeader size shall always be 0. The SignatureSize shall always be 16 (size of +/// SignatureOwner component) + 256 bytes. +/// +#define EFI_CERT_RSA2048_SHA256_GUID \ + { \ + 0xe2b36190, 0x879b, 0x4a3d, {0xad, 0x8d, 0xf2, 0xe7, 0xbb, 0xa3, 0x27, 0x84} \ + } + +/// +/// This identifies a signature containing a SHA-1 hash. The SignatureSize shall always +/// be 16 (size of SignatureOwner component) + 20 bytes. +/// +#define EFI_CERT_SHA1_GUID \ + { \ + 0x826ca512, 0xcf10, 0x4ac9, {0xb1, 0x87, 0xbe, 0x1, 0x49, 0x66, 0x31, 0xbd} \ + } + +/// +/// TThis identifies a signature containing a RSA-2048 signature of a SHA-1 hash. The +/// SignatureHeader size shall always be 0. The SignatureSize shall always be 16 (size of +/// SignatureOwner component) + 256 bytes. +/// +#define EFI_CERT_RSA2048_SHA1_GUID \ + { \ + 0x67f8444f, 0x8743, 0x48f1, {0xa3, 0x28, 0x1e, 0xaa, 0xb8, 0x73, 0x60, 0x80} \ + } + +/// +/// This identifies a signature based on an X.509 certificate. If the signature is an X.509 +/// certificate then verification of the signature of an image should validate the public +/// key certificate in the image using certificate path verification, up to this X.509 +/// certificate as a trusted root. The SignatureHeader size shall always be 0. The +/// SignatureSize may vary but shall always be 16 (size of the SignatureOwner component) + +/// the size of the certificate itself. +/// Note: This means that each certificate will normally be in a separate EFI_SIGNATURE_LIST. +/// +#define EFI_CERT_X509_GUID \ + { \ + 0xa5c059a1, 0x94e4, 0x4aa7, {0x87, 0xb5, 0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72} \ + } + +/// +/// This identifies a signature containing a SHA-224 hash. The SignatureHeader size shall +/// always be 0. The SignatureSize shall always be 16 (size of SignatureOwner component) + +/// 28 bytes. +/// +#define EFI_CERT_SHA224_GUID \ + { \ + 0xb6e5233, 0xa65c, 0x44c9, {0x94, 0x7, 0xd9, 0xab, 0x83, 0xbf, 0xc8, 0xbd} \ + } + +/// +/// This identifies a signature containing a SHA-384 hash. The SignatureHeader size shall +/// always be 0. The SignatureSize shall always be 16 (size of SignatureOwner component) + +/// 48 bytes. +/// +#define EFI_CERT_SHA384_GUID \ + { \ + 0xff3e5307, 0x9fd0, 0x48c9, {0x85, 0xf1, 0x8a, 0xd5, 0x6c, 0x70, 0x1e, 0x1} \ + } + +/// +/// This identifies a signature containing a SHA-512 hash. The SignatureHeader size shall +/// always be 0. The SignatureSize shall always be 16 (size of SignatureOwner component) + +/// 64 bytes. +/// +#define EFI_CERT_SHA512_GUID \ + { \ + 0x93e0fae, 0xa6c4, 0x4f50, {0x9f, 0x1b, 0xd4, 0x1e, 0x2b, 0x89, 0xc1, 0x9a} \ + } + +/// +/// This identifies a signature containing the SHA256 hash of an X.509 certificate's +/// To-Be-Signed contents, and a time of revocation. The SignatureHeader size shall +/// always be 0. The SignatureSize shall always be 16 (size of the SignatureOwner component) +/// + 48 bytes for an EFI_CERT_X509_SHA256 structure. If the TimeOfRevocation is non-zero, +/// the certificate should be considered to be revoked from that time and onwards, and +/// otherwise the certificate shall be considered to always be revoked. +/// +#define EFI_CERT_X509_SHA256_GUID \ + { \ + 0x3bd2a492, 0x96c0, 0x4079, {0xb4, 0x20, 0xfc, 0xf9, 0x8e, 0xf1, 0x03, 0xed } \ + } + +/// +/// This identifies a signature containing the SHA384 hash of an X.509 certificate's +/// To-Be-Signed contents, and a time of revocation. The SignatureHeader size shall +/// always be 0. The SignatureSize shall always be 16 (size of the SignatureOwner component) +/// + 64 bytes for an EFI_CERT_X509_SHA384 structure. If the TimeOfRevocation is non-zero, +/// the certificate should be considered to be revoked from that time and onwards, and +/// otherwise the certificate shall be considered to always be revoked. +/// +#define EFI_CERT_X509_SHA384_GUID \ + { \ + 0x7076876e, 0x80c2, 0x4ee6, {0xaa, 0xd2, 0x28, 0xb3, 0x49, 0xa6, 0x86, 0x5b } \ + } + +/// +/// This identifies a signature containing the SHA512 hash of an X.509 certificate's +/// To-Be-Signed contents, and a time of revocation. The SignatureHeader size shall +/// always be 0. The SignatureSize shall always be 16 (size of the SignatureOwner component) +/// + 80 bytes for an EFI_CERT_X509_SHA512 structure. If the TimeOfRevocation is non-zero, +/// the certificate should be considered to be revoked from that time and onwards, and +/// otherwise the certificate shall be considered to always be revoked. +/// +#define EFI_CERT_X509_SHA512_GUID \ + { \ + 0x446dbf63, 0x2502, 0x4cda, {0xbc, 0xfa, 0x24, 0x65, 0xd2, 0xb0, 0xfe, 0x9d } \ + } + +/// +/// This identifies a signature containing a DER-encoded PKCS #7 version 1.5 [RFC2315] +/// SignedData value. +/// +#define EFI_CERT_TYPE_PKCS7_GUID \ + { \ + 0x4aafd29d, 0x68df, 0x49ee, {0x8a, 0xa9, 0x34, 0x7d, 0x37, 0x56, 0x65, 0xa7} \ + } + +//*********************************************************************** +// Image Execution Information Table Definition +//*********************************************************************** +typedef UINT32 EFI_IMAGE_EXECUTION_ACTION; + +#define EFI_IMAGE_EXECUTION_AUTHENTICATION 0x00000007 +#define EFI_IMAGE_EXECUTION_AUTH_UNTESTED 0x00000000 +#define EFI_IMAGE_EXECUTION_AUTH_SIG_FAILED 0x00000001 +#define EFI_IMAGE_EXECUTION_AUTH_SIG_PASSED 0x00000002 +#define EFI_IMAGE_EXECUTION_AUTH_SIG_NOT_FOUND 0x00000003 +#define EFI_IMAGE_EXECUTION_AUTH_SIG_FOUND 0x00000004 +#define EFI_IMAGE_EXECUTION_POLICY_FAILED 0x00000005 +#define EFI_IMAGE_EXECUTION_INITIALIZED 0x00000008 + +// +// EFI_IMAGE_EXECUTION_INFO is added to EFI System Configuration Table +// and assigned the GUID EFI_IMAGE_SECURITY_DATABASE_GUID. +// +typedef struct { + /// + /// Describes the action taken by the firmware regarding this image. + /// + EFI_IMAGE_EXECUTION_ACTION Action; + /// + /// Size of all of the entire structure. + /// + UINT32 InfoSize; + /// + /// If this image was a UEFI device driver (for option ROM, for example) this is the + /// null-terminated, user-friendly name for the device. If the image was for an application, + /// then this is the name of the application. If this cannot be determined, then a simple + /// NULL character should be put in this position. + /// CHAR16 Name[]; + /// + + /// + /// For device drivers, this is the device path of the device for which this device driver + /// was intended. In some cases, the driver itself may be stored as part of the system + /// firmware, but this field should record the device's path, not the firmware path. For + /// applications, this is the device path of the application. If this cannot be determined, + /// a simple end-of-path device node should be put in this position. + /// EFI_DEVICE_PATH_PROTOCOL DevicePath; + /// + + /// + /// Zero or more image signatures. If the image contained no signatures, + /// then this field is empty. + /// EFI_SIGNATURE_LIST Signature; + /// +} EFI_IMAGE_EXECUTION_INFO; + + +typedef struct { + /// + /// Number of EFI_IMAGE_EXECUTION_INFO structures. + /// + UINTN NumberOfImages; + /// + /// Number of image instances of EFI_IMAGE_EXECUTION_INFO structures. + /// + // EFI_IMAGE_EXECUTION_INFO InformationInfo[] +} EFI_IMAGE_EXECUTION_INFO_TABLE; + +extern EFI_GUID gEfiImageSecurityDatabaseGuid; +extern EFI_GUID gEfiCertSha256Guid; +extern EFI_GUID gEfiCertRsa2048Guid; +extern EFI_GUID gEfiCertRsa2048Sha256Guid; +extern EFI_GUID gEfiCertSha1Guid; +extern EFI_GUID gEfiCertRsa2048Sha1Guid; +extern EFI_GUID gEfiCertX509Guid; +extern EFI_GUID gEfiCertSha224Guid; +extern EFI_GUID gEfiCertSha384Guid; +extern EFI_GUID gEfiCertSha512Guid; +extern EFI_GUID gEfiCertX509Sha256Guid; +extern EFI_GUID gEfiCertX509Sha384Guid; +extern EFI_GUID gEfiCertX509Sha512Guid; +extern EFI_GUID gEfiCertPkcs7Guid; + +#endif diff --git a/lib/libsecureboot/efi/include/Protocol/Hash.h b/lib/libsecureboot/efi/include/Protocol/Hash.h new file mode 100644 index 000000000000..094bf20964cc --- /dev/null +++ b/lib/libsecureboot/efi/include/Protocol/Hash.h @@ -0,0 +1,169 @@ +/** @file + EFI_HASH_SERVICE_BINDING_PROTOCOL as defined in UEFI 2.0. + EFI_HASH_PROTOCOL as defined in UEFI 2.0. + The EFI Hash Service Binding Protocol is used to locate hashing services support + provided by a driver and to create and destroy instances of the EFI Hash Protocol + so that a multiple drivers can use the underlying hashing services. +Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR> +This program and the accompanying materials are licensed and made available under +the terms and conditions of the BSD License that accompanies this distribution. +The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php. +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +**/ + +#ifndef __EFI_HASH_PROTOCOL_H__ +#define __EFI_HASH_PROTOCOL_H__ + +#include <sys/cdefs.h> +#define CONST const + +#define EFI_HASH_SERVICE_BINDING_PROTOCOL_GUID \ + { \ + 0x42881c98, 0xa4f3, 0x44b0, {0xa3, 0x9d, 0xdf, 0xa1, 0x86, 0x67, 0xd8, 0xcd } \ + } + +#define EFI_HASH_PROTOCOL_GUID \ + { \ + 0xc5184932, 0xdba5, 0x46db, {0xa5, 0xba, 0xcc, 0x0b, 0xda, 0x9c, 0x14, 0x35 } \ + } + +#define EFI_HASH_ALGORITHM_SHA1_GUID \ + { \ + 0x2ae9d80f, 0x3fb2, 0x4095, {0xb7, 0xb1, 0xe9, 0x31, 0x57, 0xb9, 0x46, 0xb6 } \ + } + +#define EFI_HASH_ALGORITHM_SHA224_GUID \ + { \ + 0x8df01a06, 0x9bd5, 0x4bf7, {0xb0, 0x21, 0xdb, 0x4f, 0xd9, 0xcc, 0xf4, 0x5b } \ + } + +#define EFI_HASH_ALGORITHM_SHA256_GUID \ + { \ + 0x51aa59de, 0xfdf2, 0x4ea3, {0xbc, 0x63, 0x87, 0x5f, 0xb7, 0x84, 0x2e, 0xe9 } \ + } + +#define EFI_HASH_ALGORITHM_SHA384_GUID \ + { \ + 0xefa96432, 0xde33, 0x4dd2, {0xae, 0xe6, 0x32, 0x8c, 0x33, 0xdf, 0x77, 0x7a } \ + } + +#define EFI_HASH_ALGORITHM_SHA512_GUID \ + { \ + 0xcaa4381e, 0x750c, 0x4770, {0xb8, 0x70, 0x7a, 0x23, 0xb4, 0xe4, 0x21, 0x30 } \ + } + +#define EFI_HASH_ALGORTIHM_MD5_GUID \ + { \ + 0xaf7c79c, 0x65b5, 0x4319, {0xb0, 0xae, 0x44, 0xec, 0x48, 0x4e, 0x4a, 0xd7 } \ + } + +#define EFI_HASH_ALGORITHM_SHA1_NOPAD_GUID \ + { \ + 0x24c5dc2f, 0x53e2, 0x40ca, {0x9e, 0xd6, 0xa5, 0xd9, 0xa4, 0x9f, 0x46, 0x3b } \ + } + +#define EFI_HASH_ALGORITHM_SHA256_NOPAD_GUID \ + { \ + 0x8628752a, 0x6cb7, 0x4814, {0x96, 0xfc, 0x24, 0xa8, 0x15, 0xac, 0x22, 0x26 } \ + } + +// +// Note: Use of the following algorithms with EFI_HASH_PROTOCOL is deprecated. +// EFI_HASH_ALGORITHM_SHA1_GUID +// EFI_HASH_ALGORITHM_SHA224_GUID +// EFI_HASH_ALGORITHM_SHA256_GUID +// EFI_HASH_ALGORITHM_SHA384_GUID +// EFI_HASH_ALGORITHM_SHA512_GUID +// EFI_HASH_ALGORTIHM_MD5_GUID +// + +typedef struct _EFI_HASH_PROTOCOL EFI_HASH_PROTOCOL; + +typedef UINT8 EFI_MD5_HASH[16]; +typedef UINT8 EFI_SHA1_HASH[20]; +typedef UINT8 EFI_SHA224_HASH[28]; +typedef UINT8 EFI_SHA256_HASH[32]; +typedef UINT8 EFI_SHA384_HASH[48]; +typedef UINT8 EFI_SHA512_HASH[64]; + +typedef union { + EFI_MD5_HASH *Md5Hash; + EFI_SHA1_HASH *Sha1Hash; + EFI_SHA224_HASH *Sha224Hash; + EFI_SHA256_HASH *Sha256Hash; + EFI_SHA384_HASH *Sha384Hash; + EFI_SHA512_HASH *Sha512Hash; +} EFI_HASH_OUTPUT; + +/** + Returns the size of the hash which results from a specific algorithm. + @param[in] This Points to this instance of EFI_HASH_PROTOCOL. + @param[in] HashAlgorithm Points to the EFI_GUID which identifies the algorithm to use. + @param[out] HashSize Holds the returned size of the algorithm's hash. + @retval EFI_SUCCESS Hash size returned successfully. + @retval EFI_INVALID_PARAMETER HashSize is NULL or HashAlgorithm is NULL. + @retval EFI_UNSUPPORTED The algorithm specified by HashAlgorithm is not supported + by this driver. +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_HASH_GET_HASH_SIZE)( + IN CONST EFI_HASH_PROTOCOL *This, + IN CONST EFI_GUID *HashAlgorithm, + OUT UINTN *HashSize + ); + +/** + Creates a hash for the specified message text. + @param[in] This Points to this instance of EFI_HASH_PROTOCOL. + @param[in] HashAlgorithm Points to the EFI_GUID which identifies the algorithm to use. + @param[in] Extend Specifies whether to create a new hash (FALSE) or extend the specified + existing hash (TRUE). + @param[in] Message Points to the start of the message. + @param[in] MessageSize The size of Message, in bytes. + @param[in,out] Hash On input, if Extend is TRUE, then this parameter holds a pointer + to a pointer to an array containing the hash to extend. If Extend + is FALSE, then this parameter holds a pointer to a pointer to a + caller-allocated array that will receive the result of the hash + computation. On output (regardless of the value of Extend), the + array will contain the result of the hash computation. + @retval EFI_SUCCESS Hash returned successfully. + @retval EFI_INVALID_PARAMETER Message or Hash, HashAlgorithm is NULL or MessageSize is 0. + MessageSize is not an integer multiple of block size. + @retval EFI_UNSUPPORTED The algorithm specified by HashAlgorithm is not supported by this + driver. Or, Extend is TRUE, and the algorithm doesn't support extending the hash. +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_HASH_HASH)( + IN CONST EFI_HASH_PROTOCOL *This, + IN CONST EFI_GUID *HashAlgorithm, + IN BOOLEAN Extend, + IN CONST UINT8 *Message, + IN UINT64 MessageSize, + IN OUT EFI_HASH_OUTPUT *Hash + ); + +/// +/// This protocol allows creating a hash of an arbitrary message digest +/// using one or more hash algorithms. +/// +struct _EFI_HASH_PROTOCOL { + EFI_HASH_GET_HASH_SIZE GetHashSize; + EFI_HASH_HASH Hash; +}; + +extern EFI_GUID gEfiHashServiceBindingProtocolGuid; +extern EFI_GUID gEfiHashProtocolGuid; +extern EFI_GUID gEfiHashAlgorithmSha1Guid; +extern EFI_GUID gEfiHashAlgorithmSha224Guid; +extern EFI_GUID gEfiHashAlgorithmSha256Guid; +extern EFI_GUID gEfiHashAlgorithmSha384Guid; +extern EFI_GUID gEfiHashAlgorithmSha512Guid; +extern EFI_GUID gEfiHashAlgorithmMD5Guid; +extern EFI_GUID gEfiHashAlgorithmSha1NoPadGuid; +extern EFI_GUID gEfiHashAlgorithmSha256NoPadGuid; + +#endif diff --git a/lib/libsecureboot/h/libsecureboot.h b/lib/libsecureboot/h/libsecureboot.h new file mode 100644 index 000000000000..d32df9594332 --- /dev/null +++ b/lib/libsecureboot/h/libsecureboot.h @@ -0,0 +1,98 @@ +/*- + * Copyright (c) 2017-2018, Juniper Networks, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + */ +#ifndef _LIBSECUREBOOT_H_ +#define _LIBSECUREBOOT_H_ + +#include <sys/param.h> +#ifdef _STANDALONE +#define _DEBUG_LEVEL_VAR DebugVe +#include <stand.h> +#else +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#endif + +#include <bearssl.h> + +unsigned char * read_fd(int, size_t); +#ifndef NEED_BRSSL_H +unsigned char * read_file(const char *, size_t *); +#endif + +extern int DebugVe; +extern int VerifyFlags; + +#ifndef DEBUG_PRINTF +#define DEBUG_PRINTF(n, x) if (DebugVe >= n) printf x +#endif + +int ve_trust_init(void); +size_t ve_trust_anchors_add_buf(unsigned char *, size_t); +size_t ve_trust_anchors_revoke(unsigned char *, size_t); +int ve_trust_add(const char *); +void ve_debug_set(int); +void ve_enforce_validity_set(int); +void ve_anchor_verbose_set(int); +int ve_anchor_verbose_get(void); +void ve_utc_set(time_t utc); +char *ve_error_get(void); +int ve_error_set(const char *, ...) __printflike(1,2); +int ve_self_tests(void); + +void fingerprint_info_add(const char *, const char *, const char *, + const char *, struct stat *); + +char * hexdigest(char *, size_t, unsigned char *, size_t); +int verify_fd(int, const char *, off_t, struct stat *); +int verify_open(const char *, int); + +unsigned char *verify_signed(const char *, int); +unsigned char *verify_sig(const char *, int); +unsigned char *verify_asc(const char *, int); /* OpenPGP */ + +void ve_pcr_init(void); +void ve_pcr_update(const char *, unsigned char *, size_t); +ssize_t ve_pcr_get(unsigned char *, size_t); +int ve_pcr_updating_get(void); +void ve_pcr_updating_set(int); +char * ve_pcr_hashed_get(int); + +/* flags for verify_{asc,sig,signed} */ +#define VEF_VERBOSE 1 + +#define VE_FINGERPRINT_OK 1 +#define VE_FINGERPRINT_IGNORE 2 +/* errors from verify_fd */ +#define VE_FINGERPRINT_NONE -2 +#define VE_FINGERPRINT_WRONG -3 +#define VE_FINGERPRINT_UNKNOWN -4 /* may not be an error */ + +#endif /* _LIBSECUREBOOT_H_ */ diff --git a/lib/libsecureboot/h/verify_file.h b/lib/libsecureboot/h/verify_file.h new file mode 100644 index 000000000000..f918ed6d0e38 --- /dev/null +++ b/lib/libsecureboot/h/verify_file.h @@ -0,0 +1,67 @@ +/*- + * 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. + */ +#ifndef _VERIFY_FILE_H_ +#define _VERIFY_FILE_H_ + +#define VE_GUESS -1 /* let verify_file work it out */ +#define VE_TRY 0 /* we don't mind if unverified */ +#define VE_WANT 1 /* we want this verified */ +#define VE_MUST 2 /* this must be verified */ + +#define VE_NOT_CHECKED -42 +#define VE_VERIFIED 1 /* all good */ +#define VE_UNVERIFIED_OK 0 /* not verified but that's ok */ +#define VE_NOT_VERIFYING 2 /* we are not verifying */ + +/* suitable buf size for hash_string */ +#ifndef SHA_DIGEST_LENGTH +# define SHA_DIGEST_LENGTH 20 +#endif + +struct stat; + +int verify_prep(int, const char *, off_t, struct stat *, const char *); +void ve_debug_set(int); +char *ve_error_get(void); +void ve_efi_init(void); +void ve_status_set(int, int); +int ve_status_get(int); +int load_manifest(const char *, const char *, const char *, struct stat *); +int pass_manifest(const char *, const char *); +int pass_manifest_export_envs(void); +void verify_report(const char *, int, int, struct stat *); +int verify_file(int, const char *, off_t, int, const char *); +void verify_pcr_export(void); +int hash_string(char *s, size_t n, char *buf, size_t bufsz); +int is_verified(struct stat *); +void add_verify_status(struct stat *, int); + +struct vectx; +struct vectx* vectx_open(int, const char *, off_t, struct stat *, int *, const char *); +ssize_t vectx_read(struct vectx *, void *, size_t); +off_t vectx_lseek(struct vectx *, off_t, int); +int vectx_close(struct vectx *, int, const char *); + +#endif /* _VERIFY_FILE_H_ */ diff --git a/lib/libsecureboot/libsecureboot-priv.h b/lib/libsecureboot/libsecureboot-priv.h new file mode 100644 index 000000000000..039b03c70ca2 --- /dev/null +++ b/lib/libsecureboot/libsecureboot-priv.h @@ -0,0 +1,70 @@ +/*- + * 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. + */ +/* + */ +#ifndef _LIBSECUREBOOT_PRIV_H_ +#define _LIBSECUREBOOT_PRIV_H_ + +/* public api */ +#include "libsecureboot.h" + +struct stat; + +typedef struct { + unsigned char *data; + size_t hash_size; +} hash_data; + +int ve_check_hash(br_hash_compat_context *, const br_hash_class *, + const char *, const char *, size_t); + +size_t ve_trust_anchors_add(br_x509_certificate *, size_t); +size_t ve_forbidden_anchors_add(br_x509_certificate *, size_t); +void ve_forbidden_digest_add(hash_data *digest, 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 is_verified(struct stat *stp); +void add_verify_status(struct stat *stp, int status); + +int openpgp_trust_init(void); +int openpgp_trust_add_buf(unsigned char *, size_t); +int openpgp_trust_revoke(const char *); +int openpgp_self_tests(void); + +int efi_secure_boot_enabled(void); +br_x509_certificate* efi_get_trusted_certs(size_t *count); +br_x509_certificate* efi_get_forbidden_certs(size_t *count); +hash_data* efi_get_forbidden_digests(size_t *count); + +#endif /* _LIBSECUREBOOT_PRIV_H_ */ diff --git a/lib/libsecureboot/local.trust.mk b/lib/libsecureboot/local.trust.mk new file mode 100644 index 000000000000..f2da3d3ac452 --- /dev/null +++ b/lib/libsecureboot/local.trust.mk @@ -0,0 +1,108 @@ + +# 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 + +# allow site control +.-include "site.trust.mk" + +#VE_DEBUG_LEVEL?=3 +#VE_VERBOSE_DEFAULT?=2 + +VE_HASH_LIST?= \ + SHA256 \ + SHA384 \ + +VE_SELF_TESTS?= yes + +# client for the signing server above +SIGNER?= /opt/sigs/sign.py + +.if exists(${SIGNER}) +OPENPGP_SIGNER?= ${SIGNER:H}/openpgp-sign.py +OPENPGP_SIGN_FLAGS= -a +OPENPGP_SIGN_HOST?= localhost +SIGN_HOST ?= localhost + +# A list of name/ext/url tuples. +# name should be one of ECDSA, OPENPGP or RSA, they can be repeated +# Order of ext list implies runtime preference so do not sort! +VE_SIGN_URL_LIST?= \ + ECDSA/esig/${SIGN_HOST}:${133%y:L:localtime} \ + RSA/rsig/${SIGN_HOST}:${163%y:L:localtime} \ + OPENPGP/asc/${OPENPGP_SIGN_HOST}:1234 \ + +.for sig ext url in ${VE_SIGN_URL_LIST:@x@${x:H:H} ${x:H:T} ${x:T}@} +SIGN_${sig}:= ${PYTHON} ${${sig}_SIGNER:U${SIGNER}} -u ${url} ${${sig}_SIGN_FLAGS:U-h sha256} + +VE_SIGNATURE_LIST+= ${sig} +VE_SIGNATURE_EXT_LIST+= ${ext} + +_SIGN_${sig}_USE: .USE + ${SIGN_${sig}} ${.ALLSRC} + +_TA_${sig}_USE: .USE + ${SIGN_${sig}} -C ${.TARGET} + +.if ${sig} == "OPENPGP" +ta_${sig:tl}.${ext}: _TA_${sig}_USE +ta_${ext}.h: ta_${sig:tl}.${ext} +.else +${ext:S/sig/certs/}.pem: _TA_${sig}_USE +# the last cert in the chain is the one we want +ta_${ext}.pem: ${ext:S/sig/certs/}.pem _LAST_PEM_USE +ta.h: ta_${ext}.pem +.if ${VE_SELF_TESTS} != "no" +# we use the 2nd last cert to test verification +vc_${ext}.pem: ${ext:S/sig/certs/}.pem _2ndLAST_PEM_USE +ta.h: vc_${ext}.pem +.endif +.endif +.endfor + +# cleanup duplicates +VE_SIGNATURE_LIST:= ${VE_SIGNATURE_LIST:O:u} + +.if target(ta_asc.h) +XCFLAGS.opgp_key+= -DHAVE_TA_ASC_H + +.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_asc.h: vc_openpgp.asc +.endif +.endif + +.else +VE_SIGNATURE_LIST?= RSA + +# you need to provide t*.pem or t*.asc files for each trust anchor +# below assumes they are named ta_${ext}.pem eg ta_esig.pem for ECDSA +.if empty(TRUST_ANCHORS) +TRUST_ANCHORS!= cd ${.CURDIR} && 'ls' -1 *.pem t*.asc 2> /dev/null || echo +.endif +.if empty(TRUST_ANCHORS) && ${MK_LOADER_EFI_SECUREBOOT} != "yes" +.error Need TRUST_ANCHORS see ${.PARSEDIR}/README.rst +.endif + +.if ${TRUST_ANCHORS:T:Mt*.pem} != "" +ta.h: ${TRUST_ANCHORS:M*.pem} +VE_SIGNATURE_EXT_LIST?= ${TRUST_ANCHORS:T:Mt*.pem:R:S/ta_//} +.if ${VE_SIGNATURE_EXT_LIST:Mesig} != "" +VE_SIGNATURE_LIST+= ECDSA +.endif +.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..3a216d205fe3 --- /dev/null +++ b/lib/libsecureboot/openpgp/Makefile.inc @@ -0,0 +1,49 @@ +# 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_LIST ?= ${.ALLSRC:Mt*.asc} +VC_ASC_LIST ?= ${.ALLSRC:Mv*.asc} + +ta_asc.h: +.if ${VE_SIGNATURE_LIST:MOPENPGP} != "" + @( echo '/* Autogenerated - DO NOT EDIT!!! */'; echo; \ + echo "#define HAVE_TA_ASC 1"; \ + set -- ${TA_ASC_LIST:@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[] = { ${TA_ASC_LIST:T:R:ts,}, NULL };'; \ + echo; ) > ${.TARGET} +.if ${VE_SELF_TESTS} != "no" + @( echo "#define HAVE_VC_ASC 1"; \ + set -- ${VC_ASC_LIST:@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[] = { ${VC_ASC_LIST: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..452722ff1a10 --- /dev/null +++ b/lib/libsecureboot/openpgp/dearmor.c @@ -0,0 +1,142 @@ +/*- + * 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 <sys/cdefs.h> +#define NEED_BRSSL_H +#include <libsecureboot.h> +#include <brssl.h> + +#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 <unistd.h> +#include <fcntl.h> +#include <err.h> + +/* + * 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..c59b5722c29e --- /dev/null +++ b/lib/libsecureboot/openpgp/decode.c @@ -0,0 +1,302 @@ +/*- + * 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 <sys/cdefs.h> +#include <libsecureboot.h> + +#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..1e7e2ea1f915 --- /dev/null +++ b/lib/libsecureboot/openpgp/decode.h @@ -0,0 +1,56 @@ +/*- + * 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. + */ +/* + */ + +#ifdef USE_BEARSSL +unsigned char * mpi2bn(unsigned char **pptr, size_t *sz); +#else +# include <openssl/bn.h> +# include <openssl/rsa.h> +# include <openssl/evp.h> + +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..568d334ec117 --- /dev/null +++ b/lib/libsecureboot/openpgp/opgp_key.c @@ -0,0 +1,413 @@ +/*- + * 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 <sys/cdefs.h> +#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)) { + /* Note: we do *not* free data */ + 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); + } + } + } + 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 && openpgp_trust_get(key->id) == NULL) { + if (ve_anchor_verbose_get()) + printf("openpgp_trust_add(%s)\n", key->id); + LIST_INSERT_HEAD(&trust_list, key, entries); + } +} + +/** + * @brief add trust anchor from buf + */ +int +openpgp_trust_add_buf(unsigned char *buf, size_t nbytes) +{ + OpenPGP_key *key; + + if ((key = load_key_buf(buf, nbytes))) { + openpgp_trust_add(key); + } + return (key != NULL); +} + + +/** + * @brief if keyID is in our list clobber it + * + * @return true if keyID removed + */ +int +openpgp_trust_revoke(const char *keyID) +{ + OpenPGP_key *key, *tkey; + + openpgp_trust_add(NULL); /* initialize if needed */ + + LIST_FOREACH(key, &trust_list, entries) { + if (strcmp(key->id, keyID) == 0) { + tkey = key; + LIST_REMOVE(tkey, entries); + printf("openpgp_trust_revoke(%s)\n", key->id); + memset(key, 0, sizeof(OpenPGP_key)); + free(key); + return (1); + } + } + return (0); +} + +/** + * @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); +} + +#ifdef HAVE_TA_ASC_H +#include <ta_asc.h> +#endif + +#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) +{ + OpenPGP_key *key; + + key = openpgp_trust_get(keyID); +#ifndef _STANDALONE + if (!key) + key = load_trusted_key_id(keyID); +#endif + DEBUG_PRINTF(2, ("load_key_id(%s): %s\n", keyID, key ? "found" : "nope")); + return (key); +} + +/** + * @brief initialize our internal trust store if any + */ +int +openpgp_trust_init(void) +{ + static int once = -1; +#ifdef HAVE_TA_ASC + OpenPGP_key *key; + const char **tp; + char *cp; + size_t n; +#endif + + if (once < 0) { + once = 0; +#ifdef HAVE_TA_ASC + for (tp = ta_ASC; *tp; tp++) { + if ((cp = strdup(*tp))) { + n = strlen(cp); + key = load_key_buf((unsigned char *)cp, n); + free(cp); + if (key) { + openpgp_trust_add(key); + once++; + } + } + } +#endif + } + return (once); +} + +/** + * @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; + + if (openpgp_trust_init() > 0) { + 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..8846296d7122 --- /dev/null +++ b/lib/libsecureboot/openpgp/opgp_sig.c @@ -0,0 +1,492 @@ +/*- + * 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 + * + * 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 <sys/cdefs.h> +#include "../libsecureboot-priv.h" +#ifdef _STANDALONE +#define warnx printf +#else + +#include <sys/param.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdio.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <err.h> +#endif + +#include "decode.h" +#include "packet.h" + +#ifdef USE_BEARSSL + +#define get_error_string ve_error_get + +void +initialize (void) +{ + openpgp_trust_init(); +} + +#else + +#include <openssl/err.h> + +/** + * @brief initialize OpenSSL + */ +void +initialize(void) +{ + static int once; + + if (once) + return); + once = 1; + //CRYPTO_malloc_init(); + ERR_load_crypto_strings(); + OpenSSL_add_all_algorithms(); +} + +/** + * @brief + * last error from OpenSSL as a string + */ +char * +get_error_string(void) +{ + initialize(); + return (ERR_error_string(ERR_get_error(), NULL)); +} +#endif + +/** + * @brief decode a signature packet + * + * We only support RSA + * + * @sa rfc4880:5.2 + */ +ssize_t +decode_sig(int tag, unsigned char **pptr, size_t len, OpenPGP_sig *sig) +{ + unsigned char *ptr; + unsigned char *pgpbytes; + unsigned char *sp; + int version; + int hcount = 0; + int ucount = 0; + int stag = 0; + int n; + + n = tag; /* avoid unused */ + + /* + * We need to keep a reference to the packet bytes + * as these form part of the signature data. + * + * @sa rfc4880:5.2.4 + */ + pgpbytes = ptr = *pptr; + version = *ptr++; + if (version == 3) { + ptr++; + sig->pgpbytes = malloc(5); + if (!sig->pgpbytes) + return (-1); + memcpy(sig->pgpbytes, ptr, 5); + sig->pgpbytes_len = 5; + sig->sig_type = *ptr++; + ptr += 4; + sig->key_id = octets2hex(ptr, 8); + ptr += 8; + sig->sig_alg = *ptr++; + sig->hash_alg = *ptr++; + } else if (version == 4) { + sig->sig_type = *ptr++; + sig->sig_alg = *ptr++; + sig->hash_alg = *ptr++; + hcount = octets2i(ptr, 2); + ptr += 2; + sig->pgpbytes_len = (size_t)hcount + 6; + sig->pgpbytes = malloc(sig->pgpbytes_len + 6); + if (!sig->pgpbytes) + return (-1); + memcpy(sig->pgpbytes, pgpbytes, sig->pgpbytes_len); + sp = &sig->pgpbytes[sig->pgpbytes_len]; + *sp++ = 4; + *sp++ = 255; + memcpy(sp, i2octets(4, (int)sig->pgpbytes_len), 4); + sig->pgpbytes_len += 6; + + while (hcount > 0) { + sp = decode_subpacket(&ptr, &stag, &n); + hcount -= n; + /* can check stag to see if we care */ + } + ucount = octets2i(ptr, 2); + ptr += 2; + while (ucount > 0) { + sp = decode_subpacket(&ptr, &stag, &n); + ucount -= n; + /* can check stag to see if we care */ + if (stag == 16) { + free(sig->key_id); + sig->key_id = octets2hex(sp, 8); + } + } + } else + return (-1); + ptr += 2; /* skip hash16 */ + if (sig->sig_alg == 1) { /* RSA */ + sig->sig = decode_mpi(&ptr, &sig->sig_len); + } + /* we are done */ + return ((ssize_t)len); +} + +/** + * @brief map OpenPGP hash algorithm id's to name + * + * @sa rfc4880:9.4 + */ +static struct hash_alg_map { + int halg; + const char *hname; +} hash_algs[] = { + {1, "md5"}, + {2, "sha1"}, + {8, "sha256"}, + {9, "sha384"}, + {10, "sha512"}, + {11, "sha224"}, + {0, NULL}, +}; + +static const char * +get_hname(int hash_alg) +{ + struct hash_alg_map *hmp; + + for (hmp = hash_algs; hmp->halg > 0; hmp++) { + if (hmp->halg == hash_alg) + return (hmp->hname); + } + return (NULL); +} + +/* lifted from signer.c */ +/** + * @brief verify a digest + * + * The public key, digest name, file and signature data. + * + * @return 1 on success 0 on failure, -1 on error + */ +#ifndef USE_BEARSSL +static int +verify_digest (EVP_PKEY *pkey, + const char *digest, + unsigned char *mdata, size_t mlen, + unsigned char *sdata, size_t slen) +{ + EVP_MD_CTX ctx; + const EVP_MD *md = NULL; + EVP_PKEY_CTX *pctx = NULL; + int rc = 0; + int i = -1; + + initialize(); + md = EVP_get_digestbyname(digest); + EVP_DigestInit(&ctx, md); + + pctx = EVP_PKEY_CTX_new(pkey, NULL); + if (!pctx) + goto fail; + if (EVP_PKEY_verify_init(pctx) <= 0) + goto fail; + if (EVP_PKEY_CTX_set_signature_md(pctx, ctx.digest) <= 0) + goto fail; + i = EVP_PKEY_verify(pctx, sdata, slen, mdata, mlen); + if (i >= 0) + rc = i; +fail: + EVP_PKEY_CTX_free(pctx); + return (rc); +} +#endif + + +/** + * @brief verify OpenPGP signed file + * + * + * @param[in] filename + * used to determine the signature name + * + * @param[in] fdata + * content of filename + * + * @param[in] fbytes + * of fdata + * + * @param[in] sdata + * content of signature + * + * @param[in] sbytes + * of sdata + * + * @param[in] flags + * + * @return 0 on success + */ +int +openpgp_verify(const char *filename, + unsigned char *fdata, size_t fbytes, + unsigned char *sdata, size_t sbytes, + int flags) +{ + OpenPGP_key *key; + OpenPGP_sig *sig; +#ifdef USE_BEARSSL + const br_hash_class *md; + br_hash_compat_context mctx; + const unsigned char *hash_oid; +#else + const EVP_MD *md = NULL; + EVP_MD_CTX mctx; +#endif + unsigned char mdata[64]; + unsigned char *ptr; + unsigned char *ddata = NULL; + const char *hname; + size_t mlen; + int rc = -1; + + initialize(); + + sig = NEW(OpenPGP_sig); + if (!sdata || !sig) { + warnx("cannot verify %s", filename); + goto oops; + } + if (!(sdata[0] & OPENPGP_TAG_ISTAG)) + sdata = ddata = dearmor((char *)sdata, sbytes, &sbytes); + ptr = sdata; + rc = decode_packet(2, &ptr, sbytes, (decoder_t)decode_sig, sig); + DEBUG_PRINTF(2, ("rc=%d keyID=%s\n", rc, sig->key_id ? sig->key_id : "?")); + if (rc == 0 && sig->key_id) { + key = load_key_id(sig->key_id); + if (!key) { + warnx("cannot find key-id: %s", sig->key_id); + rc = -1; + } else if (!(hname = get_hname(sig->hash_alg))) { + warnx("unsupported hash algorithm: %d", sig->hash_alg); + rc = -1; + } else { + /* + * Hash fdata according to the OpenPGP recipe + * + * @sa rfc4880:5.2.4 + */ +#ifdef USE_BEARSSL + switch (sig->hash_alg) { /* see hash_algs above */ + case 2: /* sha1 */ + md = &br_sha1_vtable; + mlen = br_sha1_SIZE; + hash_oid = BR_HASH_OID_SHA1; + break; + case 8: /* sha256 */ + md = &br_sha256_vtable; + mlen = br_sha256_SIZE; + hash_oid = BR_HASH_OID_SHA256; + break; + case 9: /* sha384 */ + md = &br_sha384_vtable; + mlen = br_sha384_SIZE; + hash_oid = BR_HASH_OID_SHA384; + break; + case 10: /* sha512 */ + md = &br_sha512_vtable; + mlen = br_sha512_SIZE; + hash_oid = BR_HASH_OID_SHA512; + break; + default: + warnx("unsupported hash algorithm: %s", hname); + rc = -1; + 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 & VEF_VERBOSE)) + printf("Verified %s signed by %s\n", + filename, + key->user ? key->user->name : "someone"); + rc = 0; /* success */ + } else if (rc == 0) { + printf("Unverified %s: %s\n", + filename, get_error_string()); + rc = 1; + } else { + printf("Unverified %s\n", filename); + } + } + } else { + warnx("cannot decode signature for %s", filename); + rc = -1; + } +oops: + free(ddata); + free(sig); + return (rc); +} + +#ifndef _STANDALONE +/** + * @brief list of extensions we handle + * + * ".asc" is preferred as it works seamlessly with openpgp + */ +static const char *sig_exts[] = { + ".asc", + ".pgp", + ".psig", + NULL, +}; + +/** + * @brief verify OpenPGP signed file + * + * + * @param[in] filename + * used to determine the signature name + * + * @param[in] fdata + * content of filename + * + * @param[in] nbytes + * of fdata + * + * @return + */ + +int +openpgp_verify_file(const char *filename, unsigned char *fdata, size_t nbytes) +{ + char pbuf[MAXPATHLEN]; + unsigned char *sdata; + const char *sname = NULL; + const char **ep; + size_t sz; + int n; + + for (ep = sig_exts; *ep; ep++) { + n = snprintf(pbuf, sizeof(pbuf), "%s%s", filename, *ep); + if (n >= (int)sizeof(pbuf)) { + warnx("cannot form signature name for %s", filename); + return (-1); + } + if (access(pbuf, R_OK) == 0) { + sname = pbuf; + break; + } + } + if (!sname) { + warnx("cannot find signature for %s", filename); + return (-1); + } + sdata = read_file(sname, &sz); + return (openpgp_verify(filename, fdata, nbytes, sdata, sz, VerifyFlags)); +} +#endif + +/** + * @brief verify OpenPGP signature + * + * @return content of signed file + */ +unsigned char * +verify_asc(const char *sigfile, int flags) +{ + char pbuf[MAXPATHLEN]; + char *cp; + size_t n; + unsigned char *fdata, *sdata; + size_t fbytes, sbytes; + + fdata = NULL; + if ((sdata = read_file(sigfile, &sbytes))) { + n = strlcpy(pbuf, sigfile, sizeof(pbuf)); + if (n < 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; + } + } + } + } + free(sdata); + return (fdata); +} diff --git a/lib/libsecureboot/openpgp/packet.h b/lib/libsecureboot/openpgp/packet.h new file mode 100644 index 000000000000..58f797cd1651 --- /dev/null +++ b/lib/libsecureboot/openpgp/packet.h @@ -0,0 +1,81 @@ +/*- + * 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 <sys/queue.h> + +/* + * 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/pass_manifest.c b/lib/libsecureboot/pass_manifest.c new file mode 100644 index 000000000000..e3ef1fa96aae --- /dev/null +++ b/lib/libsecureboot/pass_manifest.c @@ -0,0 +1,148 @@ +/*- + * Copyright (c) 2019 Stormshield. + * Copyright (c) 2019 Semihalf. + * + * 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 AUTHOR ``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 AUTHOR 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 <sys/cdefs.h> +#include <sys/stat.h> + +#include "libsecureboot-priv.h" +#include <verify_file.h> + +/* + * Values to pass to kernel by envs. + */ +static char manifest_path[MAXPATHLEN]; +static char manifest_prefix[MAXPATHLEN]; +static char manifest_hash[2 * br_sha256_SIZE + 2]; +static int manifest_present = 0; + +/* + * Verify and pass manifest path and digest to kernel through envs. + * The paths in manifest can be either absolute, + * or "prefix", if exists will be added to the ones that are not. + */ +int +pass_manifest(const char *path, const char *prefix) +{ + char *content; + struct stat st; + unsigned char digest[br_sha256_SIZE]; + const br_hash_class *md; + br_hash_compat_context ctx; + int rc; + + content = NULL; + md = &br_sha256_vtable; + + if (strnlen(path, MAXPATHLEN) == MAXPATHLEN || + strnlen(prefix, MAXPATHLEN) == MAXPATHLEN) + return (EINVAL); + + rc = stat(path, &st); + if (rc != 0) + goto out; + + if (!S_ISREG(st.st_mode)) { + rc = EINVAL; + goto out; + } + + rc = is_verified(&st); + + if (rc != VE_NOT_CHECKED && rc != VE_VERIFIED) { + rc = EPERM; + goto out; + } + + if (rc == VE_VERIFIED) + content = read_file(path, NULL); + else + content = (char *)verify_signed(path, VEF_VERBOSE); + + if (content == NULL) { + add_verify_status(&st, VE_FINGERPRINT_WRONG); + rc = EIO; + goto out; + } + + add_verify_status(&st, VE_VERIFIED); + + md->init(&ctx.vtable); + md->update(&ctx.vtable, content, st.st_size); + md->out(&ctx.vtable, digest); + + if (prefix == NULL) + manifest_prefix[0] = '\0'; + else + strcpy(manifest_prefix, prefix); + + strcpy(manifest_path, path); + + hexdigest(manifest_hash, 2 * br_sha256_SIZE + 2, + digest, br_sha256_SIZE); + manifest_hash[2*br_sha256_SIZE] = '\0'; + + manifest_present = 1; + rc = 0; + +out: + if (content != NULL) + free(content); + + return (rc); +} + +/* + * Set appropriate envs to inform kernel about manifest location and digest. + * This should be called right before boot so that envs can't be replaced. + */ +int +pass_manifest_export_envs() +{ + int rc; + + /* If we have nothing to pass make sure that envs are empty. */ + if (!manifest_present) { + unsetenv("veriexec.manifest_path"); + unsetenv("veriexec.manifest_hash"); + unsetenv("veriexec.manifest_prefix"); + return (0); + } + + rc = setenv("veriexec.manifest_path", manifest_path, 1); + if (rc != 0) + return (rc); + + rc = setenv("veriexec.manifest_hash", manifest_hash, 1); + if (rc != 0) { + unsetenv("veriexec.manifest_path"); + return (rc); + } + + if (manifest_prefix[0] != '\0') + rc = setenv("veriexec.manifest_prefix", manifest_prefix, 1); + + return (rc); +} + diff --git a/lib/libsecureboot/readfile.c b/lib/libsecureboot/readfile.c new file mode 100644 index 000000000000..19784f6f4139 --- /dev/null +++ b/lib/libsecureboot/readfile.c @@ -0,0 +1,80 @@ +/*- + * 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 <sys/cdefs.h> +#include <libsecureboot.h> + +unsigned char * +read_fd(int fd, size_t len) +{ + int m, n, x; + unsigned char *buf; + + buf = malloc(len + 1); + if (buf != NULL) { + for (x = 0, m = len; m > 0; ) { + n = read(fd, &buf[x], m); + if (n < 0) + break; + if (n > 0) { + m -= n; + x += n; + } + } + if (m == 0) { + buf[len] = '\0'; + return (buf); + } + free(buf); + } + return (NULL); +} + +unsigned char * +read_file(const char *path, size_t *len) +{ + struct stat st; + unsigned char *ucp; + int fd; + + if (len) + *len = 0; + if ((fd = open(path, O_RDONLY)) < 0) + return (NULL); + fstat(fd, &st); + ucp = read_fd(fd, st.st_size); + close(fd); + if (ucp != NULL) { + if (len != NULL) + *len = st.st_size; + } +#ifdef _STANDALONE + else + printf("%s: out of memory! %lu\n", __func__, + (unsigned long)len); +#endif + + return (ucp); +} + diff --git a/lib/libsecureboot/tests/Makefile b/lib/libsecureboot/tests/Makefile new file mode 100644 index 000000000000..b3de2b6ce5b9 --- /dev/null +++ b/lib/libsecureboot/tests/Makefile @@ -0,0 +1,19 @@ +.include <src.opts.mk> + +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 +MK_LOADER_EFI_SECUREBOOT= no +.include "../Makefile.libsa.inc" +BRSSL_CFLAGS := ${BRSSL_CFLAGS:N-DNO_STDIO} +XCFLAGS.verify_file += -DSOPEN_MAX=64 + +.include <bsd.prog.mk> diff --git a/lib/libsecureboot/tests/Makefile.depend.host b/lib/libsecureboot/tests/Makefile.depend.host new file mode 100644 index 000000000000..d31947a35098 --- /dev/null +++ b/lib/libsecureboot/tests/Makefile.depend.host @@ -0,0 +1,11 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + lib/libbearssl \ + + +.include <dirdeps.mk> + +.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..7851e79c5a2a --- /dev/null +++ b/lib/libsecureboot/tests/tvo.c @@ -0,0 +1,213 @@ +/* + * 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 <sys/cdefs.h> +#include "../libsecureboot-priv.h" + +#include <unistd.h> +#include <err.h> +#include <verify_file.h> + +/* keep clang quiet */ +extern char *Destdir; +extern size_t DestdirLen; +extern char *Skip; +extern time_t ve_utc; + +size_t DestdirLen; +char *Destdir; +char *Skip; + +int +main(int argc, char *argv[]) +{ + int n; + int fd; + int c; + int Vflag; + int vflag; + char *cp; + char *prefix; + + Destdir = NULL; + DestdirLen = 0; + prefix = NULL; + Skip = NULL; + + n = ve_trust_init(); + Vflag = 0; + vflag = 0; + + while ((c = getopt(argc, argv, "D:dp:s:T:u:Vv")) != -1) { + switch (c) { + case 'D': + Destdir = optarg; + DestdirLen = strlen(optarg); + break; + case 'd': + DebugVe++; + break; + case 'p': + prefix = optarg; + break; + case 's': + Skip = optarg; + break; + case 'T': + n = ve_trust_add(optarg); + printf("Local trust %s: %d\n", optarg, n); + break; + case 'V': + Vflag = 1; + break; + case 'v': + vflag = 1; + break; + case 'u': + ve_utc = (time_t)atoi(optarg); + break; + default: + errx(1, "unknown option: -%c", c); + break; + } + } + + if (!vflag) { + printf("Trust %d\n", n); +#ifdef VE_PCR_SUPPORT + ve_pcr_updating_set(1); +#endif + ve_self_tests(); + } + for ( ; optind < argc; optind++) { + if (Vflag) { + /* + * Simulate what loader does. + * verify_file should "just work" + */ + fd = open(argv[optind], O_RDONLY); + if (fd > 0) { + /* + * See if verify_file is happy + */ + int x; + + x = verify_file(fd, argv[optind], 0, VE_GUESS, __func__); + printf("verify_file(%s) = %d\n", argv[optind], x); + close(fd); + } + continue; + } +#ifdef VE_OPENPGP_SUPPORT + if (strstr(argv[optind], "asc")) { + cp = (char *)verify_asc(argv[optind], 1); + if (cp) { + printf("Verified: %s: %.28s...\n", + argv[optind], cp); + if (!vflag) + fingerprint_info_add(argv[optind], + prefix, Skip, cp, NULL); + } else { + fprintf(stderr, "%s: %s\n", + argv[optind], ve_error_get()); + } + } else +#endif + if (strstr(argv[optind], "sig")) { + cp = (char *)verify_sig(argv[optind], 1); + if (cp) { + printf("Verified: %s: %.28s...\n", + argv[optind], cp); + if (!vflag) + fingerprint_info_add(argv[optind], + prefix, Skip, cp, NULL); + } else { + fprintf(stderr, "%s: %s\n", + argv[optind], ve_error_get()); + } + } else if (strstr(argv[optind], "manifest")) { + cp = (char *)read_file(argv[optind], NULL); + if (cp) { + fingerprint_info_add(argv[optind], + prefix, Skip, cp, NULL); + } + } else { + fd = verify_open(argv[optind], O_RDONLY); + printf("verify_open(%s) = %d %s\n", argv[optind], fd, + (fd < 0) ? ve_error_get() : ""); + if (fd > 0) { + /* + * Check that vectx_* can also verify the file. + */ + void *vp; + char buf[BUFSIZ]; + struct stat st; + int error; + off_t off; + size_t nb; + + fstat(fd, &st); + lseek(fd, 0, SEEK_SET); + off = st.st_size % 512; + vp = vectx_open(fd, argv[optind], off, + &st, &error, __func__); + if (!vp) { + printf("vectx_open(%s) failed: %d %s\n", + argv[optind], error, + ve_error_get()); + } else { + off = vectx_lseek(vp, + (st.st_size % 1024), SEEK_SET); + /* we can seek backwards! */ + off = vectx_lseek(vp, off/2, SEEK_SET); + if (off < st.st_size) { + nb = vectx_read(vp, buf, + sizeof(buf)); + if (nb > 0) + off += nb; + } + off = vectx_lseek(vp, 0, SEEK_END); + /* repeating that should be harmless */ + off = vectx_lseek(vp, 0, SEEK_END); + error = vectx_close(vp, VE_MUST, __func__); + if (error) { + printf("vectx_close(%s) == %d %s\n", + argv[optind], error, + ve_error_get()); + } else { + printf("vectx_close: Verified: %s\n", + argv[optind]); + } + } + close(fd); + } + } + } +#ifdef VE_PCR_SUPPORT + verify_pcr_export(); + printf("pcr=%s\n", getenv("loader.ve.pcr")); +#endif + return (0); +} + diff --git a/lib/libsecureboot/vectx.c b/lib/libsecureboot/vectx.c new file mode 100644 index 000000000000..2d56830cd81d --- /dev/null +++ b/lib/libsecureboot/vectx.c @@ -0,0 +1,416 @@ +/*- + * 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 <sys/cdefs.h> +#ifndef _STANDALONE +/* Avoid unwanted userlandish components */ +#define _KERNEL +#include <sys/errno.h> +#undef _KERNEL +#endif + +#ifdef VECTX_DEBUG +static int vectx_debug = VECTX_DEBUG; +# define DEBUG_PRINTF(n, x) if (vectx_debug >= n) printf x +#endif + +#include "libsecureboot-priv.h" +#include <verify_file.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 */ + off_t vec_hashed; /* where we have hashed to */ + off_t vec_size; /* size of path */ + size_t vec_hashsz; /* size of hash */ + int vec_fd; /* file descriptor */ + int vec_status; /* verification status */ + int vec_closing; /* we are closing */ +}; + + +/** + * @brief + * verify an open file as we read it + * + * If the file has no fingerprint to match, we will still return a + * verification context containing little more than the file + * descriptor, and an error code in @c error. + * + * @param[in] fd + * open descriptor + * + * @param[in] path + * pathname to open + * + * @param[in] off + * current offset + * + * @param[in] stp + * pointer to struct stat + * + * @param[out] error + * @li 0 all is good + * @li ENOMEM out of memory + * @li VE_FINGERPRINT_NONE no entry found + * @li VE_FINGERPRINT_UNKNOWN no fingerprint in entry + * + * @return ctx or NULL on error. + * NULL is only returned for non-files or out-of-memory. + */ +struct vectx * +vectx_open(int fd, const char *path, off_t off, struct stat *stp, + int *error, const char *caller) +{ + struct vectx *ctx; + struct stat st; + size_t hashsz; + char *cp; + int rc; + + if (!stp) + stp = &st; + + rc = verify_prep(fd, path, off, stp, __func__); + + DEBUG_PRINTF(2, + ("vectx_open: caller=%s,fd=%d,name='%s',prep_rc=%d\n", + caller, fd, path, rc)); + + switch (rc) { + case VE_FINGERPRINT_NONE: + case VE_FINGERPRINT_UNKNOWN: + case VE_FINGERPRINT_WRONG: + *error = rc; + return (NULL); + } + ctx = malloc(sizeof(struct vectx)); + if (!ctx) + goto enomem; + ctx->vec_fd = fd; + ctx->vec_path = path; + ctx->vec_size = stp->st_size; + ctx->vec_off = 0; + ctx->vec_hashed = 0; + ctx->vec_want = NULL; + ctx->vec_status = 0; + ctx->vec_hashsz = hashsz = 0; + ctx->vec_closing = 0; + + if (rc == 0) { + /* we are not verifying this */ + *error = 0; + return (ctx); + } + cp = fingerprint_info_lookup(fd, path); + if (!cp) { + ctx->vec_status = VE_FINGERPRINT_NONE; + ve_error_set("%s: no entry", path); + } else { + if (strncmp(cp, "no_hash", 7) == 0) { + ctx->vec_status = VE_FINGERPRINT_IGNORE; + hashsz = 0; + } else if (strncmp(cp, "sha256=", 7) == 0) { + ctx->vec_md = &br_sha256_vtable; + hashsz = br_sha256_SIZE; + cp += 7; +#ifdef VE_SHA1_SUPPORT + } else if (strncmp(cp, "sha1=", 5) == 0) { + ctx->vec_md = &br_sha1_vtable; + hashsz = br_sha1_SIZE; + cp += 5; +#endif +#ifdef VE_SHA384_SUPPORT + } else if (strncmp(cp, "sha384=", 7) == 0) { + ctx->vec_md = &br_sha384_vtable; + hashsz = br_sha384_SIZE; + cp += 7; +#endif +#ifdef VE_SHA512_SUPPORT + } else if (strncmp(cp, "sha512=", 7) == 0) { + ctx->vec_md = &br_sha512_vtable; + hashsz = br_sha512_SIZE; + cp += 7; +#endif + } else { + ctx->vec_status = VE_FINGERPRINT_UNKNOWN; + ve_error_set("%s: no supported fingerprint", path); + } + } + *error = ctx->vec_status; + ctx->vec_hashsz = hashsz; + ctx->vec_want = cp; + if (hashsz > 0) { + ctx->vec_md->init(&ctx->vec_ctx.vtable); + + if (off > 0) { + lseek(fd, 0, SEEK_SET); + vectx_lseek(ctx, off, SEEK_SET); + } + } + DEBUG_PRINTF(2, + ("vectx_open: caller=%s,name='%s',hashsz=%lu,status=%d\n", + caller, path, (unsigned long)ctx->vec_hashsz, + ctx->vec_status)); + return (ctx); + +enomem: /* unlikely */ + *error = ENOMEM; + free(ctx); + return (NULL); +} + +/** + * @brief + * read bytes from file and update hash + * + * It is critical that all file I/O comes through here. + * We keep track of current offset. + * We also track what offset we have hashed to, + * so we won't replay data if we seek backwards. + * + * @param[in] pctx + * pointer to ctx + * + * @param[in] buf + * + * @param[in] nbytes + * + * @return bytes read or error. + */ +ssize_t +vectx_read(struct vectx *ctx, void *buf, size_t nbytes) +{ + unsigned char *bp = buf; + int d; + int n; + int delta; + int x; + size_t off; + + if (ctx->vec_hashsz == 0) /* nothing to do */ + return (read(ctx->vec_fd, buf, nbytes)); + + off = 0; + do { + /* + * Do this in reasonable chunks so + * we don't timeout if doing tftp + */ + x = nbytes - off; + x = MIN(PAGE_SIZE, x); + d = n = read(ctx->vec_fd, &bp[off], x); + if (ctx->vec_closing && n < x) { + DEBUG_PRINTF(3, + ("%s: read %d off=%ld hashed=%ld size=%ld\n", + __func__, n, (long)ctx->vec_off, + (long)ctx->vec_hashed, (long)ctx->vec_size)); + } + if (n < 0) { + return (n); + } + if (d > 0) { + /* we may have seeked backwards! */ + delta = ctx->vec_hashed - ctx->vec_off; + if (delta > 0) { + x = MIN(delta, d); + off += x; + d -= x; + ctx->vec_off += x; + } + if (d > 0) { + if (ctx->vec_closing && d < PAGE_SIZE) { + DEBUG_PRINTF(3, + ("%s: update %ld + %d\n", + __func__, + (long)ctx->vec_hashed, d)); + } + ctx->vec_md->update(&ctx->vec_ctx.vtable, &bp[off], d); + off += d; + ctx->vec_off += d; + ctx->vec_hashed += d; + } + } + } while (n > 0 && off < nbytes); + return (off); +} + +/** + * @brief + * vectx equivalent of lseek + * + * When seeking forwards we actually call vectx_read + * to reach the desired offset. + * + * We support seeking backwards. + * + * @param[in] pctx + * pointer to ctx + * + * @param[in] off + * desired offset + * + * @param[in] whence + * We try to convert whence to ``SEEK_SET``. + * We do not support ``SEEK_DATA`` or ``SEEK_HOLE``. + * + * @return offset or error. + */ +off_t +vectx_lseek(struct vectx *ctx, off_t off, int whence) +{ + unsigned char buf[PAGE_SIZE]; + size_t delta; + ssize_t n; + + if (ctx->vec_hashsz == 0) /* nothing to do */ + return (lseek(ctx->vec_fd, off, whence)); + + /* + * Convert whence to SEEK_SET + */ + DEBUG_PRINTF(3, + ("%s(%s, %ld, %d)\n", __func__, ctx->vec_path, (long)off, whence)); + if (whence == SEEK_END && off <= 0) { + if (ctx->vec_size < 0) { + if (ctx->vec_closing) { + /* size unknown - read until EOF */ + do { + n = vectx_read(ctx, buf, PAGE_SIZE); + if (n < 0) + return (n); + } while (n > 0); + return (ctx->vec_off); + } + } else { + if (ctx->vec_closing && ctx->vec_hashed < ctx->vec_size) { + DEBUG_PRINTF(3, ("%s: SEEK_END %ld\n", + __func__, + (long)(ctx->vec_size - ctx->vec_hashed))); + } + whence = SEEK_SET; + off += ctx->vec_size; + } + } else if (whence == SEEK_CUR) { + whence = SEEK_SET; + off += ctx->vec_off; + } + if (whence != SEEK_SET || + (off > ctx->vec_size && ctx->vec_size > 0)) { + printf("ERROR: %s: unsupported operation: whence=%d off=%ld -> %ld\n", + __func__, whence, (long)ctx->vec_off, (long)off); + return (-1); + } + if (off < ctx->vec_hashed) { +#ifdef _STANDALONE + struct open_file *f = fd2open_file(ctx->vec_fd); + + if (f != NULL && + strncmp(f->f_ops->fs_name, "tftp", 4) == 0) { + /* we cannot rewind if we've hashed much of the file */ + if (ctx->vec_hashed > ctx->vec_size / 5) + return (-1); /* refuse! */ + } +#endif + /* seeking backwards! just do it */ + ctx->vec_off = lseek(ctx->vec_fd, off, whence); + return (ctx->vec_off); + } + n = 0; + do { + delta = off - ctx->vec_off; + if (delta > 0) { + delta = MIN(PAGE_SIZE, delta); + n = vectx_read(ctx, buf, delta); + if (n < 0) + return (n); + } + } while (ctx->vec_off < off && n > 0); + return (ctx->vec_off); +} + +/** + * @brief + * check that hashes match and cleanup + * + * We have finished reading file, compare the hash with what + * we wanted. + * + * Be sure to call this before closing the file, since we may + * need to seek to the end to ensure hashing is complete. + * + * @param[in] pctx + * pointer to ctx + * + * @return 0 or an error. + */ +int +vectx_close(struct vectx *ctx, int severity, const char *caller) +{ + int rc; + + ctx->vec_closing = 1; + if (ctx->vec_hashsz == 0) { + rc = ctx->vec_status; + } else { +#ifdef VE_PCR_SUPPORT + /* + * Only update pcr with things that must verify + * these tend to be processed in a more deterministic + * order, which makes our pseudo pcr more useful. + */ + ve_pcr_updating_set((severity == VE_MUST)); +#endif + /* make sure we have hashed it all */ + vectx_lseek(ctx, 0, SEEK_END); + rc = ve_check_hash(&ctx->vec_ctx, ctx->vec_md, + ctx->vec_path, ctx->vec_want, ctx->vec_hashsz); + } + DEBUG_PRINTF(2, + ("vectx_close: caller=%s,name='%s',rc=%d,severity=%d\n", + caller,ctx->vec_path, rc, severity)); + verify_report(ctx->vec_path, severity, rc, NULL); + if (rc == VE_FINGERPRINT_WRONG) { +#if !defined(UNIT_TEST) && !defined(DEBUG_VECTX) + /* we are generally called with VE_MUST */ + if (severity > VE_WANT) + panic("cannot continue"); +#endif + } + 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..0a1fc59eb9b4 --- /dev/null +++ b/lib/libsecureboot/veopen.c @@ -0,0 +1,469 @@ +/*- + * 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 <sys/cdefs.h> +#include <sys/queue.h> + +#include "libsecureboot-priv.h" + + +struct fingerprint_info { + char *fi_prefix; /**< manifest entries relative to */ + char *fi_skip; /**< manifest entries prefixed with */ + const char *fi_data; /**< manifest data */ + size_t fi_prefix_len; /**< length of prefix */ + size_t fi_skip_len; /**< length of skip */ + dev_t fi_dev; /**< device id */ + LIST_ENTRY(fingerprint_info) entries; +}; + +static LIST_HEAD(, fingerprint_info) fi_list; + +static void +fingerprint_info_init(void) +{ + static int once; + + if (once) + return; + LIST_INIT(&fi_list); + once = 1; +} + +/** + * @brief + * add manifest data to list + * + * list is kept sorted by longest prefix. + * + * @param[in] prefix + * path that all manifest entries are resolved via + * + * @param[in] skip + * optional prefix within manifest entries which should be skipped + * + * @param[in] data + * manifest data + */ +void +fingerprint_info_add(const char *filename, const char *prefix, + const char *skip, const char *data, struct stat *stp) +{ + struct fingerprint_info *fip, *nfip, *lfip; + char *cp; + int n; + + fingerprint_info_init(); + nfip = malloc(sizeof(struct fingerprint_info)); + if (nfip == NULL) { +#ifdef _STANDALONE + printf("%s: out of memory! %lu\n", __func__, + (unsigned long)sizeof(struct fingerprint_info)); +#endif + return; + } + if (prefix) { + nfip->fi_prefix = strdup(prefix); + } else { + if (!filename) { + free(nfip); + return; + } + nfip->fi_prefix = strdup(filename); + cp = strrchr(nfip->fi_prefix, '/'); + if (cp == nfip->fi_prefix) { + cp[1] = '\0'; + } else if (cp) { + *cp = '\0'; + } else { + free(nfip->fi_prefix); + free(nfip); + return; + } + } + /* collapse any trailing ..[/] */ + n = 0; + while ((cp = strrchr(nfip->fi_prefix, '/')) > nfip->fi_prefix) { + if (cp[1] == '\0') { /* trailing "/" */ + *cp = '\0'; + continue; + } + if (strcmp(&cp[1], "..") == 0) { + n++; + *cp = '\0'; + continue; + } + if (n > 0) { + n--; + *cp = '\0'; + } + if (n == 0) + break; + } + nfip->fi_dev = stp->st_dev; +#ifdef UNIT_TEST + nfip->fi_dev = 0; +#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); + if (fstat(fd, &st) == 0) + dev = st.st_dev; +#ifdef UNIT_TEST + dev = 0; +#endif + /* + * get the first entry - it will have longest prefix + * so we can can work out how to initially split path + */ + fip = LIST_FIRST(&fi_list); + if (!fip) + return (NULL); + prefix = pbuf; + ep = NULL; + cp = &pbuf[fip->fi_prefix_len]; + do { + if (ep) { + *ep = '/'; + cp -= 2; + if (cp < pbuf) + break; + } + nlen = plen = 0; /* keep gcc quiet */ + if (cp > pbuf) { + for ( ; cp >= pbuf && *cp != '/'; cp--) + ; /* nothing */ + if (cp > pbuf) { + ep = cp++; + *ep = '\0'; + } else { + cp = pbuf; + } + if (ep) { + plen = ep - pbuf; + nlen = n - plen - 1; + } + } + if (cp == pbuf) { + prefix = "/"; + plen = 1; + if (*cp == '/') { + nlen = n - 1; + cp++; + } else + nlen = n; + ep = NULL; + } + + DEBUG_PRINTF(2, ("looking for %s %zu %s\n", prefix, plen, cp)); + + LIST_FOREACH(fip, &fi_list, entries) { + DEBUG_PRINTF(4, ("at %zu %s\n", + fip->fi_prefix_len, fip->fi_prefix)); + + if (fip->fi_prefix_len < plen) { + DEBUG_PRINTF(3, ("skipping prefix=%s %zu %zu\n", + fip->fi_prefix, fip->fi_prefix_len, + plen)); + break; + } + if (fip->fi_prefix_len == plen) { + if (fip->fi_dev != 0 && fip->fi_dev != dev) { + DEBUG_PRINTF(3, ( + "skipping dev=%ld != %ld\n", + (long)fip->fi_dev, + (long)dev)); + continue; + } + if (strcmp(prefix, fip->fi_prefix)) { + DEBUG_PRINTF(3, ( + "skipping prefix=%s\n", + fip->fi_prefix)); + continue; + } + DEBUG_PRINTF(3, ("checking prefix=%s\n", + fip->fi_prefix)); + if (fip->fi_skip_len) { + np = nbuf; + nplen = snprintf(nbuf, sizeof(nbuf), + "%s/%s", + fip->fi_skip, cp); + nplen = MIN(nplen, sizeof(nbuf) - 1); + } else { + np = cp; + nplen = nlen; + } + DEBUG_PRINTF(3, ("lookup: '%s'\n", np)); + if (!(fp = strstr(fip->fi_data, np))) + continue; +#ifdef MANIFEST_SKIP_MAYBE + if (fip->fi_skip_len == 0 && + fp > fip->fi_data && fp[-1] == '/') { + fp = maybe_skip(fp, fip, &nplen); + } +#endif + /* + * when we find a match: + * fp[nplen] will be space and + * fp will be fip->fi_data or + * fp[-1] will be \n + */ + if (!((fp == fip->fi_data || fp[-1] == '\n') && + fp[nplen] == ' ')) { + do { + fp++; + fp = strstr(fp, np); + if (fp) { +#ifdef MANIFEST_SKIP_MAYBE + if (fip->fi_skip_len == 0 && + fp > fip->fi_data && + fp[-1] == '/') { + fp = maybe_skip(fp, fip, &nplen); + } +#endif + DEBUG_PRINTF(3, + ("fp[-1]=%#x fp[%zu]=%#x fp=%.78s\n", + fp[-1], nplen, + fp[nplen], + fp)); + } + } while (fp != NULL && + !(fp[-1] == '\n' && + fp[nplen] == ' ')); + if (!fp) + continue; + } + DEBUG_PRINTF(2, ("found %.78s\n", fp)); + /* we have a match! */ + for (cp = &fp[nplen]; *cp == ' '; cp++) + ; /* nothing */ + return (cp); + } else { + DEBUG_PRINTF(3, + ("Ignoring prefix=%s\n", fip->fi_prefix)); + } + } + } while (cp > &pbuf[1]); + + return (NULL); +} + +static int +verify_fingerprint(int fd, const char *path, const char *cp, off_t off) +{ + unsigned char buf[PAGE_SIZE]; + const br_hash_class *md; + br_hash_compat_context mctx; + size_t hlen; + int n; + + if (strncmp(cp, "no_hash", 7) == 0) { + return (VE_FINGERPRINT_IGNORE); + } else if (strncmp(cp, "sha256=", 7) == 0) { + md = &br_sha256_vtable; + hlen = br_sha256_SIZE; + cp += 7; +#ifdef VE_SHA1_SUPPORT + } else if (strncmp(cp, "sha1=", 5) == 0) { + md = &br_sha1_vtable; + hlen = br_sha1_SIZE; + cp += 5; +#endif +#ifdef VE_SHA384_SUPPORT + } else if (strncmp(cp, "sha384=", 7) == 0) { + md = &br_sha384_vtable; + hlen = br_sha384_SIZE; + cp += 7; +#endif +#ifdef VE_SHA512_SUPPORT + } else if (strncmp(cp, "sha512=", 7) == 0) { + md = &br_sha512_vtable; + hlen = br_sha512_SIZE; + cp += 7; +#endif + } else { + ve_error_set("%s: no supported fingerprint", path); + return (VE_FINGERPRINT_UNKNOWN); + } + + md->init(&mctx.vtable); + if (off) + lseek(fd, 0, SEEK_SET); + do { + n = read(fd, buf, sizeof(buf)); + if (n < 0) + return (n); + if (n > 0) + md->update(&mctx.vtable, buf, n); + } while (n > 0); + lseek(fd, off, SEEK_SET); + return (ve_check_hash(&mctx, md, path, cp, hlen)); +} + + +/** + * @brief + * verify an open file + * + * @param[in] fd + * open descriptor + * + * @param[in] path + * pathname to open + * + * @param[in] off + * current offset + * + * @return 0, VE_FINGERPRINT_OK or VE_FINGERPRINT_NONE, VE_FINGERPRINT_WRONG + */ +int +verify_fd(int fd, const char *path, off_t off, struct stat *stp) +{ + struct stat st; + char *cp; + int rc; + + if (!stp) { + if (fstat(fd, &st) == 0) + stp = &st; + } + if (stp && !S_ISREG(stp->st_mode)) + return (0); /* not relevant */ + cp = fingerprint_info_lookup(fd, path); + if (!cp) { + ve_error_set("%s: no entry", path); + return (VE_FINGERPRINT_NONE); + } + rc = verify_fingerprint(fd, path, cp, off); + switch (rc) { + case VE_FINGERPRINT_OK: + case VE_FINGERPRINT_IGNORE: + case VE_FINGERPRINT_UNKNOWN: + return (rc); + default: + return (VE_FINGERPRINT_WRONG); + } +} + +/** + * @brief + * open a file if it can be verified + * + * @param[in] path + * pathname to open + * + * @param[in] flags + * flags for open + * + * @return fd or VE_FINGERPRINT_NONE, VE_FINGERPRINT_WRONG + */ +int +verify_open(const char *path, int flags) +{ + int fd; + int rc; + + if ((fd = open(path, flags)) >= 0) { + if ((rc = verify_fd(fd, path, 0, NULL)) < 0) { + close(fd); + fd = rc; + } + } + return (fd); +} diff --git a/lib/libsecureboot/vepcr.c b/lib/libsecureboot/vepcr.c new file mode 100644 index 000000000000..f4f146975e5e --- /dev/null +++ b/lib/libsecureboot/vepcr.c @@ -0,0 +1,167 @@ +/*- + * 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 <sys/cdefs.h> +#include <sys/queue.h> +#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; +static int pcr_updating = -1; + +struct hashed_info { + const char *hi_path; + const char *hi_basename; + STAILQ_ENTRY(hashed_info) entries; +}; + +static STAILQ_HEAD(, hashed_info) hi_list; + + +/** + * @brief initialize pcr context + * + * Real TPM registers only hold a SHA1 hash + * but we use SHA256 + */ +void +ve_pcr_init(void) +{ + if (pcr_updating < 0) { + pcr_updating = 0; + pcr_hlen = br_sha256_SIZE; + pcr_md = &br_sha256_vtable; + pcr_md->init(&pcr_ctx.vtable); + STAILQ_INIT(&hi_list); + } +} + +/** + * @brief get pcr_updating state + */ +int +ve_pcr_updating_get(void) +{ + return (pcr_updating); +} + +/** + * @brief set pcr_updating state + */ +void +ve_pcr_updating_set(int updating) +{ + pcr_updating = updating; +} + +/** + * @brief update pcr context + */ +void +ve_pcr_update(const char *path, unsigned char *data, size_t dlen) +{ + struct hashed_info *hip; + + if (pcr_updating > 0 && pcr_md != NULL) { + pcr_md->update(&pcr_ctx.vtable, data, dlen); + /* if mallocs fail, measured boot will likely fail too */ + if ((hip = malloc(sizeof(struct hashed_info)))) { + hip->hi_path = strdup(path); + if (!hip->hi_path) { + free(hip); + return; + } + hip->hi_basename = strrchr(hip->hi_path, '/'); + if (hip->hi_basename) { + hip->hi_basename++; + } else { + hip->hi_basename = hip->hi_path; + } + STAILQ_INSERT_TAIL(&hi_list, hip, entries); + } + } +} + +/** + * @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); +} + +/** + * @brief get list of paths in prc + */ +char * +ve_pcr_hashed_get(int flags) +{ + const char *cp; + char *hinfo; + struct hashed_info *hip; + size_t nbytes; + size_t x; + int n; + + n = 0; + nbytes = x = 0; + hinfo = NULL; + STAILQ_FOREACH(hip, &hi_list, entries) { + nbytes += 1 + strlen(flags ? hip->hi_basename : hip->hi_path); + } + if (nbytes > 1) { + hinfo = malloc(nbytes + 2); + if (hinfo) { + STAILQ_FOREACH(hip, &hi_list, entries) { + cp = flags ? hip->hi_basename : hip->hi_path; + n = snprintf(&hinfo[x], nbytes - x, "%s,", cp); + x += n; + } + if (x > 0) { + hinfo[x-1] = '\0'; + } + } + } + return hinfo; +} diff --git a/lib/libsecureboot/verify_file.c b/lib/libsecureboot/verify_file.c new file mode 100644 index 000000000000..753204a33b6a --- /dev/null +++ b/lib/libsecureboot/verify_file.c @@ -0,0 +1,690 @@ +/*- + * Copyright (c) 2017-2020, Juniper Networks, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Routines to verify files loaded. + */ + +#include <sys/param.h> +#include <string.h> +#include <sys/queue.h> +#include <sys/kenv.h> + +#include "libsecureboot.h" +#include <verify_file.h> +#include <manifests.h> + +#ifdef UNIT_TEST +# include <err.h> +# define panic warn +/* + * define MANIFEST_SKIP to Skip - in tests/tvo.c so that + * tvo can control the value we use in find_manifest() + */ +extern char *Destdir; +extern size_t DestdirLen; +extern char *Skip; +# undef MANIFEST_SKIP +# define MANIFEST_SKIP Skip +# undef VE_DEBUG_LEVEL +#endif + +/* + * We sometimes need to know if input is verified or not. + * The extra slot is for tracking most recently opened. + */ +#ifndef SOPEN_MAX +#define SOPEN_MAX 64 +#endif +static int ve_status[SOPEN_MAX+1]; +static int ve_status_state; +struct verify_status; +static struct verify_status *verified_files = NULL; +static int loaded_manifests = 0; /* have we loaded anything? */ + +enum { + VE_VERBOSE_SILENT, /* only report errors */ + VE_VERBOSE_UNVERIFIED, /* all unverified files */ + VE_VERBOSE_MUST, /* report VE_MUST */ + VE_VERBOSE_ALL, /* report all */ + VE_VERBOSE_DEBUG, /* extra noise */ +}; + +#ifndef VE_VERBOSE_DEFAULT +# define VE_VERBOSE_DEFAULT VE_VERBOSE_MUST +#endif +static int Verbose = VE_VERBOSE_DEFAULT; + +#define VE_STATUS_NONE 1 +#define VE_STATUS_VALID 2 + +/** + * @brief set ve status for fd + */ +void +ve_status_set(int fd, int ves) +{ + if (fd >= 0 && fd < SOPEN_MAX) { + ve_status[fd] = ves; + ve_status_state = VE_STATUS_VALID; + } + ve_status[SOPEN_MAX] = ves; +} + +/** + * @brief get ve status of fd + * + * What we return depends on ve_status_state. + * + * @return + * @li ve_status[fd] if ve_status_state is valid + * @li ve_status[SOPEN_MAX] if ve_status_state is none + * @li VE_NOT_CHECKED if ve_status_state uninitialized + */ +int +ve_status_get(int fd) +{ + if (!ve_status_state) { + return (VE_NOT_CHECKED); + } + if (ve_status_state == VE_STATUS_VALID && + fd >= 0 && fd < SOPEN_MAX) + return (ve_status[fd]); + return (ve_status[SOPEN_MAX]); /* most recent */ +} + +/** + * @brief track verify status + * + * occasionally loader will make multiple calls + * for the same file, we need only check it once. + */ +struct verify_status { + dev_t vs_dev; + ino_t vs_ino; + int vs_status; + struct verify_status *vs_next; +}; + +int +is_verified(struct stat *stp) +{ + struct verify_status *vsp; + int rc = VE_NOT_CHECKED; + + if (stp->st_ino > 0) { + for (vsp = verified_files; vsp != NULL; vsp = vsp->vs_next) { + if (stp->st_dev == vsp->vs_dev && + stp->st_ino == vsp->vs_ino) { + rc = vsp->vs_status; + break; + } + } + } + DEBUG_PRINTF(4, ("%s: dev=%lld,ino=%llu,status=%d\n", + __func__, (long long)stp->st_dev, + (unsigned long long)stp->st_ino, rc)); + return (rc); +} + +/* most recent first, since most likely to see repeated calls. */ +void +add_verify_status(struct stat *stp, int status) +{ + struct verify_status *vsp; + + vsp = malloc(sizeof(struct verify_status)); + if (vsp) { + vsp->vs_next = verified_files; + vsp->vs_dev = stp->st_dev; + vsp->vs_ino = stp->st_ino; + vsp->vs_status = status; + verified_files = vsp; + } + DEBUG_PRINTF(4, ("%s: dev=%lld,ino=%llu,status=%d\n", + __func__, (long long)stp->st_dev, + (unsigned long long)stp->st_ino, status)); +} + + +/** + * @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, VerifyFlags); + if (content) { +#ifdef UNIT_TEST + if (DestdirLen > 0 && + strncmp(name, Destdir, DestdirLen) == 0) { + name += DestdirLen; + if (prefix && + strncmp(prefix, Destdir, DestdirLen) == 0) + prefix += DestdirLen; + } +#endif + fingerprint_info_add(name, prefix, skip, content, stp); + add_verify_status(stp, VE_VERIFIED); + loaded_manifests = 1; /* we are verifying! */ + DEBUG_PRINTF(3, ("loaded: %s %s %s\n", + name, prefix, skip)); + rc = VE_VERIFIED; + } else { + rc = VE_FINGERPRINT_WRONG; + add_verify_status(stp, rc); /* remember */ + } + } + return (rc); +} + +static int +find_manifest(const char *name) +{ + struct stat st; + char buf[MAXPATHLEN]; + char *prefix; + char *skip; + const char **tp; + int rc; + + strncpy(buf, name, MAXPATHLEN - 1); + if (!(prefix = strrchr(buf, '/'))) + return (-1); + *prefix = '\0'; + prefix = strdup(buf); + rc = VE_FINGERPRINT_NONE; + for (tp = manifest_names; *tp; tp++) { + snprintf(buf, sizeof(buf), "%s/%s", prefix, *tp); + if (*tp[0] == '.') { + /* skip /../ */ + if (prefix[0] == '\0' || prefix[1] == '\0') + continue; + } + DEBUG_PRINTF(5, ("looking for %s\n", buf)); + if (stat(buf, &st) == 0 && st.st_size > 0) { +#ifdef MANIFEST_SKIP_ALWAYS /* very unlikely */ + skip = MANIFEST_SKIP_ALWAYS; +#else +#ifdef MANIFEST_SKIP /* rare */ + if (*tp[0] == '.') { + skip = MANIFEST_SKIP; + } else +#endif + skip = NULL; +#endif + rc = load_manifest(buf, skip ? prefix : NULL, + skip, &st); + break; + } + } + free(prefix); + return (rc); +} + + +#ifdef LOADER_VERIEXEC_TESTING +# define ACCEPT_NO_FP_DEFAULT VE_MUST + 1 +#else +# define ACCEPT_NO_FP_DEFAULT VE_MUST +#endif + +static int +severity_guess(const char *filename) +{ + const char *cp; + + /* + * Some files like *.conf and *.hints may be unsigned, + * a *.tgz is expected to have its own signed manifest. + * We allow *.conf to get VE_WANT, but files we expect + * to always be unverified get VE_TRY and we will not + * report them. + */ + if ((cp = strrchr(filename, '.'))) { + if (strcmp(cp, ".cookie") == 0 || + strcmp(cp, ".hints") == 0 || + strcmp(cp, ".order") == 0 || + strcmp(cp, ".tgz") == 0) + return (VE_TRY); + if (strcmp(cp, ".4th") == 0 || + strcmp(cp, ".lua") == 0 || + strcmp(cp, ".rc") == 0) + return (VE_MUST); + } + return (VE_WANT); +} + +static int Verifying = -1; /* 0 if not verifying */ + +static void +verify_tweak(int fd, off_t off, struct stat *stp, + char *tweak, int *accept_no_fp) +{ + 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 = VE_VERBOSE_ALL; + /* treat self test failure as fatal */ + if (!ve_self_tests()) { + panic("verify self tests failed"); + } + } else if (strcmp(tweak, "modules") == 0) { + /* modules/kernel must be verified */ + *accept_no_fp = VE_MUST; + } else if (strcmp(tweak, "try") == 0) { + /* best effort: always accept no fp */ + *accept_no_fp = VE_MUST + 1; + } else if (strcmp(tweak, "verbose") == 0) { + Verbose = VE_VERBOSE_ALL; + } else if (strcmp(tweak, "quiet") == 0) { + Verbose = VE_VERBOSE_UNVERIFIED; + VerifyFlags = 0; + } else if (strcmp(tweak, "silent") == 0) { + Verbose = VE_VERBOSE_SILENT; + VerifyFlags = 0; + } else if (strncmp(tweak, "trust", 5) == 0) { + /* content is trust anchor to add or revoke */ + unsigned char *ucp; + size_t num; + + if (off > 0) + lseek(fd, 0, SEEK_SET); + ucp = read_fd(fd, stp->st_size); + if (ucp == NULL) + return; + if (strstr(tweak, "revoke")) { + num = ve_trust_anchors_revoke(ucp, stp->st_size); + DEBUG_PRINTF(3, ("revoked %d trust anchors\n", + (int) num)); + } else { + num = ve_trust_anchors_add_buf(ucp, stp->st_size); + DEBUG_PRINTF(3, ("added %d trust anchors\n", + (int) num)); + } + } +} + +#ifndef VE_DEBUG_LEVEL +# define VE_DEBUG_LEVEL 0 +#endif + +static int +getenv_int(const char *var, int def) +{ + const char *cp; + char *ep; + long val; + + val = def; + cp = getenv(var); + if (cp && *cp) { + val = strtol(cp, &ep, 0); + if ((ep && *ep) || val != (int)val) { + val = def; + } + } + return (int)val; +} + + +/** + * @brief report verification status + * + * @param[in] path + * path we attempted to verify + * + * @param[in] severity + * indicator of how to handle case of missing fingerprint + * + * @param[in] status + * result of verification + * 0 not a file to be verified, > 0 success, < 0 error + * + * @param[in] stp + * pointer to struct stat, used in extra info to be output + * + * The output is dictated by combinations of the above and the setting + * of Verbose: + * + * VE_VERBOSE_SILENT + * report only failure to verify if severity is VE_WANT or higher. + * + * VE_VERBOSE_UNVERIFIED + * report any unverified file. + * + * VE_VERBOSE_MUST + * report verified only if severity is VE_MUST or higher. + * + * VE_VERBOSE_ALL + * report all verified files. + * + * VE_VERBOSE_DEBUG + * if stp is not NULL report dev,inode for path + */ +void +verify_report(const char *path, int severity, int status, struct stat *stp) +{ + if (status < 0 || status == VE_FINGERPRINT_IGNORE) { + if (Verbose < VE_VERBOSE_ALL && severity < VE_WANT) + return; + if (Verbose >= VE_VERBOSE_UNVERIFIED || severity > VE_TRY || + status <= VE_FINGERPRINT_WRONG) { + if (Verbose == VE_VERBOSE_DEBUG && stp != NULL) + printf("Unverified %s %llu,%llu\n", + ve_error_get(), + (long long)stp->st_dev, + (long long)stp->st_ino); + else + printf("Unverified %s\n", ve_error_get()); + } + } else if (status > 0 && Verbose >= VE_VERBOSE_MUST) { + if (severity >= VE_MUST || Verbose >= VE_VERBOSE_ALL) { + if (Verbose == VE_VERBOSE_DEBUG && stp != NULL) + printf("Unverified %s %llu,%llu\n", + path, + (long long)stp->st_dev, + (long long)stp->st_ino); + else + printf("Verified %s\n", path); + } + } +} + + +/** + * @brief prepare to verify an open file + * + * @param[in] fd + * open descriptor + * + * @param[in] filename + * path we opened and will use to lookup fingerprint + * + * @param[in] stp + * stat pointer so we can check file type + */ +int +verify_prep(int fd, const char *filename, off_t off, struct stat *stp, + const char *caller) +{ + int rc; + + if (Verifying < 0) { + Verifying = ve_trust_init(); + /* initialize ve_status with default result */ + rc = Verifying ? VE_NOT_CHECKED : VE_NOT_VERIFYING; + ve_status_set(0, rc); + ve_status_state = VE_STATUS_NONE; + if (Verifying) { + ve_self_tests(); + ve_anchor_verbose_set(1); + } + } + if (!Verifying || fd < 0) + return (0); + if (stp) { + if (fstat(fd, stp) < 0 || !S_ISREG(stp->st_mode)) + return (0); + } + DEBUG_PRINTF(2, + ("verify_prep: caller=%s,fd=%d,name='%s',off=%lld,dev=%lld,ino=%llu\n", + caller, fd, filename, (long long)off, (long long)stp->st_dev, + (unsigned long long)stp->st_ino)); + rc = is_verified(stp); + if (rc == VE_NOT_CHECKED) { + rc = find_manifest(filename); + if (rc == VE_VERIFIED) + rc = VE_NOT_CHECKED; + } else { + ve_status_set(fd, rc); + } + return (rc); +} + +/** + * @brief verify an open file + * + * @param[in] fd + * open descriptor + * + * @param[in] filename + * path we opened and will use to lookup fingerprint + * + * @param[in] off + * current offset in fd, must be restored on return + * + * @param[in] severity + * indicator of how to handle case of missing fingerprint + * + * We look for a signed manifest relative to the filename + * just opened and verify/load it if needed. + * + * We then use verify_fd() in libve to actually verify that hash for + * open file. If it returns < 0 we look at the severity arg to decide + * what to do about it. + * + * If verify_fd() returns VE_FINGERPRINT_NONE we accept it if severity + * is < accept_no_fp. + * + * @return >= 0 on success < 0 on failure + */ +int +verify_file(int fd, const char *filename, off_t off, int severity, + const char *caller) +{ + static int check_verbose = 1; + static int accept_no_fp = ACCEPT_NO_FP_DEFAULT; + struct stat st; + char *cp; + int rc; + + if (check_verbose) { + check_verbose = 0; + Verbose = getenv_int("VE_VERBOSE", VE_VERBOSE_DEFAULT); + VerifyFlags = getenv_int("VE_VERIFY_FLAGS", + Verbose ? VEF_VERBOSE : 0); +#ifndef UNIT_TEST + ve_debug_set(getenv_int("VE_DEBUG_LEVEL", VE_DEBUG_LEVEL)); +#endif + } + + rc = verify_prep(fd, filename, off, &st, caller); + + if (!rc) + return (0); + + if (rc != VE_FINGERPRINT_WRONG && loaded_manifests) { + if (rc != VE_NOT_CHECKED) + return (rc); + + if (severity <= VE_GUESS) + severity = severity_guess(filename); +#ifdef VE_PCR_SUPPORT + /* + * Only update pcr with things that must verify + * these tend to be processed in a more deterministic + * order, which makes our pseudo pcr more useful. + */ + ve_pcr_updating_set((severity == VE_MUST)); +#endif +#ifdef UNIT_TEST + if (DestdirLen > 0 && + strncmp(filename, Destdir, DestdirLen) == 0) { + filename += DestdirLen; + } +#endif + rc = verify_fd(fd, filename, off, &st); + verify_report(filename, severity, rc, &st); + if (rc >= 0) { + if (severity < VE_MUST) { /* not a kernel or module */ + if ((cp = strrchr(filename, '/'))) { + cp++; + if (strncmp(cp, "loader.ve.", 10) == 0) { + cp += 10; + verify_tweak(fd, off, &st, cp, + &accept_no_fp); + } + } + } + add_verify_status(&st, rc); + ve_status_set(fd, rc); + return (rc); + } + if (rc == VE_FINGERPRINT_UNKNOWN && severity < VE_MUST) + rc = VE_UNVERIFIED_OK; + else if (rc == VE_FINGERPRINT_NONE && severity < accept_no_fp) + rc = VE_UNVERIFIED_OK; + + add_verify_status(&st, rc); + + /* recheck debug/verbose level next time we are called */ + if (rc == VE_UNVERIFIED_OK) { + check_verbose = 1; + } + } +#ifdef LOADER_VERIEXEC_TESTING + else if (rc != VE_FINGERPRINT_WRONG) { + /* + * We have not loaded any manifest and + * not because of verication failure. + * Most likely reason is we have none. + * Allow boot to proceed if we are just testing. + */ + return (VE_UNVERIFIED_OK); + } +#endif + if (rc == VE_FINGERPRINT_WRONG && severity > accept_no_fp) + panic("cannot continue"); + ve_status_set(fd, rc); + return (rc); +} + +/** + * @brief get hex string for pcr value and export + * + * In case we are doing measured boot, provide + * value of the "pcr" data we have accumulated. + */ +void +verify_pcr_export(void) +{ +#ifdef VE_PCR_SUPPORT + char hexbuf[br_sha256_SIZE * 2 + 2]; + unsigned char hbuf[br_sha256_SIZE]; + char *hinfo; + char *hex; + ssize_t hlen; + + hlen = ve_pcr_get(hbuf, sizeof(hbuf)); + if (hlen > 0) { + hex = hexdigest(hexbuf, sizeof(hexbuf), hbuf, hlen); + if (hex) { + hex[hlen*2] = '\0'; /* clobber newline */ + setenv("loader.ve.pcr", hex, 1); + DEBUG_PRINTF(1, + ("%s: setenv(loader.ve.pcr, %s\n", __func__, + hex)); + hinfo = ve_pcr_hashed_get(1); + if (hinfo) { + setenv("loader.ve.hashed", hinfo, 1); + DEBUG_PRINTF(1, + ("%s: setenv(loader.ve.hashed, %s\n", + __func__, hinfo)); + if ((hlen = strlen(hinfo)) > KENV_MVALLEN) { + /* + * bump kenv_mvallen + * roundup to multiple of KENV_MVALLEN + */ + char mvallen[16]; + + hlen += KENV_MVALLEN - + (hlen % KENV_MVALLEN); + if (snprintf(mvallen, sizeof(mvallen), + "%d", (int) hlen) < (int)sizeof(mvallen)) + setenv("kenv_mvallen", mvallen, 1); + } + free(hinfo); + } + } + } +#endif +} + +/* + * For tftp and http we need to hash pathname + * to be able to fake stat(2) data. + */ +int +hash_string(char *s, size_t n, char *buf, size_t bufsz) +{ + br_hash_compat_context mctx; + const br_hash_class *md; + + switch (bufsz) { + case br_sha1_SIZE: + md = &br_sha1_vtable; + break; + case br_sha256_SIZE: + md = &br_sha256_vtable; + break; + default: + if (bufsz < br_sha1_SIZE) + return -1; + md = &br_sha1_vtable; + bufsz = br_sha1_SIZE; + break; + } + if (n == 0) + n = strlen(s); + md->init(&mctx.vtable); + md->update(&mctx.vtable, s, n); + md->out(&mctx.vtable, buf); + return bufsz; +} + + diff --git a/lib/libsecureboot/vesigned.c b/lib/libsecureboot/vesigned.c new file mode 100644 index 000000000000..4015923eabfd --- /dev/null +++ b/lib/libsecureboot/vesigned.c @@ -0,0 +1,59 @@ +/*- + * 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 <sys/cdefs.h> +#include <libsecureboot.h> + +#include <vse.h> + +/** + * @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..c0ddbfb4921f --- /dev/null +++ b/lib/libsecureboot/veta.c @@ -0,0 +1,109 @@ +/*- + * 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 <sys/cdefs.h> +/** + * @file veta.c - add to trust anchors + * + */ + +#define NEED_BRSSL_H +#include "libsecureboot-priv.h" +#include <brssl.h> +#include <sys/stat.h> +#include <dirent.h> + +#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..67d27d567485 --- /dev/null +++ b/lib/libsecureboot/vets.c @@ -0,0 +1,1156 @@ +/*- + * 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 <sys/cdefs.h> +/** + * @file vets.c - trust store + * @brief verify signatures + * + * We leverage code from BearSSL www.bearssl.org + */ + +#include <sys/time.h> +#include <stdarg.h> +#define NEED_BRSSL_H +#include "libsecureboot-priv.h" +#include <brssl.h> +#include <ta.h> + +#ifndef TRUST_ANCHOR_STR +# define TRUST_ANCHOR_STR ta_PEM +#endif + +#define EPOCH_YEAR 1970 +#define AVG_SECONDS_PER_YEAR 31556952L +#define SECONDS_PER_DAY 86400 +#define SECONDS_PER_YEAR 365 * SECONDS_PER_DAY +#ifndef VE_UTC_MAX_JUMP +# define VE_UTC_MAX_JUMP 20 * SECONDS_PER_YEAR +#endif +#define X509_DAYS_TO_UTC0 719528 + +int DebugVe = 0; + +#ifndef VE_VERIFY_FLAGS +# define VE_VERIFY_FLAGS VEF_VERBOSE +#endif +int VerifyFlags = VE_VERIFY_FLAGS; + +typedef VECTOR(br_x509_certificate) cert_list; +typedef VECTOR(hash_data) digest_list; + +static anchor_list trust_anchors = VEC_INIT; +static anchor_list forbidden_anchors = VEC_INIT; +static digest_list forbidden_digests = VEC_INIT; + +static int anchor_verbose = 0; + +void +ve_anchor_verbose_set(int n) +{ + anchor_verbose = n; +} + +int +ve_anchor_verbose_get(void) +{ + return (anchor_verbose); +} + +void +ve_debug_set(int n) +{ + DebugVe = n; +} + +/* + * For embedded systems (and boot loaders) + * we do not want to enforce certificate validity post install. + * It is generally unacceptible for infrastructure to stop working + * just because it has not been updated recently. + */ +static int enforce_validity = 0; + +void +ve_enforce_validity_set(int i) +{ + enforce_validity = i; +} + +static char ebuf[512]; + +char * +ve_error_get(void) +{ + return (ebuf); +} + +int +ve_error_set(const char *fmt, ...) +{ + int rc; + va_list ap; + + va_start(ap, fmt); + ebuf[0] = '\0'; + rc = 0; + if (fmt) { +#ifdef STAND_H + vsprintf(ebuf, fmt, ap); /* no vsnprintf in libstand */ + ebuf[sizeof(ebuf) - 1] = '\0'; + rc = strlen(ebuf); +#else + rc = vsnprintf(ebuf, sizeof(ebuf), fmt, ap); +#endif + } + va_end(ap); + return (rc); +} + +#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) + +/* + * The *approximate* date. + * + * When certificate verification fails for being + * expired or not yet valid, it helps to indicate + * our current date. + * Since libsa lacks strftime and gmtime, + * this simple implementation suffices. + */ +static const char * +gdate(char *buf, size_t bufsz, time_t clock) +{ + int days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + int year, y, m, d; + + y = clock / AVG_SECONDS_PER_YEAR; + year = EPOCH_YEAR + y; + for (y = EPOCH_YEAR; y < year; y++) { + clock -= SECONDS_PER_YEAR; + if (isleap(y)) + clock -= SECONDS_PER_DAY; + } + d = clock / SECONDS_PER_DAY; + for (m = 0; d > 1 && m < 12; m++) { + if (d > days[m]) { + d -= days[m]; + if (m == 1 && d > 0 && isleap(year)) + d--; + } else + break; + } + d++; + if (d > days[m]) { + d = 1; + m++; + if (m >= 12) { + year++; + m = 0; + } + } + (void)snprintf(buf, bufsz, "%04d-%02d-%02d", year, m+1, d); + return(buf); +} + +/* this is the time we use for verifying certs */ +#ifdef UNIT_TEST +extern time_t ve_utc; +time_t ve_utc = 0; +#else +static time_t ve_utc = 0; +#endif + +/** + * @brief + * set ve_utc used for certificate verification + * + * @param[in] utc + * time - ignored unless greater than current value + * and not a leap of 20 years or more. + */ +void +ve_utc_set(time_t utc) +{ + if (utc > ve_utc && + (ve_utc == 0 || (utc - ve_utc) < VE_UTC_MAX_JUMP)) { + DEBUG_PRINTF(2, ("Set ve_utc=%jd\n", (intmax_t)utc)); + ve_utc = utc; + } +} + +#ifdef VERIFY_CERTS_STR +static void +free_cert_contents(br_x509_certificate *xc) +{ + xfree(xc->data); +} +#endif + +/* + * a bit of a dance to get commonName from a certificate + */ +static char * +x509_cn_get(br_x509_certificate *xc, char *buf, size_t len) +{ + br_x509_minimal_context mc; + br_name_element cn; + unsigned char cn_oid[4]; + int err; + + if (buf == NULL) + return (buf); + /* + * We want the commonName field + * the OID we want is 2,5,4,3 - but DER encoded + */ + cn_oid[0] = 3; + cn_oid[1] = 0x55; + cn_oid[2] = 4; + cn_oid[3] = 3; + cn.oid = cn_oid; + cn.buf = buf; + cn.len = len; + cn.buf[0] = '\0'; + + br_x509_minimal_init(&mc, &br_sha256_vtable, NULL, 0); + br_x509_minimal_set_name_elements(&mc, &cn, 1); + /* the below actually does the work - updates cn.status */ + mc.vtable->start_chain(&mc.vtable, NULL); + mc.vtable->start_cert(&mc.vtable, xc->data_len); + mc.vtable->append(&mc.vtable, xc->data, xc->data_len); + mc.vtable->end_cert(&mc.vtable); + /* we don't actually care about cert status - just its name */ + err = mc.vtable->end_chain(&mc.vtable); + (void)err; /* keep compiler quiet */ + + if (cn.status <= 0) + buf = NULL; + return (buf); +} + +/* ASN parsing related defines */ +#define ASN1_PRIMITIVE_TAG 0x1F +#define ASN1_INF_LENGTH 0x80 +#define ASN1_LENGTH_MASK 0x7F + +/* + * Get TBS part of certificate. + * Since BearSSL doesn't provide any API to do this, + * it has to be implemented here. + */ +static void* +X509_to_tbs(unsigned char* cert, size_t* output_size) +{ + unsigned char *result; + size_t tbs_size; + int size, i; + + if (cert == NULL) + return (NULL); + + /* Strip two sequences to get to the TBS section */ + for (i = 0; i < 2; i++) { + /* + * XXX: We don't need to support extended tags since + * they should not be present in certificates. + */ + if ((*cert & ASN1_PRIMITIVE_TAG) == ASN1_PRIMITIVE_TAG) + return (NULL); + + cert++; + + if (*cert == ASN1_INF_LENGTH) + return (NULL); + + size = *cert & ASN1_LENGTH_MASK; + tbs_size = 0; + + /* Size can either be stored on a single or multiple bytes */ + if (*cert & (ASN1_LENGTH_MASK + 1)) { + cert++; + while (*cert == 0 && size > 0) { + cert++; + size--; + } + while (size-- > 0) { + tbs_size <<= 8; + tbs_size |= *(cert++); + } + } + if (i == 0) + result = cert; + } + tbs_size += (cert - result); + + if (output_size != NULL) + *output_size = tbs_size; + + return (result); +} + +void +ve_forbidden_digest_add(hash_data *digest, size_t num) +{ + while (num--) + VEC_ADD(forbidden_digests, digest[num]); +} + +static size_t +ve_anchors_add(br_x509_certificate *xcs, size_t num, anchor_list *anchors, + const char *anchors_name) +{ + br_x509_trust_anchor ta; + size_t u; + + for (u = 0; u < num; u++) { + if (certificate_to_trust_anchor_inner(&ta, &xcs[u]) < 0) { + break; + } + VEC_ADD(*anchors, ta); + if (anchor_verbose && anchors_name) { + char buf[64]; + char *cp; + + cp = x509_cn_get(&xcs[u], buf, sizeof(buf)); + if (cp) { + printf("x509_anchor(%s) %s\n", cp, anchors_name); + } + } + } + return (u); +} + +/** + * @brief + * add certs to our trust store + */ +size_t +ve_trust_anchors_add(br_x509_certificate *xcs, size_t num) +{ + return (ve_anchors_add(xcs, num, &trust_anchors, "trusted")); +} + +size_t +ve_forbidden_anchors_add(br_x509_certificate *xcs, size_t num) +{ + return (ve_anchors_add(xcs, num, &forbidden_anchors, "forbidden")); +} + + +/** + * @brief add trust anchors in buf + * + * Assume buf contains x509 certificates, but if not and + * we support OpenPGP try adding as that. + * + * @return number of anchors added + */ +size_t +ve_trust_anchors_add_buf(unsigned char *buf, size_t len) +{ + br_x509_certificate *xcs; + size_t num; + + num = 0; + if (len > 0) { + xcs = parse_certificates(buf, len, &num); + if (xcs != NULL) { + num = ve_trust_anchors_add(xcs, num); +#ifdef VE_OPENPGP_SUPPORT + } else { + num = openpgp_trust_add_buf(buf, len); +#endif + } + } + return (num); +} + +/** + * @brief revoke trust anchors in buf + * + * Assume buf contains x509 certificates, but if not and + * we support OpenPGP try revoking keyId + * + * @return number of anchors revoked + */ +size_t +ve_trust_anchors_revoke(unsigned char *buf, size_t len) +{ + br_x509_certificate *xcs; + size_t num; + + num = 0; + if (len > 0) { + xcs = parse_certificates(buf, len, &num); + if (xcs != NULL) { + num = ve_forbidden_anchors_add(xcs, num); +#ifdef VE_OPENPGP_SUPPORT + } else { + if (buf[len - 1] == '\n') + buf[len - 1] = '\0'; + num = openpgp_trust_revoke((char *)buf); +#endif + } + } + return (num); +} + +/** + * @brief + * initialize our trust_anchors from ta_PEM + */ +int +ve_trust_init(void) +{ + static int once = -1; + + if (once >= 0) + return (once); + once = 0; /* to be sure */ +#ifdef BUILD_UTC + ve_utc_set(BUILD_UTC); /* ensure sanity */ +#endif + ve_utc_set(time(NULL)); + ve_error_set(NULL); /* make sure it is empty */ +#ifdef VE_PCR_SUPPORT + ve_pcr_init(); +#endif + +#ifdef TRUST_ANCHOR_STR + if (TRUST_ANCHOR_STR != NULL && strlen(TRUST_ANCHOR_STR) != 0ul) + ve_trust_anchors_add_buf(__DECONST(unsigned char *, + TRUST_ANCHOR_STR), sizeof(TRUST_ANCHOR_STR)); +#endif + once = (int) VEC_LEN(trust_anchors); +#ifdef VE_OPENPGP_SUPPORT + once += openpgp_trust_init(); +#endif + return (once); +} + +#ifdef HAVE_BR_X509_TIME_CHECK +static int +verify_time_cb(void *tctx __unused, + uint32_t not_before_days, uint32_t not_before_seconds, + uint32_t not_after_days, uint32_t not_after_seconds) +{ + time_t not_before; + time_t not_after; + int rc; +#ifdef UNIT_TEST + char date[12], nb_date[12], na_date[12]; +#endif + + if (enforce_validity) { + not_before = ((not_before_days - X509_DAYS_TO_UTC0) * SECONDS_PER_DAY) + not_before_seconds; + not_after = ((not_after_days - X509_DAYS_TO_UTC0) * SECONDS_PER_DAY) + not_after_seconds; + if (ve_utc < not_before) + rc = -1; + else if (ve_utc > not_after) + rc = 1; + else + rc = 0; +#ifdef UNIT_TEST + printf("notBefore %s notAfter %s date %s rc %d\n", + gdate(nb_date, sizeof(nb_date), not_before), + gdate(na_date, sizeof(na_date), not_after), + gdate(date, sizeof(date), ve_utc), rc); +#endif + } else + rc = 0; /* don't fail */ + return rc; +} +#endif + +/** + * if we can verify the certificate chain in "certs", + * return the public key and if "xcp" is !NULL the associated + * certificate + */ +static br_x509_pkey * +verify_signer_xcs(br_x509_certificate *xcs, + size_t num, + br_name_element *elts, size_t num_elts, + anchor_list *anchors) +{ + br_x509_minimal_context mc; + br_x509_certificate *xc; + size_t u; + cert_list chain = VEC_INIT; + const br_x509_pkey *tpk; + br_x509_pkey *pk; + unsigned int usages; + int err; + + DEBUG_PRINTF(5, ("verify_signer: %zu certs in chain\n", num)); + VEC_ADDMANY(chain, xcs, num); + if (VEC_LEN(chain) == 0) { + ve_error_set("ERROR: no/invalid certificate chain\n"); + return (NULL); + } + + DEBUG_PRINTF(5, ("verify_signer: %zu trust anchors\n", + VEC_LEN(*anchors))); + + br_x509_minimal_init(&mc, &br_sha256_vtable, + &VEC_ELT(*anchors, 0), + VEC_LEN(*anchors)); +#ifdef VE_ECDSA_SUPPORT + br_x509_minimal_set_ecdsa(&mc, + &br_ec_prime_i31, &br_ecdsa_i31_vrfy_asn1); +#endif +#ifdef VE_RSA_SUPPORT + br_x509_minimal_set_rsa(&mc, &br_rsa_i31_pkcs1_vrfy); +#endif +#if defined(UNIT_TEST) && defined(VE_DEPRECATED_RSA_SHA1_SUPPORT) + /* This is deprecated! do not enable unless you absolutely have to */ + br_x509_minimal_set_hash(&mc, br_sha1_ID, &br_sha1_vtable); +#endif + br_x509_minimal_set_hash(&mc, br_sha256_ID, &br_sha256_vtable); +#ifdef VE_SHA384_SUPPORT + br_x509_minimal_set_hash(&mc, br_sha384_ID, &br_sha384_vtable); +#endif +#ifdef VE_SHA512_SUPPORT + br_x509_minimal_set_hash(&mc, br_sha512_ID, &br_sha512_vtable); +#endif + br_x509_minimal_set_name_elements(&mc, elts, num_elts); + +#ifdef HAVE_BR_X509_TIME_CHECK + br_x509_minimal_set_time_callback(&mc, NULL, verify_time_cb); +#else +#if defined(_STANDALONE) || defined(UNIT_TEST) + /* + * Clock is probably bogus so we use ve_utc. + */ + mc.days = (ve_utc / SECONDS_PER_DAY) + X509_DAYS_TO_UTC0; + mc.seconds = (ve_utc % SECONDS_PER_DAY); +#endif +#endif + mc.vtable->start_chain(&mc.vtable, NULL); + for (u = 0; u < VEC_LEN(chain); u ++) { + xc = &VEC_ELT(chain, u); + mc.vtable->start_cert(&mc.vtable, xc->data_len); + mc.vtable->append(&mc.vtable, xc->data, xc->data_len); + mc.vtable->end_cert(&mc.vtable); + switch (mc.err) { + case 0: + case BR_ERR_X509_OK: + case BR_ERR_X509_EXPIRED: + break; + default: + printf("u=%zu mc.err=%d\n", u, mc.err); + break; + } + } + err = mc.vtable->end_chain(&mc.vtable); + pk = NULL; + if (err) { + char date[12]; + + switch (err) { + case 54: + ve_error_set("Validation failed, certificate not valid as of %s", + gdate(date, sizeof(date), ve_utc)); + break; + default: { + const char *err_desc = NULL; + const char *err_name = find_error_name(err, &err_desc); + + if (err_name == NULL) + ve_error_set("Validation failed, err = %d", + err); + else + ve_error_set("Validation failed, %s (%s)", + err_desc, err_name); + break; } + } + } else { + tpk = mc.vtable->get_pkey(&mc.vtable, &usages); + if (tpk != NULL) { + pk = xpkeydup(tpk); + } + } + VEC_CLEAR(chain); + return (pk); +} + +/* + * Check if digest of one of the certificates from verified chain + * is present in the forbidden database. + * Since UEFI allows to store three types of digests + * all of them have to be checked separately. + */ +static int +check_forbidden_digests(br_x509_certificate *xcs, size_t num) +{ + unsigned char sha256_digest[br_sha256_SIZE]; + unsigned char sha384_digest[br_sha384_SIZE]; + unsigned char sha512_digest[br_sha512_SIZE]; + void *tbs; + hash_data *digest; + br_hash_compat_context ctx; + const br_hash_class *md; + size_t tbs_len, i; + int have_sha256, have_sha384, have_sha512; + + if (VEC_LEN(forbidden_digests) == 0) + return (0); + + /* + * Iterate through certificates, extract their To-Be-Signed section, + * and compare its digest against the ones in the forbidden database. + */ + while (num--) { + tbs = X509_to_tbs(xcs[num].data, &tbs_len); + if (tbs == NULL) { + printf("Failed to obtain TBS part of certificate\n"); + return (1); + } + have_sha256 = have_sha384 = have_sha512 = 0; + + for (i = 0; i < VEC_LEN(forbidden_digests); i++) { + digest = &VEC_ELT(forbidden_digests, i); + switch (digest->hash_size) { + case br_sha256_SIZE: + if (!have_sha256) { + have_sha256 = 1; + md = &br_sha256_vtable; + md->init(&ctx.vtable); + md->update(&ctx.vtable, tbs, tbs_len); + md->out(&ctx.vtable, sha256_digest); + } + if (!memcmp(sha256_digest, + digest->data, + br_sha256_SIZE)) + return (1); + + break; + case br_sha384_SIZE: + if (!have_sha384) { + have_sha384 = 1; + md = &br_sha384_vtable; + md->init(&ctx.vtable); + md->update(&ctx.vtable, tbs, tbs_len); + md->out(&ctx.vtable, sha384_digest); + } + if (!memcmp(sha384_digest, + digest->data, + br_sha384_SIZE)) + return (1); + + break; + case br_sha512_SIZE: + if (!have_sha512) { + have_sha512 = 1; + md = &br_sha512_vtable; + md->init(&ctx.vtable); + md->update(&ctx.vtable, tbs, tbs_len); + md->out(&ctx.vtable, sha512_digest); + } + if (!memcmp(sha512_digest, + digest->data, + br_sha512_SIZE)) + return (1); + + break; + } + } + } + + return (0); +} + +static br_x509_pkey * +verify_signer(const char *certs, + br_name_element *elts, size_t num_elts) +{ + br_x509_certificate *xcs; + br_x509_pkey *pk; + size_t num; + + pk = NULL; + + ve_trust_init(); + xcs = read_certificates(certs, &num); + if (xcs == NULL) { + ve_error_set("cannot read certificates\n"); + return (NULL); + } + + /* + * Check if either + * 1. There is a direct match between cert from forbidden_anchors + * and a cert from chain. + * 2. CA that signed the chain is found in forbidden_anchors. + */ + if (VEC_LEN(forbidden_anchors) > 0) + pk = verify_signer_xcs(xcs, num, elts, num_elts, &forbidden_anchors); + if (pk != NULL) { + ve_error_set("Certificate is on forbidden list\n"); + xfreepkey(pk); + pk = NULL; + goto out; + } + + pk = verify_signer_xcs(xcs, num, elts, num_elts, &trust_anchors); + if (pk == NULL) + goto out; + + /* + * Check if hash of tbs part of any certificate in chain + * is on the forbidden list. + */ + if (check_forbidden_digests(xcs, num)) { + ve_error_set("Certificate hash is on forbidden list\n"); + xfreepkey(pk); + pk = NULL; + } +out: + free_certificates(xcs, num); + return (pk); +} + +/** + * we need a hex digest including trailing newline below + */ +char * +hexdigest(char *buf, size_t bufsz, unsigned char *foo, size_t foo_len) +{ + char const hex2ascii[] = "0123456789abcdef"; + size_t i; + + /* every binary byte is 2 chars in hex + newline + null */ + if (bufsz < (2 * foo_len) + 2) + return (NULL); + + for (i = 0; i < foo_len; i++) { + buf[i * 2] = hex2ascii[foo[i] >> 4]; + buf[i * 2 + 1] = hex2ascii[foo[i] & 0x0f]; + } + + buf[i * 2] = 0x0A; /* we also want a newline */ + buf[i * 2 + 1] = '\0'; + + return (buf); +} + +/** + * @brief + * verify file against sigfile using pk + * + * When we generated the signature in sigfile, + * we hashed (sha256) file, and sent that to signing server + * which hashed (sha256) that hash. + * + * To verify we need to replicate that result. + * + * @param[in] pk + * br_x509_pkey + * + * @paramp[in] file + * file to be verified + * + * @param[in] sigfile + * signature (PEM encoded) + * + * @return NULL on error, otherwise content of file. + */ +#ifdef VE_ECDSA_SUPPORT +static unsigned char * +verify_ec(br_x509_pkey *pk, const char *file, const char *sigfile) +{ +#ifdef VE_ECDSA_HASH_AGAIN + char *hex, hexbuf[br_sha512_SIZE * 2 + 2]; +#endif + unsigned char rhbuf[br_sha512_SIZE]; + br_sha256_context ctx; + unsigned char *fcp, *scp; + size_t flen, slen, plen; + pem_object *po; + const br_ec_impl *ec; + br_ecdsa_vrfy vrfy; + + if ((fcp = read_file(file, &flen)) == NULL) + return (NULL); + if ((scp = read_file(sigfile, &slen)) == NULL) { + free(fcp); + return (NULL); + } + if ((po = decode_pem(scp, slen, &plen)) == NULL) { + free(fcp); + free(scp); + return (NULL); + } + br_sha256_init(&ctx); + br_sha256_update(&ctx, fcp, flen); + br_sha256_out(&ctx, rhbuf); +#ifdef VE_ECDSA_HASH_AGAIN + hex = hexdigest(hexbuf, sizeof(hexbuf), rhbuf, br_sha256_SIZE); + /* now hash that */ + if (hex) { + br_sha256_init(&ctx); + br_sha256_update(&ctx, hex, strlen(hex)); + br_sha256_out(&ctx, rhbuf); + } +#endif + ec = br_ec_get_default(); + vrfy = br_ecdsa_vrfy_asn1_get_default(); + if (!vrfy(ec, rhbuf, br_sha256_SIZE, &pk->key.ec, po->data, + po->data_len)) { + free(fcp); + fcp = NULL; + } + free(scp); + return (fcp); +} +#endif + +#if defined(VE_RSA_SUPPORT) || defined(VE_OPENPGP_SUPPORT) +/** + * @brief verify an rsa digest + * + * @return 0 on failure + */ +int +verify_rsa_digest (br_rsa_public_key *pkey, + const unsigned char *hash_oid, + unsigned char *mdata, size_t mlen, + unsigned char *sdata, size_t slen) +{ + br_rsa_pkcs1_vrfy vrfy; + unsigned char vhbuf[br_sha512_SIZE]; + + vrfy = br_rsa_pkcs1_vrfy_get_default(); + + if (!vrfy(sdata, slen, hash_oid, mlen, pkey, vhbuf) || + memcmp(vhbuf, mdata, mlen) != 0) { + return (0); /* fail */ + } + return (1); /* ok */ +} +#endif + +/** + * @brief + * verify file against sigfile using pk + * + * When we generated the signature in sigfile, + * we hashed (sha256) file, and sent that to signing server + * which hashed (sha256) that hash. + * + * Or (deprecated) we simply used sha1 hash directly. + * + * To verify we need to replicate that result. + * + * @param[in] pk + * br_x509_pkey + * + * @paramp[in] file + * file to be verified + * + * @param[in] sigfile + * signature (PEM encoded) + * + * @return NULL on error, otherwise content of file. + */ +#ifdef VE_RSA_SUPPORT +static unsigned char * +verify_rsa(br_x509_pkey *pk, const char *file, const char *sigfile) +{ + unsigned char rhbuf[br_sha512_SIZE]; + const unsigned char *hash_oid; + const br_hash_class *md; + br_hash_compat_context mctx; + unsigned char *fcp, *scp; + size_t flen, slen, plen, hlen; + pem_object *po; + + if ((fcp = read_file(file, &flen)) == NULL) + return (NULL); + if ((scp = read_file(sigfile, &slen)) == NULL) { + free(fcp); + return (NULL); + } + if ((po = decode_pem(scp, slen, &plen)) == NULL) { + free(fcp); + free(scp); + return (NULL); + } + + switch (po->data_len) { +#if defined(UNIT_TEST) && defined(VE_DEPRECATED_RSA_SHA1_SUPPORT) + case 256: + // this is our old deprecated sig method + md = &br_sha1_vtable; + hlen = br_sha1_SIZE; + hash_oid = BR_HASH_OID_SHA1; + break; +#endif + default: + md = &br_sha256_vtable; + hlen = br_sha256_SIZE; + hash_oid = BR_HASH_OID_SHA256; + break; + } + md->init(&mctx.vtable); + md->update(&mctx.vtable, fcp, flen); + md->out(&mctx.vtable, rhbuf); + if (!verify_rsa_digest(&pk->key.rsa, hash_oid, + rhbuf, hlen, po->data, po->data_len)) { + free(fcp); + fcp = NULL; + } + free(scp); + return (fcp); +} +#endif + +/** + * @brief + * verify a signature and return content of signed file + * + * @param[in] sigfile + * file containing signature + * we derrive path of signed file and certificate change from + * this. + * + * @param[in] flags + * only bit 1 significant so far + * + * @return NULL on error otherwise content of signed file + */ +unsigned char * +verify_sig(const char *sigfile, int flags) +{ + br_x509_pkey *pk; + br_name_element cn; + char cn_buf[80]; + unsigned char cn_oid[4]; + char pbuf[MAXPATHLEN]; + char *cp; + unsigned char *ucp; + size_t n; + + DEBUG_PRINTF(5, ("verify_sig: %s\n", sigfile)); + n = strlcpy(pbuf, sigfile, sizeof(pbuf)); + if (n > (sizeof(pbuf) - 5) || strcmp(&sigfile[n - 3], "sig") != 0) + return (NULL); + cp = strcpy(&pbuf[n - 3], "certs"); + /* + * We want the commonName field + * the OID we want is 2,5,4,3 - but DER encoded + */ + cn_oid[0] = 3; + cn_oid[1] = 0x55; + cn_oid[2] = 4; + cn_oid[3] = 3; + cn.oid = cn_oid; + cn.buf = cn_buf; + cn.len = sizeof(cn_buf); + + pk = verify_signer(pbuf, &cn, 1); + if (!pk) { + printf("cannot verify: %s: %s\n", pbuf, ve_error_get()); + return (NULL); + } + for (; cp > pbuf; cp--) { + if (*cp == '.') { + *cp = '\0'; + break; + } + } + switch (pk->key_type) { +#ifdef VE_ECDSA_SUPPORT + case BR_KEYTYPE_EC: + ucp = verify_ec(pk, pbuf, sigfile); + break; +#endif +#ifdef VE_RSA_SUPPORT + case BR_KEYTYPE_RSA: + ucp = verify_rsa(pk, pbuf, sigfile); + break; +#endif + default: + ucp = NULL; /* not supported */ + } + xfreepkey(pk); + if (!ucp) { + printf("Unverified %s (%s)\n", pbuf, + cn.status ? cn_buf : "unknown"); + } else if ((flags & VEF_VERBOSE) != 0) { + printf("Verified %s signed by %s\n", pbuf, + cn.status ? cn_buf : "someone we trust"); + } + return (ucp); +} + + +/** + * @brief verify hash matches + * + * We have finished hashing a file, + * see if we got the desired result. + * + * @param[in] ctx + * pointer to hash context + * + * @param[in] md + * pointer to hash class + * + * @param[in] path + * name of the file we are checking + * + * @param[in] want + * the expected result + * + * @param[in] hlen + * size of hash output + * + * @return 0 on success + */ +int +ve_check_hash(br_hash_compat_context *ctx, const br_hash_class *md, + const char *path, const char *want, size_t hlen) +{ + char hexbuf[br_sha512_SIZE * 2 + 2]; + unsigned char hbuf[br_sha512_SIZE]; + char *hex; + int rc; + int n; + + md->out(&ctx->vtable, hbuf); +#ifdef VE_PCR_SUPPORT + ve_pcr_update(path, hbuf, hlen); +#endif + hex = hexdigest(hexbuf, sizeof(hexbuf), hbuf, hlen); + if (!hex) + return (VE_FINGERPRINT_WRONG); + n = 2*hlen; + if ((rc = strncmp(hex, want, n))) { + ve_error_set("%s: %.*s != %.*s", path, n, hex, n, want); + rc = VE_FINGERPRINT_WRONG; + } + return (rc ? rc : VE_FINGERPRINT_OK); +} + +#ifdef VE_HASH_KAT_STR +static int +test_hash(const br_hash_class *md, size_t hlen, + const char *hname, const char *s, size_t slen, const char *want) +{ + br_hash_compat_context mctx; + + md->init(&mctx.vtable); + md->update(&mctx.vtable, s, slen); + return (ve_check_hash(&mctx, md, hname, want, hlen) != VE_FINGERPRINT_OK); +} + +#endif + +#define ve_test_hash(n, N) \ + printf("Testing hash: " #n "\t\t\t\t%s\n", \ + test_hash(&br_ ## n ## _vtable, br_ ## n ## _SIZE, #n, \ + VE_HASH_KAT_STR, VE_HASH_KAT_STRLEN(VE_HASH_KAT_STR), \ + vh_ ## N) ? "Failed" : "Passed") + +/** + * @brief + * run self tests on hash and signature verification + * + * Test that the hash methods (SHA1 and SHA256) work. + * Test that we can verify a certificate for each supported + * Root CA. + * + * @return cached result. + */ +int +ve_self_tests(void) +{ + static int once = -1; +#ifdef VERIFY_CERTS_STR + br_x509_certificate *xcs; + br_x509_pkey *pk; + br_name_element cn; + char cn_buf[80]; + unsigned char cn_oid[4]; + size_t num; + size_t u; +#endif + + if (once >= 0) + return (once); + once = 0; + + DEBUG_PRINTF(5, ("Self tests...\n")); +#ifdef VE_HASH_KAT_STR +#ifdef VE_SHA1_SUPPORT + ve_test_hash(sha1, SHA1); +#endif +#ifdef VE_SHA256_SUPPORT + ve_test_hash(sha256, SHA256); +#endif +#ifdef VE_SHA384_SUPPORT + ve_test_hash(sha384, SHA384); +#endif +#ifdef VE_SHA512_SUPPORT + ve_test_hash(sha512, SHA512); +#endif +#endif +#ifdef VERIFY_CERTS_STR + xcs = parse_certificates(__DECONST(unsigned char *, VERIFY_CERTS_STR), + sizeof(VERIFY_CERTS_STR), &num); + if (xcs != NULL) { + /* + * We want the commonName field + * the OID we want is 2,5,4,3 - but DER encoded + */ + cn_oid[0] = 3; + cn_oid[1] = 0x55; + cn_oid[2] = 4; + cn_oid[3] = 3; + cn.oid = cn_oid; + cn.buf = cn_buf; + + for (u = 0; u < num; u ++) { + cn.len = sizeof(cn_buf); + if ((pk = verify_signer_xcs(&xcs[u], 1, &cn, 1, &trust_anchors)) != NULL) { + free_cert_contents(&xcs[u]); + once++; + printf("Testing verify certificate: %s\tPassed\n", + cn.status ? cn_buf : ""); + xfreepkey(pk); + } + } + if (!once) + printf("Testing verify certificate:\t\t\tFailed\n"); + xfree(xcs); + } +#endif /* VERIFY_CERTS_STR */ +#ifdef VE_OPENPGP_SUPPORT + if (!openpgp_self_tests()) + once++; +#endif + return (once); +} |