From a66158d7f8cdffc32bf2f8aa5d8bbed1f08a3a3d Mon Sep 17 00:00:00 2001 From: job Date: Thu, 6 May 2021 17:03:57 +0000 Subject: [PATCH] Add an 'expires' column to CSV & JSON output The 'expires' value contains a reasonable earliest moment a VRP would expire, in light of the currently available set of CAs and CRLs. The 'expires' value can be used to avoid route selection based on stale data when generating VRP sets, when faced with loss of communication between consumer and valdiator, or validator and CA repository. OK claudio@ --- regress/usr.sbin/rpki-client/Makefile.inc | 15 +++- .../usr.sbin/rpki-client/openssl11/Makefile | 1 + regress/usr.sbin/rpki-client/test-roa.c | 10 ++- usr.sbin/rpki-client/extern.h | 4 +- usr.sbin/rpki-client/output-csv.c | 9 +-- usr.sbin/rpki-client/output-json.c | 7 +- usr.sbin/rpki-client/parser.c | 71 +++++++++++++++++-- usr.sbin/rpki-client/roa.c | 49 +++++++++++-- usr.sbin/rpki-client/rpki-client.8 | 18 +++-- 9 files changed, 155 insertions(+), 29 deletions(-) diff --git a/regress/usr.sbin/rpki-client/Makefile.inc b/regress/usr.sbin/rpki-client/Makefile.inc index 5aa3cf9b843..5b2e6b5b48e 100644 --- a/regress/usr.sbin/rpki-client/Makefile.inc +++ b/regress/usr.sbin/rpki-client/Makefile.inc @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile.inc,v 1.9 2021/04/01 06:47:18 claudio Exp $ +# $OpenBSD: Makefile.inc,v 1.10 2021/05/06 17:03:57 job Exp $ .PATH: ${.CURDIR}/../../../../usr.sbin/rpki-client @@ -40,14 +40,23 @@ mft_gen.c: mft.c cat $> >> $@.tmp mv -f $@.tmp $@ -CLEANFILES += mft_gen.c mft_gen.c.tmp +# Provide missing prototypes for OpenSSL +roa_gen.c: roa.c + echo '#include \n' > $@.tmp + echo 'int ASN1_time_parse(const char *, size_t, struct tm *, int);' \ + >> $@.tmp + echo 'int ASN1_time_tm_cmp(struct tm *, struct tm *);' >> $@.tmp + cat $> >> $@.tmp + mv -f $@.tmp $@ + +CLEANFILES += mft_gen.c mft_gen.c.tmp roa_gen.c roa_gen.c.tmp SRCS_test-mft+= test-mft.c mft_gen.c cms.c x509.c io.c log.c validate.c \ encoding.c dummy.c run-regress-test-mft: test-mft ./test-mft -v ${.CURDIR}/../mft/*.mft -SRCS_test-roa= test-roa.c roa.c cms.c x509.c ip.c as.c io.c log.c encoding.c +SRCS_test-roa+= test-roa.c roa_gen.c cms.c x509.c ip.c as.c io.c log.c encoding.c run-regress-test-roa: test-roa ./test-roa -v ${.CURDIR}/../roa/*.roa diff --git a/regress/usr.sbin/rpki-client/openssl11/Makefile b/regress/usr.sbin/rpki-client/openssl11/Makefile index 9481fa80e74..87b83348323 100644 --- a/regress/usr.sbin/rpki-client/openssl11/Makefile +++ b/regress/usr.sbin/rpki-client/openssl11/Makefile @@ -13,6 +13,7 @@ a_time_tm_gen.c: a_time_tm.c CLEANFILES += a_time_tm_gen.c a_time_tm_gen.c.tmp SRCS_test-mft = a_time_tm_gen.c o_time.c +SRCS_test-roa = a_time_tm_gen.c o_time.c CFLAGS += -I${.CURDIR}/../../../../lib/libcrypto/ .PATH: ${.CURDIR}/.. diff --git a/regress/usr.sbin/rpki-client/test-roa.c b/regress/usr.sbin/rpki-client/test-roa.c index 2f3c77b0f8b..da22e4b8b4d 100644 --- a/regress/usr.sbin/rpki-client/test-roa.c +++ b/regress/usr.sbin/rpki-client/test-roa.c @@ -1,4 +1,4 @@ -/* $Id: test-roa.c,v 1.10 2021/03/29 15:47:34 claudio Exp $ */ +/* $Id: test-roa.c,v 1.11 2021/05/06 17:03:57 job Exp $ */ /* * Copyright (c) 2019 Kristaps Dzonsons * @@ -32,6 +32,14 @@ #include "test-common.c" +#ifndef ASN1error +void +ASN1error(int err) +{ + ASN1err(0, err); +} +#endif + int verbose; static void diff --git a/usr.sbin/rpki-client/extern.h b/usr.sbin/rpki-client/extern.h index 1323c33ffd8..f6a7a768474 100644 --- a/usr.sbin/rpki-client/extern.h +++ b/usr.sbin/rpki-client/extern.h @@ -1,4 +1,4 @@ -/* $OpenBSD: extern.h,v 1.63 2021/04/14 18:05:47 benno Exp $ */ +/* $OpenBSD: extern.h,v 1.64 2021/05/06 17:03:57 job Exp $ */ /* * Copyright (c) 2019 Kristaps Dzonsons * @@ -188,6 +188,7 @@ struct roa { char *aki; /* AKI */ char *ski; /* SKI */ char *tal; /* basename of TAL for this cert */ + time_t expires; /* do not use after */ }; /* @@ -210,6 +211,7 @@ struct vrp { char *tal; /* basename of TAL for this cert */ enum afi afi; unsigned char maxlength; + time_t expires; /* transitive expiry moment */ }; /* * Tree of VRP sorted by afi, addr, maxlength and asid diff --git a/usr.sbin/rpki-client/output-csv.c b/usr.sbin/rpki-client/output-csv.c index 26033eb5ae2..971672bd217 100644 --- a/usr.sbin/rpki-client/output-csv.c +++ b/usr.sbin/rpki-client/output-csv.c @@ -1,4 +1,4 @@ -/* $OpenBSD: output-csv.c,v 1.9 2021/04/19 17:04:35 deraadt Exp $ */ +/* $OpenBSD: output-csv.c,v 1.10 2021/05/06 17:03:57 job Exp $ */ /* * Copyright (c) 2019 Claudio Jeker * @@ -24,15 +24,16 @@ output_csv(FILE *out, struct vrp_tree *vrps, struct stats *st) { struct vrp *v; - if (fprintf(out, "ASN,IP Prefix,Max Length,Trust Anchor\n") < 0) + if (fprintf(out, "ASN,IP Prefix,Max Length,Trust Anchor,Expires\n") < 0) return -1; RB_FOREACH(v, vrp_tree, vrps) { char buf[64]; ip_addr_print(&v->addr, v->afi, buf, sizeof(buf)); - if (fprintf(out, "AS%u,%s,%u,%s\n", v->asid, buf, v->maxlength, - v->tal) < 0) + + if (fprintf(out, "AS%u,%s,%u,%s,%lld\n", v->asid, buf, + v->maxlength, v->tal, (long long)v->expires) < 0) return -1; } return 0; diff --git a/usr.sbin/rpki-client/output-json.c b/usr.sbin/rpki-client/output-json.c index 8ca411ae245..d1cc32ba712 100644 --- a/usr.sbin/rpki-client/output-json.c +++ b/usr.sbin/rpki-client/output-json.c @@ -1,4 +1,4 @@ -/* $OpenBSD: output-json.c,v 1.16 2021/05/05 17:25:44 job Exp $ */ +/* $OpenBSD: output-json.c,v 1.17 2021/05/06 17:03:57 job Exp $ */ /* * Copyright (c) 2019 Claudio Jeker * @@ -101,8 +101,9 @@ output_json(FILE *out, struct vrp_tree *vrps, struct stats *st) ip_addr_print(&v->addr, v->afi, buf, sizeof(buf)); if (fprintf(out, "\t\t{ \"asn\": %u, \"prefix\": \"%s\", " - "\"maxLength\": %u, \"ta\": \"%s\" }", - v->asid, buf, v->maxlength, v->tal) < 0) + "\"maxLength\": %u, \"ta\": \"%s\", \"expires\": %lld }", + v->asid, buf, v->maxlength, v->tal, (long long)v->expires) + < 0) return -1; } diff --git a/usr.sbin/rpki-client/parser.c b/usr.sbin/rpki-client/parser.c index 7ba4221251f..1fc86c26ab9 100644 --- a/usr.sbin/rpki-client/parser.c +++ b/usr.sbin/rpki-client/parser.c @@ -1,4 +1,4 @@ -/* $OpenBSD: parser.c,v 1.7 2021/04/01 08:29:10 claudio Exp $ */ +/* $OpenBSD: parser.c,v 1.8 2021/05/06 17:03:57 job Exp $ */ /* * Copyright (c) 2019 Claudio Jeker * Copyright (c) 2019 Kristaps Dzonsons @@ -52,10 +52,13 @@ proc_parser_roa(struct entity *entp, { struct roa *roa; X509 *x509; - int c; + int c, i; struct auth *a; STACK_OF(X509) *chain; STACK_OF(X509_CRL) *crls; + const ASN1_TIME *at; + struct tm expires_tm; + time_t expires; if ((roa = roa_parse(&x509, entp->file)) == NULL) return NULL; @@ -85,9 +88,62 @@ proc_parser_roa(struct entity *entp, return NULL; } X509_STORE_CTX_cleanup(ctx); - sk_X509_free(chain); - sk_X509_CRL_free(crls); - X509_free(x509); + + /* + * Scan the stack of CRLs to figure out the soonest transitive + * expiry moment + */ + for (i = 0; i < sk_X509_CRL_num(crls); i++) { + X509_CRL *ci = sk_X509_CRL_value(crls, i); + if (ci->crl == NULL) { + err(1, "sk_X509_value failed"); + goto out; + } + at = X509_CRL_get0_nextUpdate(ci); + if (at == NULL) { + err(1, "X509_CRL_get0_nextUpdate failed"); + goto out; + } + if (ASN1_time_parse(at->data, at->length, &expires_tm, + V_ASN1_UTCTIME) != V_ASN1_UTCTIME) { + err(1, "ASN1_time_parse failed"); + goto out; + } + if ((expires = mktime(&expires_tm)) == -1) { + err(1, "mktime failed"); + goto out; + } + if (roa->expires > expires) + roa->expires = expires; + } + + /* + * Scan the stack of CAs to figure out the soonest transitive + * expiry moment + */ + for (i = 0; i < sk_X509_num(chain); i++) { + X509 *xi = sk_X509_value(chain, i); + if (xi->cert_info == NULL) { + err(1, "sk_X509_value failed"); + goto out; + } + at = X509_get0_notAfter(xi); + if (at == NULL) { + err(1, "X509_get0_notafter failed"); + goto out; + } + if (ASN1_time_parse(at->data, at->length, &expires_tm, + V_ASN1_UTCTIME) != V_ASN1_UTCTIME) { + err(1, "ASN1_time_parse failed"); + goto out; + } + if ((expires = mktime(&expires_tm)) == -1) { + err(1, "mktime failed"); + goto out; + } + if (roa->expires > expires) + roa->expires = expires; + } /* * If the ROA isn't valid, we accept it anyway and depend upon @@ -97,6 +153,11 @@ proc_parser_roa(struct entity *entp, if (valid_roa(entp->file, auths, roa)) roa->valid = 1; +out: + sk_X509_free(chain); + sk_X509_CRL_free(crls); + X509_free(x509); + return roa; } diff --git a/usr.sbin/rpki-client/roa.c b/usr.sbin/rpki-client/roa.c index f9e34f0fe2a..236df321d5a 100644 --- a/usr.sbin/rpki-client/roa.c +++ b/usr.sbin/rpki-client/roa.c @@ -1,4 +1,4 @@ -/* $OpenBSD: roa.c,v 1.17 2021/03/29 06:50:44 tb Exp $ */ +/* $OpenBSD: roa.c,v 1.18 2021/05/06 17:03:57 job Exp $ */ /* * Copyright (c) 2019 Kristaps Dzonsons * @@ -335,6 +335,9 @@ roa_parse(X509 **x509, const char *fn) size_t cmsz; unsigned char *cms; int rc = 0; + const ASN1_TIME *at; + struct tm expires_tm; + time_t expires; memset(&p, 0, sizeof(struct parse)); p.fn = fn; @@ -358,6 +361,21 @@ roa_parse(X509 **x509, const char *fn) goto out; } + at = X509_get0_notAfter(*x509); + if (at == NULL) { + warnx("%s: X509_get0_notAfter failed", fn); + goto out; + } + if (ASN1_time_parse(at->data, at->length, &expires_tm, 0) == -1) { + warnx("%s: ASN1_time_parse failed", fn); + goto out; + } + if ((expires = mktime(&expires_tm)) == -1) { + err(1, "mktime failed"); + goto out; + } + p.res->expires = expires; + if (!roa_parse_econtent(cms, cmsz, &p)) goto out; @@ -404,6 +422,7 @@ roa_buffer(struct ibuf *b, const struct roa *p) io_simple_buffer(b, &p->valid, sizeof(int)); io_simple_buffer(b, &p->asid, sizeof(uint32_t)); io_simple_buffer(b, &p->ipsz, sizeof(size_t)); + io_simple_buffer(b, &p->expires, sizeof(time_t)); for (i = 0; i < p->ipsz; i++) { io_simple_buffer(b, &p->ips[i].afi, sizeof(enum afi)); @@ -436,6 +455,7 @@ roa_read(int fd) io_simple_read(fd, &p->valid, sizeof(int)); io_simple_read(fd, &p->asid, sizeof(uint32_t)); io_simple_read(fd, &p->ipsz, sizeof(size_t)); + io_simple_read(fd, &p->expires, sizeof(time_t)); if ((p->ips = calloc(p->ipsz, sizeof(struct roa_ip))) == NULL) err(1, NULL); @@ -466,8 +486,8 @@ void roa_insert_vrps(struct vrp_tree *tree, struct roa *roa, size_t *vrps, size_t *uniqs) { - struct vrp *v; - size_t i; + struct vrp *v, *found; + size_t i; for (i = 0; i < roa->ipsz; i++) { if ((v = malloc(sizeof(*v))) == NULL) @@ -478,10 +498,27 @@ roa_insert_vrps(struct vrp_tree *tree, struct roa *roa, size_t *vrps, v->asid = roa->asid; if ((v->tal = strdup(roa->tal)) == NULL) err(1, NULL); - if (RB_INSERT(vrp_tree, tree, v) == NULL) - (*uniqs)++; - else /* already exists */ + v->expires = roa->expires; + + /* + * Check if a similar VRP already exists in the tree. + * If the found VRP expires sooner, update it to this + * ROAs later expiry moment. + */ + if ((found = RB_INSERT(vrp_tree, tree, v)) != NULL) { + /* already exists */ + if (found->expires < v->expires) { + /* update found with preferred data */ + found->expires = roa->expires; + free(found->tal); + found->tal = v->tal; + v->tal = NULL; + } + free(v->tal); free(v); + } else + (*uniqs)++; + (*vrps)++; } } diff --git a/usr.sbin/rpki-client/rpki-client.8 b/usr.sbin/rpki-client/rpki-client.8 index e0f5f88d83d..d35e79e3585 100644 --- a/usr.sbin/rpki-client/rpki-client.8 +++ b/usr.sbin/rpki-client/rpki-client.8 @@ -1,4 +1,4 @@ -.\" $OpenBSD: rpki-client.8,v 1.44 2021/05/05 17:24:00 job Exp $ +.\" $OpenBSD: rpki-client.8,v 1.45 2021/05/06 17:03:57 job Exp $ .\" .\" Copyright (c) 2019 Kristaps Dzonsons .\" @@ -14,7 +14,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: May 5 2021 $ +.Dd $Mdocdate: May 6 2021 $ .Dt RPKI-CLIENT 8 .Os .Sh NAME @@ -66,9 +66,13 @@ with multiple interfaces. .It Fl c Create output in the file .Pa csv -in the output directory as comma-separated values of the prefix in slash notation, -the maximum prefix length, the autonomous system number, and an abbreviation -for the trust anchor the entry is derived from. +in the output directory as comma-separated values of the +.Em Autonomous System , +the prefix in slash notation, the maximum prefix length, an abbreviation for +the +.Em Trust Anchor +the entry is derived from, and the moment the VRP will expire derived from +the chain of X.509 certificates and CRLs in seconds since the Epoch, UTC. .It Fl d Ar cachedir The directory where .Nm @@ -90,7 +94,9 @@ flags and connect with rsync-protocol locations. Create output in the file .Pa json in the output directory as JSON object. -This format is similar to that produced by other RPKI validators. +See +.Fl c +for a description of the fields. .It Fl n Offline mode. Validate the contents of -- 2.20.1