-# $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
-/* $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 <job@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
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)
{
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)
{
-/* $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 <kristaps@bsd.lv>
*
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)
-/* $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 <kristaps@bsd.lv>
*
* Tree of CRLs sorted by uri
*/
RB_HEAD(crl_tree, crl);
-RB_PROTOTYPE(crl_tree, crl, entry, crlcmp);
/*
* An authentication tuple.
* 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 *);
/* 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. */
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 *,
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. */
/* 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 *);
--- /dev/null
+/* $OpenBSD: filemode.c,v 1.1 2022/04/21 09:53:07 claudio Exp $ */
+/*
+ * Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/types.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/err.h>
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+
+#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);
+}
-/* $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 <claudio@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
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);
}
/*
if (rsyncpid == 0) {
close(proc);
proc_rsync(rsync_prog, bind_addr, rsync);
- errx(1, "rsync process returned");
}
} else {
rsync = -1;
close(proc);
close(rsync);
proc_http(bind_addr, http);
- errx(1, "http process returned");
}
} else {
http = -1;
close(rsync);
close(http);
proc_rrdp(rrdp);
- errx(1, "rrdp process returned");
}
} else {
rrdp = -1;
-/* $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 <claudio@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
#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;
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.
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;
*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);
}
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);
}
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;
}
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
*/
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.
*/
}
}
-/*
- * 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
}
}
- if (!filemode)
- parse_entity(&q, &msgq);
- else
- parse_file(&q, &msgq);
+ parse_entity(&q, &msgq);
}
while ((entp = TAILQ_FIRST(&q)) != NULL) {
-/* $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 <kristaps@bsd.lv>
*
#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.
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;
+}