#include <sys/types.h>
#include <sys/queue.h>
#include <sys/tree.h>
#include <assert.h>
#include <err.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
#include <imsg.h>
#include <openssl/asn1.h>
#include <openssl/cms.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include "extern.h"
#include "json.h"
static X509_STORE_CTX *ctx;
static struct auth_tree auths = RB_INITIALIZER(&auths);
static struct crl_tree crls = RB_INITIALIZER(&crls);
struct tal *talobj[TALSZ_MAX];
struct uripath {
RB_ENTRY(uripath) entry;
const char *uri;
struct cert *cert;
};
static RB_HEAD(uripath_tree, uripath) uritree;
static inline int
uripathcmp(const struct uripath *a, const struct uripath *b)
{
return strcmp(a->uri, b->uri);
}
RB_PROTOTYPE(uripath_tree, uripath, entry, uripathcmp);
static void
uripath_add(const char *uri, struct cert *cert)
{
struct uripath *up;
if ((up = calloc(1, sizeof(*up))) == NULL)
err(1, NULL);
if ((up->uri = strdup(uri)) == NULL)
err(1, NULL);
up->cert = cert;
if (RB_INSERT(uripath_tree, &uritree, up) != NULL)
errx(1, "corrupt AIA lookup tree");
}
static struct cert *
uripath_lookup(const char *uri)
{
struct uripath needle = { .uri = uri };
struct uripath *up;
up = RB_FIND(uripath_tree, &uritree, &needle);
if (up == NULL)
return NULL;
return up->cert;
}
RB_GENERATE(uripath_tree, uripath, entry, uripathcmp);
static void
parse_load_crl(char *uri)
{
struct crl *crl;
char *f;
size_t flen;
if (uri == NULL)
return;
if (strncmp(uri, RSYNC_PROTO, RSYNC_PROTO_LEN) != 0) {
warnx("bad CRL distribution point URI %s", uri);
return;
}
uri += RSYNC_PROTO_LEN;
f = load_file(uri, &flen);
if (f == NULL) {
warn("parse file %s", uri);
return;
}
crl = crl_parse(uri, f, flen);
if (crl != NULL && !crl_insert(&crls, crl))
crl_free(crl);
free(f);
}
static struct cert *
parse_load_cert(char *uri)
{
struct cert *cert = NULL;
char *f;
size_t flen;
if (uri == NULL)
return NULL;
if (strncmp(uri, RSYNC_PROTO, RSYNC_PROTO_LEN) != 0) {
warnx("bad authority information access URI %s", uri);
return NULL;
}
uri += RSYNC_PROTO_LEN;
f = load_file(uri, &flen);
if (f == NULL) {
warn("parse file %s", uri);
goto done;
}
cert = cert_parse_filemode(uri, f, flen);
free(f);
if (cert == NULL)
goto done;
if (cert->purpose != CERT_PURPOSE_CA) {
warnx("AIA reference to %s in %s",
purpose2str(cert->purpose), uri);
goto done;
}
parse_load_crl(cert->crl);
return cert;
done:
cert_free(cert);
return NULL;
}
static struct auth *
parse_load_certchain(char *uri)
{
struct cert *stack[MAX_CERT_DEPTH] = { 0 };
char *filestack[MAX_CERT_DEPTH];
struct cert *cert;
struct crl *crl;
struct auth *a;
const char *errstr;
int i;
for (i = 0; i < MAX_CERT_DEPTH; i++) {
if ((cert = uripath_lookup(uri)) != NULL) {
a = auth_find(&auths, cert->certid);
if (a == NULL) {
warnx("failed to find issuer for %s", uri);
goto fail;
}
break;
}
filestack[i] = uri;
stack[i] = cert = parse_load_cert(uri);
if (cert == NULL || cert->purpose != CERT_PURPOSE_CA) {
warnx("failed to build authority chain: %s", uri);
goto fail;
}
uri = cert->aia;
}
if (i >= MAX_CERT_DEPTH) {
warnx("authority chain exceeds max depth of %d",
MAX_CERT_DEPTH);
goto fail;
}
for (; i > 0; i--) {
cert = stack[i - 1];
uri = filestack[i - 1];
crl = crl_get(&crls, a);
if (!valid_x509(uri, ctx, cert->x509, a, crl, &errstr) ||
!valid_cert(uri, a, cert)) {
if (errstr != NULL)
warnx("%s: %s", uri, errstr);
goto fail;
}
cert->talid = a->cert->talid;
a = auth_insert(uri, &auths, cert, a);
uripath_add(uri, cert);
stack[i - 1] = NULL;
}
return a;
fail:
for (i = 0; i < MAX_CERT_DEPTH; i++)
cert_free(stack[i]);
return NULL;
}
static void
parse_load_ta(struct tal *tal)
{
const char *filename;
struct cert *cert;
unsigned char *f = NULL;
char *file;
size_t flen, i;
filename = strrchr(tal->uri[0], '/');
assert(filename);
if (asprintf(&file, "ta/%s%s", tal->descr, filename) == -1)
err(1, NULL);
f = load_file(file, &flen);
if (f == NULL) {
warn("parse file %s", file);
goto out;
}
cert = cert_parse_ta(file, f, flen, tal->spki, tal->spkisz);
if (cert == NULL)
goto out;
cert->talid = tal->id;
auth_insert(file, &auths, cert, NULL);
for (i = 0; i < tal->num_uris; i++) {
if (strncasecmp(tal->uri[i], RSYNC_PROTO, RSYNC_PROTO_LEN) != 0)
continue;
uripath_add(tal->uri[i], cert);
}
out:
free(file);
free(f);
}
static struct tal *
find_tal(struct cert *cert)
{
EVP_PKEY *cert_pkey, *tal_pkey;
struct tal *tal;
int i;
if ((cert_pkey = X509_get0_pubkey(cert->x509)) == NULL)
return NULL;
for (i = 0; i < TALSZ_MAX; i++) {
const unsigned char *spki;
if (talobj[i] == NULL)
break;
tal = talobj[i];
spki = tal->spki;
tal_pkey = d2i_PUBKEY(NULL, &spki, tal->spkisz);
if (tal_pkey == NULL)
continue;
if (EVP_PKEY_cmp(cert_pkey, tal_pkey) == 1) {
EVP_PKEY_free(tal_pkey);
return tal;
}
EVP_PKEY_free(tal_pkey);
}
return NULL;
}
static void
print_signature_path(const char *crl, const char *aia, const struct auth *a)
{
if (crl != NULL)
printf("Signature path: %s\n", crl);
if (a != NULL)
printf(" %s\n", a->cert->mft);
if (aia != NULL)
printf(" %s\n", aia);
for (; a != NULL; a = a->issuer) {
if (a->cert->crl != NULL)
printf(" %s\n", a->cert->crl);
if (a->issuer != NULL)
printf(" %s\n",
a->issuer->cert->mft);
if (a->cert->aia != NULL)
printf(" %s\n", a->cert->aia);
}
}
static enum rtype
rtype_from_der(const char *fn, const unsigned char *der, size_t len)
{
CMS_ContentInfo *cms = NULL;
X509 *x509 = NULL;
X509_CRL *crl = NULL;
const unsigned char *p;
enum rtype rtype = RTYPE_INVALID;
p = der;
if ((cms = d2i_CMS_ContentInfo(NULL, &p, len)) != NULL) {
const ASN1_OBJECT *obj;
if ((obj = CMS_get0_type(cms)) != NULL) {
if (OBJ_cmp(obj, ccr_oid) == 0) {
rtype = RTYPE_CCR;
goto out;
}
}
if (CMS_get0_SignerInfos(cms) == NULL) {
warnx("%s: CMS object not signedData", fn);
goto out;
}
if ((obj = CMS_get0_eContentType(cms)) == NULL) {
warnx("%s: RFC 6488, section 2.1.3.1: eContentType: "
"OID object is NULL", fn);
goto out;
}
if (OBJ_cmp(obj, aspa_oid) == 0)
rtype = RTYPE_ASPA;
else if (OBJ_cmp(obj, mft_oid) == 0)
rtype = RTYPE_MFT;
else if (OBJ_cmp(obj, roa_oid) == 0)
rtype = RTYPE_ROA;
else if (OBJ_cmp(obj, rsc_oid) == 0)
rtype = RTYPE_RSC;
else if (OBJ_cmp(obj, spl_oid) == 0)
rtype = RTYPE_SPL;
else if (OBJ_cmp(obj, tak_oid) == 0)
rtype = RTYPE_TAK;
goto out;
}
p = der;
if ((x509 = d2i_X509(NULL, &p, len)) != NULL) {
rtype = RTYPE_CER;
goto out;
}
p = der;
if ((crl = d2i_X509_CRL(NULL, &p, len)) != NULL) {
rtype = RTYPE_CRL;
goto out;
}
out:
CMS_ContentInfo_free(cms);
X509_free(x509);
X509_CRL_free(crl);
return rtype;
}
static void
proc_parser_file(char *file, unsigned char *in_buf, size_t len)
{
unsigned char *buf = in_buf;
static int num;
struct aspa *aspa = NULL;
struct cert *cert = NULL;
struct ccr *ccr = NULL;
struct crl *crl = NULL;
struct mft *mft = NULL;
struct roa *roa = NULL;
struct rsc *rsc = NULL;
struct spl *spl = NULL;
struct tak *tak = NULL;
struct tal *tal = NULL;
char *aia = NULL;
time_t *notbefore = NULL, *expires = NULL, *notafter = NULL;
time_t now;
struct auth *a = NULL;
struct crl *c;
const char *errstr = NULL, *valid;
int status = 0;
char filehash[SHA256_DIGEST_LENGTH];
char *hash;
enum rtype type;
int is_ta = 0;
now = get_current_time();
if (outformats & FORMAT_JSON) {
json_do_start(stdout);
} else {
if (num++ > 0)
printf("--\n");
}
if (strncmp(file, RSYNC_PROTO, RSYNC_PROTO_LEN) == 0) {
file += RSYNC_PROTO_LEN;
buf = load_file(file, &len);
if (buf == NULL) {
warn("parse file %s", file);
return;
}
}
if (rtype_from_file_extension(file) == RTYPE_GZ) {
unsigned char *full_buf = NULL;
size_t full_len;
char *gz_ext;
if ((full_buf = inflate_buffer(buf, len, &full_len)) == NULL) {
warnx("%s: gzip decompression failed", file);
goto out;
}
if (buf != in_buf)
free(buf);
buf = full_buf;
len = full_len;
if ((gz_ext = strrchr(file, '.')) == NULL) {
warnx("%s: unreachable: missing . in filename?", file);
goto out;
}
*gz_ext = '\0';
}
if (!EVP_Digest(buf, len, filehash, NULL, EVP_sha256(), NULL))
errx(1, "EVP_Digest failed in %s", __func__);
if (base64_encode(filehash, sizeof(filehash), &hash) == -1)
errx(1, "base64_encode failed in %s", __func__);
if (outformats & FORMAT_JSON) {
json_do_string("file", file);
json_do_string("hash_id", hash);
} else {
printf("File: %s\n", file);
printf("Hash identifier: %s\n", hash);
}
free(hash);
type = rtype_from_file_extension(file);
if (type == RTYPE_INVALID)
type = rtype_from_der(file, buf, len);
switch (type) {
case RTYPE_ASPA:
aspa = aspa_parse(&cert, file, -1, buf, len);
if (aspa == NULL)
break;
aia = cert->aia;
expires = &aspa->expires;
notbefore = &cert->notbefore;
notafter = &cert->notafter;
break;
case RTYPE_CCR:
ccr = ccr_parse(file, buf, len);
if (ccr == NULL)
break;
ccr_print(ccr);
break;
case RTYPE_CER:
cert = cert_parse_filemode(file, buf, len);
if (cert == NULL)
break;
is_ta = (cert->purpose == CERT_PURPOSE_TA);
aia = cert->aia;
expires = &cert->expires;
notbefore = &cert->notbefore;
notafter = &cert->notafter;
break;
case RTYPE_CRL:
crl = crl_parse(file, buf, len);
if (crl == NULL)
break;
crl_print(crl);
break;
case RTYPE_MFT:
mft = mft_parse(&cert, file, -1, buf, len);
if (mft == NULL)
break;
aia = cert->aia;
expires = &mft->expires;
notbefore = &mft->thisupdate;
notafter = &mft->nextupdate;
break;
case RTYPE_ROA:
roa = roa_parse(&cert, file, -1, buf, len);
if (roa == NULL)
break;
aia = cert->aia;
expires = &roa->expires;
notbefore = &cert->notbefore;
notafter = &cert->notafter;
break;
case RTYPE_RSC:
rsc = rsc_parse(&cert, file, -1, buf, len);
if (rsc == NULL)
break;
aia = cert->aia;
expires = &rsc->expires;
notbefore = &cert->notbefore;
notafter = &cert->notafter;
break;
case RTYPE_SPL:
spl = spl_parse(&cert, file, -1, buf, len);
if (spl == NULL)
break;
aia = cert->aia;
expires = &spl->expires;
notbefore = &cert->notbefore;
notafter = &cert->notafter;
break;
case RTYPE_TAK:
tak = tak_parse(&cert, file, -1, buf, len);
if (tak == NULL)
break;
aia = cert->aia;
expires = &tak->expires;
notbefore = &cert->notbefore;
notafter = &cert->notafter;
break;
case RTYPE_TAL:
tal = tal_parse(file, buf, len);
if (tal == NULL)
break;
tal_print(tal);
break;
default:
errstr = "unsupported file type";
break;
}
if (aia != NULL) {
parse_load_crl(cert->crl);
a = parse_load_certchain(aia);
c = crl_get(&crls, a);
if ((status = valid_x509(file, ctx, cert->x509, a, c, &errstr))) {
switch (type) {
case RTYPE_ASPA:
status = aspa->valid;
break;
case RTYPE_ROA:
status = roa->valid;
break;
case RTYPE_RSC:
status = rsc->valid;
break;
case RTYPE_SPL:
status = spl->valid;
default:
break;
}
}
if (status) {
int cvs;
cert->talid = a->cert->talid;
cvs = constraints_validate(file, cert);
if (cert->purpose == CERT_PURPOSE_BGPSEC_ROUTER)
status = cvs;
}
} else if (is_ta) {
expires = NULL;
notafter = NULL;
if ((tal = find_tal(cert)) != NULL) {
cert = ta_validate(file, cert, tal->spki, tal->spkisz);
status = (cert != NULL);
if (status) {
expires = &cert->expires;
notafter = &cert->notafter;
}
if (outformats & FORMAT_JSON)
json_do_string("tal", tal->descr);
else
printf("TAL: %s\n",
tal->descr);
tal = NULL;
} else {
cert_free(cert);
cert = NULL;
status = 0;
}
}
if (expires != NULL) {
if ((status && aia != NULL) || is_ta)
*expires = x509_find_expires(*notafter, a, &crls);
switch (type) {
case RTYPE_ASPA:
aspa_print(cert, aspa);
break;
case RTYPE_CER:
cert_print(cert);
break;
case RTYPE_MFT:
mft_print(cert, mft);
break;
case RTYPE_ROA:
roa_print(cert, roa);
break;
case RTYPE_RSC:
rsc_print(cert, rsc);
break;
case RTYPE_SPL:
spl_print(cert, spl);
break;
case RTYPE_TAK:
tak_print(cert, tak);
break;
default:
break;
}
}
if (status) {
if (notbefore != NULL && *notbefore > now)
valid = "Not yet valid";
else if (notafter != NULL && *notafter < now)
valid = "Expired";
else if (expires != NULL && *expires < now)
valid = "Signature path expired";
else
valid = "OK";
} else if (aia == NULL)
valid = "N/A";
else
valid = "Failed";
if (outformats & FORMAT_JSON) {
json_do_string("validation", valid);
if (errstr != NULL)
json_do_string("error", errstr);
} else {
printf("Validation: %s", valid);
if (errstr != NULL)
printf(", %s", errstr);
}
if (outformats & FORMAT_JSON)
json_do_finish();
else {
printf("\n");
if (aia != NULL && status) {
print_signature_path(cert->crl, aia, a);
if (expires != NULL)
printf("Signature path expires: %s\n",
time2str(*expires));
}
if (cert == NULL)
goto out;
if (type == RTYPE_TAL || type == RTYPE_CRL)
goto out;
if (verbose) {
if (!X509_print_ex_fp(stdout, cert->x509,
XN_FLAG_COMPAT, X509V3_EXT_DUMP_UNKNOWN))
errx(1, "X509_print_fp");
}
if (verbose > 1) {
if (!PEM_write_X509(stdout, cert->x509))
errx(1, "PEM_write_X509");
}
}
out:
if (buf != in_buf)
free(buf);
aspa_free(aspa);
cert_free(cert);
ccr_free(ccr);
crl_free(crl);
mft_free(mft);
roa_free(roa);
rsc_free(rsc);
spl_free(spl);
tak_free(tak);
tal_free(tal);
}
static void
parse_file(struct entityq *q, struct msgbuf *msgq)
{
struct entity *entp;
struct ibuf *b;
struct tal *tal;
time_t dummy = 0;
while ((entp = TAILQ_FIRST(q)) != NULL) {
TAILQ_REMOVE(q, entp, entries);
switch (entp->type) {
case RTYPE_FILE:
proc_parser_file(entp->file, entp->data, entp->datasz);
break;
case RTYPE_TAL:
if ((tal = tal_parse(entp->file, entp->data,
entp->datasz)) == NULL)
errx(1, "%s: could not parse tal file",
entp->file);
tal->id = entp->talid;
talobj[tal->id] = tal;
parse_load_ta(tal);
break;
default:
errx(1, "unhandled entity type %d", entp->type);
}
b = io_new_buffer();
io_simple_buffer(b, &entp->type, sizeof(entp->type));
io_simple_buffer(b, &entp->repoid, sizeof(entp->repoid));
io_simple_buffer(b, &entp->talid, sizeof(entp->talid));
io_str_buffer(b, entp->file);
io_simple_buffer(b, &dummy, sizeof(dummy));
io_close_buffer(msgq, b);
entity_free(entp);
}
}
void
proc_filemode(int fd)
{
struct entityq q;
struct pollfd pfd;
struct msgbuf *msgq;
struct entity *entp;
struct ibuf *b, *inbuf = NULL;
if (unveil(".", "r") == -1)
err(1, "unveil cachedir");
if (pledge("stdio rpath", NULL) == -1)
err(1, "pledge");
constraints_parse();
if ((ctx = X509_STORE_CTX_new()) == NULL)
err(1, "X509_STORE_CTX_new");
TAILQ_INIT(&q);
if ((msgq = msgbuf_new_reader(sizeof(size_t), io_parse_hdr, NULL)) ==
NULL)
err(1, NULL);
pfd.fd = fd;
for (;;) {
pfd.events = POLLIN;
if (msgbuf_queuelen(msgq) > 0)
pfd.events |= POLLOUT;
if (poll(&pfd, 1, INFTIM) == -1) {
if (errno == EINTR)
continue;
err(1, "poll");
}
if ((pfd.revents & (POLLERR|POLLNVAL)))
errx(1, "poll: bad descriptor");
if ((pfd.revents & POLLHUP))
break;
if ((pfd.revents & POLLIN)) {
switch (ibuf_read(fd, msgq)) {
case -1:
err(1, "ibuf_read");
case 0:
errx(1, "ibuf_read: connection closed");
}
while ((b = io_buf_get(msgq)) != NULL) {
entp = calloc(1, sizeof(struct entity));
if (entp == NULL)
err(1, NULL);
entity_read_req(b, entp);
TAILQ_INSERT_TAIL(&q, entp, entries);
ibuf_free(b);
}
}
if (pfd.revents & POLLOUT) {
if (msgbuf_write(fd, msgq) == -1) {
if (errno == EPIPE)
errx(1, "write: connection closed");
else
err(1, "write");
}
}
parse_file(&q, msgq);
}
msgbuf_free(msgq);
while ((entp = TAILQ_FIRST(&q)) != NULL) {
TAILQ_REMOVE(&q, entp, entries);
entity_free(entp);
}
auth_tree_free(&auths);
crl_tree_free(&crls);
X509_STORE_CTX_free(ctx);
ibuf_free(inbuf);
exit(0);
}