From: claudio Date: Thu, 4 Feb 2021 08:10:24 +0000 (+0000) Subject: Shuffle code around, move all the bits of proc_parser into parser.c. X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=eae583789f8bc208d9afbdee4dfd51065fad6f09;p=openbsd Shuffle code around, move all the bits of proc_parser into parser.c. OK tb@ --- diff --git a/usr.sbin/rpki-client/Makefile b/usr.sbin/rpki-client/Makefile index 66280c5eab0..2ed7edab7ed 100644 --- a/usr.sbin/rpki-client/Makefile +++ b/usr.sbin/rpki-client/Makefile @@ -1,9 +1,9 @@ -# $OpenBSD: Makefile,v 1.17 2021/02/02 18:33:11 claudio Exp $ +# $OpenBSD: Makefile,v 1.18 2021/02/04 08:10:24 claudio Exp $ PROG= rpki-client SRCS= as.c cert.c cms.c crl.c gbr.c io.c ip.c log.c main.c mft.c mkdir.c \ output.c output-bgpd.c output-bird.c output-csv.c output-json.c \ - roa.c rsync.c tal.c validate.c x509.c + parser.c roa.c rsync.c tal.c validate.c x509.c MAN= rpki-client.8 LDADD+= -lcrypto -lutil diff --git a/usr.sbin/rpki-client/extern.h b/usr.sbin/rpki-client/extern.h index 99504cf9e7b..69908a830ac 100644 --- a/usr.sbin/rpki-client/extern.h +++ b/usr.sbin/rpki-client/extern.h @@ -1,4 +1,4 @@ -/* $OpenBSD: extern.h,v 1.39 2021/02/02 18:33:11 claudio Exp $ */ +/* $OpenBSD: extern.h,v 1.40 2021/02/04 08:10:24 claudio Exp $ */ /* * Copyright (c) 2019 Kristaps Dzonsons * @@ -17,6 +17,7 @@ #ifndef EXTERN_H #define EXTERN_H +#include #include #include @@ -258,6 +259,22 @@ enum rtype { RTYPE_GBR, }; +/* + * An entity (MFT, ROA, certificate, etc.) that needs to be downloaded + * and parsed. + */ +struct entity { + enum rtype type; /* type of entity (not RTYPE_EOF) */ + char *uri; /* file or rsync:// URI */ + ssize_t repo; /* repo index or <0 if w/o repo */ + int has_pkey; /* whether pkey/sz is specified */ + unsigned char *pkey; /* public key (optional) */ + size_t pkeysz; /* public key length (optional) */ + char *descr; /* tal description */ + TAILQ_ENTRY(entity) entries; +}; +TAILQ_HEAD(entityq, entity); + /* * Statistics collected during run-time. */ @@ -367,6 +384,11 @@ int as_check_overlap(const struct cert_as *, const char *, int as_check_covered(uint32_t, uint32_t, const struct cert_as *, size_t); +/* Parser-specific */ +void entity_free(struct entity *); +void entity_read_req(int fd, struct entity *); +void proc_parser(int) __attribute__((noreturn)); + /* Rsync-specific. */ int rsync_uri_parse(const char **, size_t *, diff --git a/usr.sbin/rpki-client/main.c b/usr.sbin/rpki-client/main.c index 83e3ba59832..4289bc49c38 100644 --- a/usr.sbin/rpki-client/main.c +++ b/usr.sbin/rpki-client/main.c @@ -1,4 +1,4 @@ -/* $OpenBSD: main.c,v 1.92 2021/02/02 18:35:38 claudio Exp $ */ +/* $OpenBSD: main.c,v 1.93 2021/02/04 08:10:24 claudio Exp $ */ /* * Copyright (c) 2019 Kristaps Dzonsons * @@ -56,7 +56,6 @@ #include #include #include -#include #include #include #include @@ -103,23 +102,6 @@ static struct repotab { size_t reposz; /* number of repos */ } rt; -/* - * An entity (MFT, ROA, certificate, etc.) that needs to be downloaded - * and parsed. - */ -struct entity { - enum rtype type; /* type of entity (not RTYPE_EOF) */ - char *uri; /* file or rsync:// URI */ - ssize_t repo; /* repo index or <0 if w/o repo */ - int has_pkey; /* whether pkey/sz is specified */ - unsigned char *pkey; /* public key (optional) */ - size_t pkeysz; /* public key length (optional) */ - char *descr; /* tal description */ - TAILQ_ENTRY(entity) entries; -}; - -TAILQ_HEAD(entityq, entity); - /* * Database of all file path accessed during a run. */ @@ -138,15 +120,8 @@ RB_HEAD(filepath_tree, filepath); RB_PROTOTYPE(filepath_tree, filepath, entry, filepathcmp); struct filepath_tree fpt = RB_INITIALIZER(&fpt); -/* - * Mark that our subprocesses will never return. - */ static void entityq_flush(struct msgbuf *, struct entityq *, const struct repo *); -static void proc_parser(int) __attribute__((noreturn)); -static void build_chain(const struct auth *, STACK_OF(X509) **); -static void build_crls(const struct auth *, struct crl_tree *, - STACK_OF(X509_CRL) **); const char *bird_tablename = "ROAS"; @@ -202,7 +177,7 @@ filepath_exists(char *file) RB_GENERATE(filepath_tree, filepath, entry, filepathcmp); -static void +void entity_free(struct entity *ent) { @@ -220,7 +195,7 @@ entity_free(struct entity *ent) * Matched by entity_buffer_req(). * The pointer must be passed entity_free(). */ -static void +void entity_read_req(int fd, struct entity *ent) { @@ -564,594 +539,6 @@ queue_add_from_cert(struct msgbuf *procq, struct msgbuf *rsyncq, entityq_add(procq, q, nfile, RTYPE_MFT, repo, NULL, 0, NULL); } -/* - * Parse and validate a ROA. - * This is standard stuff. - * Returns the roa on success, NULL on failure. - */ -static struct roa * -proc_parser_roa(struct entity *entp, - X509_STORE *store, X509_STORE_CTX *ctx, - struct auth_tree *auths, struct crl_tree *crlt) -{ - struct roa *roa; - X509 *x509; - int c; - struct auth *a; - STACK_OF(X509) *chain; - STACK_OF(X509_CRL) *crls; - - if ((roa = roa_parse(&x509, entp->uri)) == NULL) - return NULL; - - a = valid_ski_aki(entp->uri, auths, roa->ski, roa->aki); - - build_chain(a, &chain); - build_crls(a, crlt, &crls); - - assert(x509 != NULL); - if (!X509_STORE_CTX_init(ctx, store, x509, chain)) - cryptoerrx("X509_STORE_CTX_init"); - X509_STORE_CTX_set_flags(ctx, - X509_V_FLAG_IGNORE_CRITICAL | X509_V_FLAG_CRL_CHECK); - X509_STORE_CTX_set0_crls(ctx, crls); - - if (X509_verify_cert(ctx) <= 0) { - c = X509_STORE_CTX_get_error(ctx); - X509_STORE_CTX_cleanup(ctx); - if (verbose > 0 || c != X509_V_ERR_UNABLE_TO_GET_CRL) - warnx("%s: %s", entp->uri, - X509_verify_cert_error_string(c)); - X509_free(x509); - roa_free(roa); - sk_X509_free(chain); - sk_X509_CRL_free(crls); - return NULL; - } - X509_STORE_CTX_cleanup(ctx); - sk_X509_free(chain); - sk_X509_CRL_free(crls); - X509_free(x509); - - /* - * If the ROA isn't valid, we accept it anyway and depend upon - * the code around roa_read() to check the "valid" field itself. - */ - - if (valid_roa(entp->uri, auths, roa)) - roa->valid = 1; - - return roa; -} - -/* - * Parse and validate a manifest file. - * Here we *don't* validate against the list of CRLs, because the - * certificate used to sign the manifest may specify a CRL that the root - * certificate didn't, and we haven't scanned for it yet. - * This chicken-and-egg isn't important, however, because we'll catch - * the revocation list by the time we scan for any contained resources - * (ROA, CER) and will see it then. - * Return the mft on success or NULL on failure. - */ -static struct mft * -proc_parser_mft(struct entity *entp, X509_STORE *store, X509_STORE_CTX *ctx, - struct auth_tree *auths, struct crl_tree *crlt) -{ - struct mft *mft; - X509 *x509; - int c; - struct auth *a; - STACK_OF(X509) *chain; - - if ((mft = mft_parse(&x509, entp->uri)) == NULL) - return NULL; - - a = valid_ski_aki(entp->uri, auths, mft->ski, mft->aki); - build_chain(a, &chain); - - if (!X509_STORE_CTX_init(ctx, store, x509, chain)) - cryptoerrx("X509_STORE_CTX_init"); - - /* CRL checked disabled here because CRL is referenced from mft */ - X509_STORE_CTX_set_flags(ctx, X509_V_FLAG_IGNORE_CRITICAL); - - if (X509_verify_cert(ctx) <= 0) { - c = X509_STORE_CTX_get_error(ctx); - X509_STORE_CTX_cleanup(ctx); - warnx("%s: %s", entp->uri, X509_verify_cert_error_string(c)); - mft_free(mft); - X509_free(x509); - sk_X509_free(chain); - return NULL; - } - - X509_STORE_CTX_cleanup(ctx); - sk_X509_free(chain); - X509_free(x509); - - if (!mft_check(entp->uri, mft)) { - mft_free(mft); - return NULL; - } - - return mft; -} - -/* - * Certificates are from manifests (has a digest and is signed with - * another certificate) Parse the certificate, make sure its - * signatures are valid (with CRLs), then validate the RPKI content. - * This returns a certificate (which must not be freed) or NULL on - * parse failure. - */ -static struct cert * -proc_parser_cert(const struct entity *entp, - X509_STORE *store, X509_STORE_CTX *ctx, - struct auth_tree *auths, struct crl_tree *crlt) -{ - struct cert *cert; - X509 *x509; - int c; - struct auth *a = NULL, *na; - char *tal; - STACK_OF(X509) *chain; - STACK_OF(X509_CRL) *crls; - - assert(!entp->has_pkey); - - /* Extract certificate data and X509. */ - - cert = cert_parse(&x509, entp->uri); - if (cert == NULL) - return NULL; - - a = valid_ski_aki(entp->uri, auths, cert->ski, cert->aki); - build_chain(a, &chain); - build_crls(a, crlt, &crls); - - /* - * Validate certificate chain w/CRLs. - * Only check the CRLs if specifically asked. - */ - - assert(x509 != NULL); - if (!X509_STORE_CTX_init(ctx, store, x509, chain)) - cryptoerrx("X509_STORE_CTX_init"); - - X509_STORE_CTX_set_flags(ctx, - X509_V_FLAG_IGNORE_CRITICAL | X509_V_FLAG_CRL_CHECK); - X509_STORE_CTX_set0_crls(ctx, crls); - - if (X509_verify_cert(ctx) <= 0) { - c = X509_STORE_CTX_get_error(ctx); - warnx("%s: %s", entp->uri, - X509_verify_cert_error_string(c)); - X509_STORE_CTX_cleanup(ctx); - cert_free(cert); - sk_X509_free(chain); - sk_X509_CRL_free(crls); - X509_free(x509); - return NULL; - } - - X509_STORE_CTX_cleanup(ctx); - sk_X509_free(chain); - sk_X509_CRL_free(crls); - - /* Validate the cert to get the parent */ - if (!valid_cert(entp->uri, auths, cert)) { - X509_free(x509); // needed? XXX - return cert; - } - - /* - * Add validated certs to the RPKI auth tree. - */ - - cert->valid = 1; - - na = malloc(sizeof(*na)); - if (na == NULL) - err(1, NULL); - - tal = a->tal; - - na->parent = a; - na->cert = cert; - na->tal = tal; - na->fn = strdup(entp->uri); - if (na->fn == NULL) - err(1, NULL); - - if (RB_INSERT(auth_tree, auths, na) != NULL) - err(1, "auth tree corrupted"); - - return cert; -} - - -/* - * Root certificates come from TALs (has a pkey and is self-signed). - * Parse the certificate, ensure that it's public key matches the - * known public key from the TAL, and then validate the RPKI - * content. If valid, we add it as a trusted root (trust anchor) to - * "store". - * - * This returns a certificate (which must not be freed) or NULL on - * parse failure. - */ -static struct cert * -proc_parser_root_cert(const struct entity *entp, - X509_STORE *store, X509_STORE_CTX *ctx, - struct auth_tree *auths, struct crl_tree *crlt) -{ - char subject[256]; - ASN1_TIME *notBefore, *notAfter; - X509_NAME *name; - struct cert *cert; - X509 *x509; - struct auth *na; - char *tal; - - assert(entp->has_pkey); - - /* Extract certificate data and X509. */ - - cert = ta_parse(&x509, entp->uri, entp->pkey, entp->pkeysz); - if (cert == NULL) - return NULL; - - if ((name = X509_get_subject_name(x509)) == NULL) { - warnx("%s Unable to get certificate subject", entp->uri); - goto badcert; - } - if (X509_NAME_oneline(name, subject, sizeof(subject)) == NULL) { - warnx("%s: Unable to parse certificate subject name", - entp->uri); - goto badcert; - } - if ((notBefore = X509_get_notBefore(x509)) == NULL) { - warnx("%s: certificate has invalid notBefore, subject='%s'", - entp->uri, subject); - goto badcert; - } - if ((notAfter = X509_get_notAfter(x509)) == NULL) { - warnx("%s: certificate has invalid notAfter, subject='%s'", - entp->uri, subject); - goto badcert; - } - if (X509_cmp_current_time(notBefore) != -1) { - warnx("%s: certificate not yet valid, subject='%s'", entp->uri, - subject); - goto badcert; - } - if (X509_cmp_current_time(notAfter) != 1) { - warnx("%s: certificate has expired, subject='%s'", entp->uri, - subject); - goto badcert; - } - if (!valid_ta(entp->uri, auths, cert)) { - warnx("%s: certificate not a valid ta, subject='%s'", - entp->uri, subject); - goto badcert; - } - - /* - * Add valid roots to the RPKI auth tree and as a trusted root - * for chain validation to the X509_STORE. - */ - - cert->valid = 1; - - na = malloc(sizeof(*na)); - if (na == NULL) - err(1, NULL); - - if ((tal = strdup(entp->descr)) == NULL) - err(1, NULL); - - na->parent = NULL; - na->cert = cert; - na->tal = tal; - na->fn = strdup(entp->uri); - if (na->fn == NULL) - err(1, NULL); - - if (RB_INSERT(auth_tree, auths, na) != NULL) - err(1, "auth tree corrupted"); - - X509_STORE_add_cert(store, x509); - - return cert; - badcert: - X509_free(x509); // needed? XXX - return cert; -} - -/* - * Parse a certificate revocation list - * This simply parses the CRL content itself, optionally validating it - * within the digest if it comes from a manifest, then adds it to the - * store of CRLs. - */ -static void -proc_parser_crl(struct entity *entp, X509_STORE *store, - X509_STORE_CTX *ctx, struct crl_tree *crlt) -{ - X509_CRL *x509_crl; - struct crl *crl; - - if ((x509_crl = crl_parse(entp->uri)) != NULL) { - if ((crl = malloc(sizeof(*crl))) == NULL) - err(1, NULL); - if ((crl->aki = x509_crl_get_aki(x509_crl)) == NULL) - errx(1, "x509_crl_get_aki failed"); - crl->x509_crl = x509_crl; - - if (RB_INSERT(crl_tree, crlt, crl) != NULL) { - warnx("%s: duplicate AKI %s", entp->uri, crl->aki); - free_crl(crl); - } - } -} - -/* - * Parse a ghostbuster record - */ -static void -proc_parser_gbr(struct entity *entp, X509_STORE *store, - X509_STORE_CTX *ctx, struct auth_tree *auths, struct crl_tree *crlt) -{ - struct gbr *gbr; - X509 *x509; - int c; - struct auth *a; - STACK_OF(X509) *chain; - STACK_OF(X509_CRL) *crls; - - if ((gbr = gbr_parse(&x509, entp->uri)) == NULL) - return; - - a = valid_ski_aki(entp->uri, auths, gbr->ski, gbr->aki); - - build_chain(a, &chain); - build_crls(a, crlt, &crls); - - assert(x509 != NULL); - if (!X509_STORE_CTX_init(ctx, store, x509, chain)) - cryptoerrx("X509_STORE_CTX_init"); - X509_STORE_CTX_set_flags(ctx, - X509_V_FLAG_IGNORE_CRITICAL | X509_V_FLAG_CRL_CHECK); - X509_STORE_CTX_set0_crls(ctx, crls); - - if (X509_verify_cert(ctx) <= 0) { - c = X509_STORE_CTX_get_error(ctx); - if (verbose > 0 || c != X509_V_ERR_UNABLE_TO_GET_CRL) - warnx("%s: %s", entp->uri, - X509_verify_cert_error_string(c)); - } - - X509_STORE_CTX_cleanup(ctx); - sk_X509_free(chain); - sk_X509_CRL_free(crls); - X509_free(x509); - gbr_free(gbr); -} - -/* use the parent (id) to walk the tree to the root and - build a certificate chain from cert->x509 */ -static void -build_chain(const struct auth *a, STACK_OF(X509) **chain) -{ - *chain = NULL; - - if (a == NULL) - return; - - if ((*chain = sk_X509_new_null()) == NULL) - err(1, "sk_X509_new_null"); - for (; a != NULL; a = a->parent) { - assert(a->cert->x509 != NULL); - if (!sk_X509_push(*chain, a->cert->x509)) - errx(1, "sk_X509_push"); - } -} - -/* use the parent (id) to walk the tree to the root and - build a stack of CRLs */ -static void -build_crls(const struct auth *a, struct crl_tree *crlt, - STACK_OF(X509_CRL) **crls) -{ - struct crl find, *found; - - if ((*crls = sk_X509_CRL_new_null()) == NULL) - errx(1, "sk_X509_CRL_new_null"); - - if (a == NULL) - return; - - find.aki = a->cert->ski; - found = RB_FIND(crl_tree, crlt, &find); - if (found && !sk_X509_CRL_push(*crls, found->x509_crl)) - err(1, "sk_X509_CRL_push"); -} - -/* - * Process responsible for parsing and validating content. - * All this process does is wait to be told about a file to parse, then - * it parses it and makes sure that the data being returned is fully - * validated and verified. - * The process will exit cleanly only when fd is closed. - */ -static void -proc_parser(int fd) -{ - struct tal *tal; - struct cert *cert; - struct mft *mft; - struct roa *roa; - struct entity *entp; - struct entityq q; - int c, rc = 1; - struct msgbuf msgq; - struct pollfd pfd; - struct ibuf *b; - X509_STORE *store; - X509_STORE_CTX *ctx; - struct auth_tree auths = RB_INITIALIZER(&auths); - struct crl_tree crlt = RB_INITIALIZER(&crlt); - - ERR_load_crypto_strings(); - OpenSSL_add_all_ciphers(); - OpenSSL_add_all_digests(); - - if ((store = X509_STORE_new()) == NULL) - cryptoerrx("X509_STORE_new"); - if ((ctx = X509_STORE_CTX_new()) == NULL) - cryptoerrx("X509_STORE_CTX_new"); - - TAILQ_INIT(&q); - - msgbuf_init(&msgq); - msgq.fd = fd; - - pfd.fd = fd; - - io_socket_nonblocking(pfd.fd); - - for (;;) { - pfd.events = POLLIN; - if (msgq.queued) - pfd.events |= POLLOUT; - - if (poll(&pfd, 1, INFTIM) == -1) - err(1, "poll"); - if ((pfd.revents & (POLLERR|POLLNVAL))) - errx(1, "poll: bad descriptor"); - - /* If the parent closes, return immediately. */ - - if ((pfd.revents & POLLHUP)) - break; - - /* - * Start with read events. - * This means that the parent process is sending us - * something we need to parse. - * We don't actually parse it til we have space in our - * outgoing buffer for responding, though. - */ - - if ((pfd.revents & POLLIN)) { - io_socket_blocking(fd); - entp = calloc(1, sizeof(struct entity)); - if (entp == NULL) - err(1, NULL); - entity_read_req(fd, entp); - TAILQ_INSERT_TAIL(&q, entp, entries); - io_socket_nonblocking(fd); - } - - if (pfd.revents & POLLOUT) { - switch (msgbuf_write(&msgq)) { - case 0: - errx(1, "write: connection closed"); - case -1: - err(1, "write"); - } - } - - /* - * If there's nothing to parse, then stop waiting for - * the write signal. - */ - - if (TAILQ_EMPTY(&q)) { - pfd.events &= ~POLLOUT; - continue; - } - - entp = TAILQ_FIRST(&q); - assert(entp != NULL); - - if ((b = ibuf_dynamic(256, UINT_MAX)) == NULL) - err(1, NULL); - io_simple_buffer(b, &entp->type, sizeof(entp->type)); - - switch (entp->type) { - case RTYPE_TAL: - if ((tal = tal_parse(entp->uri, entp->descr)) == NULL) - goto out; - tal_buffer(b, tal); - tal_free(tal); - break; - case RTYPE_CER: - if (entp->has_pkey) - cert = proc_parser_root_cert(entp, store, ctx, - &auths, &crlt); - else - cert = proc_parser_cert(entp, store, ctx, - &auths, &crlt); - c = (cert != NULL); - io_simple_buffer(b, &c, sizeof(int)); - if (cert != NULL) - cert_buffer(b, cert); - /* - * The parsed certificate data "cert" is now - * managed in the "auths" table, so don't free - * it here (see the loop after "out"). - */ - break; - case RTYPE_MFT: - mft = proc_parser_mft(entp, store, ctx, &auths, &crlt); - c = (mft != NULL); - io_simple_buffer(b, &c, sizeof(int)); - if (mft != NULL) - mft_buffer(b, mft); - mft_free(mft); - break; - case RTYPE_CRL: - proc_parser_crl(entp, store, ctx, &crlt); - break; - case RTYPE_ROA: - roa = proc_parser_roa(entp, store, ctx, &auths, &crlt); - c = (roa != NULL); - io_simple_buffer(b, &c, sizeof(int)); - if (roa != NULL) - roa_buffer(b, roa); - roa_free(roa); - break; - case RTYPE_GBR: - proc_parser_gbr(entp, store, ctx, &auths, &crlt); - break; - default: - abort(); - } - - ibuf_close(&msgq, b); - TAILQ_REMOVE(&q, entp, entries); - entity_free(entp); - } - - rc = 0; -out: - while ((entp = TAILQ_FIRST(&q)) != NULL) { - TAILQ_REMOVE(&q, entp, entries); - entity_free(entp); - } - - /* XXX free auths and crl tree */ - - X509_STORE_CTX_free(ctx); - X509_STORE_free(store); - - msgbuf_clear(&msgq); - - exit(rc); -} - /* * Process parsed content. * For non-ROAs, we grok for more data. diff --git a/usr.sbin/rpki-client/parser.c b/usr.sbin/rpki-client/parser.c new file mode 100644 index 00000000000..b8c4dc5387f --- /dev/null +++ b/usr.sbin/rpki-client/parser.c @@ -0,0 +1,628 @@ +/* $OpenBSD: parser.c,v 1.1 2021/02/04 08:10:25 claudio Exp $ */ +/* + * Copyright (c) 2019 Claudio Jeker + * Copyright (c) 2019 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "extern.h" + +static void build_chain(const struct auth *, STACK_OF(X509) **); +static void build_crls(const struct auth *, struct crl_tree *, + STACK_OF(X509_CRL) **); +/* + * Parse and validate a ROA. + * This is standard stuff. + * Returns the roa on success, NULL on failure. + */ +static struct roa * +proc_parser_roa(struct entity *entp, + X509_STORE *store, X509_STORE_CTX *ctx, + struct auth_tree *auths, struct crl_tree *crlt) +{ + struct roa *roa; + X509 *x509; + int c; + struct auth *a; + STACK_OF(X509) *chain; + STACK_OF(X509_CRL) *crls; + + if ((roa = roa_parse(&x509, entp->uri)) == NULL) + return NULL; + + a = valid_ski_aki(entp->uri, auths, roa->ski, roa->aki); + + build_chain(a, &chain); + build_crls(a, crlt, &crls); + + assert(x509 != NULL); + if (!X509_STORE_CTX_init(ctx, store, x509, chain)) + cryptoerrx("X509_STORE_CTX_init"); + X509_STORE_CTX_set_flags(ctx, + X509_V_FLAG_IGNORE_CRITICAL | X509_V_FLAG_CRL_CHECK); + X509_STORE_CTX_set0_crls(ctx, crls); + + if (X509_verify_cert(ctx) <= 0) { + c = X509_STORE_CTX_get_error(ctx); + X509_STORE_CTX_cleanup(ctx); + if (verbose > 0 || c != X509_V_ERR_UNABLE_TO_GET_CRL) + warnx("%s: %s", entp->uri, + X509_verify_cert_error_string(c)); + X509_free(x509); + roa_free(roa); + sk_X509_free(chain); + sk_X509_CRL_free(crls); + return NULL; + } + X509_STORE_CTX_cleanup(ctx); + sk_X509_free(chain); + sk_X509_CRL_free(crls); + X509_free(x509); + + /* + * If the ROA isn't valid, we accept it anyway and depend upon + * the code around roa_read() to check the "valid" field itself. + */ + + if (valid_roa(entp->uri, auths, roa)) + roa->valid = 1; + + return roa; +} + +/* + * Parse and validate a manifest file. + * Here we *don't* validate against the list of CRLs, because the + * certificate used to sign the manifest may specify a CRL that the root + * certificate didn't, and we haven't scanned for it yet. + * This chicken-and-egg isn't important, however, because we'll catch + * the revocation list by the time we scan for any contained resources + * (ROA, CER) and will see it then. + * Return the mft on success or NULL on failure. + */ +static struct mft * +proc_parser_mft(struct entity *entp, X509_STORE *store, X509_STORE_CTX *ctx, + struct auth_tree *auths, struct crl_tree *crlt) +{ + struct mft *mft; + X509 *x509; + int c; + struct auth *a; + STACK_OF(X509) *chain; + + if ((mft = mft_parse(&x509, entp->uri)) == NULL) + return NULL; + + a = valid_ski_aki(entp->uri, auths, mft->ski, mft->aki); + build_chain(a, &chain); + + if (!X509_STORE_CTX_init(ctx, store, x509, chain)) + cryptoerrx("X509_STORE_CTX_init"); + + /* CRL checked disabled here because CRL is referenced from mft */ + X509_STORE_CTX_set_flags(ctx, X509_V_FLAG_IGNORE_CRITICAL); + + if (X509_verify_cert(ctx) <= 0) { + c = X509_STORE_CTX_get_error(ctx); + X509_STORE_CTX_cleanup(ctx); + warnx("%s: %s", entp->uri, X509_verify_cert_error_string(c)); + mft_free(mft); + X509_free(x509); + sk_X509_free(chain); + return NULL; + } + + X509_STORE_CTX_cleanup(ctx); + sk_X509_free(chain); + X509_free(x509); + + if (!mft_check(entp->uri, mft)) { + mft_free(mft); + return NULL; + } + + return mft; +} + +/* + * Certificates are from manifests (has a digest and is signed with + * another certificate) Parse the certificate, make sure its + * signatures are valid (with CRLs), then validate the RPKI content. + * This returns a certificate (which must not be freed) or NULL on + * parse failure. + */ +static struct cert * +proc_parser_cert(const struct entity *entp, + X509_STORE *store, X509_STORE_CTX *ctx, + struct auth_tree *auths, struct crl_tree *crlt) +{ + struct cert *cert; + X509 *x509; + int c; + struct auth *a = NULL, *na; + char *tal; + STACK_OF(X509) *chain; + STACK_OF(X509_CRL) *crls; + + assert(!entp->has_pkey); + + /* Extract certificate data and X509. */ + + cert = cert_parse(&x509, entp->uri); + if (cert == NULL) + return NULL; + + a = valid_ski_aki(entp->uri, auths, cert->ski, cert->aki); + build_chain(a, &chain); + build_crls(a, crlt, &crls); + + /* + * Validate certificate chain w/CRLs. + * Only check the CRLs if specifically asked. + */ + + assert(x509 != NULL); + if (!X509_STORE_CTX_init(ctx, store, x509, chain)) + cryptoerrx("X509_STORE_CTX_init"); + + X509_STORE_CTX_set_flags(ctx, + X509_V_FLAG_IGNORE_CRITICAL | X509_V_FLAG_CRL_CHECK); + X509_STORE_CTX_set0_crls(ctx, crls); + + if (X509_verify_cert(ctx) <= 0) { + c = X509_STORE_CTX_get_error(ctx); + warnx("%s: %s", entp->uri, + X509_verify_cert_error_string(c)); + X509_STORE_CTX_cleanup(ctx); + cert_free(cert); + sk_X509_free(chain); + sk_X509_CRL_free(crls); + X509_free(x509); + return NULL; + } + + X509_STORE_CTX_cleanup(ctx); + sk_X509_free(chain); + sk_X509_CRL_free(crls); + + /* Validate the cert to get the parent */ + if (!valid_cert(entp->uri, auths, cert)) { + X509_free(x509); // needed? XXX + return cert; + } + + /* + * Add validated certs to the RPKI auth tree. + */ + + cert->valid = 1; + + na = malloc(sizeof(*na)); + if (na == NULL) + err(1, NULL); + + tal = a->tal; + + na->parent = a; + na->cert = cert; + na->tal = tal; + na->fn = strdup(entp->uri); + if (na->fn == NULL) + err(1, NULL); + + if (RB_INSERT(auth_tree, auths, na) != NULL) + err(1, "auth tree corrupted"); + + return cert; +} + + +/* + * Root certificates come from TALs (has a pkey and is self-signed). + * Parse the certificate, ensure that it's public key matches the + * known public key from the TAL, and then validate the RPKI + * content. If valid, we add it as a trusted root (trust anchor) to + * "store". + * + * This returns a certificate (which must not be freed) or NULL on + * parse failure. + */ +static struct cert * +proc_parser_root_cert(const struct entity *entp, + X509_STORE *store, X509_STORE_CTX *ctx, + struct auth_tree *auths, struct crl_tree *crlt) +{ + char subject[256]; + ASN1_TIME *notBefore, *notAfter; + X509_NAME *name; + struct cert *cert; + X509 *x509; + struct auth *na; + char *tal; + + assert(entp->has_pkey); + + /* Extract certificate data and X509. */ + + cert = ta_parse(&x509, entp->uri, entp->pkey, entp->pkeysz); + if (cert == NULL) + return NULL; + + if ((name = X509_get_subject_name(x509)) == NULL) { + warnx("%s Unable to get certificate subject", entp->uri); + goto badcert; + } + if (X509_NAME_oneline(name, subject, sizeof(subject)) == NULL) { + warnx("%s: Unable to parse certificate subject name", + entp->uri); + goto badcert; + } + if ((notBefore = X509_get_notBefore(x509)) == NULL) { + warnx("%s: certificate has invalid notBefore, subject='%s'", + entp->uri, subject); + goto badcert; + } + if ((notAfter = X509_get_notAfter(x509)) == NULL) { + warnx("%s: certificate has invalid notAfter, subject='%s'", + entp->uri, subject); + goto badcert; + } + if (X509_cmp_current_time(notBefore) != -1) { + warnx("%s: certificate not yet valid, subject='%s'", entp->uri, + subject); + goto badcert; + } + if (X509_cmp_current_time(notAfter) != 1) { + warnx("%s: certificate has expired, subject='%s'", entp->uri, + subject); + goto badcert; + } + if (!valid_ta(entp->uri, auths, cert)) { + warnx("%s: certificate not a valid ta, subject='%s'", + entp->uri, subject); + goto badcert; + } + + /* + * Add valid roots to the RPKI auth tree and as a trusted root + * for chain validation to the X509_STORE. + */ + + cert->valid = 1; + + na = malloc(sizeof(*na)); + if (na == NULL) + err(1, NULL); + + if ((tal = strdup(entp->descr)) == NULL) + err(1, NULL); + + na->parent = NULL; + na->cert = cert; + na->tal = tal; + na->fn = strdup(entp->uri); + if (na->fn == NULL) + err(1, NULL); + + if (RB_INSERT(auth_tree, auths, na) != NULL) + err(1, "auth tree corrupted"); + + X509_STORE_add_cert(store, x509); + + return cert; + badcert: + X509_free(x509); // needed? XXX + return cert; +} + +/* + * Parse a certificate revocation list + * This simply parses the CRL content itself, optionally validating it + * within the digest if it comes from a manifest, then adds it to the + * store of CRLs. + */ +static void +proc_parser_crl(struct entity *entp, X509_STORE *store, + X509_STORE_CTX *ctx, struct crl_tree *crlt) +{ + X509_CRL *x509_crl; + struct crl *crl; + + if ((x509_crl = crl_parse(entp->uri)) != NULL) { + if ((crl = malloc(sizeof(*crl))) == NULL) + err(1, NULL); + if ((crl->aki = x509_crl_get_aki(x509_crl)) == NULL) + errx(1, "x509_crl_get_aki failed"); + crl->x509_crl = x509_crl; + + if (RB_INSERT(crl_tree, crlt, crl) != NULL) { + warnx("%s: duplicate AKI %s", entp->uri, crl->aki); + free_crl(crl); + } + } +} + +/* + * Parse a ghostbuster record + */ +static void +proc_parser_gbr(struct entity *entp, X509_STORE *store, + X509_STORE_CTX *ctx, struct auth_tree *auths, struct crl_tree *crlt) +{ + struct gbr *gbr; + X509 *x509; + int c; + struct auth *a; + STACK_OF(X509) *chain; + STACK_OF(X509_CRL) *crls; + + if ((gbr = gbr_parse(&x509, entp->uri)) == NULL) + return; + + a = valid_ski_aki(entp->uri, auths, gbr->ski, gbr->aki); + + build_chain(a, &chain); + build_crls(a, crlt, &crls); + + assert(x509 != NULL); + if (!X509_STORE_CTX_init(ctx, store, x509, chain)) + cryptoerrx("X509_STORE_CTX_init"); + X509_STORE_CTX_set_flags(ctx, + X509_V_FLAG_IGNORE_CRITICAL | X509_V_FLAG_CRL_CHECK); + X509_STORE_CTX_set0_crls(ctx, crls); + + if (X509_verify_cert(ctx) <= 0) { + c = X509_STORE_CTX_get_error(ctx); + if (verbose > 0 || c != X509_V_ERR_UNABLE_TO_GET_CRL) + warnx("%s: %s", entp->uri, + X509_verify_cert_error_string(c)); + } + + X509_STORE_CTX_cleanup(ctx); + sk_X509_free(chain); + sk_X509_CRL_free(crls); + X509_free(x509); + gbr_free(gbr); +} + +/* use the parent (id) to walk the tree to the root and + build a certificate chain from cert->x509 */ +static void +build_chain(const struct auth *a, STACK_OF(X509) **chain) +{ + *chain = NULL; + + if (a == NULL) + return; + + if ((*chain = sk_X509_new_null()) == NULL) + err(1, "sk_X509_new_null"); + for (; a != NULL; a = a->parent) { + assert(a->cert->x509 != NULL); + if (!sk_X509_push(*chain, a->cert->x509)) + errx(1, "sk_X509_push"); + } +} + +/* use the parent (id) to walk the tree to the root and + build a stack of CRLs */ +static void +build_crls(const struct auth *a, struct crl_tree *crlt, + STACK_OF(X509_CRL) **crls) +{ + struct crl find, *found; + + if ((*crls = sk_X509_CRL_new_null()) == NULL) + errx(1, "sk_X509_CRL_new_null"); + + if (a == NULL) + return; + + find.aki = a->cert->ski; + found = RB_FIND(crl_tree, crlt, &find); + if (found && !sk_X509_CRL_push(*crls, found->x509_crl)) + err(1, "sk_X509_CRL_push"); +} + +/* + * Process responsible for parsing and validating content. + * All this process does is wait to be told about a file to parse, then + * it parses it and makes sure that the data being returned is fully + * validated and verified. + * The process will exit cleanly only when fd is closed. + */ +void +proc_parser(int fd) +{ + struct tal *tal; + struct cert *cert; + struct mft *mft; + struct roa *roa; + struct entity *entp; + struct entityq q; + int c, rc = 1; + struct msgbuf msgq; + struct pollfd pfd; + struct ibuf *b; + X509_STORE *store; + X509_STORE_CTX *ctx; + struct auth_tree auths = RB_INITIALIZER(&auths); + struct crl_tree crlt = RB_INITIALIZER(&crlt); + + ERR_load_crypto_strings(); + OpenSSL_add_all_ciphers(); + OpenSSL_add_all_digests(); + + if ((store = X509_STORE_new()) == NULL) + cryptoerrx("X509_STORE_new"); + if ((ctx = X509_STORE_CTX_new()) == NULL) + cryptoerrx("X509_STORE_CTX_new"); + + TAILQ_INIT(&q); + + msgbuf_init(&msgq); + msgq.fd = fd; + + pfd.fd = fd; + + io_socket_nonblocking(pfd.fd); + + for (;;) { + pfd.events = POLLIN; + if (msgq.queued) + pfd.events |= POLLOUT; + + if (poll(&pfd, 1, INFTIM) == -1) + err(1, "poll"); + if ((pfd.revents & (POLLERR|POLLNVAL))) + errx(1, "poll: bad descriptor"); + + /* If the parent closes, return immediately. */ + + if ((pfd.revents & POLLHUP)) + break; + + /* + * Start with read events. + * This means that the parent process is sending us + * something we need to parse. + * We don't actually parse it til we have space in our + * outgoing buffer for responding, though. + */ + + if ((pfd.revents & POLLIN)) { + io_socket_blocking(fd); + entp = calloc(1, sizeof(struct entity)); + if (entp == NULL) + err(1, NULL); + entity_read_req(fd, entp); + TAILQ_INSERT_TAIL(&q, entp, entries); + io_socket_nonblocking(fd); + } + + if (pfd.revents & POLLOUT) { + switch (msgbuf_write(&msgq)) { + case 0: + errx(1, "write: connection closed"); + case -1: + err(1, "write"); + } + } + + /* + * If there's nothing to parse, then stop waiting for + * the write signal. + */ + + if (TAILQ_EMPTY(&q)) { + pfd.events &= ~POLLOUT; + continue; + } + + entp = TAILQ_FIRST(&q); + assert(entp != NULL); + + if ((b = ibuf_dynamic(256, UINT_MAX)) == NULL) + err(1, NULL); + io_simple_buffer(b, &entp->type, sizeof(entp->type)); + + switch (entp->type) { + case RTYPE_TAL: + if ((tal = tal_parse(entp->uri, entp->descr)) == NULL) + goto out; + tal_buffer(b, tal); + tal_free(tal); + break; + case RTYPE_CER: + if (entp->has_pkey) + cert = proc_parser_root_cert(entp, store, ctx, + &auths, &crlt); + else + cert = proc_parser_cert(entp, store, ctx, + &auths, &crlt); + c = (cert != NULL); + io_simple_buffer(b, &c, sizeof(int)); + if (cert != NULL) + cert_buffer(b, cert); + /* + * The parsed certificate data "cert" is now + * managed in the "auths" table, so don't free + * it here (see the loop after "out"). + */ + break; + case RTYPE_MFT: + mft = proc_parser_mft(entp, store, ctx, &auths, &crlt); + c = (mft != NULL); + io_simple_buffer(b, &c, sizeof(int)); + if (mft != NULL) + mft_buffer(b, mft); + mft_free(mft); + break; + case RTYPE_CRL: + proc_parser_crl(entp, store, ctx, &crlt); + break; + case RTYPE_ROA: + roa = proc_parser_roa(entp, store, ctx, &auths, &crlt); + c = (roa != NULL); + io_simple_buffer(b, &c, sizeof(int)); + if (roa != NULL) + roa_buffer(b, roa); + roa_free(roa); + break; + case RTYPE_GBR: + proc_parser_gbr(entp, store, ctx, &auths, &crlt); + break; + default: + abort(); + } + + ibuf_close(&msgq, b); + TAILQ_REMOVE(&q, entp, entries); + entity_free(entp); + } + + rc = 0; +out: + while ((entp = TAILQ_FIRST(&q)) != NULL) { + TAILQ_REMOVE(&q, entp, entries); + entity_free(entp); + } + + /* XXX free auths and crl tree */ + + X509_STORE_CTX_free(ctx); + X509_STORE_free(store); + + msgbuf_clear(&msgq); + + exit(rc); +}