Index: lib/libbearssl/Makefile =================================================================== --- /dev/null +++ lib/libbearssl/Makefile @@ -0,0 +1,129 @@ +# $FreeBSD$ + +# This is a reach over build of BearSSL (www.BearSSL.org) + +.include + +.if ${MK_BEARSSL} == "yes" +LIB= bearssl + +.include "Makefile.libsa.inc" + +INCS= \ + inc/bearssl.h \ + inc/bearssl_aead.h \ + inc/bearssl_block.h \ + inc/bearssl_ec.h \ + inc/bearssl_hash.h \ + inc/bearssl_hmac.h \ + inc/bearssl_pem.h \ + inc/bearssl_prf.h \ + inc/bearssl_rand.h \ + inc/bearssl_rsa.h \ + inc/bearssl_ssl.h \ + inc/bearssl_x509.h \ + +INCS:= ${INCS:S,^,${BEARSSL}/,} + + +.if ${MK_BEARSSL_SSL:Uno} == "yes" +SRCS+= \ + mac/hmac.c \ + mac/hmac_ct.c \ + rand/hmac_drbg.c \ + ssl/prf.c \ + ssl/prf_md5sha1.c \ + ssl/prf_sha256.c \ + ssl/prf_sha384.c \ + ssl/ssl_ccert_single_ec.c \ + ssl/ssl_ccert_single_rsa.c \ + ssl/ssl_client.c \ + ssl/ssl_client_default_rsapub.c \ + ssl/ssl_client_full.c \ + ssl/ssl_engine.c \ + ssl/ssl_engine_default_aescbc.c \ + ssl/ssl_engine_default_aesgcm.c \ + ssl/ssl_engine_default_chapol.c \ + ssl/ssl_engine_default_descbc.c \ + ssl/ssl_engine_default_ec.c \ + ssl/ssl_engine_default_ecdsa.c \ + ssl/ssl_engine_default_rsavrfy.c \ + ssl/ssl_hashes.c \ + ssl/ssl_hs_client.c \ + ssl/ssl_hs_server.c \ + ssl/ssl_io.c \ + ssl/ssl_lru.c \ + ssl/ssl_rec_cbc.c \ + ssl/ssl_rec_chapol.c \ + ssl/ssl_rec_gcm.c \ + ssl/ssl_scert_single_ec.c \ + ssl/ssl_scert_single_rsa.c \ + ssl/ssl_server.c \ + ssl/ssl_server_full_ec.c \ + ssl/ssl_server_full_rsa.c \ + ssl/ssl_server_mine2c.c \ + ssl/ssl_server_mine2g.c \ + ssl/ssl_server_minf2c.c \ + ssl/ssl_server_minf2g.c \ + ssl/ssl_server_minr2g.c \ + ssl/ssl_server_minu2g.c \ + ssl/ssl_server_minv2g.c \ + +SRCS+= \ + symcipher/aes_big_cbcdec.c \ + symcipher/aes_big_cbcenc.c \ + symcipher/aes_big_ctr.c \ + symcipher/aes_big_dec.c \ + symcipher/aes_big_enc.c \ + symcipher/aes_common.c \ + symcipher/aes_ct.c \ + symcipher/aes_ct64.c \ + symcipher/aes_ct64_cbcdec.c \ + symcipher/aes_ct64_cbcenc.c \ + symcipher/aes_ct64_ctr.c \ + symcipher/aes_ct64_dec.c \ + symcipher/aes_ct64_enc.c \ + symcipher/aes_ct_cbcdec.c \ + symcipher/aes_ct_cbcenc.c \ + symcipher/aes_ct_ctr.c \ + symcipher/aes_ct_dec.c \ + symcipher/aes_ct_enc.c \ + symcipher/aes_pwr8.c \ + symcipher/aes_pwr8_cbcdec.c \ + symcipher/aes_pwr8_cbcenc.c \ + symcipher/aes_pwr8_ctr.c \ + symcipher/aes_small_cbcdec.c \ + symcipher/aes_small_cbcenc.c \ + symcipher/aes_small_ctr.c \ + symcipher/aes_small_dec.c \ + symcipher/aes_small_enc.c \ + symcipher/aes_x86ni.c \ + symcipher/aes_x86ni_cbcdec.c \ + symcipher/aes_x86ni_cbcenc.c \ + symcipher/aes_x86ni_ctr.c \ + symcipher/chacha20_ct.c \ + symcipher/des_ct.c \ + symcipher/des_ct_cbcdec.c \ + symcipher/des_ct_cbcenc.c \ + symcipher/des_support.c \ + symcipher/des_tab.c \ + symcipher/des_tab_cbcdec.c \ + symcipher/des_tab_cbcenc.c \ + symcipher/poly1305_ctmul.c \ + symcipher/poly1305_ctmul32.c \ + symcipher/poly1305_ctmulq.c \ + symcipher/poly1305_i15.c \ + +.endif + +.include + +CWARNFLAGS.clang+= -Wno-cast-qual -Wno-shadow +CWARNFLAGS.gcc+= -Wno-cast-qual -Wno-shadow +.if ${MACHINE} == "host" +CWARNFLAGS+= -Wno-error +.endif + +.else +all ${.TARGETS}: +.endif Index: lib/libbearssl/Makefile.depend =================================================================== --- /dev/null +++ lib/libbearssl/Makefile.depend @@ -0,0 +1,17 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + gnu/lib/csu \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + + +.include + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif Index: lib/libbearssl/Makefile.inc =================================================================== --- /dev/null +++ lib/libbearssl/Makefile.inc @@ -0,0 +1,8 @@ +# $FreeBSD$ + +# we expect BEARSSL to be set. + +BEARSSL_SRC= ${BEARSSL}/src + +CFLAGS+= -I${BEARSSL}/inc + Index: lib/libbearssl/Makefile.libsa.inc =================================================================== --- /dev/null +++ lib/libbearssl/Makefile.libsa.inc @@ -0,0 +1,171 @@ +# $FreeBSD$ + +.PATH: ${.PARSEDIR} + +.include "Makefile.inc" + +.PATH: ${BEARSSL_SRC} + +CFLAGS+= -I${BEARSSL_SRC} + +# we do not need/want nested objdirs +OBJS_SRCS_FILTER = T R + +# just how much of this does libsa need? +SRCS+= \ + codec/ccopy.c \ + codec/dec16be.c \ + codec/dec16le.c \ + codec/dec32be.c \ + codec/dec32le.c \ + codec/dec64be.c \ + codec/dec64le.c \ + codec/enc16be.c \ + codec/enc16le.c \ + codec/enc32be.c \ + codec/enc32le.c \ + codec/enc64be.c \ + codec/enc64le.c \ + +SRCS+= \ + codec/pemdec.c \ + +SRCS+= \ + ec/ec_all_m15.c \ + ec/ec_all_m31.c \ + ec/ec_c25519_i15.c \ + ec/ec_c25519_i31.c \ + ec/ec_c25519_m15.c \ + ec/ec_c25519_m31.c \ + ec/ec_curve25519.c \ + ec/ec_default.c \ + ec/ec_p256_m15.c \ + ec/ec_p256_m31.c \ + ec/ec_prime_i15.c \ + ec/ec_prime_i31.c \ + ec/ec_secp256r1.c \ + ec/ec_secp384r1.c \ + ec/ec_secp521r1.c \ + ec/ecdsa_atr.c \ + ec/ecdsa_default_sign_asn1.c \ + ec/ecdsa_default_sign_raw.c \ + ec/ecdsa_default_vrfy_asn1.c \ + ec/ecdsa_default_vrfy_raw.c \ + ec/ecdsa_i15_bits.c \ + ec/ecdsa_i15_sign_asn1.c \ + ec/ecdsa_i15_sign_raw.c \ + ec/ecdsa_i15_vrfy_asn1.c \ + ec/ecdsa_i15_vrfy_raw.c \ + ec/ecdsa_i31_bits.c \ + ec/ecdsa_i31_sign_asn1.c \ + ec/ecdsa_i31_sign_raw.c \ + ec/ecdsa_i31_vrfy_asn1.c \ + ec/ecdsa_i31_vrfy_raw.c \ + ec/ecdsa_rta.c \ + +SRCS+= \ + hash/dig_oid.c \ + hash/dig_size.c \ + hash/ghash_ctmul.c \ + hash/ghash_ctmul32.c \ + hash/ghash_ctmul64.c \ + hash/ghash_pwr8.c \ + hash/md5.c \ + hash/md5sha1.c \ + hash/multihash.c \ + hash/sha1.c \ + hash/sha2big.c \ + hash/sha2small.c \ + +# this one does not compile for some cpus +x+= hash/ghash_pclmul.c \ + +SRCS+= \ + int/i15_add.c \ + int/i15_bitlen.c \ + int/i15_decmod.c \ + int/i15_decode.c \ + int/i15_decred.c \ + int/i15_encode.c \ + int/i15_fmont.c \ + int/i15_iszero.c \ + int/i15_modpow.c \ + int/i15_modpow2.c \ + int/i15_montmul.c \ + int/i15_mulacc.c \ + int/i15_muladd.c \ + int/i15_ninv15.c \ + int/i15_reduce.c \ + int/i15_rshift.c \ + int/i15_sub.c \ + int/i15_tmont.c \ + int/i31_add.c \ + int/i31_bitlen.c \ + int/i31_decmod.c \ + int/i31_decode.c \ + int/i31_decred.c \ + int/i31_encode.c \ + int/i31_fmont.c \ + int/i31_iszero.c \ + int/i31_modpow.c \ + int/i31_modpow2.c \ + int/i31_montmul.c \ + int/i31_mulacc.c \ + int/i31_muladd.c \ + int/i31_ninv31.c \ + int/i31_reduce.c \ + int/i31_rshift.c \ + int/i31_sub.c \ + int/i31_tmont.c \ + int/i32_add.c \ + int/i32_bitlen.c \ + int/i32_decmod.c \ + int/i32_decode.c \ + int/i32_decred.c \ + int/i32_div32.c \ + int/i32_encode.c \ + int/i32_fmont.c \ + int/i32_iszero.c \ + int/i32_modpow.c \ + int/i32_montmul.c \ + int/i32_mulacc.c \ + int/i32_muladd.c \ + int/i32_ninv32.c \ + int/i32_reduce.c \ + int/i32_sub.c \ + int/i32_tmont.c \ + int/i62_modpow2.c \ + +SRCS+= \ + rsa/rsa_default_pkcs1_sign.c \ + rsa/rsa_default_pkcs1_vrfy.c \ + rsa/rsa_default_priv.c \ + rsa/rsa_default_pub.c \ + rsa/rsa_i15_pkcs1_sign.c \ + rsa/rsa_i15_pkcs1_vrfy.c \ + rsa/rsa_i15_priv.c \ + rsa/rsa_i15_pub.c \ + rsa/rsa_i31_pkcs1_sign.c \ + rsa/rsa_i31_pkcs1_vrfy.c \ + rsa/rsa_i31_priv.c \ + rsa/rsa_i31_pub.c \ + rsa/rsa_i32_pkcs1_sign.c \ + rsa/rsa_i32_pkcs1_vrfy.c \ + rsa/rsa_i32_priv.c \ + rsa/rsa_i32_pub.c \ + rsa/rsa_i62_priv.c \ + rsa/rsa_i62_pkcs1_sign.c \ + rsa/rsa_i62_pkcs1_vrfy.c \ + rsa/rsa_i62_pub.c \ + rsa/rsa_pkcs1_sig_pad.c \ + rsa/rsa_pkcs1_sig_unpad.c \ + rsa/rsa_ssl_decrypt.c \ + + +SRCS+= \ + x509/skey_decoder.c \ + x509/x509_decoder.c \ + x509/x509_knownkey.c \ + x509/x509_minimal.c \ + x509/x509_minimal_full.c \ + Index: lib/libve/Makefile =================================================================== --- /dev/null +++ lib/libve/Makefile @@ -0,0 +1,18 @@ +# $FreeBSD$ + +.include + +.if ${MK_BEARSSL} == "yes" + +LIB= ve + +.include "../libbearssl/Makefile.inc" +.include "Makefile.inc" + +INCS= h/libve.h + +.include + +.else +all ${.TARGETS}: +.endif Index: lib/libve/Makefile.depend =================================================================== --- /dev/null +++ lib/libve/Makefile.depend @@ -0,0 +1,17 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + gnu/lib/csu \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + + +.include + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif Index: lib/libve/Makefile.depend.host =================================================================== --- /dev/null +++ lib/libve/Makefile.depend.host @@ -0,0 +1,12 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + lib/libstand \ + + +.include + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif Index: lib/libve/Makefile.inc =================================================================== --- /dev/null +++ lib/libve/Makefile.inc @@ -0,0 +1,118 @@ +# $FreeBSD$ + +libve_src:= ${.PARSEDIR} + +CFLAGS+= -I${libve_src}/h + +.PATH: ${.PARSEDIR} + +SRCS+= \ + readfile.c \ + vectx.c \ + veopen.c \ + brf.c \ + vepcr.c \ + vets.c + +.if ${.CURDIR:M*libve*} != "" +SRCS+= veta.c +.endif + +.PATH: ${BEARSSL}/tools + +CFLAGS+= ${XCFLAGS.${.TARGET:T:R}:U} + +BRSSL_SRCS+= \ + xmem.c \ + vector.c \ + +SRCS+= ${BRSSL_SRCS} + +BRSSL_CFLAGS+= -I${BEARSSL}/tools + +# extract the last cert from a chain (should be rootCA) +_LAST_PEM_USE: .USE + sed "1,`grep -n .-END ${.ALLSRC:M*.pem} | tail -2 | head -1 | sed 's,:.*,,'`d" ${.ALLSRC:M*.pem} > ${.TARGET} + +# extract 2nd last cert from chain - we use this for self-test +_2ndLAST_PEM_USE: .USE + sed -n "`grep -n .-BEGIN ${.ALLSRC:M*.pem} | tail -2 | \ + sed 's,:.*,,' | xargs | (read a b; echo $$a,$$(($$b - 1)))`p" ${.ALLSRC:M*.pem} > ${.TARGET} + +# list of hashes we support +VE_HASH_LIST?= SHA256 + +# list of signatures we support +VE_SIGNATURE_LIST?= ECDSA + +# needs to be yes for FIPS 140-2 compliance +VE_SELF_TESTS?= no + +# rules to populate the [tv]*.pem files we use to generate ta.h +# and can add/alter VE_*_LIST as desired. +.-include "local.trust.mk" + +# this is what we use as our trust anchor +CFLAGS+= -I. -DTRUST_ANCHOR_STR=ta_PEM + +.if ${VE_SELF_TESTS} != "no" +CFLAGS+= -DVERIFY_CERTS_STR=vc_PEM +.endif + +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 + + +# Generate ta.h containing ta_PEM as an array of strings +# each holding a PEM encoded trust anchor. +# +# If we are doing self-tests, we define another arrary vc_PEM +# containing certificates that we can verify for each trust anchor. +# this is typically a subordinate CA cert. +# Finally we generate a hash of vc_PEM using each supported hash method +# to use as a Known Answer Test (needed for FIPS 140-2) +# +ta.h: ${.ALLTARGETS:M[tv]*pem:O:u} + @( echo '/* Autogenerated - DO NOT EDIT!!! */'; echo; \ + echo "static char ta_PEM[] ="; \ + for f in ${.ALLSRC:N*crl*:Mt*.pem}; do \ + sed 's,^\(.*\),"\1\\n",' $$f; \ + done; echo ';'; echo; ) > ${.TARGET} +.if ${VE_SIGNATURE_LIST:MOPENPGP} != "" + @(echo "static char ta_ASC[] ="; \ + for f in ${.ALLSRC:Mt*.asc}; do \ + sed -e '/:/d' -e 's,^\(.*\),"\1\\n",' $$f; \ + done; echo ';'; echo; ) >> ${.TARGET} +.if ${VE_SELF_TESTS} != "no" + @( echo "static char vc_ASC[] ="; \ + for f in ${.ALLSRC:Mv*.asc}; do \ + sed 's,^\(.*\),"\1\\n",' $$f; \ + done; echo ';'; \ + ) >> ${.TARGET} +.endif +.endif + echo "${.newline}${VE_HASH_LIST:@H@static char vc_$H[] = \"`cat ${.ALLSRC:N*crl*:Mv*.pem} | ${$H:U${H:tl}}`\";${.newline}@}" >> ${.TARGET} +.if ${VE_SELF_TESTS} != "no" + ( echo "static char vc_PEM[] ="; \ + for f in ${.ALLSRC:N*crl*:Mv*.pem}; do \ + sed 's,^\(.*\),"\1\\n",' $$f; \ + done; echo ';'; \ + ) >> ${.TARGET} +.endif +.if !empty(BUILD_UTC_FILE) + echo '#define BUILD_UTC ${${STAT:Ustat} -f %m ${BUILD_UTC_FILE}:L:sh}' >> ${.TARGET} ${.OODATE:MNOMETA_CMP} +.endif + +vets.o: ta.h + +.for s in ${BRSSL_SRCS} brf.c vets.c veta.c +XCFLAGS.${s:R}+= ${BRSSL_CFLAGS} +.endfor Index: lib/libve/Makefile.libsa.inc =================================================================== --- /dev/null +++ lib/libve/Makefile.libsa.inc @@ -0,0 +1,25 @@ +# $FreeBSD$ + +BRSSL_CFLAGS+= -DNO_STDIO + +.include "Makefile.inc" + +CFLAGS+= -DLOADER_VERIEXEC + +SRCS+= verify.c + +.if ${VE_SIGNATURE_LIST:MECDSA} != "" +VE_SIG_EXT?= esig +.endif +VE_SIG_EXT?= sig +XCFLAGS.verify+= \ + -DVE_DEBUG_LEVEL=${VE_DEBUG_LEVEL:U0} \ + -DMANIFEST_SIG=\"manifest.${VE_SIG_EXT}\" + +.if ${VE_SIGNATURE_LIST:MOPENPGP} != "" +XCFLAGS.verify+= -DMANIFEST_ASC=\"manifest.asc\" +.endif + +.if !empty(MANIFEST_SKIP_ALWAYS) +XCFLAGS.verify+= -DMANIFEST_SKIP_ALWAYS=\"${MANIFEST_SKIP_ALWAYS}\" +.endif Index: lib/libve/brf.c =================================================================== --- /dev/null +++ lib/libve/brf.c @@ -0,0 +1,403 @@ +// The functions here are derrived from BearSSL/tools/*.c +// When that is refactored suitably we can use them directly. +/* + * Copyright (c) 2016 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +__FBSDID("$FreeBSD$"); + +#define NEED_BRSSL_H +#include "libve-priv.h" +#include + + +static int +is_ign(int c) +{ + if (c == 0) { + return (0); + } + if (c <= 32 || c == '-' || c == '_' || c == '.' + || c == '/' || c == '+' || c == ':') + { + return (1); + } + return (0); +} + +/* + * Get next non-ignored character, normalised: + * ASCII letters are converted to lowercase + * control characters, space, '-', '_', '.', '/', '+' and ':' are ignored + * A terminating zero is returned as 0. + */ +static int +next_char(const char **ps, const char *limit) +{ + for (;;) { + int c; + + if (*ps == limit) { + return (0); + } + c = *(*ps) ++; + if (c == 0) { + return (0); + } + if (c >= 'A' && c <= 'Z') { + c += 'a' - 'A'; + } + if (!is_ign(c)) { + return (c); + } + } +} + +/* + * Partial string equality comparison, with normalisation. + */ +static int +eqstr_chunk(const char *s1, size_t s1_len, const char *s2, size_t s2_len) +{ + const char *lim1, *lim2; + + lim1 = s1 + s1_len; + lim2 = s2 + s2_len; + for (;;) { + int c1, c2; + + c1 = next_char(&s1, lim1); + c2 = next_char(&s2, lim2); + if (c1 != c2) { + return (0); + } + if (c1 == 0) { + return (1); + } + } +} + +/* see brssl.h */ +int +eqstr(const char *s1, const char *s2) +{ + return (eqstr_chunk(s1, strlen(s1), s2, strlen(s2))); +} + +int +looks_like_DER(const unsigned char *buf, size_t len) +{ + int fb; + size_t dlen; + + if (len < 2) { + return (0); + } + if (*buf ++ != 0x30) { + return (0); + } + fb = *buf ++; + len -= 2; + if (fb < 0x80) { + return ((size_t)fb == len); + } else if (fb == 0x80) { + return (0); + } else { + fb -= 0x80; + if (len < (size_t)fb + 2) { + return (0); + } + len -= (size_t)fb; + dlen = 0; + while (fb -- > 0) { + if (dlen > (len >> 8)) { + return (0); + } + dlen = (dlen << 8) + (size_t)*buf ++; + } + return (dlen == len); + } +} + +static void +vblob_append(void *cc, const void *data, size_t len) +{ + bvector *bv; + + bv = cc; + VEC_ADDMANY(*bv, data, len); +} + +void +free_pem_object_contents(pem_object *po) +{ + if (po != NULL) { + xfree(po->name); + xfree(po->data); + } +} + +pem_object * +decode_pem(const void *src, size_t len, size_t *num) +{ + VECTOR(pem_object) pem_list = VEC_INIT; + br_pem_decoder_context pc; + pem_object po, *pos; + const unsigned char *buf; + bvector bv = VEC_INIT; + int inobj; + int extra_nl; + + *num = 0; + br_pem_decoder_init(&pc); + buf = src; + inobj = 0; + po.name = NULL; + po.data = NULL; + po.data_len = 0; + extra_nl = 1; + while (len > 0) { + size_t tlen; + + tlen = br_pem_decoder_push(&pc, buf, len); + buf += tlen; + len -= tlen; + switch (br_pem_decoder_event(&pc)) { + + case BR_PEM_BEGIN_OBJ: + po.name = xstrdup(br_pem_decoder_name(&pc)); + br_pem_decoder_setdest(&pc, vblob_append, &bv); + inobj = 1; + break; + + case BR_PEM_END_OBJ: + if (inobj) { + po.data = VEC_TOARRAY(bv); + po.data_len = VEC_LEN(bv); + VEC_ADD(pem_list, po); + VEC_CLEAR(bv); + po.name = NULL; + po.data = NULL; + po.data_len = 0; + inobj = 0; + } + break; + + case BR_PEM_ERROR: + xfree(po.name); + VEC_CLEAR(bv); + ve_error_set("ERROR: invalid PEM encoding"); + VEC_CLEAREXT(pem_list, &free_pem_object_contents); + return (NULL); + } + + /* + * We add an extra newline at the end, in order to + * support PEM files that lack the newline on their last + * line (this is somwehat invalid, but PEM format is not + * standardised and such files do exist in the wild, so + * we'd better accept them). + */ + if (len == 0 && extra_nl) { + extra_nl = 0; + buf = (const unsigned char *)"\n"; + len = 1; + } + } + if (inobj) { + ve_error_set("ERROR: unfinished PEM object"); + xfree(po.name); + VEC_CLEAR(bv); + VEC_CLEAREXT(pem_list, &free_pem_object_contents); + return (NULL); + } + + *num = VEC_LEN(pem_list); + VEC_ADD(pem_list, po); + pos = VEC_TOARRAY(pem_list); + VEC_CLEAR(pem_list); + return (pos); +} + +br_x509_certificate * +parse_certificates(unsigned char *buf, size_t len, size_t *num) +{ + VECTOR(br_x509_certificate) cert_list = VEC_INIT; + pem_object *pos; + size_t u, num_pos; + br_x509_certificate *xcs; + br_x509_certificate dummy; + + *num = 0; + + /* + * Check for a DER-encoded certificate. + */ + if (looks_like_DER(buf, len)) { + xcs = xmalloc(2 * sizeof *xcs); + xcs[0].data = buf; + xcs[0].data_len = len; + xcs[1].data = NULL; + xcs[1].data_len = 0; + *num = 1; + return (xcs); + } + + pos = decode_pem(buf, len, &num_pos); + if (pos == NULL) { + return (NULL); + } + for (u = 0; u < num_pos; u ++) { + if (eqstr(pos[u].name, "CERTIFICATE") + || eqstr(pos[u].name, "X509 CERTIFICATE")) + { + br_x509_certificate xc; + + xc.data = pos[u].data; + xc.data_len = pos[u].data_len; + pos[u].data = NULL; + VEC_ADD(cert_list, xc); + } + } + for (u = 0; u < num_pos; u ++) { + free_pem_object_contents(&pos[u]); + } + xfree(pos); + + if (VEC_LEN(cert_list) == 0) { + return (NULL); + } + *num = VEC_LEN(cert_list); + dummy.data = NULL; + dummy.data_len = 0; + VEC_ADD(cert_list, dummy); + xcs = VEC_TOARRAY(cert_list); + VEC_CLEAR(cert_list); + return (xcs); +} + +br_x509_certificate * +read_certificates(const char *fname, size_t *num) +{ + br_x509_certificate *xcs; + unsigned char *buf; + size_t len; + + *num = 0; + + /* + * TODO: reading the whole file is crude; we could parse them + * in a streamed fashion. But it does not matter much in practice. + */ + buf = read_file(fname, &len); + if (buf == NULL) { + return (NULL); + } + xcs = parse_certificates(buf, len, num); + if (xcs == NULL) { + ve_error_set("ERROR: no certificate in file '%s'\n", fname); + } + xfree(buf); + return (xcs); +} + +/* see brssl.h */ +void +free_certificates(br_x509_certificate *certs, size_t num) +{ + size_t u; + + for (u = 0; u < num; u ++) { + xfree(certs[u].data); + } + xfree(certs); +} + + +static void +dn_append(void *ctx, const void *buf, size_t len) +{ + VEC_ADDMANY(*(bvector *)ctx, buf, len); +} + +int +certificate_to_trust_anchor_inner(br_x509_trust_anchor *ta, + br_x509_certificate *xc) +{ + br_x509_decoder_context dc; + bvector vdn = VEC_INIT; + br_x509_pkey *pk; + + br_x509_decoder_init(&dc, dn_append, &vdn); + br_x509_decoder_push(&dc, xc->data, xc->data_len); + pk = br_x509_decoder_get_pkey(&dc); + if (pk == NULL) { + ve_error_set("ERROR: CA decoding failed with error %d\n", + br_x509_decoder_last_error(&dc)); + VEC_CLEAR(vdn); + return (-1); + } + ta->dn.data = VEC_TOARRAY(vdn); + ta->dn.len = VEC_LEN(vdn); + VEC_CLEAR(vdn); + ta->flags = 0; + if (br_x509_decoder_isCA(&dc)) { + ta->flags |= BR_X509_TA_CA; + } + switch (pk->key_type) { + case BR_KEYTYPE_RSA: + ta->pkey.key_type = BR_KEYTYPE_RSA; + ta->pkey.key.rsa.n = xblobdup(pk->key.rsa.n, pk->key.rsa.nlen); + ta->pkey.key.rsa.nlen = pk->key.rsa.nlen; + ta->pkey.key.rsa.e = xblobdup(pk->key.rsa.e, pk->key.rsa.elen); + ta->pkey.key.rsa.elen = pk->key.rsa.elen; + break; + case BR_KEYTYPE_EC: + ta->pkey.key_type = BR_KEYTYPE_EC; + ta->pkey.key.ec.curve = pk->key.ec.curve; + ta->pkey.key.ec.q = xblobdup(pk->key.ec.q, pk->key.ec.qlen); + ta->pkey.key.ec.qlen = pk->key.ec.qlen; + break; + default: + ve_error_set("ERROR: unsupported public key type in CA\n"); + xfree(ta->dn.data); + return (-1); + } + return (0); +} + +/* see brssl.h */ +void +free_ta_contents(br_x509_trust_anchor *ta) +{ + xfree(ta->dn.data); + switch (ta->pkey.key_type) { + case BR_KEYTYPE_RSA: + xfree(ta->pkey.key.rsa.n); + xfree(ta->pkey.key.rsa.e); + break; + case BR_KEYTYPE_EC: + xfree(ta->pkey.key.ec.q); + break; + } +} Index: lib/libve/h/libve.h =================================================================== --- /dev/null +++ lib/libve/h/libve.h @@ -0,0 +1,86 @@ +/*- + * Copyright (c) 2017, Juniper Networks, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * $FreeBSD$ + */ +#include +#ifndef LOADER_VERIEXEC +#include +#include +#include +#include +#include +#include + +#elif !defined(STAND_H) +#include +#endif + +#include + +#ifndef NEED_BRSSL_H +unsigned char * read_file(const char *, size_t *); +#endif + +extern int DebugVe; + +#define DEBUG_PRINTF(n, x) if (DebugVe >= n) printf x + +int ve_trust_init(void); +int ve_trust_add(const char *); +void ve_debug_set(int); +void ve_utc_set(time_t utc); +char *ve_error_get(void); +int ve_error_set(const char *, ...) __printflike(1,2); +int ve_self_tests(void); + +void fingerprint_info_add(const char *, const char *, const char *, + const char *, struct stat *); + +int ve_check_hash(br_hash_compat_context *, const br_hash_class *, + const char *, const char *, size_t); + +struct vectx; +struct vectx* vectx_open(int, const char *, off_t, struct stat *, int *); +ssize_t vectx_read(struct vectx *, void *, size_t); +off_t vectx_lseek(struct vectx *, off_t, int); +int vectx_close(struct vectx *); + +char * hexdigest(char *, size_t, unsigned char *, size_t); +unsigned char *verify_sig(const char *, int); +int verify_fd(int, const char *, off_t, struct stat *); +int verify_open(const char *, int); + +unsigned char *verify_asc(const char *, int); /* OpenPGP */ + +void ve_pcr_init(size_t); +void ve_pcr_update(unsigned char *, size_t); +ssize_t ve_pcr_get(unsigned char *, size_t); + +#define VE_FINGERPRINT_OK 1 +/* errors from verify_fd */ +#define VE_FINGERPRINT_NONE -2 +#define VE_FINGERPRINT_WRONG -3 +#define VE_FINGERPRINT_UNKNOWN -4 /* may not be an error */ Index: lib/libve/h/verify.h =================================================================== --- /dev/null +++ lib/libve/h/verify.h @@ -0,0 +1,40 @@ +/*- + * 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. + */ + +#define VE_GUESS -1 /* let verify_file work it out */ +#define VE_TRY 0 /* we don't mind if unverified */ +#define VE_WANT 1 /* we want this verified */ +#define VE_MUST 2 /* this must be verified */ + +#define VE_VERIFIED 1 /* all good */ +#define VE_UNVERIFIED_OK 0 /* not verified but that's ok */ +#define VE_NOT_VERIFYING 2 /* we are not verifying */ + +void ve_debug_set(int); +int ve_status_get(int); +int load_manifest(const char *, const char *, const char *, struct stat *); +int verify_file(int, const char *, off_t, int); +void verify_pcr_export(void); + Index: lib/libve/libve-priv.h =================================================================== --- /dev/null +++ lib/libve/libve-priv.h @@ -0,0 +1,45 @@ +/*- + * Copyright (c) 2017, Juniper Networks, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * $FreeBSD$ + */ + +/* public api */ +#include "libve.h" + +size_t ve_trust_anchors_add(br_x509_certificate *, size_t); +char *fingerprint_info_lookup(int, const char *); + +br_x509_certificate * parse_certificates(unsigned char *, size_t, size_t *); +int certificate_to_trust_anchor_inner(br_x509_trust_anchor *, + br_x509_certificate *); + +int verify_rsa_digest(br_rsa_public_key *pkey, + const unsigned char *hash_oid, + unsigned char *mdata, size_t mlen, + unsigned char *sdata, size_t slen); + +int openpgp_self_tests(void); + Index: lib/libve/local.trust.mk =================================================================== --- /dev/null +++ lib/libve/local.trust.mk @@ -0,0 +1,82 @@ +# $FreeBSD$ + +# Consider this file an example. +# +# For Junos this is how we obtain trust anchor .pems +# the signing server (http://www.crufty.net/sjg/blog/signing-server.htm) +# for each key will provide the appropriate certificate chain on request + +# force these for Junos +MANIFEST_SKIP_ALWAYS= boot +VE_HASH_LIST+= \ + SHA1 \ + SHA256 \ + SHA384 + +VE_SIGNATURE_LIST+= \ + ECDSA + +VE_SELF_TESTS= yes + +.if ${MACHINE} == "host" && ${.CURDIR:T} == "tests" +# for testing +VE_HASH_LIST+= \ + SHA512 + +VE_SIGNATURE_LIST+= \ + RSA \ + DEPRECATED_RSA_SHA1 +.endif + +SIGNER = ${SB_TOOLS_PATH:U/volume/buildtools/bin}/sign.py +SIGN_HOST ?= ${SB_SITE:Usvl}-junos-signer.juniper.net +ECDSA_PORT:= ${133%y:L:gmtime} +SIGN_ECDSA= ${PYTHON} ${SIGNER} -u ${SIGN_HOST}:${ECDSA_PORT} -h sha256 +RSA2_PORT:= ${163%y:L:gmtime} +SIGN_RSA2= ${PYTHON} ${SIGNER} -u ${SIGN_HOST}:${RSA2_PORT} -h sha256 + +.if !empty(OPENPGP_SIGN_URL) +VE_SIGNATURE_LIST+= OPENPGP + +SIGN_OPENPGP= ${PYTHON} ${SIGNER:H}/openpgp-sign.py -a -u ${OPENPGP_SIGN_URL} + +ta_openpgp.asc: + ${SIGN_OPENPGP} -C ${.TARGET} + +ta.h: ta_openpgp.asc + +.if ${VE_SELF_TESTS} != "no" +# for self test +vc_openpgp.asc: ta_openpgp.asc + ${SIGN_OPENPGP} ${.ALLSRC:M*.asc} + mv ta_openpgp.asc.asc ${.TARGET} + +ta.h: vc_openpgp.asc +.endif +.endif + +rcerts.pem: + ${SIGN_RSA2} -C ${.TARGET} + +ecerts.pem: + ${SIGN_ECDSA} -C ${.TARGET} + +.if ${VE_SIGNATURE_LIST:tu:MECDSA} != "" +# the last cert in the chain is the one we want +ta_ec.pem: ecerts.pem _LAST_PEM_USE + +.if ${VE_SELF_TESTS} != "no" +# these are for verification self test +vc_ec.pem: ecerts.pem _2ndLAST_PEM_USE +.endif +.endif + +.if ${VE_SIGNATURE_LIST:tu:MRSA} != "" +ta_rsa.pem: rcerts.pem _LAST_PEM_USE +.if ${VE_SELF_TESTS} != "no" +vc_rsa.pem: rcerts.pem _2ndLAST_PEM_USE +.endif +.endif + +# we take the mtime of this as our baseline time +BUILD_UTC_FILE= ecerts.pem Index: lib/libve/openpgp/Makefile.inc =================================================================== --- /dev/null +++ lib/libve/openpgp/Makefile.inc @@ -0,0 +1,14 @@ +# $FreeBSD$ + +# decode OpenPGP signatures per rfc4880 +.PATH: ${.PARSEDIR} + +CFLAGS+= -DUSE_BEARSSL + +BRSSL_SRCS+= dearmor.c +SRCS+= \ + decode.c \ + opgp_key.c \ + opgp_sig.c + +opgp_key.o: ta.h Index: lib/libve/openpgp/dearmor.c =================================================================== --- /dev/null +++ lib/libve/openpgp/dearmor.c @@ -0,0 +1,144 @@ +/*- + * Copyright (c) 2018, Juniper Networks, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#define NEED_BRSSL_H +#include +#include + +#include "decode.h" + +/** + * @brief decode ascii armor + * + * once we get rid of the trailing checksum + * we can treat as PEM. + * + * @sa rfc4880:6.2 + */ +unsigned char * +dearmor(char *pem, size_t nbytes, size_t *len) +{ +#ifdef USE_BEARSSL + pem_object *po; + size_t npo; +#else + BIO *bp; + char *name = NULL; + char *header = NULL; +#endif + unsigned char *data = NULL; + char *cp; + char *ep; + + /* we need to remove the Armor tail */ + if ((cp = strstr((char *)pem, "\n=")) && + (ep = strstr(cp, "\n---"))) { + memmove(cp, ep, nbytes - (size_t)(ep - pem)); + nbytes -= (size_t)(ep - cp); + pem[nbytes] = '\0'; + } +#ifdef USE_BEARSSL + /* we also need to remove any headers */ + if ((cp = strstr((char *)pem, "---\n")) && + (ep = strstr(cp, "\n\n"))) { + cp += 4; + ep += 2; + memmove(cp, ep, nbytes - (size_t)(ep - pem)); + nbytes -= (size_t)(ep - cp); + pem[nbytes] = '\0'; + } + if ((po = decode_pem(pem, nbytes, &npo))) { + data = po->data; + *len = po->data_len; + } +#else + if ((bp = BIO_new_mem_buf(pem, (int)nbytes))) { + long llen = (long)nbytes; + + if (!PEM_read_bio(bp, &name, &header, &data, &llen)) + data = NULL; + BIO_free(bp); + *len = (size_t)llen; + } +#endif + return (data); +} + +#ifdef MAIN_DEARMOR +#include +#include +#include + +/* + * Mostly a unit test. + */ +int +main(int argc, char *argv[]) +{ + const char *infile, *outfile; + unsigned char *data; + size_t n, x; + int fd; + int o; + + infile = outfile = NULL; + while ((o = getopt(argc, argv, "i:o:")) != -1) { + switch (o) { + case 'i': + infile = optarg; + break; + case 'o': + outfile = optarg; + break; + default: + errx(1, "unknown option: -%c", o); + } + } + if (!infile) + errx(1, "need -i infile"); + if (outfile) { + if ((fd = open(outfile, O_WRONLY|O_CREAT|O_TRUNC)) < 0) + err(1, "cannot open %s", outfile); + } else { + fd = 1; /* stdout */ + } + data = read_file(infile, &n); + if (!(data[0] & 0200)) + 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 Index: lib/libve/openpgp/decode.h =================================================================== --- /dev/null +++ lib/libve/openpgp/decode.h @@ -0,0 +1,52 @@ +/*- + * Copyright (c) 2018, Juniper Networks, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * $FreeBSD$ + */ + +#ifdef USE_BEARSSL +unsigned char * mpi2bn(unsigned char **pptr, size_t *sz); +#else +# include +# include +# include + +BIGNUM * mpi2bn(unsigned char **pptr); +#endif + +#define NEW(x) calloc(1,sizeof(x)) + +typedef int (*decoder_t)(int, unsigned char **, int, void *); + +unsigned char * i2octets(int n, int i); +int octets2i(unsigned char *ptr, int n); +char * octets2hex(unsigned char *ptr, int 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); +unsigned char * read_file(const char *filename, size_t *sz); Index: lib/libve/openpgp/decode.c =================================================================== --- /dev/null +++ lib/libve/openpgp/decode.c @@ -0,0 +1,304 @@ +/*- + * Copyright (c) 2018, Juniper Networks, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include + +#include "decode.h" + +char * +octets2hex(unsigned char *ptr, int n) +{ + char *hex; + char *cp; + int i; + + hex = malloc((size_t)(2*n + 1)); + if (hex) { + for (i = 0, cp = hex; i < n; i++) { + snprintf(&cp[i*2], 3, "%02X", ptr[i]); + } + } + return (hex); +} + +unsigned char * +i2octets(int n, int 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, int n) +{ + int 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 & 0200)) + return (-1); /* we are lost! */ + *isnew = tag & 0100; + if (*isnew) { + *ltype = -1; /* irrelevant */ + tag &= 077; + } else { + *ltype = tag & 03; + tag = (tag & 074) >> 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); +} Index: lib/libve/openpgp/opgp_key.c =================================================================== --- /dev/null +++ lib/libve/openpgp/opgp_key.c @@ -0,0 +1,342 @@ +/*- + * Copyright (c) 2018, Juniper Networks, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include "../libve-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]; +#else + RSA *rsa = NULL; + const EVP_MD *md = NULL; + EVP_MD_CTX mctx; + unsigned char mdata[EVP_MAX_MD_SIZE]; +#endif + size_t mlen; + + 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 = sizeof(mdata); + EVP_DigestFinal(&mctx,mdata,(unsigned int *)&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] & 0200) == 0) { + data = dearmor((char *)buf, nbytes, &nbytes); + ptr = data; + } else + ptr = buf; + key = NEW(OpenPGP_key); + if (key) { + rc = decode_packet(0, &ptr, nbytes, (decoder_t)decode_key, + key); + if (rc < 0) { + free(key); + key = NULL; + } else if (rc > 8) { + int isnew, ltype; + + tag = decode_tag(ptr, &isnew, <ype); + if (tag == 13) { + key->user = NEW(OpenPGP_user); + rc = decode_packet(0, &ptr, (size_t)rc, + (decoder_t)decode_user, key->user); + } + } + } + free(data); + return (key); +} + +static LIST_HEAD(, OpenPGP_key_) trust_list; + +/** + * @brief add a key to our list + */ +void +openpgp_trust_add(OpenPGP_key *key) +{ + static int once = 0; + + if (!once) { + once = 1; + + LIST_INIT(&trust_list); + } + if (key) + LIST_INSERT_HEAD(&trust_list, key, entries); +} + +/** + * @brief if keyID is in our list return the key + * + * @return key or NULL + */ +OpenPGP_key * +openpgp_trust_get(const char *keyID) +{ + OpenPGP_key *key; + + openpgp_trust_add(NULL); /* initialize if needed */ + + LIST_FOREACH(key, &trust_list, entries) { + if (strcmp(key->id, keyID) == 0) + return (key); + } + return (NULL); +} + +/** + * @brief load a key from file + */ +OpenPGP_key * +load_key_file(const char *kfile) +{ + unsigned char *data = NULL; + size_t n; + OpenPGP_key *key; + + data = read_file(kfile, &n); + key = load_key_buf(data, n); + free(data); + openpgp_trust_add(key); + return (key); +} + +#include + +#ifndef LOADER_VERIEXEC +/* we can lookup keyID in filesystem */ + +static const char *trust_store[] = { + "/var/db/trust", + "/etc/db/trust", + NULL, +}; + +/** + * @brief lookup key id in trust store + * + */ +static OpenPGP_key * +load_trusted_key_id(const char *keyID) +{ + char kfile[MAXPATHLEN]; + const char **tp; + size_t n; + + for (tp = trust_store; *tp; tp++) { + n = (size_t)snprintf(kfile, sizeof(kfile), "%s/%s", *tp, keyID); + if (n >= sizeof(kfile)) + return (NULL); + if (access(kfile, R_OK) == 0) { + return (load_key_file(kfile)); + } + } + return (NULL); +} +#endif + +/** + * @brief return key if trusted + */ +OpenPGP_key * +load_key_id(const char *keyID) +{ + static int once = 0; + OpenPGP_key *key; + + if (!once) { +#ifdef ta_ASC + char *cp; + size_t n; + + if ((cp = strdup(ta_ASC))) { + n = strlen(cp); + key = load_key_buf((unsigned char *)cp, n); + free(cp); + openpgp_trust_add(key); + } +#endif + once = 1; + } + key = openpgp_trust_get(keyID); +#ifndef LOADER_VERIEXEC + if (!key) + key = load_trusted_key_id(keyID); +#endif + return (key); +} + +/** + * @brief test that we can verify a signature + */ +int +openpgp_self_tests(void) +{ + int rc = -1; +#ifdef ta_ASC + char *fdata, *sdata = NULL; + size_t fbytes, sbytes; + + if ((fdata = strdup(ta_ASC)) && + (sdata = strdup(vc_ASC))) { + fbytes = strlen(fdata); + sbytes = strlen(sdata); + rc = openpgp_verify("ta_ASC", + (unsigned char *)fdata, fbytes, + (unsigned char *)sdata, sbytes, 0); + if (!rc) { + printf("Testing verify OpenPGP signature:\t\tPassed\n"); + } + } + free(fdata); + free(sdata); +#endif + return (rc); +} Index: lib/libve/openpgp/opgp_sig.c =================================================================== --- /dev/null +++ lib/libve/openpgp/opgp_sig.c @@ -0,0 +1,482 @@ +/*- + * Copyright (c) 2018, Juniper Networks, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * RCSid: + * from: signer.c,v 1.10 2018/03/23 01:14:30 sjg + * + * @(#) Copyright (c) 2012 Simon J. Gerraty + * + * This file is provided in the hope that it will + * be of use. There is absolutely NO WARRANTY. + * Permission to copy, redistribute or otherwise + * use this file is hereby granted provided that + * the above copyright notice and this notice are + * left intact. + * + * Please send copies of changes and bug-fixes to: + * sjg@crufty.net + */ + +#include +__FBSDID("$FreeBSD$"); + +#include "../libve-priv.h" +#define get_error_string ve_error_get +#ifdef LOADER_VERIEXEC +#define warnx printf +#else + +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include "decode.h" +#include "packet.h" + +#ifdef USE_BEARSSL +void +initialize (void) +{ +#ifdef LOADER_VERIEXEC + ve_trust_init(); +#endif +} + +#else + +#include + +/** + * @brief intialize OpenSSL + */ +void +initialize(void) +{ + static int once; + + if (once) + return); + once = 1; + //CRYPTO_malloc_init(); + ERR_load_crypto_strings(); + OpenSSL_add_all_algorithms(); +} + +/** + * @brief + * last error from OpenSSL as a string + */ +char * +get_error_string(void) +{ + initialize(); + return (ERR_error_string(ERR_get_error(), NULL)); +} +#endif + +/** + * @brief decode a signature packet + * + * We only support RSA + * + * @sa rfc4880:5.2 + */ +ssize_t +decode_sig(int tag, unsigned char **pptr, size_t len, OpenPGP_sig *sig) +{ + unsigned char *ptr; + unsigned char *pgpbytes; + unsigned char *sp; + int version; + int hcount = 0; + int ucount = 0; + int stag = 0; + int n; + + n = tag; /* avoid unused */ + + /* + * We need to keep a reference to the packet bytes + * as these form part of the signature data. + * + * @sa rfc4880:5.2.4 + */ + pgpbytes = ptr = *pptr; + version = *ptr++; + if (version == 3) { + ptr++; + sig->pgpbytes = malloc(5); + if (!sig->pgpbytes) + return (-1); + memcpy(sig->pgpbytes, ptr, 5); + sig->pgpbytes_len = 5; + sig->sig_type = *ptr++; + ptr += 4; + sig->key_id = octets2hex(ptr, 8); + ptr += 8; + sig->sig_alg = *ptr++; + sig->hash_alg = *ptr++; + } else if (version == 4) { + sig->sig_type = *ptr++; + sig->sig_alg = *ptr++; + sig->hash_alg = *ptr++; + hcount = octets2i(ptr, 2); + ptr += 2; + sig->pgpbytes_len = (size_t)hcount + 6; + sig->pgpbytes = malloc(sig->pgpbytes_len + 6); + if (!sig->pgpbytes) + return (-1); + memcpy(sig->pgpbytes, pgpbytes, sig->pgpbytes_len); + sp = &sig->pgpbytes[sig->pgpbytes_len]; + *sp++ = 4; + *sp++ = 255; + memcpy(sp, i2octets(4, (int)sig->pgpbytes_len), 4); + sig->pgpbytes_len += 6; + + while (hcount > 0) { + sp = decode_subpacket(&ptr, &stag, &n); + hcount -= n; + /* can check stag to see if we care */ + } + ucount = octets2i(ptr, 2); + ptr += 2; + while (ucount > 0) { + sp = decode_subpacket(&ptr, &stag, &n); + ucount -= n; + /* can check stag to see if we care */ + if (stag == 16) { + free(sig->key_id); + sig->key_id = octets2hex(sp, 8); + } + } + } else + return (-1); + ptr += 2; /* skip hash16 */ + if (sig->sig_alg == 1) { /* RSA */ + sig->sig = decode_mpi(&ptr, &sig->sig_len); + } + /* we are done */ + return ((ssize_t)len); +} + +/** + * @brief map OpenPGP hash algorithm id's to name + * + * @sa rfc4880:9.4 + */ +static struct hash_alg_map { + int halg; + const char *hname; +} hash_algs[] = { + {1, "md5"}, + {2, "sha1"}, + {8, "sha256"}, + {9, "sha384"}, + {10, "sha512"}, + {11, "sha224"}, + {0, NULL}, +}; + +static const char * +get_hname(int hash_alg) +{ + struct hash_alg_map *hmp; + + for (hmp = hash_algs; hmp->halg > 0; hmp++) { + if (hmp->halg == hash_alg) + return (hmp->hname); + } + return (NULL); +} + +/* lifted from signer.c */ +/** + * @brief verify a digest + * + * The public key, digest name, file and signature data. + * + * @return 1 on success 0 on failure, -1 on error + */ +#ifndef USE_BEARSSL +static int +verify_digest (EVP_PKEY *pkey, + const char *digest, + unsigned char *mdata, size_t mlen, + unsigned char *sdata, size_t slen) +{ + EVP_MD_CTX ctx; + const EVP_MD *md = NULL; + EVP_PKEY_CTX *pctx = NULL; + int rc = 0; + int i = -1; + + initialize(); + md = EVP_get_digestbyname(digest); + EVP_DigestInit(&ctx, md); + + pctx = EVP_PKEY_CTX_new(pkey, NULL); + if (!pctx) + goto fail; + if (EVP_PKEY_verify_init(pctx) <= 0) + goto fail; + if (EVP_PKEY_CTX_set_signature_md(pctx, ctx.digest) <= 0) + goto fail; + i = EVP_PKEY_verify(pctx, sdata, slen, mdata, mlen); + if (i >= 0) + rc = i; +fail: + EVP_PKEY_CTX_free(pctx); + return (rc); +} +#endif + + +/** + * @brief verify OpenPGP signed file + * + * + * @param[in] filename + * used to determine the signature name + * + * @param[in] fdata + * content of filename + * + * @param[in] fbytes + * of fdata + * + * @param[in] sdata + * content of signature + * + * @param[in] sbytes + * of sdata + * + * @param[in] flags + * + * @return 0 on success + */ +int +openpgp_verify(const char *filename, + unsigned char *fdata, size_t fbytes, + unsigned char *sdata, size_t sbytes, + int flags) +{ + OpenPGP_key *key; + OpenPGP_sig *sig; +#ifdef USE_BEARSSL + const br_hash_class *md; + br_hash_compat_context mctx; + const unsigned char *hash_oid; +#else + const EVP_MD *md = NULL; + EVP_MD_CTX mctx; +#endif + unsigned char mdata[64]; + unsigned char *ptr; + unsigned char *ddata = NULL; + const char *hname; + size_t mlen; + int rc = -1; + + initialize(); + + sig = NEW(OpenPGP_sig); + if (!sdata || !sig) { + warnx("cannot verify %s", filename); + goto oops; + } + if (!(sdata[0] & 0200)) + sdata = ddata = dearmor((char *)sdata, sbytes, &sbytes); + ptr = sdata; + rc = decode_packet(2, &ptr, sbytes, (decoder_t)decode_sig, sig); + if (rc == 0 && sig->key_id) { + key = load_key_id(sig->key_id); + if (!key) { + warnx("cannot find key-id: %s", sig->key_id); + rc = -1; + } else if (!(hname = get_hname(sig->hash_alg))) { + warnx("unsupported hash algorithm: %d", sig->hash_alg); + rc = -1; + } else { + /* + * Hash fdata according to the OpenPGP recipe + * + * @sa rfc4880:5.2.4 + */ +#ifdef USE_BEARSSL + switch (sig->hash_alg) { /* see hash_algs above */ + case 2: /* sha1 */ + md = &br_sha1_vtable; + mlen = br_sha1_SIZE; + hash_oid = BR_HASH_OID_SHA1; + break; + case 8: /* sha256 */ + md = &br_sha256_vtable; + mlen = br_sha256_SIZE; + hash_oid = BR_HASH_OID_SHA256; + break; + default: + warnx("unsupported hash algorithm: %s", hname); + goto oops; + } + md->init(&mctx.vtable); + md->update(&mctx.vtable, fdata, fbytes); + md->update(&mctx.vtable, sig->pgpbytes, + sig->pgpbytes_len); + md->out(&mctx.vtable, mdata); + + rc = verify_rsa_digest(key->key, hash_oid, + mdata, mlen, sig->sig, sig->sig_len); +#else + md = EVP_get_digestbyname(hname); + EVP_DigestInit(&mctx, md); + EVP_DigestUpdate(&mctx, fdata, fbytes); + EVP_DigestUpdate(&mctx, sig->pgpbytes, + sig->pgpbytes_len); + mlen = sizeof(mdata); + EVP_DigestFinal(&mctx,mdata,(unsigned int *)&mlen); + + rc = verify_digest(key->key, hname, mdata, mlen, + sig->sig, sig->sig_len); +#endif + + if (rc > 0) { + if ((flags & 1)) + printf("Verified %s signed by %s\n", + filename, + key->user ? key->user->name : "someone"); + rc = 0; /* success */ + } else if (rc == 0) { + printf("Unverified %s: %s\n", + filename, get_error_string()); + rc = 1; + } else { + printf("Unverified %s\n", filename); + } + } + } else { + warnx("cannot decode signature for %s", filename); + rc = -1; + } +oops: + free(ddata); + free(sig); + return (rc); +} + +#ifndef LOADER_VERIEXEC +/** + * @brief list of extensions we handle + * + * ".asc" is preferred as it works seamlessly with openpgp + */ +static const char *sig_exts[] = { + ".asc", + ".pgp", + ".psig", + NULL, +}; + +/** + * @brief verify OpenPGP signed file + * + * + * @param[in] filename + * used to determine the signature name + * + * @param[in] fdata + * content of filename + * + * @param[in] nbytes + * of fdata + * + * @return + */ + +int +openpgp_verify_file(const char *filename, unsigned char *fdata, size_t nbytes) +{ + char pbuf[MAXPATHLEN]; + unsigned char *sdata; + const char *sname = NULL; + const char **ep; + size_t sz; + int n; + + for (ep = sig_exts; *ep; ep++) { + n = snprintf(pbuf, sizeof(pbuf), "%s%s", filename, *ep); + if (n >= (int)sizeof(pbuf)) { + warnx("cannot form signature name for %s", filename); + return (-1); + } + if (access(pbuf, R_OK) == 0) { + sname = pbuf; + break; + } + } + if (!sname) { + warnx("cannot find signature for %s", filename); + return (-1); + } + sdata = read_file(sname, &sz); + return (openpgp_verify(filename, fdata, nbytes, sdata, sz, 1)); +} +#endif + +/** + * @brief verify OpenPGP signature + * + * @return content of signed file + */ +unsigned char * +verify_asc(const char *sigfile, int flags) +{ + char pbuf[MAXPATHLEN]; + char *cp; + size_t n; + unsigned char *fdata, *sdata; + size_t fbytes, sbytes; + + if ((sdata = read_file(sigfile, &sbytes))) { + n = strlcpy(pbuf, sigfile, sizeof(pbuf)); + if ((cp = strrchr(pbuf, '.'))) + *cp = '\0'; + if ((fdata = read_file(pbuf, &fbytes))) { + if (openpgp_verify(pbuf, fdata, fbytes, sdata, + sbytes, flags)) { + free(fdata); + fdata = NULL; + } + } + } else + fdata = NULL; + free(sdata); + return (fdata); +} Index: lib/libve/openpgp/packet.h =================================================================== --- /dev/null +++ lib/libve/openpgp/packet.h @@ -0,0 +1,83 @@ +/*- + * Copyright (c) 2018, Juniper Networks, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * $FreeBSD$ + */ + +#include + +/* + * Structs to represent what we need + */ + +typedef struct OpenPGP_user { + char *id; + char *name; +} OpenPGP_user; + +struct OpenPGP_key_ { + char *id; + int sig_alg; + OpenPGP_user *user; +#ifdef USE_BEARSSL + br_rsa_public_key *key; +#else + EVP_PKEY *key; +#endif + LIST_ENTRY(OpenPGP_key_) entries; +}; + +typedef struct OpenPGP_key_ OpenPGP_key; + +typedef struct OpenPGP_sig { + char *key_id; + int sig_type; + int sig_alg; + int hash_alg; + unsigned char *pgpbytes; + size_t pgpbytes_len; + unsigned char *sig; + size_t sig_len; +} OpenPGP_sig; + +void openpgp_trust_add(OpenPGP_key *key); +OpenPGP_key * openpgp_trust_get(const char *keyID); +OpenPGP_key * load_key_file(const char *kfile); +OpenPGP_key * load_key_id(const char *keyID); +void initialize(void); +char * get_error_string(void); +int openpgp_verify(const char *filename, unsigned char *fdata, size_t fbytes, + unsigned char *sdata, size_t sbytes, int flags); +int openpgp_verify_file(const char *filename, unsigned char *fdata, + size_t nbytes); +unsigned char * verify_asc (const char *sigfile, int flags); + +/* 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); Index: lib/libve/pcr.c =================================================================== --- /dev/null +++ lib/libve/pcr.c @@ -0,0 +1,73 @@ +/*- + * 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. + */ +#include +__FBSDID("$FreeBSD$"); + +#include "libve-priv.h" + +static const br_hash_class *pcr_md = NULL; +static br_hash_compat_context pcr_ctx; +static size_t pcr_hlen = 0; + +/** + * @brief initialize pcr context + */ +void +ve_pcr_init(size_t hlen) +{ + if (hlen <= br_sha1_SIZE) { + pcr_hlen = br_sha1_SIZE; + pcr_md = &br_sha1_vtable; + } else { + pcr_hlen = br_sha256_SIZE; + pcr_md = &br_sha256_vtable; + } + pcr_md->init(&pcr_ctx.vtable); +} + +/** + * @brief update pcr context + */ +void +ve_pcr_update(const char *data, size_t dlen) +{ + if (pcr_md) + pcr_md->update(&pcr_ctx.vtable, hex, dlen); +} + +/** + * @brief get pcr result + */ +ssize_t +ve_pcr_get(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); +} + Index: lib/libve/readfile.c =================================================================== --- /dev/null +++ lib/libve/readfile.c @@ -0,0 +1,61 @@ +/*- + * 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. + */ +#include +__FBSDID("$FreeBSD$"); + +#include + +unsigned char * +read_file(const char *path, size_t *len) +{ + int fd, m, n, x; + struct stat st; + unsigned char *buf; + + if (len) + *len = 0; + if ((fd = open(path, O_RDONLY)) < 0) + return (NULL); + fstat(fd, &st); + if (len) + *len = st.st_size; + buf = malloc(st.st_size + 1); + for (x = 0, m = st.st_size; m > 0; ) { + n = read(fd, &buf[x], m); + if (n < 0) + break; + if (n > 0) { + m -= n; + x += n; + } + } + close(fd); + if (m == 0) { + buf[st.st_size] = '\0'; + return (buf); + } + free(buf); + return (NULL); +} Index: lib/libve/tests/Makefile =================================================================== --- /dev/null +++ lib/libve/tests/Makefile @@ -0,0 +1,12 @@ +# $FreeBSD$ + +PROG= tvo + +SRCS+= tvo.c +CFLAGS+= -DUNIT_TEST -g -O0 + +LDADD+= -static +LIBADD+= bearssl +MAN= + +.include Index: lib/libve/tests/Makefile.depend.host =================================================================== --- /dev/null +++ lib/libve/tests/Makefile.depend.host @@ -0,0 +1,12 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + lib/libbearssl \ + + +.include + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif Index: lib/libve/tests/tvo.c =================================================================== --- /dev/null +++ lib/libve/tests/tvo.c @@ -0,0 +1,154 @@ +/* + * 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. + */ +#include +__FBSDID("$FreeBSD$"); + +#include "../libve-priv.h" + +#include +#include + +int +main(int argc, char *argv[]) +{ + int n; + int fd; + int c; + char *cp; + char *skip; + char *prefix; + + prefix = "/"; + skip = ""; + + n = ve_trust_init(); + printf("Trust %d\n", n); + + while ((c = getopt(argc, argv, "dp:s:T:")) != -1) { + switch (c) { + case 'd': + DebugVe++; + break; + case 'p': + prefix = optarg; + break; + case 's': + skip = optarg; + break; + case 'T': + n = ve_trust_add(optarg); + printf("Local trust %s: %d\n", optarg, n); + break; + default: + errx(1, "unknown option: -%c", c); + break; + } + } + + for ( ; optind < argc; optind++) { +#ifdef VE_OPENPGP_SUPPORT + if (strstr(argv[optind], "asc")) { + cp = (char *)verify_asc(argv[optind], 1); + if (cp) { + printf("Verified: %s: %.28s...\n", + argv[optind], cp); + fingerprint_info_add(argv[optind], + prefix, skip, cp, NULL); + } else { + fprintf(stderr, "%s: %s\n", + argv[optind], ve_error_get()); + } + } else +#endif + if (strstr(argv[optind], "sig")) { + cp = (char *)verify_sig(argv[optind], 1); + if (cp) { + printf("Verified: %s: %.28s...\n", + argv[optind], cp); + fingerprint_info_add(argv[optind], + prefix, skip, cp, NULL); + } else { + fprintf(stderr, "%s: %s\n", + argv[optind], ve_error_get()); + } + } else if (strstr(argv[optind], "manifest")) { + cp = (char *)read_file(argv[optind], NULL); + if (cp) { + fingerprint_info_add(argv[optind], + prefix, skip, cp, NULL); + } + } else { + fd = verify_open(argv[optind], O_RDONLY); + printf("verify_open(%s) = %d %s\n", argv[optind], fd, + (fd < 0) ? ve_error_get() : ""); + if (fd > 0) { + /* + * Check that vectx_* can also verify the file. + */ + void *vp; + char buf[BUFSIZ]; + struct stat st; + int error; + size_t off, n; + + fstat(fd, &st); + lseek(fd, 0, SEEK_SET); + off = st.st_size % 512; + vp = vectx_open(fd, argv[optind], off, + &st, &error); + if (!vp) { + printf("vectx_open(%s) failed: %d %s\n", + argv[optind], error, + ve_error_get()); + } else { + off = vectx_lseek(vp, + (st.st_size % 1024), SEEK_SET); + + if (off < st.st_size) { + n = vectx_read(vp, buf, + sizeof(buf)); + if (n > 0) + off += n; + } + off = vectx_lseek(vp, 0, SEEK_END); + /* repeating that should be harmless */ + off = vectx_lseek(vp, 0, SEEK_END); + error = vectx_close(vp); + if (error) { + printf("vectx_close(%s) == %d %s\n", + argv[optind], error, + ve_error_get()); + } else { + printf("vectx_close: Verified: %s\n", + argv[optind]); + } + } + close(fd); + } + } + } + return (0); +} + Index: lib/libve/vectx.c =================================================================== --- /dev/null +++ lib/libve/vectx.c @@ -0,0 +1,291 @@ +/*- + * Copyright (c) 2018, Juniper Networks, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +__FBSDID("$FreeBSD$"); + +#ifndef LOADER_VERIEXEC +/* Avoid unwanted userlandish components */ +#define _KERNEL +#include +#undef _KERNEL +#endif + +#include "libve-priv.h" + +/** + * @file vectx.c + * @brief api to verify file while reading + * + * This API allows the hash of a file to be computed as it is read. + * Key to this is seeking by reading. + * + * On close an indication of the verification result is returned. + */ + +struct vectx { + br_hash_compat_context vec_ctx; /* hash ctx */ + const br_hash_class *vec_md; /* hash method */ + const char *vec_path; /* path we are verifying */ + const char *vec_want; /* hash value we want */ + off_t vec_off; /* current offset */ + size_t vec_size; /* size of path */ + size_t vec_hashsz; /* size of hash */ + int vec_fd; /* file descriptor */ + int vec_status; /* verification status */ +}; + +/** + * @brief + * verify an open file as we read it + * + * If the file has no fingerprint to match, we will still return a + * verification context containing little more than the file + * descriptor, and an error code in @c error. + * + * @param[in] fd + * open descriptor + * + * @param[in] path + * pathname to open + * + * @param[in] off + * current offset + * + * @param[in] stp + * pointer to struct stat + * + * @param[out] error + * @li 0 all is good + * @li ENOMEM out of memory + * @li VE_FINGERPRINT_NONE no entry found + * @li VE_FINGERPRINT_UNKNOWN no fingerprint in entry + * + * @return ctx or NULL on error. + * NULL is only returned for non-files or out-of-memory. + */ +struct vectx * +vectx_open(int fd, const char *path, off_t off, struct stat *stp, int *error) +{ + struct vectx *ctx; + struct stat st; + size_t hashsz; + char *cp; + + if (!stp) { + if (fstat(fd, &st) == 0) + stp = &st; + } + + /* we *should* only get called for files */ + if (stp && !S_ISREG(stp->st_mode)) { + *error = 0; + return (NULL); + } + + ctx = malloc(sizeof(struct vectx)); + if (!ctx) + goto enomem; + ctx->vec_fd = fd; + ctx->vec_path = path; + ctx->vec_size = stp->st_size; + ctx->vec_off = 0; + ctx->vec_want = NULL; + ctx->vec_status = 0; + hashsz = 0; + + cp = fingerprint_info_lookup(fd, path); + if (!cp) { + ctx->vec_status = VE_FINGERPRINT_NONE; + ve_error_set("%s: no entry", path); + } else { + if (strncmp(cp, "sha256=", 7) == 0) { + ctx->vec_md = &br_sha256_vtable; + hashsz = br_sha256_SIZE; + cp += 7; +#ifdef VE_SHA1_SUPPORT + } else if (strncmp(cp, "sha1=", 5) == 0) { + ctx->vec_md = &br_sha1_vtable; + hashsz = br_sha1_SIZE; + cp += 5; +#endif +#ifdef VE_SHA384_SUPPORT + } else if (strncmp(cp, "sha384=", 7) == 0) { + ctx->vec_md = &br_sha384_vtable; + hashsz = br_sha384_SIZE; + cp += 7; +#endif +#ifdef VE_SHA512_SUPPORT + } else if (strncmp(cp, "sha512=", 7) == 0) { + ctx->vec_md = &br_sha512_vtable; + hashsz = br_sha512_SIZE; + cp += 7; +#endif + } else { + ctx->vec_status = VE_FINGERPRINT_UNKNOWN; + ve_error_set("%s: no fingerprint", path); + } + } + *error = ctx->vec_status; + ctx->vec_hashsz = hashsz; + ctx->vec_want = cp; + ctx->vec_md->init(&ctx->vec_ctx.vtable); + + if (hashsz > 0 && off > 0) { + lseek(fd, 0, SEEK_SET); + vectx_lseek(ctx, off, SEEK_SET); + } + return (ctx); + +enomem: /* unlikely */ + *error = ENOMEM; + free(ctx); + return (NULL); +} + +/** + * @brief + * read bytes from file and update hash + * + * It is critical that all file I/O comes through here. + * We keep track of current offset. + * + * @param[in] pctx + * pointer to ctx + * + * @param[in] buf + * + * @param[in] nbytes + * + * @return bytes read or error. + */ +ssize_t +vectx_read(struct vectx *ctx, void *buf, size_t nbytes) +{ + unsigned char *bp = buf; + int n; + size_t off; + + if (ctx->vec_hashsz == 0) /* nothing to do */ + return (read(ctx->vec_fd, buf, nbytes)); + + off = 0; + do { + n = read(ctx->vec_fd, &bp[off], nbytes - off); + if (n < 0) + return (n); + if (n > 0) { + ctx->vec_md->update(&ctx->vec_ctx.vtable, &bp[off], n); + off += n; + ctx->vec_off += n; + } + } while (n > 0 && off < nbytes); + return (off); +} + +/** + * @brief + * vectx equivalent of lseek + * + * We do not actually, seek, but call vectx_read + * to reach the desired offset. + * + * We do not support seeking backwards. + * + * @param[in] pctx + * pointer to ctx + * + * @param[in] off + * desired offset + * + * @param[in] whence + * + * @return offset or error. + */ +off_t +vectx_lseek(struct vectx *ctx, off_t off, int whence) +{ + unsigned char buf[PAGE_SIZE]; + size_t delta; + ssize_t n; + + if (ctx->vec_hashsz == 0) /* nothing to do */ + return (lseek(ctx->vec_fd, off, whence)); + + /* + * Try to convert whence to SEEK_SET + * but we cannot support seeking backwards! + * Nor beyond end of file. + */ + if (whence == SEEK_END && off <= 0) { + whence = SEEK_SET; + off += ctx->vec_size; + } else if (whence == SEEK_CUR && off >= 0) { + whence = SEEK_SET; + off += ctx->vec_off; + } + if (whence != SEEK_SET || off < ctx->vec_off || + (size_t)off > ctx->vec_size) { + printf("ERROR: %s: unsupported operation\n", __func__); + return (-1); + } + n = 0; + do { + delta = off - ctx->vec_off; + if (delta > 0) { + delta = MIN(PAGE_SIZE, delta); + n = vectx_read(ctx, buf, delta); + if (n < 0) + return (n); + } + } while (ctx->vec_off < off && n > 0); + return (ctx->vec_off); +} + +/** + * @brief + * check that hashes match and cleanup + * + * We have finished reading file, compare the hash with what + * we wanted. + * + * @param[in] pctx + * pointer to ctx + * + * @return 0 or an error. + */ +int +vectx_close(struct vectx *ctx) +{ + int rc; + + if (ctx->vec_hashsz == 0) { + rc = ctx->vec_status; + } else { + rc = ve_check_hash(&ctx->vec_ctx, ctx->vec_md, + ctx->vec_path, ctx->vec_want, ctx->vec_hashsz); + } + free(ctx); + return ((rc < 0) ? rc : 0); +} Index: lib/libve/veopen.c =================================================================== --- /dev/null +++ lib/libve/veopen.c @@ -0,0 +1,433 @@ +/*- + * 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. + */ +#include +__FBSDID("$FreeBSD$"); +#include + +#include "libve-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; + + fingerprint_info_init(); + nfip = malloc(sizeof(struct fingerprint_info)); + if (prefix) { + nfip->fi_prefix = strdup(prefix); + } else { + if (!filename) { + free(nfip); + return; + } + nfip->fi_prefix = strdup(filename); + cp = strrchr(nfip->fi_prefix, '/'); + if (cp) + *cp = '\0'; + else { + free(nfip->fi_prefix); + free(nfip); + return; + } + } +#ifdef UNIT_TEST + nfip->fi_dev = 0; +#else + nfip->fi_dev = stp->st_dev; +#endif + nfip->fi_data = data; + nfip->fi_prefix_len = strlen(nfip->fi_prefix); + if (skip) { + nfip->fi_skip = strdup(skip); + nfip->fi_skip_len = strlen(skip); + } 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)); +} + +char * +fingerprint_info_lookup(int fd, const char *path) +{ + char pbuf[MAXPATHLEN+1]; + char nbuf[MAXPATHLEN+1]; + struct stat st; + struct fingerprint_info *fip; + char *cp, *ep, *fp, *np; + const char *prefix; + size_t n, plen, nlen, nplen; + dev_t dev = 0; + + fingerprint_info_init(); + + n = strlcpy(pbuf, path, sizeof(pbuf)); + if (n >= sizeof(pbuf)) + return (NULL); +#ifndef UNIT_TEST + if (fstat(fd, &st) == 0) + dev = st.st_dev; +#endif + /* + * get the first entry - it will have longest prefix + * so we can can work out how to initially split path + */ + fip = LIST_FIRST(&fi_list); + if (!fip) + return (NULL); + prefix = pbuf; + ep = NULL; + cp = &pbuf[fip->fi_prefix_len]; + while (cp > &pbuf[1]) { + if (ep) { + *ep = '/'; + cp -= 2; + } + nlen = plen = 0; /* keep gcc quiet */ + if (cp > pbuf) { + for ( ; cp >= pbuf && *cp != '/'; cp--) + ; /* nothing */ + if (cp > pbuf) { + ep = cp++; + *ep = '\0'; + } + plen = ep - pbuf; + nlen = n - plen - 1; + } + if (cp == pbuf) { + prefix = "/"; + plen = 1; + nlen = n - 1; + cp++; + 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; + /* + * 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) { + 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)); + } + } + } + return (NULL); +} + + +/** + * @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); + ve_pcr_update(hbuf, hlen); + 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); +} + +static int +verify_fingerprint(int fd, const char *path, const char *cp, off_t off) +{ + unsigned char buf[PAGE_SIZE]; + const br_hash_class *md; + br_hash_compat_context mctx; + size_t hlen; + int n; + + if (strncmp(cp, "sha256=", 7) == 0) { + md = &br_sha256_vtable; + hlen = br_sha256_SIZE; + cp += 7; +#ifdef VE_SHA1_SUPPORT + } else if (strncmp(cp, "sha1=", 5) == 0) { + md = &br_sha1_vtable; + hlen = br_sha1_SIZE; + cp += 5; +#endif +#ifdef VE_SHA384_SUPPORT + } else if (strncmp(cp, "sha384=", 7) == 0) { + md = &br_sha384_vtable; + hlen = br_sha384_SIZE; + cp += 7; +#endif +#ifdef VE_SHA512_SUPPORT + } else if (strncmp(cp, "sha512=", 7) == 0) { + md = &br_sha512_vtable; + hlen = br_sha512_SIZE; + cp += 7; +#endif + } else { + ve_error_set("%s: no supported fingerprint", path); + return (VE_FINGERPRINT_UNKNOWN); + } + + md->init(&mctx.vtable); + if (off) + lseek(fd, 0, SEEK_SET); + do { + n = read(fd, buf, sizeof(buf)); + if (n < 0) + return (n); + if (n > 0) + md->update(&mctx.vtable, buf, n); + } while (n > 0); + lseek(fd, off, SEEK_SET); + return (ve_check_hash(&mctx, md, path, cp, hlen)); +} + + +/** + * @brief + * verify an open file + * + * @param[in] fd + * open descriptor + * + * @param[in] path + * pathname to open + * + * @param[in] off + * current offset + * + * @return 0, VE_FINGERPRINT_OK or VE_FINGERPRINT_NONE, VE_FINGERPRINT_WRONG + */ +int +verify_fd(int fd, const char *path, off_t off, struct stat *stp) +{ + struct stat st; + char *cp; + int rc; + + if (!stp) { + if (fstat(fd, &st) == 0) + stp = &st; + } + if (stp && !S_ISREG(stp->st_mode)) + return (0); /* not relevant */ + cp = fingerprint_info_lookup(fd, path); + if (!cp) { + ve_error_set("%s: no entry", path); + return (VE_FINGERPRINT_NONE); + } + rc = verify_fingerprint(fd, path, cp, off); + switch (rc) { + case VE_FINGERPRINT_OK: + case VE_FINGERPRINT_UNKNOWN: + return (rc); + default: + return (VE_FINGERPRINT_WRONG); + } +} + +/** + * @brief + * open a file if it can be verified + * + * @param[in] path + * pathname to open + * + * @param[in] flags + * flags for open + * + * @return fd or VE_FINGERPRINT_NONE, VE_FINGERPRINT_WRONG + */ +int +verify_open(const char *path, int flags) +{ + int fd; + int rc; + + if ((fd = open(path, flags)) >= 0) { + if ((rc = verify_fd(fd, path, 0, NULL)) < 0) { + close(fd); + fd = rc; + } + } + return (fd); +} Index: lib/libve/vepcr.c =================================================================== --- /dev/null +++ lib/libve/vepcr.c @@ -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 +__FBSDID("$FreeBSD$"); + +#include "libve-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. + */ + +static const br_hash_class *pcr_md = NULL; +static br_hash_compat_context pcr_ctx; +static size_t pcr_hlen = 0; + +/** + * @brief initialize pcr context + */ +void +ve_pcr_init(size_t hlen) +{ + if (hlen <= br_sha1_SIZE) { + pcr_hlen = br_sha1_SIZE; + pcr_md = &br_sha1_vtable; + } else { + pcr_hlen = br_sha256_SIZE; + pcr_md = &br_sha256_vtable; + } + pcr_md->init(&pcr_ctx.vtable); +} + +/** + * @brief update pcr context + */ +void +ve_pcr_update(unsigned char *data, size_t dlen) +{ + if (pcr_md) + pcr_md->update(&pcr_ctx.vtable, data, dlen); +} + +/** + * @brief get pcr result + */ +ssize_t +ve_pcr_get(unsigned char *buf, size_t sz) +{ + if (!pcr_md) + return (-1); + if (sz < pcr_hlen) + return (-1); + pcr_md->out(&pcr_ctx.vtable, buf); + return (pcr_hlen); +} + Index: lib/libve/verify.c =================================================================== --- /dev/null +++ lib/libve/verify.c @@ -0,0 +1,430 @@ +/*- + * 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. + */ +/* + * Routines to verify files loaded. + */ +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include "libve.h" +#include "verify.h" + +#define VE_NOT_CHECKED -42 + +/* + * We sometimes need to know if input is verified or not. + * The extra slot is for tracking most recently opened. + */ +static int ve_status[SOPEN_MAX+1]; +static int ve_status_state; + +#define VE_STATUS_NONE 1 +#define VE_STATUS_VALID 2 + +/** + * @brief set ve status for fd + */ +static void +ve_status_set(int fd, int ves) +{ + if (fd >= 0 && fd < SOPEN_MAX) { + ve_status[fd] = ves; + ve_status_state = VE_STATUS_VALID; + } + ve_status[SOPEN_MAX] = ves; +} + +/** + * @brief get ve status of fd + * + * What we return depends on ve_status_state. + * + * @return + * @li ve_status[fd] if ve_status_state is valid + * @li ve_status[SOPEN_MAX] if ve_status_state is none + * @li VE_NOT_CHECKED if ve_status_state uninitialized + */ +int +ve_status_get(int fd) +{ + if (!ve_status_state) { + return (VE_NOT_CHECKED); + } + if (ve_status_state == VE_STATUS_VALID && + fd >= 0 && fd < SOPEN_MAX) + return (ve_status[fd]); + return (ve_status[SOPEN_MAX]); /* most recent */ +} + +/** + * @brief track verify status + * + * occasionally loader will make multiple calls + * for the same file, we need only check it once. + */ +struct verify_status; +struct verify_status { + dev_t vs_dev; + ino_t vs_ino; + int vs_status; + struct verify_status *vs_next; +}; + +struct verify_status *verified_files = NULL; + +static int +is_verified(struct stat *stp) +{ + struct verify_status *vsp; + + for (vsp = verified_files; vsp != NULL; vsp = vsp->vs_next) { + if (stp->st_dev == vsp->vs_dev && + stp->st_ino == vsp->vs_ino) + return (vsp->vs_status); + } + return (VE_NOT_CHECKED); +} + +/* most recent first, since most likely to see repeated calls. */ +static void +add_verify_status(struct stat *stp, int status) +{ + struct verify_status *vsp; + + vsp = malloc(sizeof(struct verify_status)); + vsp->vs_next = verified_files; + vsp->vs_dev = stp->st_dev; + vsp->vs_ino = stp->st_ino; + vsp->vs_status = status; + verified_files = vsp; +} + +static int loaded_manifests = 0; /* have we loaded anything? */ + +int +load_manifest(const char *name, const char *prefix, + const char *skip, struct stat *stp) +{ + struct stat st; + char *cp, *mname; + size_t n; + int rc; + + cp = NULL; + rc = VE_FINGERPRINT_NONE; + n = strlen(name); + if (n > 4 && ( +#ifdef MANIFEST_ASC + strncmp(&name[n - 3], "asc", 3) == 0 || +#endif + strncmp(&name[n - 3], "sig", 3) == 0)) { + /* + * Since stat() does not provide a meaningful st_dev + * we have to use st_size of the actual manifest. + */ + mname = strdup(name); + for (cp = &mname[n - 3]; cp > mname; cp--) { + if (*cp == '.') { + *cp = '\0'; + break; + } + } + stat(mname, &st); + rc = is_verified(&st); + if (rc != VE_NOT_CHECKED) { + free(mname); + return (rc); + } + stp = &st; + ve_utc_set(st.st_mtime); +#ifdef MANIFEST_ASC + if (strncmp(&name[n - 3], "asc", 3) == 0) + cp = (char *)verify_asc(name, 1); + else +#endif + cp = (char *)verify_sig(name, 1); + if (!cp) { + rc = VE_FINGERPRINT_WRONG; + add_verify_status(stp, rc); /* remember */ + } + } + if (cp) { + if (!stp) { + if (stat(name, &st) < 0) { + return (-1); + } + stp = &st; + } + fingerprint_info_add(name, prefix, skip, cp, stp); + add_verify_status(stp, VE_VERIFIED); + loaded_manifests = 1; /* we are verifying! */ + DEBUG_PRINTF(3, ("loaded: %s %s %s\n", name, prefix, skip)); + if (mname != name) + free(mname); + rc = 0; + } + return (rc); +} + +#ifndef MANIFEST_SIG +# define MANIFEST_SIG "manifest.sig" +#endif + +static int +find_manifest(const char *name) +{ + struct stat st; + char buf[MAXPATHLEN]; + char *prefix; + char *skip; + char *mnames[] = { + MANIFEST_SIG, + "../" MANIFEST_SIG, +#ifdef MANIFEST_ASC + MANIFEST_ASC, + "../" MANIFEST_ASC, +#endif + NULL, + }; + 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 = mnames; *tp; tp++) { + snprintf(buf, sizeof(buf), "%s/%s", prefix, *tp); + DEBUG_PRINTF(5, ("looking for %s\n", buf)); + if (stat(buf, &st) == 0) { +#ifdef MANIFEST_SKIP_ALWAYS + skip = MANIFEST_SKIP_ALWAYS; +#else + if (*tp[0] == '.') { +#ifdef MANIFEST_SKIP + skip = MANIFEST_SKIP; +#else + if ((skip = strrchr(prefix, '/'))) + skip++; +#endif + } else + skip = NULL; +#endif + rc = load_manifest(buf, prefix, skip, &st); + break; + } + } + free(prefix); + return (rc); +} + + +#ifdef LOADER_VERIEXEC_TESTING +# define ACCEPT_NO_FP_DEFAULT VE_MUST + 1 +#else +# define ACCEPT_NO_FP_DEFAULT VE_MUST +#endif +#ifndef VE_VERBOSE_DEFAULT +# define VE_VERBOSE_DEFAULT 0 +#endif + +static int +severity_guess(const char *filename) +{ + const char *cp; + + /* Some files like *.conf and *.hints may be unsigned */ + if ((cp = strrchr(filename, '.'))) { + if (strcmp(cp, ".conf") == 0 || + strcmp(cp, ".cookie") == 0 || + strcmp(cp, ".hints") == 0) + return (VE_TRY); + } + return (VE_WANT); +} + +static void +verify_tweak(char *tweak, int *accept_no_fp, int *verbose, int *verifying) +{ + if (strcmp(tweak, "off") == 0) { + *verifying = 0; + } else if (strcmp(tweak, "strict") == 0) { + /* anything caller wants verified must be */ + *accept_no_fp = VE_WANT; + *verbose = 1; /* warn of anything unverified */ + /* treat self test failure as fatal */ + if (!ve_self_tests()) { + panic("verify self tests failed"); + } + } else if (strcmp(tweak, "modules") == 0) { + /* modules/kernel must be verified */ + *accept_no_fp = VE_MUST; + } else if (strcmp(tweak, "try") == 0) { + /* best effort: always accept no fp */ + *accept_no_fp = VE_MUST + 1; + } else if (strcmp(tweak, "verbose") == 0) { + *verbose = 1; + } else if (strcmp(tweak, "quiet") == 0) { + *verbose = 0; + } +} + +/** + * @brief verify an open file + * + * @param[in] fd + * open descriptor + * + * @param[in] filename + * path we opened and will use to lookup fingerprint + * + * @param[in] off + * current offset in fd, must be restored on return + * + * @param[in] severity + * indicator of how to handle case of missing fingerprint + * + * We look for a signed manifest relative to the filename + * just opened and verify/load it if needed. + * + * We then use verify_fd() in libve to actually verify that hash for + * open file. If it returns < 0 we look at the severity arg to decide + * what to do about it. + * + * If verify_fd() returns VE_FINGERPRINT_NONE we accept it if severity + * is < accept_no_fp. + * + * @return >= 0 on success < 0 on failure + */ +int +verify_file(int fd, const char *filename, off_t off, int severity) +{ + static int verifying = -1; + static int accept_no_fp = ACCEPT_NO_FP_DEFAULT; + static int verbose = VE_VERBOSE_DEFAULT; + struct stat st; + char *cp; + int rc; + + if (verifying < 0) { + verifying = ve_trust_init(); +#ifdef VE_DEBUG_LEVEL + ve_debug_set(VE_DEBUG_LEVEL); +#endif + /* initialize ve_status with default result */ + rc = verifying ? VE_NOT_CHECKED : VE_NOT_VERIFYING; + ve_status_set(0, rc); + ve_status_state = VE_STATUS_NONE; + } + if (!verifying) + return (0); + + if (fd < 0 || fstat(fd, &st) < 0 || !S_ISREG(st.st_mode)) + return (0); + + DEBUG_PRINTF(3, ("fd=%d,name='%s',off=%lld,dev=%lld,ino=%lld\n", + fd, filename, (long long)off, st.st_dev,st.st_ino)); + + + rc = is_verified(&st); + if (rc != VE_NOT_CHECKED) { + ve_status_set(fd, rc); + return (rc); + } + rc = find_manifest(filename); + if (rc != VE_FINGERPRINT_WRONG && loaded_manifests) { + if (severity <= VE_GUESS) + severity = severity_guess(filename); + if ((rc = verify_fd(fd, filename, off, &st)) >= 0) { + if (verbose || severity > VE_WANT) { +#if defined(VE_DEBUG_LEVEL) && VE_DEBUG_LEVEL > 0 + printf("Verified %s %llu,%llu\n", filename, + st.st_dev, st.st_ino); +#else + printf("Verified %s\n", filename); +#endif + } + if (severity < VE_MUST) { /* not a kernel or module */ + + if ((cp = strrchr(filename, '/'))) { + cp++; + if (strncmp(cp, "loader.ve.", 10) == 0) { + cp += 10; + verify_tweak(cp, + &accept_no_fp, &verbose, + &verifying); + } + } + } + add_verify_status(&st, rc); + ve_status_set(fd, rc); + return (rc); + } + + if (severity || verbose) + printf("Unverified: %s\n", ve_error_get()); + if (rc == VE_FINGERPRINT_UNKNOWN && severity < VE_MUST) + rc = VE_UNVERIFIED_OK; + else if (rc == VE_FINGERPRINT_NONE && severity < accept_no_fp) + rc = VE_UNVERIFIED_OK; + + add_verify_status(&st, rc); + } + 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) +{ + char hexbuf[br_sha512_SIZE * 2 + 2]; + unsigned char hbuf[br_sha512_SIZE]; + char *hex; + ssize_t hlen; + + hlen = ve_pcr_get(hbuf, sizeof(hbuf)); + if (hlen > 0) { + hex = hexdigest(hexbuf, sizeof(hexbuf), hbuf, hlen); + if (hex) { + hex[hlen*2] = '\0'; /* clobber newline */ + setenv("loader.ve.pcr", hex, 1); + } + } +} Index: lib/libve/veta.c =================================================================== --- /dev/null +++ lib/libve/veta.c @@ -0,0 +1,111 @@ +/*- + * Copyright (c) 2018, Juniper Networks, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +__FBSDID("$FreeBSD$"); + +/** + * @file veta.c - add to trust anchors + * + */ + +#define NEED_BRSSL_H +#include "libve-priv.h" +#include +#include +#include + +#ifdef VE_OPENPGP_SUPPORT +#include "openpgp/packet.h" +#endif + +/** + * @brief add trust anchors from a file + * + * The file might contain X.509 certs + * or OpenPGP public key + */ +static size_t +trust_file_add(const char *trust) +{ + br_x509_certificate *xcs; + size_t num; + + xcs = read_certificates(trust, &num); + if (xcs) { + num = ve_trust_anchors_add(xcs, num); + } +#ifdef VE_OPENPGP_SUPPORT + else if (load_key_file(trust)) { + num = 1; + } +#endif + return (num); +} + +/** + * @brief add trust anchors from a directory + * + * Pass each file in directory to trust_file_add + */ +static size_t +trust_dir_add(const char *trust) +{ + char fbuf[MAXPATHLEN]; + DIR *dh; + struct dirent *de; + struct stat st; + ssize_t sz; + size_t num; + + if (!(dh = opendir(trust))) + return (0); + for (num = 0, de = readdir(dh); de; de = readdir(dh)) { + if (de->d_name[0] == '.') + continue; + sz = snprintf(fbuf, sizeof(fbuf), "%s/%s", trust, de->d_name); + if (sz >= (ssize_t)sizeof(fbuf)) + continue; + if (stat(fbuf, &st) < 0 || S_ISDIR(st.st_mode)) + continue; + num += trust_file_add(fbuf); + } + closedir(dh); + return (num); +} + +/** + * @brief add trust anchors + */ +int +ve_trust_add(const char *trust) +{ + struct stat st; + + if (stat(trust, &st) < 0) + return (-1); + if (S_ISDIR(st.st_mode)) + return (trust_dir_add(trust)); + return (trust_file_add(trust)); +} Index: lib/libve/vets.c =================================================================== --- /dev/null +++ lib/libve/vets.c @@ -0,0 +1,650 @@ +/*- + * 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. + */ +#include +__FBSDID("$FreeBSD$"); + +/** + * @file vets.c - trust store + * @brief verify signatures + * + * We leverage code from BearSSL www.bearssl.org + */ + +#include +#include +#define NEED_BRSSL_H +#include "libve-priv.h" +#include +#include "ta.h" + +#ifndef TRUST_ANCHOR_STR +# define TRUST_ANCHOR_STR ta_PEM +#endif + +#define SECONDS_PER_DAY 86400 +#define X509_DAYS_TO_UTC0 719528 + +int DebugVe = 0; + +typedef VECTOR(br_x509_certificate) cert_list; + +static anchor_list trust_anchors = VEC_INIT; + +void +ve_debug_set(int n) +{ + DebugVe = n; +} + +static char ebuf[512]; + +char * +ve_error_get(void) +{ + return (ebuf); +} + +int +ve_error_set(const char *fmt, ...) +{ + int rc; + va_list ap; + + va_start(ap, fmt); + ebuf[0] = '\0'; + rc = 0; + if (fmt) { +#ifdef STAND_H + vsprintf(ebuf, fmt, ap); /* no vsnprintf in libstand */ + ebuf[sizeof(ebuf) - 1] = '\0'; + rc = strlen(ebuf); +#else + rc = vsnprintf(ebuf, sizeof(ebuf), fmt, ap); +#endif + } + va_end(ap); + return (rc); +} + +/* this is the time we use for verifying certs */ +static time_t ve_utc = 0; + +/** + * @brief + * set ve_utc used for certificate verification + * + * @param[in] utc + * time - ignored unless greater than current value. + */ +void +ve_utc_set(time_t utc) +{ + if (utc > ve_utc) { + DEBUG_PRINTF(2, ("Set ve_utc=%jd\n", (intmax_t)utc)); + ve_utc = utc; + } +} + +static void +free_cert_contents(br_x509_certificate *xc) +{ + xfree(xc->data); +} + +/** + * @brief + * add certs to our trust store + */ +size_t +ve_trust_anchors_add(br_x509_certificate *xcs, size_t num) +{ + br_x509_trust_anchor ta; + size_t u; + + for (u = 0; u < num; u++) { + if (certificate_to_trust_anchor_inner(&ta, &xcs[u]) < 0) { + break; + } + VEC_ADD(trust_anchors, ta); + } + return (u); +} + +/** + * @brief + * initialize our trust_anchors from ta_PEM + */ +int +ve_trust_init(void) +{ + br_x509_certificate *xcs; + static int once = -1; + size_t num; + + if (once >= 0) + return (once); + once = 0; + + ve_utc_set(time(NULL)); +#ifdef BUILD_UTC + ve_utc_set(BUILD_UTC); /* just in case */ +#endif + ve_error_set(NULL); /* make sure it is empty */ + ve_pcr_init(0); /* default pcr size */ + +#ifdef TRUST_ANCHOR_STR + xcs = parse_certificates((unsigned char *)TRUST_ANCHOR_STR, + sizeof(TRUST_ANCHOR_STR), &num); + if (xcs == NULL) + return (0); + num = ve_trust_anchors_add(xcs, num); + once = (int) num; +#else + num = 0; +#endif + return (num); +} + +/** + * if we can verify the certificate chain in "certs", + * return the public key and if "xcp" is !NULL the associated + * certificate + */ +static br_x509_pkey * +verify_signer_xcs(br_x509_certificate *xcs, + size_t num, + br_name_element *elts, size_t num_elts) +{ + br_x509_minimal_context mc; + br_x509_certificate *xc; + size_t u; + cert_list chain = VEC_INIT; + const br_x509_pkey *tpk; + br_x509_pkey *pk; + unsigned int usages; + int err; + + DEBUG_PRINTF(5, ("verify_signer: %zu certs in chain\n", num)); + VEC_ADDMANY(chain, xcs, num); + if (VEC_LEN(chain) == 0) { + ve_error_set("ERROR: no/invalid certificate chain\n"); + return (NULL); + } + + DEBUG_PRINTF(5, ("verify_signer: %zu trust anchors\n", + VEC_LEN(trust_anchors))); + + br_x509_minimal_init(&mc, &br_sha256_vtable, + &VEC_ELT(trust_anchors, 0), + VEC_LEN(trust_anchors)); +#ifdef VE_ECDSA_SUPPORT + br_x509_minimal_set_ecdsa(&mc, + &br_ec_prime_i31, &br_ecdsa_i31_vrfy_asn1); +#endif +#ifdef VE_RSA_SUPPORT + br_x509_minimal_set_rsa(&mc, &br_rsa_i31_pkcs1_vrfy); +#endif +#if defined(UNIT_TEST) && defined(VE_DEPRECATED_RSA_SHA1_SUPPORT) + /* This is deprecated! do not enable unless you absoultely have to */ + br_x509_minimal_set_hash(&mc, br_sha1_ID, &br_sha1_vtable); +#endif + br_x509_minimal_set_hash(&mc, br_sha256_ID, &br_sha256_vtable); +#ifdef VE_SHA384_SUPPORT + br_x509_minimal_set_hash(&mc, br_sha384_ID, &br_sha384_vtable); +#endif +#ifdef VE_SHA512_SUPPORT + br_x509_minimal_set_hash(&mc, br_sha512_ID, &br_sha512_vtable); +#endif + br_x509_minimal_set_name_elements(&mc, elts, num_elts); + +#ifdef LOADER_VERIEXEC + /* + * Clock is probably bogus so we use ve_utc. + * The conversion is lifted from BearSSL x509_minimal. + */ + mc.days = (ve_utc / SECONDS_PER_DAY) + X509_DAYS_TO_UTC0; + mc.seconds = (ve_utc % SECONDS_PER_DAY); +#endif + + mc.vtable->start_chain(&mc.vtable, NULL); + for (u = 0; u < VEC_LEN(chain); u ++) { + xc = &VEC_ELT(chain, u); + mc.vtable->start_cert(&mc.vtable, xc->data_len); + mc.vtable->append(&mc.vtable, xc->data, xc->data_len); + mc.vtable->end_cert(&mc.vtable); + switch (mc.err) { + case 0: + case BR_ERR_X509_OK: + case BR_ERR_X509_EXPIRED: + break; + default: + printf("u=%zu mc.err=%d\n", u, mc.err); + break; + } + } + err = mc.vtable->end_chain(&mc.vtable); + pk = NULL; + if (err) { + ve_error_set("Validation failed, err = %d", err); + } else { + tpk = mc.vtable->get_pkey(&mc.vtable, &usages); + if (tpk != NULL) { + pk = xpkeydup(tpk); + } + } + VEC_CLEAREXT(chain, &free_cert_contents); + return (pk); +} + +static br_x509_pkey * +verify_signer(const char *certs, + br_name_element *elts, size_t num_elts) +{ + br_x509_certificate *xcs; + br_x509_pkey *pk; + size_t num; + + ve_trust_init(); + ve_self_tests(); + xcs = read_certificates(certs, &num); + if (xcs == NULL) { + ve_error_set("cannot read certificates\n"); + return (NULL); + } + pk = verify_signer_xcs(xcs, num, elts, num_elts); + xfree(xcs); + return (pk); +} + +/** + * we need a hex digest including trailing newline below + */ +char * +hexdigest(char *buf, size_t bufsz, unsigned char *foo, size_t foo_len) +{ + char const hex2ascii[] = "0123456789abcdef"; + size_t i; + + /* every binary byte is 2 chars in hex + newline + null */ + if (bufsz < (2 * foo_len) + 2) + return (NULL); + + for (i = 0; i < foo_len; i++) { + buf[i * 2] = hex2ascii[foo[i] >> 4]; + buf[i * 2 + 1] = hex2ascii[foo[i] & 0x0f]; + } + + buf[i * 2] = 0x0A; /* we also want a newline */ + buf[i * 2 + 1] = '\0'; + + return (buf); +} + +/** + * @brief + * verify file against sigfile using pk + * + * When we generated the signature in sigfile, + * we hashed (sha256) file, and sent that to signing server + * which hashed (sha256) that hash. + * + * To verify we need to replicate that result. + * + * @param[in] pk + * br_x509_pkey + * + * @paramp[in] file + * file to be verified + * + * @param[in] sigfile + * signature (PEM encoded) + * + * @return NULL on error, otherwise content of file. + */ +#ifdef VE_ECDSA_SUPPORT +static unsigned char * +verify_ec(br_x509_pkey *pk, const char *file, const char *sigfile) +{ + char hexbuf[br_sha512_SIZE * 2 + 2]; + unsigned char rhbuf[br_sha512_SIZE]; + char *hex; + br_sha256_context ctx; + unsigned char *fcp, *scp; + size_t flen, slen, plen; + pem_object *po; + const br_ec_impl *ec; + br_ecdsa_vrfy vrfy; + + if ((fcp = read_file(file, &flen)) == NULL) + return (NULL); + if ((scp = read_file(sigfile, &slen)) == NULL) { + free(fcp); + return (NULL); + } + if ((po = decode_pem(scp, slen, &plen)) == NULL) { + free(fcp); + free(scp); + return (NULL); + } + br_sha256_init(&ctx); + br_sha256_update(&ctx, fcp, flen); + br_sha256_out(&ctx, rhbuf); + hex = hexdigest(hexbuf, sizeof(hexbuf), rhbuf, br_sha256_SIZE); + /* now hash that */ + if (hex) { + br_sha256_init(&ctx); + br_sha256_update(&ctx, hex, strlen(hex)); + br_sha256_out(&ctx, rhbuf); + } + ec = br_ec_get_default(); + vrfy = br_ecdsa_vrfy_asn1_get_default(); + if (!vrfy(ec, rhbuf, br_sha256_SIZE, &pk->key.ec, po->data, + po->data_len)) { + free(fcp); + fcp = NULL; + } + free(scp); + return (fcp); +} +#endif + +#if defined(VE_RSA_SUPPORT) || defined(VE_OPENPGP_SUPPORT) +/** + * @brief verify an rsa digest + * + * @return 0 on failure + */ +int +verify_rsa_digest (br_rsa_public_key *pkey, + const unsigned char *hash_oid, + unsigned char *mdata, size_t mlen, + unsigned char *sdata, size_t slen) +{ + br_rsa_pkcs1_vrfy vrfy; + unsigned char vhbuf[br_sha512_SIZE]; + + vrfy = br_rsa_pkcs1_vrfy_get_default(); + + if (!vrfy(sdata, slen, hash_oid, mlen, pkey, vhbuf) || + memcmp(vhbuf, mdata, mlen) != 0) { + return (0); /* fail */ + } + return (1); /* ok */ +} +#endif + +/** + * @brief + * verify file against sigfile using pk + * + * When we generated the signature in sigfile, + * we hashed (sha256) file, and sent that to signing server + * which hashed (sha256) that hash. + * + * Or (deprecated) we simply used sha1 hash directly. + * + * To verify we need to replicate that result. + * + * @param[in] pk + * br_x509_pkey + * + * @paramp[in] file + * file to be verified + * + * @param[in] sigfile + * signature (PEM encoded) + * + * @return NULL on error, otherwise content of file. + */ +#ifdef VE_RSA_SUPPORT +static unsigned char * +verify_rsa(br_x509_pkey *pk, const char *file, const char *sigfile) +{ + unsigned char rhbuf[br_sha512_SIZE]; + unsigned char vhbuf[br_sha512_SIZE]; + const unsigned char *hash_oid; + const br_hash_class *md; + br_hash_compat_context mctx; + unsigned char *fcp, *scp; + size_t flen, slen, plen, hlen; + pem_object *po; + + if ((fcp = read_file(file, &flen)) == NULL) + return (NULL); + if ((scp = read_file(sigfile, &slen)) == NULL) { + free(fcp); + return (NULL); + } + if ((po = decode_pem(scp, slen, &plen)) == NULL) { + free(fcp); + free(scp); + return (NULL); + } + + switch (po->data_len) { +#if defined(UNIT_TEST) && defined(VE_DEPRECATED_RSA_SHA1_SUPPORT) + case 256: + // this is our old deprecated sig method + md = &br_sha1_vtable; + hlen = br_sha1_SIZE; + hash_oid = BR_HASH_OID_SHA1; + break; +#endif + default: + md = &br_sha256_vtable; + hlen = br_sha256_SIZE; + hash_oid = BR_HASH_OID_SHA256; + break; + } + md->init(&mctx.vtable); + md->update(&mctx.vtable, fcp, flen); + md->out(&mctx.vtable, rhbuf); + if (!verify_rsa_digest(&pk->key.rsa, hash_oid, + rhbuf, hlen, po->data, po->data_len)) { + free(fcp); + fcp = NULL; + } + free(scp); + return (fcp); +} +#endif + +/** + * @brief + * verify a signature and return content of signed file + * + * @param[in] sigfile + * file containing signature + * we derrive path of signed file and certificate change from + * this. + * + * @param[in] flags + * only bit 1 significant so far + * + * @return NULL on error otherwise content of signed file + */ +unsigned char * +verify_sig(const char *sigfile, int flags) +{ + br_x509_pkey *pk; + br_name_element cn; + char cn_buf[80]; + unsigned char cn_oid[4]; + char pbuf[MAXPATHLEN]; + char *cp; + unsigned char *ucp; + size_t n; + + DEBUG_PRINTF(5, ("verify_sig: %s\n", sigfile)); + n = strlcpy(pbuf, sigfile, sizeof(pbuf)); + if (n > (sizeof(pbuf) - 5) || strcmp(&sigfile[n - 3], "sig") != 0) + return (NULL); + cp = strcpy(&pbuf[n - 3], "certs"); + /* + * We want the commonName field + * the OID we want is 2,5,4,3 - but DER encoded + */ + cn_oid[0] = 3; + cn_oid[1] = 0x55; + cn_oid[2] = 4; + cn_oid[3] = 3; + cn.oid = cn_oid; + cn.buf = cn_buf; + cn.len = sizeof(cn_buf); + + pk = verify_signer(pbuf, &cn, 1); + if (!pk) { + printf("cannot verify: %s: %s\n", pbuf, ve_error_get()); + return (NULL); + } + for (; cp > pbuf; cp--) { + if (*cp == '.') { + *cp = '\0'; + break; + } + } + switch (pk->key_type) { +#ifdef VE_ECDSA_SUPPORT + case BR_KEYTYPE_EC: + ucp = verify_ec(pk, pbuf, sigfile); + break; +#endif +#ifdef VE_RSA_SUPPORT + case BR_KEYTYPE_RSA: + ucp = verify_rsa(pk, pbuf, sigfile); + break; +#endif + default: + ucp = NULL; /* not supported */ + } + xfreepkey(pk); + if (!ucp) { + printf("Unverified %s (%s)\n", pbuf, + cn.status ? cn_buf : "unknown"); + } else if ((flags & 1) != 0) { + printf("Verified %s signed by %s\n", pbuf, + cn.status ? cn_buf : "someone we trust"); + } + return (ucp); +} + +#ifdef VERIFY_CERTS_STR +static int +test_hash(const br_hash_class *md, size_t hlen, + const char *hname, + const char *s, const char *want) +{ + br_hash_compat_context mctx; + + md->init(&mctx.vtable); + md->update(&mctx.vtable, s, strlen(s)); + 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, \ + VERIFY_CERTS_STR, vc_ ## 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 VERIFY_CERTS_STR + xcs = parse_certificates((unsigned char *)VERIFY_CERTS_STR, + sizeof(VERIFY_CERTS_STR), &num); + if (xcs == NULL) + return (0); +#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 + /* + * We want the commonName field + * the OID we want is 2,5,4,3 - but DER encoded + */ + cn_oid[0] = 3; + cn_oid[1] = 0x55; + cn_oid[2] = 4; + cn_oid[3] = 3; + cn.oid = cn_oid; + cn.buf = cn_buf; + + for (u = 0; u < num; u ++) { + cn.len = sizeof(cn_buf); + if ((pk = verify_signer_xcs(&xcs[u], 1, &cn, 1)) != NULL) { + once++; + printf("Testing verify certificate: %s\tPassed\n", + cn.status ? cn_buf : ""); + xfreepkey(pk); + } + } + if (!once) + printf("Testing verify certificate:\t\t\tFailed\n"); + xfree(xcs); +#else + printf("No X.509 self tests\n"); +#endif /* VERIFY_CERTS_STR */ +#ifdef VE_OPENPGP_SUPPORT + if (!openpgp_self_tests()) + once++; +#endif + return (once); +} Index: share/mk/src.libnames.mk =================================================================== --- share/mk/src.libnames.mk +++ share/mk/src.libnames.mk @@ -208,6 +208,16 @@ osmvendor .endif +.if ${MK_BEARSSL} == "yes" +_INTERNALLIBS+= \ + bearssl \ + ve \ + +LIBBEARSSL?= ${DESTDIR}${LIBDIR}/libbearssl.a +LIBVE?= ${DESTDIR}${LIBDIR}/libve.a +.endif + + # Each library's LIBADD needs to be duplicated here for static linkage of # 2nd+ order consumers. Auto-generating this would be better. _DP_80211= sbuf bsdxml Index: share/mk/src.opts.mk =================================================================== --- share/mk/src.opts.mk +++ share/mk/src.opts.mk @@ -187,6 +187,7 @@ ZONEINFO __DEFAULT_NO_OPTIONS = \ + BEARSSL \ BSD_GREP \ CLANG_EXTRAS \ DTRACE_TESTS \ @@ -212,6 +213,8 @@ __DEFAULT_DEPENDENT_OPTIONS= \ CLANG_FULL/CLANG \ LLVM_TARGET_ALL/CLANG \ + LOADER_VERIEXEC/BEARSSL \ + VERIEXEC/BEARSSL \ # MK_*_SUPPORT options which default to "yes" unless their corresponding # MK_* variable is set to "no". Index: stand/common/boot.c =================================================================== --- stand/common/boot.c +++ stand/common/boot.c @@ -106,6 +106,10 @@ if (archsw.arch_autoload() != 0) return(CMD_ERROR); +#ifdef LOADER_VERIEXEC + verify_pcr_export(); /* for measured boot */ +#endif + /* Call the exec handler from the loader matching the kernel */ file_formats[fp->f_loader]->l_exec(fp); return(CMD_ERROR); Index: stand/common/bootstrap.h =================================================================== --- stand/common/bootstrap.h +++ stand/common/bootstrap.h @@ -324,6 +324,9 @@ /* Probe ZFS pool(s), if needed. */ void (*arch_zfs_probe)(void); + /* Return the hypervisor name/type or NULL if not virtualized. */ + const char *(*arch_hypervisor)(void); + /* For kexec-type loaders, get ksegment structure */ void (*arch_kexec_kseg_get)(int *nseg, void **kseg); }; @@ -340,4 +343,8 @@ #define CTASSERT(x) _Static_assert(x, "compile-time assertion failed") #endif +#ifdef LOADER_VERIEXEC +#include +#endif + #endif /* !_BOOTSTRAP_H_ */ Index: stand/common/interp_forth.c =================================================================== --- stand/common/interp_forth.c +++ stand/common/interp_forth.c @@ -377,6 +377,13 @@ return(CMD_ERROR); } +#ifdef LOADER_VERIEXEC + if (verify_file(fd, filename, 0, VE_GUESS) < 0) { + close(fd); + sprintf(command_errbuf,"can't verify '%s'", filename); + return(CMD_ERROR); + } +#endif /* * Read the script into memory. */ Index: stand/common/interp_simple.c =================================================================== --- stand/common/interp_simple.c +++ stand/common/interp_simple.c @@ -94,6 +94,14 @@ return(CMD_ERROR); } +#ifdef LOADER_VERIEXEC + if (verify_file(fd, filename, 0, VE_GUESS) < 0) { + close(fd); + sprintf(command_errbuf,"can't verify '%s'", filename); + return(CMD_ERROR); + } +#endif + /* * Read the script into memory. */ Index: stand/common/load_elf.c =================================================================== --- stand/common/load_elf.c +++ stand/common/load_elf.c @@ -245,6 +245,12 @@ goto error; } +#ifdef LOADER_VERIEXEC + if (verify_file(ef->fd, filename, bytes_read, VE_MUST) < 0) { + err = EAUTH; + goto error; + } +#endif return (0); error: Index: stand/common/load_elf_obj.c =================================================================== --- stand/common/load_elf_obj.c +++ stand/common/load_elf_obj.c @@ -129,6 +129,13 @@ goto oerr; } +#ifdef LOADER_VERIEXEC + if (verify_file(ef.fd, filename, bytes_read, VE_MUST) < 0) { + err = EAUTH; + goto oerr; + } +#endif + kfp = file_findfile(NULL, __elfN(obj_kerneltype)); if (kfp == NULL) { printf("elf" __XSTRING(__ELF_WORD_SIZE) Index: stand/common/module.c =================================================================== --- stand/common/module.c +++ stand/common/module.c @@ -104,6 +104,8 @@ { struct preloaded_file *fp; char *typestr; + char *prefix; + char *skip; int dofile, dokld, ch, error; dokld = dofile = 0; @@ -114,11 +116,18 @@ command_errmsg = "no filename specified"; return (CMD_CRIT); } - while ((ch = getopt(argc, argv, "kt:")) != -1) { + prefix = skip = NULL; + while ((ch = getopt(argc, argv, "kp:s:t:")) != -1) { switch(ch) { case 'k': dokld = 1; break; + case 'p': + prefix = optarg; + break; + case 's': + skip = optarg; + break; case 't': typestr = optarg; dofile = 1; @@ -141,6 +150,12 @@ return (CMD_CRIT); } +#ifdef LOADER_VERIEXEC + if (strncmp(typestr, "manifest", 8) == 0) { + return (load_manifest(argv[1], prefix, skip, NULL)); + } +#endif + fp = file_findfile(argv[1], typestr); if (fp) { snprintf(command_errbuf, sizeof(command_errbuf), @@ -435,6 +450,15 @@ return(NULL); } +#ifdef LOADER_VERIEXEC + if (verify_file(fd, name, 0, VE_MUST) < 0) { + sprintf(command_errbuf, "can't verify '%s'", name); + free(name); + close(fd); + return(NULL); + } +#endif + if (archsw.arch_loadaddr != NULL) loadaddr = archsw.arch_loadaddr(LOAD_RAW, name, loadaddr); Index: stand/defs.mk =================================================================== --- stand/defs.mk +++ stand/defs.mk @@ -151,6 +151,9 @@ # Make sure we use the machine link we're about to create CFLAGS+=-I. +# size matters! +CFLAGS+= -O1 + all: ${PROG} .if !defined(NO_OBJ) Index: stand/ficl/Makefile.depend =================================================================== --- stand/ficl/Makefile.depend +++ stand/ficl/Makefile.depend @@ -2,9 +2,7 @@ # Autogenerated - do NOT edit! DIRDEPS = \ - include \ - include/xlocale \ - lib/msun \ + stand/libsa \ .include Index: stand/ficl/ficl.h =================================================================== --- stand/ficl/ficl.h +++ stand/ficl/ficl.h @@ -1157,6 +1157,10 @@ DATA_SET(Xficl_compile_set, func) SET_DECLARE(Xficl_compile_set, ficlCompileFcn); +#ifdef LOADER_VERIEXEC +#include +#endif + #ifdef __cplusplus } #endif Index: stand/ficl/fileaccess.c =================================================================== --- stand/ficl/fileaccess.c +++ stand/ficl/fileaccess.c @@ -67,14 +67,21 @@ if (f == NULL) stackPushPtr(pVM->pStack, NULL); else +#ifdef LOADER_VERIEXEC + if (*mode == 'r' && + verify_file(fileno(f), filename, 0, VE_GUESS) < 0) { + fclose(f); + stackPushPtr(pVM->pStack, NULL); + } else +#endif { - ficlFILE *ff = (ficlFILE *)malloc(sizeof(ficlFILE)); - strcpy(ff->filename, filename); - ff->f = f; - stackPushPtr(pVM->pStack, ff); + ficlFILE *ff = (ficlFILE *)malloc(sizeof(ficlFILE)); + strcpy(ff->filename, filename); + ff->f = f; + stackPushPtr(pVM->pStack, ff); - fseek(f, 0, SEEK_SET); - } + fseek(f, 0, SEEK_SET); + } pushIor(pVM, f != NULL); } Index: stand/ficl32/Makefile.depend =================================================================== --- stand/ficl32/Makefile.depend +++ stand/ficl32/Makefile.depend @@ -2,9 +2,7 @@ # Autogenerated - do NOT edit! DIRDEPS = \ - include \ - include/xlocale \ - lib/msun \ + stand/libsa \ .include Index: stand/i386/loader/Makefile =================================================================== --- stand/i386/loader/Makefile +++ stand/i386/loader/Makefile @@ -1,5 +1,5 @@ # $FreeBSD$ - +.if 0 HAVE_GELI= yes LOADER_NET_SUPPORT?= yes @@ -11,6 +11,10 @@ LOADER_UFS_SUPPORT?= yes LOADER_GZIP_SUPPORT?= yes LOADER_BZIP2_SUPPORT?= yes +.else +LOADER_NET_SUPPORT?= yes +LOADER_UFS_SUPPORT?= yes +.endif .include Index: stand/i386/loader/Makefile.depend =================================================================== --- stand/i386/loader/Makefile.depend +++ stand/i386/loader/Makefile.depend @@ -2,15 +2,12 @@ # Autogenerated - do NOT edit! DIRDEPS = \ - include \ - include/xlocale \ - stand/ficl32 \ - stand/geli \ - stand/i386/btx/btx \ - stand/i386/btx/btxldr \ - stand/i386/btx/lib \ - stand/i386/libi386 \ - stand/libsa32 \ + stand/${MACHINE_CPUARCH}/btx/btx \ + stand/${MACHINE_CPUARCH}/btx/btxldr \ + stand/${MACHINE_CPUARCH}/btx/lib \ + stand/${MACHINE_CPUARCH}/libi386 \ + stand/ficl \ + stand/libsa \ .include Index: stand/i386/loader/main.c =================================================================== --- stand/i386/loader/main.c +++ stand/i386/loader/main.c @@ -128,6 +128,7 @@ * We can use printf() etc. once this is done. * If the previous boot stage has requested a serial console, prefer that. */ + initial_howto |= RB_SERIAL; /* JUNOS */ bi_setboothowto(initial_howto); if (initial_howto & RB_MULTIPLE) { if (initial_howto & RB_SERIAL) @@ -166,6 +167,7 @@ archsw.arch_readin = i386_readin; archsw.arch_isainb = isa_inb; archsw.arch_isaoutb = isa_outb; + archsw.arch_hypervisor = i386_hypervisor; #ifdef LOADER_ZFS_SUPPORT archsw.arch_zfs_probe = i386_zfs_probe; Index: stand/libsa/Makefile =================================================================== --- stand/libsa/Makefile +++ stand/libsa/Makefile @@ -155,4 +155,9 @@ .include "${SASRC}/geli/Makefile.inc" .endif +.if ${MK_LOADER_VERIEXEC} == "yes" && ${MK_BEARSSL} == "yes" +.include "${SRCTOP}/lib/libbearssl/Makefile.libsa.inc" +.include "${SRCTOP}/lib/libve/Makefile.libsa.inc" +.endif + .include Index: stand/libsa/Makefile.depend =================================================================== --- stand/libsa/Makefile.depend +++ stand/libsa/Makefile.depend @@ -2,10 +2,6 @@ # Autogenerated - do NOT edit! DIRDEPS = \ - include \ - include/arpa \ - include/xlocale \ - lib/libbz2 \ .include Index: stand/libsa32/Makefile.depend =================================================================== --- stand/libsa32/Makefile.depend +++ stand/libsa32/Makefile.depend @@ -2,10 +2,7 @@ # Autogenerated - do NOT edit! DIRDEPS = \ - include \ - include/arpa \ - include/xlocale \ - lib/libbz2 \ + stand/libsa \ .include Index: stand/loader.mk =================================================================== --- stand/loader.mk +++ stand/loader.mk @@ -57,6 +57,10 @@ SRCS+= pnp.c .endif +.if ${MK_LOADER_VERIEXEC} != "no" +CFLAGS+= -DLOADER_VERIEXEC -I${SRCTOP}/lib/libve/h +.endif + # Forth interpreter .if ${MK_LOADER_LUA} != "no" SRCS+= interp_lua.c Index: tools/build/options/WITH_BEARSSL =================================================================== --- /dev/null +++ tools/build/options/WITH_BEARSSL @@ -0,0 +1,14 @@ +.\" $FreeBSD$ +BearSSL is a tiny SSL library suitable for embedded environments. +For details see +http://www.BearSSL.org/ +.Pp +This library is not yet part of FreeBSD, but +libbearssl can be built by setting +.Va BEARSSL +to point to the top of the BearSSL checkout. +.Pp +This library is currently only used to perform +signature verification and related operations +for Verified Exec and +.Xr loader 8 . Index: tools/build/options/WITH_LOADER_VERIEXEC =================================================================== --- /dev/null +++ tools/build/options/WITH_LOADER_VERIEXEC @@ -0,0 +1,7 @@ +.\" $FreeBSD$ +This option enables building +.Xr loader 8 +with support for verifcation similar to Verified Exec. +.Pp +It depends on +.Va WITH_BEARSSL Index: tools/build/options/WITH_VERIEXEC =================================================================== --- /dev/null +++ tools/build/options/WITH_VERIEXEC @@ -0,0 +1,9 @@ +.\" $FreeBSD$ +This option enables building +.Xr veriexec 8 +which loads the contents of verified manifests into the kernel +for use by +.Xr mac_veriexec 4 +.Pp +It depends on +.Va WITH_BEARSSL