59b95f1bfb
We need a valid st_dev, st_ino and st_mtime to correctly track which files have been verified and to update our notion of time. ve_utc_set(): ignore utc if it would jump our current time by more than VE_UTC_MAX_JUMP (20 years). Allow testing of install command via userboot. Need to fix its stat implementation too. bhyveload also needs stat fixed - due to change to userboot.h Call ve_error_get() from vectx_close() when hash is wrong. Track the names of files we have hashed into pcr For the purposes of measured boot, it is important to be able to reproduce the hash reflected in loader.ve.pcr so loader.ve.hashed provides a list of names in the order they were added. Reviewed by: imp MFC after: 1 week Sponsored by: Juniper Networks Differential Revision: https://reviews.freebsd.org//D24027
1024 lines
22 KiB
C
1024 lines
22 KiB
C
/*-
|
|
* Copyright (c) 2017-2018, Juniper Networks, Inc.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
/**
|
|
* @file vets.c - trust store
|
|
* @brief verify signatures
|
|
*
|
|
* We leverage code from BearSSL www.bearssl.org
|
|
*/
|
|
|
|
#include <sys/time.h>
|
|
#include <stdarg.h>
|
|
#define NEED_BRSSL_H
|
|
#include "libsecureboot-priv.h"
|
|
#include <brssl.h>
|
|
#include <ta.h>
|
|
|
|
#ifndef TRUST_ANCHOR_STR
|
|
# define TRUST_ANCHOR_STR ta_PEM
|
|
#endif
|
|
|
|
#define SECONDS_PER_DAY 86400
|
|
#define SECONDS_PER_YEAR 365 * SECONDS_PER_DAY
|
|
#ifndef VE_UTC_MAX_JUMP
|
|
# define VE_UTC_MAX_JUMP 20 * SECONDS_PER_YEAR
|
|
#endif
|
|
#define X509_DAYS_TO_UTC0 719528
|
|
|
|
int DebugVe = 0;
|
|
|
|
typedef VECTOR(br_x509_certificate) cert_list;
|
|
typedef VECTOR(hash_data) digest_list;
|
|
|
|
static anchor_list trust_anchors = VEC_INIT;
|
|
static anchor_list forbidden_anchors = VEC_INIT;
|
|
static digest_list forbidden_digests = VEC_INIT;
|
|
|
|
static int anchor_verbose = 0;
|
|
|
|
void
|
|
ve_anchor_verbose_set(int n)
|
|
{
|
|
anchor_verbose = n;
|
|
}
|
|
|
|
int
|
|
ve_anchor_verbose_get(void)
|
|
{
|
|
return (anchor_verbose);
|
|
}
|
|
|
|
void
|
|
ve_debug_set(int n)
|
|
{
|
|
DebugVe = n;
|
|
}
|
|
|
|
static char ebuf[512];
|
|
|
|
char *
|
|
ve_error_get(void)
|
|
{
|
|
return (ebuf);
|
|
}
|
|
|
|
int
|
|
ve_error_set(const char *fmt, ...)
|
|
{
|
|
int rc;
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
ebuf[0] = '\0';
|
|
rc = 0;
|
|
if (fmt) {
|
|
#ifdef STAND_H
|
|
vsprintf(ebuf, fmt, ap); /* no vsnprintf in libstand */
|
|
ebuf[sizeof(ebuf) - 1] = '\0';
|
|
rc = strlen(ebuf);
|
|
#else
|
|
rc = vsnprintf(ebuf, sizeof(ebuf), fmt, ap);
|
|
#endif
|
|
}
|
|
va_end(ap);
|
|
return (rc);
|
|
}
|
|
|
|
/* 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
|
|
* and not a leap of 20 years or more.
|
|
*/
|
|
void
|
|
ve_utc_set(time_t utc)
|
|
{
|
|
if (utc > ve_utc &&
|
|
(ve_utc == 0 || (utc - ve_utc) < VE_UTC_MAX_JUMP)) {
|
|
DEBUG_PRINTF(2, ("Set ve_utc=%jd\n", (intmax_t)utc));
|
|
ve_utc = utc;
|
|
}
|
|
}
|
|
|
|
static void
|
|
free_cert_contents(br_x509_certificate *xc)
|
|
{
|
|
xfree(xc->data);
|
|
}
|
|
|
|
/*
|
|
* a bit of a dance to get commonName from a certificate
|
|
*/
|
|
static char *
|
|
x509_cn_get(br_x509_certificate *xc, char *buf, size_t len)
|
|
{
|
|
br_x509_minimal_context mc;
|
|
br_name_element cn;
|
|
unsigned char cn_oid[4];
|
|
int err;
|
|
|
|
if (buf == NULL)
|
|
return (buf);
|
|
/*
|
|
* We want the commonName field
|
|
* the OID we want is 2,5,4,3 - but DER encoded
|
|
*/
|
|
cn_oid[0] = 3;
|
|
cn_oid[1] = 0x55;
|
|
cn_oid[2] = 4;
|
|
cn_oid[3] = 3;
|
|
cn.oid = cn_oid;
|
|
cn.buf = buf;
|
|
cn.len = len;
|
|
cn.buf[0] = '\0';
|
|
|
|
br_x509_minimal_init(&mc, &br_sha256_vtable, NULL, 0);
|
|
br_x509_minimal_set_name_elements(&mc, &cn, 1);
|
|
/* the below actually does the work - updates cn.status */
|
|
mc.vtable->start_chain(&mc.vtable, NULL);
|
|
mc.vtable->start_cert(&mc.vtable, xc->data_len);
|
|
mc.vtable->append(&mc.vtable, xc->data, xc->data_len);
|
|
mc.vtable->end_cert(&mc.vtable);
|
|
/* we don' actually care about cert status - just its name */
|
|
err = mc.vtable->end_chain(&mc.vtable);
|
|
|
|
if (!cn.status)
|
|
buf = NULL;
|
|
return (buf);
|
|
}
|
|
|
|
/* ASN parsing related defines */
|
|
#define ASN1_PRIMITIVE_TAG 0x1F
|
|
#define ASN1_INF_LENGTH 0x80
|
|
#define ASN1_LENGTH_MASK 0x7F
|
|
|
|
/*
|
|
* Get TBS part of certificate.
|
|
* Since BearSSL doesn't provide any API to do this,
|
|
* it has to be implemented here.
|
|
*/
|
|
static void*
|
|
X509_to_tbs(unsigned char* cert, size_t* output_size)
|
|
{
|
|
unsigned char *result;
|
|
size_t tbs_size;
|
|
int size, i;
|
|
|
|
if (cert == NULL)
|
|
return (NULL);
|
|
|
|
/* Strip two sequences to get to the TBS section */
|
|
for (i = 0; i < 2; i++) {
|
|
/*
|
|
* XXX: We don't need to support extended tags since
|
|
* they should not be present in certificates.
|
|
*/
|
|
if ((*cert & ASN1_PRIMITIVE_TAG) == ASN1_PRIMITIVE_TAG)
|
|
return (NULL);
|
|
|
|
cert++;
|
|
|
|
if (*cert == ASN1_INF_LENGTH)
|
|
return (NULL);
|
|
|
|
size = *cert & ASN1_LENGTH_MASK;
|
|
tbs_size = 0;
|
|
|
|
/* Size can either be stored on a single or multiple bytes */
|
|
if (*cert & (ASN1_LENGTH_MASK + 1)) {
|
|
cert++;
|
|
while (*cert == 0 && size > 0) {
|
|
cert++;
|
|
size--;
|
|
}
|
|
while (size-- > 0) {
|
|
tbs_size <<= 8;
|
|
tbs_size |= *(cert++);
|
|
}
|
|
}
|
|
if (i == 0)
|
|
result = cert;
|
|
}
|
|
tbs_size += (cert - result);
|
|
|
|
if (output_size != NULL)
|
|
*output_size = tbs_size;
|
|
|
|
return (result);
|
|
}
|
|
|
|
void
|
|
ve_forbidden_digest_add(hash_data *digest, size_t num)
|
|
{
|
|
while (num--)
|
|
VEC_ADD(forbidden_digests, digest[num]);
|
|
}
|
|
|
|
static size_t
|
|
ve_anchors_add(br_x509_certificate *xcs, size_t num, anchor_list *anchors,
|
|
const char *anchors_name)
|
|
{
|
|
br_x509_trust_anchor ta;
|
|
size_t u;
|
|
|
|
for (u = 0; u < num; u++) {
|
|
if (certificate_to_trust_anchor_inner(&ta, &xcs[u]) < 0) {
|
|
break;
|
|
}
|
|
VEC_ADD(*anchors, ta);
|
|
if (anchor_verbose && anchors_name) {
|
|
char buf[64];
|
|
char *cp;
|
|
|
|
cp = x509_cn_get(&xcs[u], buf, sizeof(buf));
|
|
if (cp) {
|
|
printf("x509_anchor(%s) %s\n", cp, anchors_name);
|
|
}
|
|
}
|
|
}
|
|
return (u);
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* add certs to our trust store
|
|
*/
|
|
size_t
|
|
ve_trust_anchors_add(br_x509_certificate *xcs, size_t num)
|
|
{
|
|
return (ve_anchors_add(xcs, num, &trust_anchors, "trusted"));
|
|
}
|
|
|
|
size_t
|
|
ve_forbidden_anchors_add(br_x509_certificate *xcs, size_t num)
|
|
{
|
|
return (ve_anchors_add(xcs, num, &forbidden_anchors, "forbidden"));
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief add trust anchors in buf
|
|
*
|
|
* Assume buf contains x509 certificates, but if not and
|
|
* we support OpenPGP try adding as that.
|
|
*
|
|
* @return number of anchors added
|
|
*/
|
|
size_t
|
|
ve_trust_anchors_add_buf(unsigned char *buf, size_t len)
|
|
{
|
|
br_x509_certificate *xcs;
|
|
size_t num;
|
|
|
|
num = 0;
|
|
xcs = parse_certificates(buf, len, &num);
|
|
if (xcs != NULL) {
|
|
num = ve_trust_anchors_add(xcs, num);
|
|
#ifdef VE_OPENPGP_SUPPORT
|
|
} else {
|
|
num = openpgp_trust_add_buf(buf, len);
|
|
#endif
|
|
}
|
|
return (num);
|
|
}
|
|
|
|
/**
|
|
* @brief revoke trust anchors in buf
|
|
*
|
|
* Assume buf contains x509 certificates, but if not and
|
|
* we support OpenPGP try revoking keyId
|
|
*
|
|
* @return number of anchors revoked
|
|
*/
|
|
size_t
|
|
ve_trust_anchors_revoke(unsigned char *buf, size_t len)
|
|
{
|
|
br_x509_certificate *xcs;
|
|
size_t num;
|
|
|
|
num = 0;
|
|
xcs = parse_certificates(buf, len, &num);
|
|
if (xcs != NULL) {
|
|
num = ve_forbidden_anchors_add(xcs, num);
|
|
#ifdef VE_OPENPGP_SUPPORT
|
|
} else {
|
|
if (buf[len - 1] == '\n')
|
|
buf[len - 1] = '\0';
|
|
num = openpgp_trust_revoke((char *)buf);
|
|
#endif
|
|
}
|
|
return (num);
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* initialize our trust_anchors from ta_PEM
|
|
*/
|
|
int
|
|
ve_trust_init(void)
|
|
{
|
|
static int once = -1;
|
|
|
|
if (once >= 0)
|
|
return (once);
|
|
once = 0; /* to be sure */
|
|
#ifdef BUILD_UTC
|
|
ve_utc_set(BUILD_UTC); /* ensure sanity */
|
|
#endif
|
|
ve_utc_set(time(NULL));
|
|
ve_error_set(NULL); /* make sure it is empty */
|
|
#ifdef VE_PCR_SUPPORT
|
|
ve_pcr_init();
|
|
#endif
|
|
|
|
#ifdef TRUST_ANCHOR_STR
|
|
ve_trust_anchors_add_buf(__DECONST(unsigned char *, TRUST_ANCHOR_STR),
|
|
sizeof(TRUST_ANCHOR_STR));
|
|
#endif
|
|
once = (int) VEC_LEN(trust_anchors);
|
|
#ifdef VE_OPENPGP_SUPPORT
|
|
once += openpgp_trust_init();
|
|
#endif
|
|
return (once);
|
|
}
|
|
|
|
/**
|
|
* if we can verify the certificate chain in "certs",
|
|
* return the public key and if "xcp" is !NULL the associated
|
|
* certificate
|
|
*/
|
|
static br_x509_pkey *
|
|
verify_signer_xcs(br_x509_certificate *xcs,
|
|
size_t num,
|
|
br_name_element *elts, size_t num_elts,
|
|
anchor_list *anchors)
|
|
{
|
|
br_x509_minimal_context mc;
|
|
br_x509_certificate *xc;
|
|
size_t u;
|
|
cert_list chain = VEC_INIT;
|
|
const br_x509_pkey *tpk;
|
|
br_x509_pkey *pk;
|
|
unsigned int usages;
|
|
int err;
|
|
|
|
DEBUG_PRINTF(5, ("verify_signer: %zu certs in chain\n", num));
|
|
VEC_ADDMANY(chain, xcs, num);
|
|
if (VEC_LEN(chain) == 0) {
|
|
ve_error_set("ERROR: no/invalid certificate chain\n");
|
|
return (NULL);
|
|
}
|
|
|
|
DEBUG_PRINTF(5, ("verify_signer: %zu trust anchors\n",
|
|
VEC_LEN(*anchors)));
|
|
|
|
br_x509_minimal_init(&mc, &br_sha256_vtable,
|
|
&VEC_ELT(*anchors, 0),
|
|
VEC_LEN(*anchors));
|
|
#ifdef VE_ECDSA_SUPPORT
|
|
br_x509_minimal_set_ecdsa(&mc,
|
|
&br_ec_prime_i31, &br_ecdsa_i31_vrfy_asn1);
|
|
#endif
|
|
#ifdef VE_RSA_SUPPORT
|
|
br_x509_minimal_set_rsa(&mc, &br_rsa_i31_pkcs1_vrfy);
|
|
#endif
|
|
#if defined(UNIT_TEST) && defined(VE_DEPRECATED_RSA_SHA1_SUPPORT)
|
|
/* This is deprecated! do not enable unless you absoultely have to */
|
|
br_x509_minimal_set_hash(&mc, br_sha1_ID, &br_sha1_vtable);
|
|
#endif
|
|
br_x509_minimal_set_hash(&mc, br_sha256_ID, &br_sha256_vtable);
|
|
#ifdef VE_SHA384_SUPPORT
|
|
br_x509_minimal_set_hash(&mc, br_sha384_ID, &br_sha384_vtable);
|
|
#endif
|
|
#ifdef VE_SHA512_SUPPORT
|
|
br_x509_minimal_set_hash(&mc, br_sha512_ID, &br_sha512_vtable);
|
|
#endif
|
|
br_x509_minimal_set_name_elements(&mc, elts, num_elts);
|
|
|
|
#ifdef _STANDALONE
|
|
/*
|
|
* Clock is probably bogus so we use ve_utc.
|
|
*/
|
|
mc.days = (ve_utc / SECONDS_PER_DAY) + X509_DAYS_TO_UTC0;
|
|
mc.seconds = (ve_utc % SECONDS_PER_DAY);
|
|
#endif
|
|
|
|
mc.vtable->start_chain(&mc.vtable, NULL);
|
|
for (u = 0; u < VEC_LEN(chain); u ++) {
|
|
xc = &VEC_ELT(chain, u);
|
|
mc.vtable->start_cert(&mc.vtable, xc->data_len);
|
|
mc.vtable->append(&mc.vtable, xc->data, xc->data_len);
|
|
mc.vtable->end_cert(&mc.vtable);
|
|
switch (mc.err) {
|
|
case 0:
|
|
case BR_ERR_X509_OK:
|
|
case BR_ERR_X509_EXPIRED:
|
|
break;
|
|
default:
|
|
printf("u=%zu mc.err=%d\n", u, mc.err);
|
|
break;
|
|
}
|
|
}
|
|
err = mc.vtable->end_chain(&mc.vtable);
|
|
pk = NULL;
|
|
if (err) {
|
|
ve_error_set("Validation failed, err = %d", err);
|
|
} else {
|
|
tpk = mc.vtable->get_pkey(&mc.vtable, &usages);
|
|
if (tpk != NULL) {
|
|
pk = xpkeydup(tpk);
|
|
}
|
|
}
|
|
VEC_CLEAR(chain);
|
|
return (pk);
|
|
}
|
|
|
|
/*
|
|
* Check if digest of one of the certificates from verified chain
|
|
* is present in the forbidden database.
|
|
* Since UEFI allows to store three types of digests
|
|
* all of them have to be checked separately.
|
|
*/
|
|
static int
|
|
check_forbidden_digests(br_x509_certificate *xcs, size_t num)
|
|
{
|
|
unsigned char sha256_digest[br_sha256_SIZE];
|
|
unsigned char sha384_digest[br_sha384_SIZE];
|
|
unsigned char sha512_digest[br_sha512_SIZE];
|
|
void *tbs;
|
|
hash_data *digest;
|
|
br_hash_compat_context ctx;
|
|
const br_hash_class *md;
|
|
size_t tbs_len, i;
|
|
int have_sha256, have_sha384, have_sha512;
|
|
|
|
if (VEC_LEN(forbidden_digests) == 0)
|
|
return (0);
|
|
|
|
/*
|
|
* Iterate through certificates, extract their To-Be-Signed section,
|
|
* and compare its digest against the ones in the forbidden database.
|
|
*/
|
|
while (num--) {
|
|
tbs = X509_to_tbs(xcs[num].data, &tbs_len);
|
|
if (tbs == NULL) {
|
|
printf("Failed to obtain TBS part of certificate\n");
|
|
return (1);
|
|
}
|
|
have_sha256 = have_sha384 = have_sha512 = 0;
|
|
|
|
for (i = 0; i < VEC_LEN(forbidden_digests); i++) {
|
|
digest = &VEC_ELT(forbidden_digests, i);
|
|
switch (digest->hash_size) {
|
|
case br_sha256_SIZE:
|
|
if (!have_sha256) {
|
|
have_sha256 = 1;
|
|
md = &br_sha256_vtable;
|
|
md->init(&ctx.vtable);
|
|
md->update(&ctx.vtable, tbs, tbs_len);
|
|
md->out(&ctx.vtable, sha256_digest);
|
|
}
|
|
if (!memcmp(sha256_digest,
|
|
digest->data,
|
|
br_sha256_SIZE))
|
|
return (1);
|
|
|
|
break;
|
|
case br_sha384_SIZE:
|
|
if (!have_sha384) {
|
|
have_sha384 = 1;
|
|
md = &br_sha384_vtable;
|
|
md->init(&ctx.vtable);
|
|
md->update(&ctx.vtable, tbs, tbs_len);
|
|
md->out(&ctx.vtable, sha384_digest);
|
|
}
|
|
if (!memcmp(sha384_digest,
|
|
digest->data,
|
|
br_sha384_SIZE))
|
|
return (1);
|
|
|
|
break;
|
|
case br_sha512_SIZE:
|
|
if (!have_sha512) {
|
|
have_sha512 = 1;
|
|
md = &br_sha512_vtable;
|
|
md->init(&ctx.vtable);
|
|
md->update(&ctx.vtable, tbs, tbs_len);
|
|
md->out(&ctx.vtable, sha512_digest);
|
|
}
|
|
if (!memcmp(sha512_digest,
|
|
digest->data,
|
|
br_sha512_SIZE))
|
|
return (1);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static br_x509_pkey *
|
|
verify_signer(const char *certs,
|
|
br_name_element *elts, size_t num_elts)
|
|
{
|
|
br_x509_certificate *xcs;
|
|
br_x509_pkey *pk;
|
|
size_t num;
|
|
|
|
pk = NULL;
|
|
|
|
ve_trust_init();
|
|
xcs = read_certificates(certs, &num);
|
|
if (xcs == NULL) {
|
|
ve_error_set("cannot read certificates\n");
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Check if either
|
|
* 1. There is a direct match between cert from forbidden_anchors
|
|
* and a cert from chain.
|
|
* 2. CA that signed the chain is found in forbidden_anchors.
|
|
*/
|
|
if (VEC_LEN(forbidden_anchors) > 0)
|
|
pk = verify_signer_xcs(xcs, num, elts, num_elts, &forbidden_anchors);
|
|
if (pk != NULL) {
|
|
ve_error_set("Certificate is on forbidden list\n");
|
|
xfreepkey(pk);
|
|
pk = NULL;
|
|
goto out;
|
|
}
|
|
|
|
pk = verify_signer_xcs(xcs, num, elts, num_elts, &trust_anchors);
|
|
if (pk == NULL)
|
|
goto out;
|
|
|
|
/*
|
|
* Check if hash of tbs part of any certificate in chain
|
|
* is on the forbidden list.
|
|
*/
|
|
if (check_forbidden_digests(xcs, num)) {
|
|
ve_error_set("Certificate hash is on forbidden list\n");
|
|
xfreepkey(pk);
|
|
pk = NULL;
|
|
}
|
|
out:
|
|
free_certificates(xcs, num);
|
|
return (pk);
|
|
}
|
|
|
|
/**
|
|
* we need a hex digest including trailing newline below
|
|
*/
|
|
char *
|
|
hexdigest(char *buf, size_t bufsz, unsigned char *foo, size_t foo_len)
|
|
{
|
|
char const hex2ascii[] = "0123456789abcdef";
|
|
size_t i;
|
|
|
|
/* every binary byte is 2 chars in hex + newline + null */
|
|
if (bufsz < (2 * foo_len) + 2)
|
|
return (NULL);
|
|
|
|
for (i = 0; i < foo_len; i++) {
|
|
buf[i * 2] = hex2ascii[foo[i] >> 4];
|
|
buf[i * 2 + 1] = hex2ascii[foo[i] & 0x0f];
|
|
}
|
|
|
|
buf[i * 2] = 0x0A; /* we also want a newline */
|
|
buf[i * 2 + 1] = '\0';
|
|
|
|
return (buf);
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* verify file against sigfile using pk
|
|
*
|
|
* When we generated the signature in sigfile,
|
|
* we hashed (sha256) file, and sent that to signing server
|
|
* which hashed (sha256) that hash.
|
|
*
|
|
* To verify we need to replicate that result.
|
|
*
|
|
* @param[in] pk
|
|
* br_x509_pkey
|
|
*
|
|
* @paramp[in] file
|
|
* file to be verified
|
|
*
|
|
* @param[in] sigfile
|
|
* signature (PEM encoded)
|
|
*
|
|
* @return NULL on error, otherwise content of file.
|
|
*/
|
|
#ifdef VE_ECDSA_SUPPORT
|
|
static unsigned char *
|
|
verify_ec(br_x509_pkey *pk, const char *file, const char *sigfile)
|
|
{
|
|
#ifdef VE_ECDSA_HASH_AGAIN
|
|
char *hex, hexbuf[br_sha512_SIZE * 2 + 2];
|
|
#endif
|
|
unsigned char rhbuf[br_sha512_SIZE];
|
|
br_sha256_context ctx;
|
|
unsigned char *fcp, *scp;
|
|
size_t flen, slen, plen;
|
|
pem_object *po;
|
|
const br_ec_impl *ec;
|
|
br_ecdsa_vrfy vrfy;
|
|
|
|
if ((fcp = read_file(file, &flen)) == NULL)
|
|
return (NULL);
|
|
if ((scp = read_file(sigfile, &slen)) == NULL) {
|
|
free(fcp);
|
|
return (NULL);
|
|
}
|
|
if ((po = decode_pem(scp, slen, &plen)) == NULL) {
|
|
free(fcp);
|
|
free(scp);
|
|
return (NULL);
|
|
}
|
|
br_sha256_init(&ctx);
|
|
br_sha256_update(&ctx, fcp, flen);
|
|
br_sha256_out(&ctx, rhbuf);
|
|
#ifdef VE_ECDSA_HASH_AGAIN
|
|
hex = hexdigest(hexbuf, sizeof(hexbuf), rhbuf, br_sha256_SIZE);
|
|
/* now hash that */
|
|
if (hex) {
|
|
br_sha256_init(&ctx);
|
|
br_sha256_update(&ctx, hex, strlen(hex));
|
|
br_sha256_out(&ctx, rhbuf);
|
|
}
|
|
#endif
|
|
ec = br_ec_get_default();
|
|
vrfy = br_ecdsa_vrfy_asn1_get_default();
|
|
if (!vrfy(ec, rhbuf, br_sha256_SIZE, &pk->key.ec, po->data,
|
|
po->data_len)) {
|
|
free(fcp);
|
|
fcp = NULL;
|
|
}
|
|
free(scp);
|
|
return (fcp);
|
|
}
|
|
#endif
|
|
|
|
#if defined(VE_RSA_SUPPORT) || defined(VE_OPENPGP_SUPPORT)
|
|
/**
|
|
* @brief verify an rsa digest
|
|
*
|
|
* @return 0 on failure
|
|
*/
|
|
int
|
|
verify_rsa_digest (br_rsa_public_key *pkey,
|
|
const unsigned char *hash_oid,
|
|
unsigned char *mdata, size_t mlen,
|
|
unsigned char *sdata, size_t slen)
|
|
{
|
|
br_rsa_pkcs1_vrfy vrfy;
|
|
unsigned char vhbuf[br_sha512_SIZE];
|
|
|
|
vrfy = br_rsa_pkcs1_vrfy_get_default();
|
|
|
|
if (!vrfy(sdata, slen, hash_oid, mlen, pkey, vhbuf) ||
|
|
memcmp(vhbuf, mdata, mlen) != 0) {
|
|
return (0); /* fail */
|
|
}
|
|
return (1); /* ok */
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @brief
|
|
* verify file against sigfile using pk
|
|
*
|
|
* When we generated the signature in sigfile,
|
|
* we hashed (sha256) file, and sent that to signing server
|
|
* which hashed (sha256) that hash.
|
|
*
|
|
* Or (deprecated) we simply used sha1 hash directly.
|
|
*
|
|
* To verify we need to replicate that result.
|
|
*
|
|
* @param[in] pk
|
|
* br_x509_pkey
|
|
*
|
|
* @paramp[in] file
|
|
* file to be verified
|
|
*
|
|
* @param[in] sigfile
|
|
* signature (PEM encoded)
|
|
*
|
|
* @return NULL on error, otherwise content of file.
|
|
*/
|
|
#ifdef VE_RSA_SUPPORT
|
|
static unsigned char *
|
|
verify_rsa(br_x509_pkey *pk, const char *file, const char *sigfile)
|
|
{
|
|
unsigned char rhbuf[br_sha512_SIZE];
|
|
const unsigned char *hash_oid;
|
|
const br_hash_class *md;
|
|
br_hash_compat_context mctx;
|
|
unsigned char *fcp, *scp;
|
|
size_t flen, slen, plen, hlen;
|
|
pem_object *po;
|
|
|
|
if ((fcp = read_file(file, &flen)) == NULL)
|
|
return (NULL);
|
|
if ((scp = read_file(sigfile, &slen)) == NULL) {
|
|
free(fcp);
|
|
return (NULL);
|
|
}
|
|
if ((po = decode_pem(scp, slen, &plen)) == NULL) {
|
|
free(fcp);
|
|
free(scp);
|
|
return (NULL);
|
|
}
|
|
|
|
switch (po->data_len) {
|
|
#if defined(UNIT_TEST) && defined(VE_DEPRECATED_RSA_SHA1_SUPPORT)
|
|
case 256:
|
|
// this is our old deprecated sig method
|
|
md = &br_sha1_vtable;
|
|
hlen = br_sha1_SIZE;
|
|
hash_oid = BR_HASH_OID_SHA1;
|
|
break;
|
|
#endif
|
|
default:
|
|
md = &br_sha256_vtable;
|
|
hlen = br_sha256_SIZE;
|
|
hash_oid = BR_HASH_OID_SHA256;
|
|
break;
|
|
}
|
|
md->init(&mctx.vtable);
|
|
md->update(&mctx.vtable, fcp, flen);
|
|
md->out(&mctx.vtable, rhbuf);
|
|
if (!verify_rsa_digest(&pk->key.rsa, hash_oid,
|
|
rhbuf, hlen, po->data, po->data_len)) {
|
|
free(fcp);
|
|
fcp = NULL;
|
|
}
|
|
free(scp);
|
|
return (fcp);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @brief
|
|
* verify a signature and return content of signed file
|
|
*
|
|
* @param[in] sigfile
|
|
* file containing signature
|
|
* we derrive path of signed file and certificate change from
|
|
* this.
|
|
*
|
|
* @param[in] flags
|
|
* only bit 1 significant so far
|
|
*
|
|
* @return NULL on error otherwise content of signed file
|
|
*/
|
|
unsigned char *
|
|
verify_sig(const char *sigfile, int flags)
|
|
{
|
|
br_x509_pkey *pk;
|
|
br_name_element cn;
|
|
char cn_buf[80];
|
|
unsigned char cn_oid[4];
|
|
char pbuf[MAXPATHLEN];
|
|
char *cp;
|
|
unsigned char *ucp;
|
|
size_t n;
|
|
|
|
DEBUG_PRINTF(5, ("verify_sig: %s\n", sigfile));
|
|
n = strlcpy(pbuf, sigfile, sizeof(pbuf));
|
|
if (n > (sizeof(pbuf) - 5) || strcmp(&sigfile[n - 3], "sig") != 0)
|
|
return (NULL);
|
|
cp = strcpy(&pbuf[n - 3], "certs");
|
|
/*
|
|
* We want the commonName field
|
|
* the OID we want is 2,5,4,3 - but DER encoded
|
|
*/
|
|
cn_oid[0] = 3;
|
|
cn_oid[1] = 0x55;
|
|
cn_oid[2] = 4;
|
|
cn_oid[3] = 3;
|
|
cn.oid = cn_oid;
|
|
cn.buf = cn_buf;
|
|
cn.len = sizeof(cn_buf);
|
|
|
|
pk = verify_signer(pbuf, &cn, 1);
|
|
if (!pk) {
|
|
printf("cannot verify: %s: %s\n", pbuf, ve_error_get());
|
|
return (NULL);
|
|
}
|
|
for (; cp > pbuf; cp--) {
|
|
if (*cp == '.') {
|
|
*cp = '\0';
|
|
break;
|
|
}
|
|
}
|
|
switch (pk->key_type) {
|
|
#ifdef VE_ECDSA_SUPPORT
|
|
case BR_KEYTYPE_EC:
|
|
ucp = verify_ec(pk, pbuf, sigfile);
|
|
break;
|
|
#endif
|
|
#ifdef VE_RSA_SUPPORT
|
|
case BR_KEYTYPE_RSA:
|
|
ucp = verify_rsa(pk, pbuf, sigfile);
|
|
break;
|
|
#endif
|
|
default:
|
|
ucp = NULL; /* not supported */
|
|
}
|
|
xfreepkey(pk);
|
|
if (!ucp) {
|
|
printf("Unverified %s (%s)\n", pbuf,
|
|
cn.status ? cn_buf : "unknown");
|
|
} else if ((flags & 1) != 0) {
|
|
printf("Verified %s signed by %s\n", pbuf,
|
|
cn.status ? cn_buf : "someone we trust");
|
|
}
|
|
return (ucp);
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief verify hash matches
|
|
*
|
|
* We have finished hashing a file,
|
|
* see if we got the desired result.
|
|
*
|
|
* @param[in] ctx
|
|
* pointer to hash context
|
|
*
|
|
* @param[in] md
|
|
* pointer to hash class
|
|
*
|
|
* @param[in] path
|
|
* name of the file we are checking
|
|
*
|
|
* @param[in] want
|
|
* the expected result
|
|
*
|
|
* @param[in] hlen
|
|
* size of hash output
|
|
*
|
|
* @return 0 on success
|
|
*/
|
|
int
|
|
ve_check_hash(br_hash_compat_context *ctx, const br_hash_class *md,
|
|
const char *path, const char *want, size_t hlen)
|
|
{
|
|
char hexbuf[br_sha512_SIZE * 2 + 2];
|
|
unsigned char hbuf[br_sha512_SIZE];
|
|
char *hex;
|
|
int rc;
|
|
int n;
|
|
|
|
md->out(&ctx->vtable, hbuf);
|
|
#ifdef VE_PCR_SUPPORT
|
|
ve_pcr_update(path, hbuf, hlen);
|
|
#endif
|
|
hex = hexdigest(hexbuf, sizeof(hexbuf), hbuf, hlen);
|
|
if (!hex)
|
|
return (VE_FINGERPRINT_WRONG);
|
|
n = 2*hlen;
|
|
if ((rc = strncmp(hex, want, n))) {
|
|
ve_error_set("%s: %.*s != %.*s", path, n, hex, n, want);
|
|
rc = VE_FINGERPRINT_WRONG;
|
|
}
|
|
return (rc ? rc : VE_FINGERPRINT_OK);
|
|
}
|
|
|
|
#ifdef VE_HASH_KAT_STR
|
|
static int
|
|
test_hash(const br_hash_class *md, size_t hlen,
|
|
const char *hname, const char *s, size_t slen, const char *want)
|
|
{
|
|
br_hash_compat_context mctx;
|
|
|
|
md->init(&mctx.vtable);
|
|
md->update(&mctx.vtable, s, slen);
|
|
return (ve_check_hash(&mctx, md, hname, want, hlen) != VE_FINGERPRINT_OK);
|
|
}
|
|
|
|
#endif
|
|
|
|
#define ve_test_hash(n, N) \
|
|
printf("Testing hash: " #n "\t\t\t\t%s\n", \
|
|
test_hash(&br_ ## n ## _vtable, br_ ## n ## _SIZE, #n, \
|
|
VE_HASH_KAT_STR, VE_HASH_KAT_STRLEN(VE_HASH_KAT_STR), \
|
|
vh_ ## N) ? "Failed" : "Passed")
|
|
|
|
/**
|
|
* @brief
|
|
* run self tests on hash and signature verification
|
|
*
|
|
* Test that the hash methods (SHA1 and SHA256) work.
|
|
* Test that we can verify a certificate for each supported
|
|
* Root CA.
|
|
*
|
|
* @return cached result.
|
|
*/
|
|
int
|
|
ve_self_tests(void)
|
|
{
|
|
static int once = -1;
|
|
#ifdef VERIFY_CERTS_STR
|
|
br_x509_certificate *xcs;
|
|
br_x509_pkey *pk;
|
|
br_name_element cn;
|
|
char cn_buf[80];
|
|
unsigned char cn_oid[4];
|
|
size_t num;
|
|
size_t u;
|
|
#endif
|
|
|
|
if (once >= 0)
|
|
return (once);
|
|
once = 0;
|
|
|
|
DEBUG_PRINTF(5, ("Self tests...\n"));
|
|
#ifdef VE_HASH_KAT_STR
|
|
#ifdef VE_SHA1_SUPPORT
|
|
ve_test_hash(sha1, SHA1);
|
|
#endif
|
|
#ifdef VE_SHA256_SUPPORT
|
|
ve_test_hash(sha256, SHA256);
|
|
#endif
|
|
#ifdef VE_SHA384_SUPPORT
|
|
ve_test_hash(sha384, SHA384);
|
|
#endif
|
|
#ifdef VE_SHA512_SUPPORT
|
|
ve_test_hash(sha512, SHA512);
|
|
#endif
|
|
#endif
|
|
#ifdef VERIFY_CERTS_STR
|
|
xcs = parse_certificates(__DECONST(unsigned char *, VERIFY_CERTS_STR),
|
|
sizeof(VERIFY_CERTS_STR), &num);
|
|
if (xcs != NULL) {
|
|
/*
|
|
* We want the commonName field
|
|
* the OID we want is 2,5,4,3 - but DER encoded
|
|
*/
|
|
cn_oid[0] = 3;
|
|
cn_oid[1] = 0x55;
|
|
cn_oid[2] = 4;
|
|
cn_oid[3] = 3;
|
|
cn.oid = cn_oid;
|
|
cn.buf = cn_buf;
|
|
|
|
for (u = 0; u < num; u ++) {
|
|
cn.len = sizeof(cn_buf);
|
|
if ((pk = verify_signer_xcs(&xcs[u], 1, &cn, 1, &trust_anchors)) != NULL) {
|
|
free_cert_contents(&xcs[u]);
|
|
once++;
|
|
printf("Testing verify certificate: %s\tPassed\n",
|
|
cn.status ? cn_buf : "");
|
|
xfreepkey(pk);
|
|
}
|
|
}
|
|
if (!once)
|
|
printf("Testing verify certificate:\t\t\tFailed\n");
|
|
xfree(xcs);
|
|
}
|
|
#endif /* VERIFY_CERTS_STR */
|
|
#ifdef VE_OPENPGP_SUPPORT
|
|
if (!openpgp_self_tests())
|
|
once++;
|
|
#endif
|
|
return (once);
|
|
}
|