From: claudio Date: Thu, 21 Apr 2022 09:53:07 +0000 (+0000) Subject: The filemode code is enough different from the regular parser code that it X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=c4a9443c2d5e5c9028ce984677bcc19b1f69aad6;p=openbsd The filemode code is enough different from the regular parser code that it makes sense to totally split it out. Duplicate proc_parser_cert_validate() and proc_parser_root_cert() for now. The valid_x509() plus the required static functions are moved to validate.c. The crl_tree code moved into crl.c similar to the auth_tree handling in cert.c. All the proc functions are now tagged with __attribute(noreturn) which allows to remove the errx() after them. OK tb@ --- diff --git a/usr.sbin/rpki-client/Makefile b/usr.sbin/rpki-client/Makefile index df64ea00d1f..6f53821e674 100644 --- a/usr.sbin/rpki-client/Makefile +++ b/usr.sbin/rpki-client/Makefile @@ -1,8 +1,8 @@ -# $OpenBSD: Makefile,v 1.23 2021/11/24 15:24:16 claudio Exp $ +# $OpenBSD: Makefile,v 1.24 2022/04/21 09:53:07 claudio Exp $ PROG= rpki-client -SRCS= as.c cert.c cms.c crl.c encoding.c gbr.c http.c io.c ip.c log.c \ - main.c mft.c mkdir.c output.c output-bgpd.c output-bird.c \ +SRCS= as.c cert.c cms.c crl.c encoding.c filemode.c gbr.c http.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 parser.c print.c repo.c roa.c rrdp.c \ rrdp_delta.c rrdp_notification.c rrdp_snapshot.c rrdp_util.c \ rsync.c tal.c validate.c x509.c diff --git a/usr.sbin/rpki-client/cert.c b/usr.sbin/rpki-client/cert.c index 79a1010891a..5da34a47003 100644 --- a/usr.sbin/rpki-client/cert.c +++ b/usr.sbin/rpki-client/cert.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cert.c,v 1.69 2022/04/12 09:48:23 tb Exp $ */ +/* $OpenBSD: cert.c,v 1.70 2022/04/21 09:53:07 claudio Exp $ */ /* * Copyright (c) 2021 Job Snijders * Copyright (c) 2019 Kristaps Dzonsons @@ -1186,6 +1186,14 @@ cert_read(struct ibuf *b) return p; } +static inline int +authcmp(struct auth *a, struct auth *b) +{ + return strcmp(a->cert->ski, b->cert->ski); +} + +RB_GENERATE_STATIC(auth_tree, auth, entry, authcmp); + struct auth * auth_find(struct auth_tree *auths, const char *aki) { @@ -1215,14 +1223,6 @@ auth_insert(struct auth_tree *auths, struct cert *cert, struct auth *parent) err(1, "auth tree corrupted"); } -static inline int -authcmp(struct auth *a, struct auth *b) -{ - return strcmp(a->cert->ski, b->cert->ski); -} - -RB_GENERATE(auth_tree, auth, entry, authcmp); - static void insert_brk(struct brk_tree *tree, struct cert *cert, int asid) { diff --git a/usr.sbin/rpki-client/crl.c b/usr.sbin/rpki-client/crl.c index 8d2235f7c77..749f23c7d8d 100644 --- a/usr.sbin/rpki-client/crl.c +++ b/usr.sbin/rpki-client/crl.c @@ -1,4 +1,4 @@ -/* $OpenBSD: crl.c,v 1.14 2022/02/10 15:33:47 claudio Exp $ */ +/* $OpenBSD: crl.c,v 1.15 2022/04/21 09:53:07 claudio Exp $ */ /* * Copyright (c) 2019 Kristaps Dzonsons * @@ -87,7 +87,27 @@ crlcmp(struct crl *a, struct crl *b) return strcmp(a->aki, b->aki); } -RB_GENERATE(crl_tree, crl, entry, crlcmp); +RB_GENERATE_STATIC(crl_tree, crl, entry, crlcmp); + +/* + * Find a CRL based on the auth SKI value. + */ +struct crl * +crl_get(struct crl_tree *crlt, const struct auth *a) +{ + struct crl find; + + if (a == NULL) + return NULL; + find.aki = a->cert->ski; + return RB_FIND(crl_tree, crlt, &find); +} + +int +crl_insert(struct crl_tree *crlt, struct crl *crl) +{ + return RB_INSERT(crl_tree, crlt, crl) == NULL; +} void crl_free(struct crl *crl) diff --git a/usr.sbin/rpki-client/extern.h b/usr.sbin/rpki-client/extern.h index 37a6b2bf172..7d33f276141 100644 --- a/usr.sbin/rpki-client/extern.h +++ b/usr.sbin/rpki-client/extern.h @@ -1,4 +1,4 @@ -/* $OpenBSD: extern.h,v 1.130 2022/04/20 15:38:24 deraadt Exp $ */ +/* $OpenBSD: extern.h,v 1.131 2022/04/21 09:53:07 claudio Exp $ */ /* * Copyright (c) 2019 Kristaps Dzonsons * @@ -291,7 +291,6 @@ struct crl { * Tree of CRLs sorted by uri */ RB_HEAD(crl_tree, crl); -RB_PROTOTYPE(crl_tree, crl, entry, crlcmp); /* * An authentication tuple. @@ -307,7 +306,6 @@ struct auth { * Tree of auth sorted by ski */ RB_HEAD(auth_tree, auth); -RB_PROTOTYPE(auth_tree, auth, entry, authcmp); struct auth *auth_find(struct auth_tree *, const char *); void auth_insert(struct auth_tree *, struct cert *, struct auth *); @@ -454,6 +452,8 @@ struct gbr *gbr_parse(X509 **, const char *, const unsigned char *, /* crl.c */ struct crl *crl_parse(const char *, const unsigned char *, size_t); +struct crl *crl_get(struct crl_tree *, const struct auth *); +int crl_insert(struct crl_tree *, struct crl *); void crl_free(struct crl *); /* Validation of our objects. */ @@ -468,6 +468,8 @@ int valid_filehash(int, const char *, size_t); int valid_hash(unsigned char *, size_t, const char *, size_t); int valid_uri(const char *, size_t, const char *); int valid_origin(const char *, const char *); +int valid_x509(char *, X509_STORE_CTX *, X509 *, struct auth *, + struct crl *, int); /* Working with CMS. */ unsigned char *cms_parse_validate(X509 **, const char *, @@ -508,6 +510,7 @@ void entity_free(struct entity *); void entity_read_req(struct ibuf *, struct entity *); void entityq_flush(struct entityq *, struct repo *); void proc_parser(int) __attribute__((noreturn)); +void proc_filemode(int) __attribute__((noreturn)); /* Rsync-specific. */ @@ -516,8 +519,8 @@ void proc_rsync(char *, char *, int) __attribute__((noreturn)); /* HTTP and RRDP processes. */ -void proc_http(char *, int); -void proc_rrdp(int); +void proc_http(char *, int) __attribute__((noreturn)); +void proc_rrdp(int) __attribute__((noreturn)); /* Repository handling */ int filepath_add(struct filepath_tree *, char *); diff --git a/usr.sbin/rpki-client/filemode.c b/usr.sbin/rpki-client/filemode.c new file mode 100644 index 00000000000..a90d965b893 --- /dev/null +++ b/usr.sbin/rpki-client/filemode.c @@ -0,0 +1,589 @@ +/* $OpenBSD: filemode.c,v 1.1 2022/04/21 09:53:07 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 +#include +#include + +#include "extern.h" + +static X509_STORE_CTX *ctx; +static struct auth_tree auths = RB_INITIALIZER(&auths); +static struct crl_tree crlt = RB_INITIALIZER(&crlt); + +struct tal *talobj[TALSZ_MAX]; + +/* + * Validate a certificate, if invalid free the resouces and return NULL. + */ +static struct cert * +proc_parser_cert_validate(char *file, struct cert *cert) +{ + struct auth *a; + struct crl *crl; + + a = valid_ski_aki(file, &auths, cert->ski, cert->aki); + crl = crl_get(&crlt, a); + + if (!valid_x509(file, ctx, cert->x509, a, crl, 0)) { + cert_free(cert); + return NULL; + } + + cert->talid = a->cert->talid; + + /* Validate the cert */ + if (!valid_cert(file, a, cert)) { + cert_free(cert); + return NULL; + } + + /* + * Add validated CA certs to the RPKI auth tree. + */ + if (cert->purpose == CERT_PURPOSE_CA) + auth_insert(&auths, cert, a); + + return cert; +} + +/* + * Root certificates come from TALs (has a pkey and is self-signed). + * Parse the certificate, ensure that its public key matches the + * known public key from the TAL, and then validate the RPKI + * content. + * + * This returns a certificate (which must not be freed) or NULL on + * parse failure. + */ +static struct cert * +proc_parser_root_cert(char *file, const unsigned char *der, size_t len, + unsigned char *pkey, size_t pkeysz, int talid) +{ + struct cert *cert; + + /* Extract certificate data. */ + + cert = cert_parse_pre(file, der, len); + if (cert == NULL) + return NULL; + cert = ta_parse(file, cert, pkey, pkeysz); + if (cert == NULL) + return NULL; + + if (!valid_ta(file, &auths, cert)) { + warnx("%s: certificate not a valid ta", file); + cert_free(cert); + return NULL; + } + + cert->talid = talid; + + /* + * Add valid roots to the RPKI auth tree. + */ + auth_insert(&auths, cert, NULL); + + return cert; +} + +/* + * Use the X509 CRL Distribution Points to locate the CRL needed for + * verification. + */ +static void +parse_load_crl(char *uri) +{ + struct crl *crl; + char *f; + size_t flen; + + if (uri == NULL) + return; + if (strncmp(uri, "rsync://", strlen("rsync://")) != 0) { + warnx("bad CRL distribution point URI %s", uri); + return; + } + uri += strlen("rsync://"); + + 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(&crlt, crl)) + crl_free(crl); + + free(f); +} + +/* + * Parse the cert pointed at by the AIA URI while doing that also load + * the CRL of this cert. While the CRL is validated the returned cert + * is not. The caller needs to make sure it is validated once all + * necessary certs were loaded. Returns NULL on failure. + */ +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://", strlen("rsync://")) != 0) { + warnx("bad authority information access URI %s", uri); + return NULL; + } + uri += strlen("rsync://"); + + f = load_file(uri, &flen); + if (f == NULL) { + warn("parse file %s", uri); + goto done; + } + + cert = cert_parse_pre(uri, f, flen); + free(f); + + if (cert == NULL) + goto done; + if (cert->purpose != CERT_PURPOSE_CA) { + warnx("AIA reference to bgpsec cert %s", uri); + goto done; + } + /* try to load the CRL of this cert */ + parse_load_crl(cert->crl); + + return cert; + + done: + cert_free(cert); + return NULL; +} + +/* + * Build the certificate chain by using the Authority Information Access. + * This requires that the TA are already validated and added to the auths + * tree. Once the TA is located in the chain the chain is validated in + * reverse order. + */ +static void +parse_load_certchain(char *uri) +{ + struct cert *stack[MAX_CERT_DEPTH]; + char *filestack[MAX_CERT_DEPTH]; + struct cert *cert; + int i, failed; + + for (i = 0; i < MAX_CERT_DEPTH; i++) { + cert = parse_load_cert(uri); + if (cert == NULL) { + warnx("failed to build authority chain"); + return; + } + if (auth_find(&auths, cert->ski) != NULL) { + assert(i == 0); + cert_free(cert); + return; /* cert already added */ + } + stack[i] = cert; + filestack[i] = uri; + if (auth_find(&auths, cert->aki) != NULL) + break; /* found chain to TA */ + uri = cert->aia; + } + + if (i >= MAX_CERT_DEPTH) { + warnx("authority chain exceeds max depth of %d", + MAX_CERT_DEPTH); + for (i = 0; i < MAX_CERT_DEPTH; i++) + cert_free(stack[i]); + return; + } + + /* TA found play back the stack and add all certs */ + for (failed = 0; i >= 0; i--) { + cert = stack[i]; + uri = filestack[i]; + + if (failed) + cert_free(cert); + else if (proc_parser_cert_validate(uri, cert) == NULL) + failed = 1; + } +} + +static void +parse_load_ta(struct tal *tal) +{ + const char *file; + char *nfile, *f; + size_t flen; + + /* does not matter which URI, all end with same filename */ + file = strrchr(tal->uri[0], '/'); + assert(file); + + if (asprintf(&nfile, "ta/%s%s", tal->descr, file) == -1) + err(1, NULL); + + f = load_file(nfile, &flen); + if (f == NULL) { + warn("parse file %s", nfile); + free(nfile); + return; + } + + /* if TA is valid it was added as a root which is all we need */ + proc_parser_root_cert(nfile, f, flen, tal->pkey, tal->pkeysz, tal->id); + free(nfile); + free(f); +} + +static struct tal * +find_tal(struct cert *cert) +{ + EVP_PKEY *pk, *opk; + struct tal *tal; + int i; + + if ((opk = X509_get0_pubkey(cert->x509)) == NULL) + return NULL; + + for (i = 0; i < TALSZ_MAX; i++) { + const unsigned char *pkey; + + if (talobj[i] == NULL) + break; + tal = talobj[i]; + pkey = tal->pkey; + pk = d2i_PUBKEY(NULL, &pkey, tal->pkeysz); + if (pk == NULL) + continue; + if (EVP_PKEY_cmp(pk, opk) == 1) { + EVP_PKEY_free(pk); + return tal; + } + EVP_PKEY_free(pk); + } + return NULL; +} + +/* + * Parse file passed with -f option. + */ +static void +proc_parser_file(char *file, unsigned char *buf, size_t len) +{ + static int num; + X509 *x509 = NULL; + struct cert *cert = NULL; + struct crl *crl = NULL; + struct mft *mft = NULL; + struct roa *roa = NULL; + struct gbr *gbr = NULL; + struct tal *tal = NULL; + char *aia = NULL, *aki = NULL; + enum rtype type; + int is_ta = 0; + + if (num++ > 0) { + if (outformats & FORMAT_JSON) + printf("\n"); + else + printf("--\n"); + } + + if (strncmp(file, "rsync://", strlen("rsync://")) == 0) { + file += strlen("rsync://"); + buf = load_file(file, &len); + if (buf == NULL) { + warn("parse file %s", file); + return; + } + } + + if (outformats & FORMAT_JSON) + printf("{\n\t\"file\": \"%s\",\n", file); + else + printf("File: %s\n", file); + + type = rtype_from_file_extension(file); + + switch (type) { + case RTYPE_CER: + cert = cert_parse_pre(file, buf, len); + if (cert == NULL) + break; + is_ta = X509_get_extension_flags(cert->x509) & EXFLAG_SS; + if (!is_ta) + cert = cert_parse(file, cert); + if (cert == NULL) + break; + cert_print(cert); + aia = cert->aia; + aki = cert->aki; + x509 = cert->x509; + if (X509_up_ref(x509) == 0) + errx(1, "%s: X509_up_ref failed", __func__); + break; + case RTYPE_CRL: + crl = crl_parse(file, buf, len); + if (crl == NULL) + break; + crl_print(crl); + break; + case RTYPE_MFT: + mft = mft_parse(&x509, file, buf, len); + if (mft == NULL) + break; + mft_print(x509, mft); + aia = mft->aia; + aki = mft->aki; + break; + case RTYPE_ROA: + roa = roa_parse(&x509, file, buf, len); + if (roa == NULL) + break; + roa_print(x509, roa); + aia = roa->aia; + aki = roa->aki; + break; + case RTYPE_GBR: + gbr = gbr_parse(&x509, file, buf, len); + if (gbr == NULL) + break; + gbr_print(x509, gbr); + aia = gbr->aia; + aki = gbr->aki; + break; + case RTYPE_TAL: + tal = tal_parse(file, buf, len); + if (tal == NULL) + break; + tal_print(tal); + break; + default: + printf("%s: unsupported file type\n", file); + break; + } + + if (outformats & FORMAT_JSON) + printf("\t\"validation\": \""); + else + printf("Validation: "); + + if (aia != NULL) { + struct auth *a; + struct crl *c; + char *crl_uri; + + x509_get_crl(x509, file, &crl_uri); + parse_load_crl(crl_uri); + free(crl_uri); + if (auth_find(&auths, aki) == NULL) + parse_load_certchain(aia); + a = auth_find(&auths, aki); + c = crl_get(&crlt, a); + + if (valid_x509(file, ctx, x509, a, c, 0)) + printf("OK"); + else + printf("Failed"); + } else if (is_ta) { + if ((tal = find_tal(cert)) != NULL) { + cert = ta_parse(file, cert, tal->pkey, tal->pkeysz); + if (cert != NULL) + printf("OK"); + else + printf("Failed"); + if (outformats & FORMAT_JSON) + printf("\",\n\t\"tal\": \"%s", tal->descr); + else + printf("\nTAL: %s", tal->descr); + tal = NULL; + } else { + cert_free(cert); + cert = NULL; + printf("Failed"); + } + } + + if (outformats & FORMAT_JSON) + printf("\"\n}"); + else + printf("\n"); + + X509_free(x509); + cert_free(cert); + crl_free(crl); + mft_free(mft); + roa_free(roa); + gbr_free(gbr); + tal_free(tal); +} + +/* + * Process a file request, in general don't send anything back. + */ +static void +parse_file(struct entityq *q, struct msgbuf *msgq) +{ + struct entity *entp; + struct ibuf *b; + struct tal *tal; + + 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_str_buffer(b, entp->file); + io_close_buffer(msgq, b); + entity_free(entp); + } +} + +/* + * 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_filemode(int fd) +{ + struct entityq q; + struct msgbuf msgq; + struct pollfd pfd; + struct entity *entp; + struct ibuf *b, *inbuf = NULL; + + /* Only allow access to the cache directory. */ + if (unveil(".", "r") == -1) + err(1, "unveil cachedir"); + if (pledge("stdio rpath", NULL) == -1) + err(1, "pledge"); + + ERR_load_crypto_strings(); + OpenSSL_add_all_ciphers(); + OpenSSL_add_all_digests(); + x509_init_oid(); + + if ((ctx = X509_STORE_CTX_new()) == NULL) + cryptoerrx("X509_STORE_CTX_new"); + TAILQ_INIT(&q); + + msgbuf_init(&msgq); + msgq.fd = fd; + + pfd.fd = fd; + + for (;;) { + pfd.events = POLLIN; + if (msgq.queued) + 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 the parent closes, return immediately. */ + + if ((pfd.revents & POLLHUP)) + break; + + if ((pfd.revents & POLLIN)) { + b = io_buf_read(fd, &inbuf); + if (b != 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) { + switch (msgbuf_write(&msgq)) { + case 0: + errx(1, "write: connection closed"); + case -1: + err(1, "write"); + } + } + + parse_file(&q, &msgq); + } + + msgbuf_clear(&msgq); + 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); + + exit(0); +} diff --git a/usr.sbin/rpki-client/main.c b/usr.sbin/rpki-client/main.c index c800e2cf72e..cfc67d13458 100644 --- a/usr.sbin/rpki-client/main.c +++ b/usr.sbin/rpki-client/main.c @@ -1,4 +1,4 @@ -/* $OpenBSD: main.c,v 1.198 2022/04/20 04:40:33 tb Exp $ */ +/* $OpenBSD: main.c,v 1.199 2022/04/21 09:53:07 claudio Exp $ */ /* * Copyright (c) 2021 Claudio Jeker * Copyright (c) 2019 Kristaps Dzonsons @@ -861,8 +861,10 @@ main(int argc, char *argv[]) procpid = process_start("parser", &proc); if (procpid == 0) { - proc_parser(proc); - errx(1, "parser process returned"); + if (!filemode) + proc_parser(proc); + else + proc_filemode(proc); } /* @@ -877,7 +879,6 @@ main(int argc, char *argv[]) if (rsyncpid == 0) { close(proc); proc_rsync(rsync_prog, bind_addr, rsync); - errx(1, "rsync process returned"); } } else { rsync = -1; @@ -897,7 +898,6 @@ main(int argc, char *argv[]) close(proc); close(rsync); proc_http(bind_addr, http); - errx(1, "http process returned"); } } else { http = -1; @@ -917,7 +917,6 @@ main(int argc, char *argv[]) close(rsync); close(http); proc_rrdp(rrdp); - errx(1, "rrdp process returned"); } } else { rrdp = -1; diff --git a/usr.sbin/rpki-client/parser.c b/usr.sbin/rpki-client/parser.c index d11d044c0fd..f45c5b341ed 100644 --- a/usr.sbin/rpki-client/parser.c +++ b/usr.sbin/rpki-client/parser.c @@ -1,4 +1,4 @@ -/* $OpenBSD: parser.c,v 1.71 2022/04/20 15:13:08 job Exp $ */ +/* $OpenBSD: parser.c,v 1.72 2022/04/21 09:53:07 claudio Exp $ */ /* * Copyright (c) 2019 Claudio Jeker * Copyright (c) 2019 Kristaps Dzonsons @@ -39,18 +39,10 @@ #include "extern.h" -static void build_chain(const struct auth *, STACK_OF(X509) **); -static struct crl *get_crl(const struct auth *); -static void build_crls(const struct crl *, STACK_OF(X509_CRL) **); - static X509_STORE_CTX *ctx; static struct auth_tree auths = RB_INITIALIZER(&auths); static struct crl_tree crlt = RB_INITIALIZER(&crlt); -struct tal *talobj[TALSZ_MAX]; - -extern ASN1_OBJECT *certpol_oid; - struct parse_repo { RB_ENTRY(parse_repo) entry; char *path; @@ -128,122 +120,6 @@ parse_filepath(unsigned int repoid, const char *path, const char *file, return fn; } -/* - * Callback for X509_verify_cert() to handle critical extensions in old - * LibreSSL libraries or OpenSSL libs without RFC3779 support. - */ -static int -verify_cb(int ok, X509_STORE_CTX *store_ctx) -{ - X509 *cert; - const STACK_OF(X509_EXTENSION) *exts; - X509_EXTENSION *ext; - ASN1_OBJECT *obj; - char *file; - int depth, error, i, nid; - - error = X509_STORE_CTX_get_error(store_ctx); - depth = X509_STORE_CTX_get_error_depth(store_ctx); - - if (error != X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION) - return ok; - - if ((file = X509_STORE_CTX_get_app_data(store_ctx)) == NULL) - cryptoerrx("X509_STORE_CTX_get_app_data"); - - if ((cert = X509_STORE_CTX_get_current_cert(store_ctx)) == NULL) { - warnx("%s: got no current cert", file); - return 0; - } - if ((exts = X509_get0_extensions(cert)) == NULL) { - warnx("%s: got no cert extensions", file); - return 0; - } - - for (i = 0; i < sk_X509_EXTENSION_num(exts); i++) { - ext = sk_X509_EXTENSION_value(exts, i); - - /* skip over non-critical and known extensions */ - if (!X509_EXTENSION_get_critical(ext)) - continue; - if (X509_supported_extension(ext)) - continue; - - if ((obj = X509_EXTENSION_get_object(ext)) == NULL) { - warnx("%s: got no extension object", file); - return 0; - } - - nid = OBJ_obj2nid(obj); - switch (nid) { - case NID_sbgp_ipAddrBlock: - case NID_sbgp_autonomousSysNum: - continue; - default: - warnx("%s: depth %d: unknown extension: nid %d", - file, depth, nid); - return 0; - } - } - - return 1; -} - -/* - * Validate the X509 certificate. If crl is NULL don't check CRL. - * Returns 1 for valid certificates, returns 0 if there is a verify error - */ -static int -valid_x509(char *file, X509 *x509, struct auth *a, struct crl *crl, int nowarn) -{ - X509_VERIFY_PARAM *params; - ASN1_OBJECT *cp_oid; - STACK_OF(X509) *chain; - STACK_OF(X509_CRL) *crls = NULL; - unsigned long flags; - int c; - - build_chain(a, &chain); - build_crls(crl, &crls); - - assert(x509 != NULL); - if (!X509_STORE_CTX_init(ctx, NULL, x509, NULL)) - cryptoerrx("X509_STORE_CTX_init"); - - if ((params = X509_STORE_CTX_get0_param(ctx)) == NULL) - cryptoerrx("X509_STORE_CTX_get0_param"); - if ((cp_oid = OBJ_dup(certpol_oid)) == NULL) - cryptoerrx("OBJ_dup"); - if (!X509_VERIFY_PARAM_add0_policy(params, cp_oid)) - cryptoerrx("X509_VERIFY_PARAM_add0_policy"); - - X509_STORE_CTX_set_verify_cb(ctx, verify_cb); - if (!X509_STORE_CTX_set_app_data(ctx, file)) - cryptoerrx("X509_STORE_CTX_set_app_data"); - flags = X509_V_FLAG_CRL_CHECK; - flags |= X509_V_FLAG_EXPLICIT_POLICY; - flags |= X509_V_FLAG_INHIBIT_MAP; - X509_STORE_CTX_set_flags(ctx, flags); - X509_STORE_CTX_set_depth(ctx, MAX_CERT_DEPTH); - X509_STORE_CTX_set0_trusted_stack(ctx, chain); - X509_STORE_CTX_set0_crls(ctx, crls); - - if (X509_verify_cert(ctx) <= 0) { - c = X509_STORE_CTX_get_error(ctx); - if (!nowarn || verbose > 1) - warnx("%s: %s", file, X509_verify_cert_error_string(c)); - X509_STORE_CTX_cleanup(ctx); - sk_X509_free(chain); - sk_X509_CRL_free(crls); - return 0; - } - - X509_STORE_CTX_cleanup(ctx); - sk_X509_free(chain); - sk_X509_CRL_free(crls); - return 1; -} - /* * Parse and validate a ROA. * This is standard stuff. @@ -261,9 +137,9 @@ proc_parser_roa(char *file, const unsigned char *der, size_t len) return NULL; a = valid_ski_aki(file, &auths, roa->ski, roa->aki); - crl = get_crl(a); + crl = crl_get(&crlt, a); - if (!valid_x509(file, x509, a, crl, 0)) { + if (!valid_x509(file, ctx, x509, a, crl, 0)) { X509_free(x509); roa_free(roa); return NULL; @@ -399,7 +275,7 @@ proc_parser_mft_pre(char *file, const unsigned char *der, size_t len, *crl = parse_load_crl_from_mft(entp, mft, loc); a = valid_ski_aki(file, &auths, mft->ski, mft->aki); - if (!valid_x509(file, x509, a, *crl, 1)) { + if (!valid_x509(file, ctx, x509, a, *crl, 1)) { X509_free(x509); mft_free(mft); crl_free(*crl); @@ -502,7 +378,7 @@ proc_parser_mft(struct entity *entp, struct mft **mp) } if (*mp != NULL) { - if (RB_INSERT(crl_tree, &crlt, crl) != NULL) { + if (!crl_insert(&crlt, crl)) { warnx("%s: duplicate AKI %s", file, crl->aki); crl_free(crl); } @@ -522,9 +398,9 @@ proc_parser_cert_validate(char *file, struct cert *cert) struct crl *crl; a = valid_ski_aki(file, &auths, cert->ski, cert->aki); - crl = get_crl(a); + crl = crl_get(&crlt, a); - if (!valid_x509(file, cert->x509, a, crl, 0)) { + if (!valid_x509(file, ctx, cert->x509, a, crl, 0)) { cert_free(cert); return NULL; } @@ -611,27 +487,6 @@ proc_parser_root_cert(char *file, const unsigned char *der, size_t len, 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 - * CRL tree. - */ -static void -proc_parser_crl(char *file, const unsigned char *der, size_t len) -{ - struct crl *crl; - - if ((crl = crl_parse(file, der, len)) == NULL) - return; - - if (RB_INSERT(crl_tree, &crlt, crl) != NULL) { - if (!filemode) - warnx("%s: duplicate AKI %s", file, crl->aki); - crl_free(crl); - } -} - /* * Parse a ghostbuster record */ @@ -647,68 +502,15 @@ proc_parser_gbr(char *file, const unsigned char *der, size_t len) return; a = valid_ski_aki(file, &auths, gbr->ski, gbr->aki); - crl = get_crl(a); + crl = crl_get(&crlt, a); /* return value can be ignored since nothing happens here */ - valid_x509(file, x509, a, crl, 0); + valid_x509(file, ctx, x509, a, crl, 0); X509_free(x509); gbr_free(gbr); } -/* - * Walk the certificate tree to the root and build a certificate - * chain from cert->x509. All certs in the tree are validated and - * can be loaded as trusted stack into the validator. - */ -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"); - } -} - -/* - * Find a CRL based on the auth SKI value. - */ -static struct crl * -get_crl(const struct auth *a) -{ - struct crl find; - - if (a == NULL) - return NULL; - find.aki = a->cert->ski; - return RB_FIND(crl_tree, &crlt, &find); -} - -/* - * Add the CRL based on the certs SKI value. - * No need to insert any other CRL since those were already checked. - */ -static void -build_crls(const struct crl *crl, STACK_OF(X509_CRL) **crls) -{ - *crls = NULL; - - if (crl == NULL) - return; - if ((*crls = sk_X509_CRL_new_null()) == NULL) - errx(1, "sk_X509_CRL_new_null"); - if (!sk_X509_CRL_push(*crls, crl->x509_crl)) - err(1, "sk_X509_CRL_push"); -} - /* * Load the file specified by the entity information. */ @@ -836,382 +638,6 @@ parse_entity(struct entityq *q, struct msgbuf *msgq) } } -/* - * Use the X509 CRL Distribution Points to locate the CRL needed for - * verification. - */ -static void -parse_load_crl(char *uri) -{ - char *f; - size_t flen; - - if (uri == NULL) - return; - if (strncmp(uri, "rsync://", strlen("rsync://")) != 0) { - warnx("bad CRL distribution point URI %s", uri); - return; - } - uri += strlen("rsync://"); - - f = load_file(uri, &flen); - if (f == NULL) { - warn("parse file %s", uri); - return; - } - - proc_parser_crl(uri, f, flen); - - free(f); -} - -/* - * Parse the cert pointed at by the AIA URI while doing that also load - * the CRL of this cert. While the CRL is validated the returned cert - * is not. The caller needs to make sure it is validated once all - * necessary certs were loaded. Returns NULL on failure. - */ -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://", strlen("rsync://")) != 0) { - warnx("bad authority information access URI %s", uri); - return NULL; - } - uri += strlen("rsync://"); - - f = load_file(uri, &flen); - if (f == NULL) { - warn("parse file %s", uri); - goto done; - } - - cert = cert_parse_pre(uri, f, flen); - free(f); - - if (cert == NULL) - goto done; - if (cert->purpose != CERT_PURPOSE_CA) { - warnx("AIA reference to bgpsec cert %s", uri); - goto done; - } - /* try to load the CRL of this cert */ - parse_load_crl(cert->crl); - - return cert; - - done: - cert_free(cert); - return NULL; -} - -/* - * Build the certificate chain by using the Authority Information Access. - * This requires that the TA are already validated and added to the auths - * tree. Once the TA is located in the chain the chain is validated in - * reverse order. - */ -static void -parse_load_certchain(char *uri) -{ - struct cert *stack[MAX_CERT_DEPTH]; - char *filestack[MAX_CERT_DEPTH]; - struct cert *cert; - int i, failed; - - for (i = 0; i < MAX_CERT_DEPTH; i++) { - cert = parse_load_cert(uri); - if (cert == NULL) { - warnx("failed to build authority chain"); - return; - } - if (auth_find(&auths, cert->ski) != NULL) { - assert(i == 0); - cert_free(cert); - return; /* cert already added */ - } - stack[i] = cert; - filestack[i] = uri; - if (auth_find(&auths, cert->aki) != NULL) - break; /* found chain to TA */ - uri = cert->aia; - } - - if (i >= MAX_CERT_DEPTH) { - warnx("authority chain exceeds max depth of %d", - MAX_CERT_DEPTH); - for (i = 0; i < MAX_CERT_DEPTH; i++) - cert_free(stack[i]); - return; - } - - /* TA found play back the stack and add all certs */ - for (failed = 0; i >= 0; i--) { - cert = stack[i]; - uri = filestack[i]; - - if (failed) - cert_free(cert); - else if (proc_parser_cert_validate(uri, cert) == NULL) - failed = 1; - } -} - -static void -parse_load_ta(struct tal *tal) -{ - const char *file; - char *nfile, *f; - size_t flen; - - /* does not matter which URI, all end with same filename */ - file = strrchr(tal->uri[0], '/'); - assert(file); - - if (asprintf(&nfile, "ta/%s%s", tal->descr, file) == -1) - err(1, NULL); - - f = load_file(nfile, &flen); - if (f == NULL) { - warn("parse file %s", nfile); - free(nfile); - return; - } - - /* if TA is valid it was added as a root which is all we need */ - proc_parser_root_cert(nfile, f, flen, tal->pkey, tal->pkeysz, tal->id); - free(nfile); - free(f); -} - -static struct tal * -find_tal(struct cert *cert) -{ - EVP_PKEY *pk, *opk; - struct tal *tal; - int i; - - if ((opk = X509_get0_pubkey(cert->x509)) == NULL) - return NULL; - - for (i = 0; i < TALSZ_MAX; i++) { - const unsigned char *pkey; - - if (talobj[i] == NULL) - break; - tal = talobj[i]; - pkey = tal->pkey; - pk = d2i_PUBKEY(NULL, &pkey, tal->pkeysz); - if (pk == NULL) - continue; - if (EVP_PKEY_cmp(pk, opk) == 1) { - EVP_PKEY_free(pk); - return tal; - } - EVP_PKEY_free(pk); - } - return NULL; -} - -/* - * Parse file passed with -f option. - */ -static void -proc_parser_file(char *file, unsigned char *buf, size_t len) -{ - static int num; - X509 *x509 = NULL; - struct cert *cert = NULL; - struct crl *crl = NULL; - struct mft *mft = NULL; - struct roa *roa = NULL; - struct gbr *gbr = NULL; - struct tal *tal = NULL; - char *aia = NULL, *aki = NULL; - enum rtype type; - int is_ta = 0; - - if (num++ > 0) { - if (outformats & FORMAT_JSON) - printf("\n"); - else - printf("--\n"); - } - - if (strncmp(file, "rsync://", strlen("rsync://")) == 0) { - file += strlen("rsync://"); - buf = load_file(file, &len); - if (buf == NULL) { - warn("parse file %s", file); - return; - } - } - - if (outformats & FORMAT_JSON) - printf("{\n\t\"file\": \"%s\",\n", file); - else - printf("File: %s\n", file); - - type = rtype_from_file_extension(file); - - switch (type) { - case RTYPE_CER: - cert = cert_parse_pre(file, buf, len); - if (cert == NULL) - break; - is_ta = X509_get_extension_flags(cert->x509) & EXFLAG_SS; - if (!is_ta) - cert = cert_parse(file, cert); - if (cert == NULL) - break; - cert_print(cert); - aia = cert->aia; - aki = cert->aki; - x509 = cert->x509; - if (X509_up_ref(x509) == 0) - errx(1, "%s: X509_up_ref failed", __func__); - break; - case RTYPE_CRL: - crl = crl_parse(file, buf, len); - if (crl == NULL) - break; - crl_print(crl); - break; - case RTYPE_MFT: - mft = mft_parse(&x509, file, buf, len); - if (mft == NULL) - break; - mft_print(x509, mft); - aia = mft->aia; - aki = mft->aki; - break; - case RTYPE_ROA: - roa = roa_parse(&x509, file, buf, len); - if (roa == NULL) - break; - roa_print(x509, roa); - aia = roa->aia; - aki = roa->aki; - break; - case RTYPE_GBR: - gbr = gbr_parse(&x509, file, buf, len); - if (gbr == NULL) - break; - gbr_print(x509, gbr); - aia = gbr->aia; - aki = gbr->aki; - break; - case RTYPE_TAL: - tal = tal_parse(file, buf, len); - if (tal == NULL) - break; - tal_print(tal); - break; - default: - printf("%s: unsupported file type\n", file); - break; - } - - if (outformats & FORMAT_JSON) - printf("\t\"validation\": \""); - else - printf("Validation: "); - - if (aia != NULL) { - struct auth *a; - struct crl *c; - char *crl_uri; - - x509_get_crl(x509, file, &crl_uri); - parse_load_crl(crl_uri); - free(crl_uri); - if (auth_find(&auths, aki) == NULL) - parse_load_certchain(aia); - a = auth_find(&auths, aki); - c = get_crl(a); - - if (valid_x509(file, x509, a, c, 0)) - printf("OK"); - else - printf("Failed"); - } else if (is_ta) { - if ((tal = find_tal(cert)) != NULL) { - cert = ta_parse(file, cert, tal->pkey, tal->pkeysz); - if (cert != NULL) - printf("OK"); - else - printf("Failed"); - if (outformats & FORMAT_JSON) - printf("\",\n\t\"tal\": \"%s", tal->descr); - else - printf("\nTAL: %s", tal->descr); - tal = NULL; - } else { - cert_free(cert); - cert = NULL; - printf("Failed"); - } - } - - if (outformats & FORMAT_JSON) - printf("\"\n}"); - else - printf("\n"); - - X509_free(x509); - cert_free(cert); - crl_free(crl); - mft_free(mft); - roa_free(roa); - gbr_free(gbr); - tal_free(tal); -} - -/* - * Process a file request, in general don't send anything back. - */ -static void -parse_file(struct entityq *q, struct msgbuf *msgq) -{ - struct entity *entp; - struct ibuf *b; - struct tal *tal; - - 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_str_buffer(b, entp->file); - io_close_buffer(msgq, b); - entity_free(entp); - } -} - /* * Process responsible for parsing and validating content. * All this process does is wait to be told about a file to parse, then @@ -1288,10 +714,7 @@ proc_parser(int fd) } } - if (!filemode) - parse_entity(&q, &msgq); - else - parse_file(&q, &msgq); + parse_entity(&q, &msgq); } while ((entp = TAILQ_FIRST(&q)) != NULL) { diff --git a/usr.sbin/rpki-client/validate.c b/usr.sbin/rpki-client/validate.c index 93eead135e2..4677093f1b7 100644 --- a/usr.sbin/rpki-client/validate.c +++ b/usr.sbin/rpki-client/validate.c @@ -1,4 +1,4 @@ -/* $OpenBSD: validate.c,v 1.30 2022/04/19 09:52:29 claudio Exp $ */ +/* $OpenBSD: validate.c,v 1.31 2022/04/21 09:53:07 claudio Exp $ */ /* * Copyright (c) 2019 Kristaps Dzonsons * @@ -30,6 +30,8 @@ #include "extern.h" +extern ASN1_OBJECT *certpol_oid; + /* * Walk up the chain of certificates trying to match our AS number to * one of the allocations in that chain. @@ -327,3 +329,160 @@ valid_origin(const char *uri, const char *proto) return 1; } + +/* + * Callback for X509_verify_cert() to handle critical extensions in old + * LibreSSL libraries or OpenSSL libs without RFC3779 support. + */ +static int +verify_cb(int ok, X509_STORE_CTX *store_ctx) +{ + X509 *cert; + const STACK_OF(X509_EXTENSION) *exts; + X509_EXTENSION *ext; + ASN1_OBJECT *obj; + char *file; + int depth, error, i, nid; + + error = X509_STORE_CTX_get_error(store_ctx); + depth = X509_STORE_CTX_get_error_depth(store_ctx); + + if (error != X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION) + return ok; + + if ((file = X509_STORE_CTX_get_app_data(store_ctx)) == NULL) + cryptoerrx("X509_STORE_CTX_get_app_data"); + + if ((cert = X509_STORE_CTX_get_current_cert(store_ctx)) == NULL) { + warnx("%s: got no current cert", file); + return 0; + } + if ((exts = X509_get0_extensions(cert)) == NULL) { + warnx("%s: got no cert extensions", file); + return 0; + } + + for (i = 0; i < sk_X509_EXTENSION_num(exts); i++) { + ext = sk_X509_EXTENSION_value(exts, i); + + /* skip over non-critical and known extensions */ + if (!X509_EXTENSION_get_critical(ext)) + continue; + if (X509_supported_extension(ext)) + continue; + + if ((obj = X509_EXTENSION_get_object(ext)) == NULL) { + warnx("%s: got no extension object", file); + return 0; + } + + nid = OBJ_obj2nid(obj); + switch (nid) { + case NID_sbgp_ipAddrBlock: + case NID_sbgp_autonomousSysNum: + continue; + default: + warnx("%s: depth %d: unknown extension: nid %d", + file, depth, nid); + return 0; + } + } + + return 1; +} + +/* + * Walk the certificate tree to the root and build a certificate + * chain from cert->x509. All certs in the tree are validated and + * can be loaded as trusted stack into the validator. + */ +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"); + } +} + +/* + * Add the CRL based on the certs SKI value. + * No need to insert any other CRL since those were already checked. + */ +static void +build_crls(const struct crl *crl, STACK_OF(X509_CRL) **crls) +{ + *crls = NULL; + + if (crl == NULL) + return; + if ((*crls = sk_X509_CRL_new_null()) == NULL) + errx(1, "sk_X509_CRL_new_null"); + if (!sk_X509_CRL_push(*crls, crl->x509_crl)) + err(1, "sk_X509_CRL_push"); +} + +/* + * Validate the X509 certificate. If crl is NULL don't check CRL. + * Returns 1 for valid certificates, returns 0 if there is a verify error + */ +int +valid_x509(char *file, X509_STORE_CTX *store_ctx, X509 *x509, struct auth *a, + struct crl *crl, int nowarn) +{ + X509_VERIFY_PARAM *params; + ASN1_OBJECT *cp_oid; + STACK_OF(X509) *chain; + STACK_OF(X509_CRL) *crls = NULL; + unsigned long flags; + int c; + + build_chain(a, &chain); + build_crls(crl, &crls); + + assert(store_ctx != NULL); + assert(x509 != NULL); + if (!X509_STORE_CTX_init(store_ctx, NULL, x509, NULL)) + cryptoerrx("X509_STORE_CTX_init"); + + if ((params = X509_STORE_CTX_get0_param(store_ctx)) == NULL) + cryptoerrx("X509_STORE_CTX_get0_param"); + if ((cp_oid = OBJ_dup(certpol_oid)) == NULL) + cryptoerrx("OBJ_dup"); + if (!X509_VERIFY_PARAM_add0_policy(params, cp_oid)) + cryptoerrx("X509_VERIFY_PARAM_add0_policy"); + + X509_STORE_CTX_set_verify_cb(store_ctx, verify_cb); + if (!X509_STORE_CTX_set_app_data(store_ctx, file)) + cryptoerrx("X509_STORE_CTX_set_app_data"); + flags = X509_V_FLAG_CRL_CHECK; + flags |= X509_V_FLAG_EXPLICIT_POLICY; + flags |= X509_V_FLAG_INHIBIT_MAP; + X509_STORE_CTX_set_flags(store_ctx, flags); + X509_STORE_CTX_set_depth(store_ctx, MAX_CERT_DEPTH); + X509_STORE_CTX_set0_trusted_stack(store_ctx, chain); + X509_STORE_CTX_set0_crls(store_ctx, crls); + + if (X509_verify_cert(store_ctx) <= 0) { + c = X509_STORE_CTX_get_error(store_ctx); + if (!nowarn || verbose > 1) + warnx("%s: %s", file, X509_verify_cert_error_string(c)); + X509_STORE_CTX_cleanup(store_ctx); + sk_X509_free(chain); + sk_X509_CRL_free(crls); + return 0; + } + + X509_STORE_CTX_cleanup(store_ctx); + sk_X509_free(chain); + sk_X509_CRL_free(crls); + return 1; +}