Hi all,
The below changeset adds a column containing the soonest expiration
moment to rpki-client(8)'s CSV and JSON output. This can be useful to CA
operators to monitor progression/stalling of the signer pipeline, but
also to relying parties who wish to avoid routing based on stale RPKI
data.
A VRP's transitive expiration moment is useful in advanced pipelines. A
risk I forsee and wish to mitigate is for an operator to accidentally
end up loading last year's RPKI data into today's routing decision
making. When a RPKI pipeline stops moving, I'd like (old) information
at the tail of the pipeline to start timing out, with per-VRP granularity.
Example of what it looks like:
$ head -1 /var/db/rpki-client/csv ; sort -R /var/db/rpki-client/csv | head
-5
ASN,IP Prefix,Max Length,Trust Anchor,Until
AS38971,178.57.71.0/24,24,ripe,2021-05-04T05:39:30Z
AS24631,81.29.244.0/24,24,ripe,2021-05-04T06:39:47Z
AS206262,185.179.28.0/22,22,ripe,2021-05-04T06:19:24Z
AS21854,65.48.95.0/24,24,arin,2021-05-04T16:00:00Z
AS25229,77.123.160.0/19,20,ripe,2021-05-04T07:19:09Z
There are many expiry timers in the RPKI: the TA's 'notAfter', each CA's
.cer 'notAfter', a Manifest's EE 'notAfter', and the mft's eContent
'nextUpdate', also the 'nextUpdate' on each CRL, and finally the ROA's
own EE cert 'notAfter'.
The below proposal gets the ROA's EE 'notAfter' as starting point (often
1 or more years into the future), and then scans the applicable CRL tree
to find the 'soonest' expiration. I don't think the other timers have
practical applicability in context of the desired outcome. The moment
that matters is the soonest moment an RP starts yeeting ROAs out of the
VRP set, if the RP loses access to the CA Repository via the network.
Kind regards,
Job
Index: usr.sbin/rpki-client/extern.h
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/extern.h,v
retrieving revision 1.63
diff -u -p -r1.63 extern.h
--- usr.sbin/rpki-client/extern.h 14 Apr 2021 18:05:47 -0000 1.63
+++ usr.sbin/rpki-client/extern.h 3 May 2021 13:53:44 -0000
@@ -188,6 +188,7 @@ struct roa {
char *aki; /* AKI */
char *ski; /* SKI */
char *tal; /* basename of TAL for this cert */
+ time_t until; /* 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 until;
};
/*
* Tree of VRP sorted by afi, addr, maxlength and asid
Index: usr.sbin/rpki-client/output-csv.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/output-csv.c,v
retrieving revision 1.9
diff -u -p -r1.9 output-csv.c
--- usr.sbin/rpki-client/output-csv.c 19 Apr 2021 17:04:35 -0000 1.9
+++ usr.sbin/rpki-client/output-csv.c 3 May 2021 13:53:44 -0000
@@ -24,15 +24,21 @@ output_csv(FILE *out, struct vrp_tree *v
{
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,Until\n") < 0)
return -1;
RB_FOREACH(v, vrp_tree, vrps) {
- char buf[64];
+ char buf[64], tbuf[26];
+ struct tm *until;
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)
+
+ setenv("TZ", "UTC", 1);
+ until = localtime(&v->until);
+ strftime(tbuf, sizeof(tbuf), "%FT%TZ", until);
+
+ if (fprintf(out, "AS%u,%s,%u,%s,%s\n", v->asid, buf,
+ v->maxlength, v->tal, tbuf) < 0)
return -1;
}
return 0;
Index: usr.sbin/rpki-client/output-json.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/output-json.c,v
retrieving revision 1.15
diff -u -p -r1.15 output-json.c
--- usr.sbin/rpki-client/output-json.c 8 Apr 2021 19:49:27 -0000 1.15
+++ usr.sbin/rpki-client/output-json.c 3 May 2021 13:53:44 -0000
@@ -80,8 +80,9 @@ outputheader_json(FILE *out, struct stat
int
output_json(FILE *out, struct vrp_tree *vrps, struct stats *st)
{
- char buf[64];
+ char buf[64], tbuf[26];
struct vrp *v;
+ struct tm *until;
int first = 1;
if (outputheader_json(out, st) < 0)
@@ -100,9 +101,13 @@ output_json(FILE *out, struct vrp_tree *
ip_addr_print(&v->addr, v->afi, buf, sizeof(buf));
+ setenv("TZ", "UTC", 1);
+ until = localtime(&v->until);
+ strftime(tbuf, sizeof(tbuf), "%FT%TZ", until);
+
if (fprintf(out, "\t\t{ \"asn\": \"AS%u\", \"prefix\": \"%s\", "
- "\"maxLength\": %u, \"ta\": \"%s\" }",
- v->asid, buf, v->maxlength, v->tal) < 0)
+ "\"maxLength\": %u, \"ta\": \"%s\", \"until\": \"%s\" }",
+ v->asid, buf, v->maxlength, v->tal, tbuf) < 0)
return -1;
}
Index: usr.sbin/rpki-client/parser.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/parser.c,v
retrieving revision 1.7
diff -u -p -r1.7 parser.c
--- usr.sbin/rpki-client/parser.c 1 Apr 2021 08:29:10 -0000 1.7
+++ usr.sbin/rpki-client/parser.c 3 May 2021 13:53:44 -0000
@@ -52,10 +52,12 @@ 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 until;
if ((roa = roa_parse(&x509, entp->file)) == NULL)
return NULL;
@@ -85,17 +87,46 @@ 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, &until,
+ V_ASN1_UTCTIME) != V_ASN1_UTCTIME) {
+ err(1, "ASN1_time_parse failed");
+ goto out;
+ }
+ if (mktime(&until) == -1) {
+ err(1, "mktime failed");
+ goto out;
+ }
+ if (roa->until > mktime(&until))
+ roa->until = mktime(&until);
+ }
/*
* 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->file, auths, roa))
roa->valid = 1;
+
+out:
+ sk_X509_free(chain);
+ sk_X509_CRL_free(crls);
+ X509_free(x509);
return roa;
}
Index: usr.sbin/rpki-client/roa.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/roa.c,v
retrieving revision 1.17
diff -u -p -r1.17 roa.c
--- usr.sbin/rpki-client/roa.c 29 Mar 2021 06:50:44 -0000 1.17
+++ usr.sbin/rpki-client/roa.c 3 May 2021 13:53:44 -0000
@@ -335,6 +335,8 @@ roa_parse(X509 **x509, const char *fn)
size_t cmsz;
unsigned char *cms;
int rc = 0;
+ const ASN1_TIME *at;
+ struct tm until;
memset(&p, 0, sizeof(struct parse));
p.fn = fn;
@@ -358,6 +360,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, &until, 0) == -1) {
+ warnx("%s: ASN1_time_parse failed", fn);
+ goto out;
+ }
+ if (mktime(&until) == -1) {
+ err(1, "mktime failed");
+ goto out;
+ }
+ p.res->until = mktime(&until);
+
if (!roa_parse_econtent(cms, cmsz, &p))
goto out;
@@ -404,6 +421,7 @@ roa_buffer(struct ibuf *b, const struct
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->until, sizeof(time_t));
for (i = 0; i < p->ipsz; i++) {
io_simple_buffer(b, &p->ips[i].afi, sizeof(enum afi));
@@ -436,6 +454,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->until, sizeof(time_t));
if ((p->ips = calloc(p->ipsz, sizeof(struct roa_ip))) == NULL)
err(1, NULL);
@@ -476,6 +495,7 @@ roa_insert_vrps(struct vrp_tree *tree, s
v->addr = roa->ips[i].addr;
v->maxlength = roa->ips[i].maxlength;
v->asid = roa->asid;
+ v->until = roa->until;
if ((v->tal = strdup(roa->tal)) == NULL)
err(1, NULL);
if (RB_INSERT(vrp_tree, tree, v) == NULL)
@@ -522,6 +542,11 @@ vrpcmp(struct vrp *a, struct vrp *b)
if (a->asid < b->asid)
return -1;
+ if (a->until > b->until)
+ return 1;
+ if (a->until < b->until)
+ return -1;
+
return 0;
}
Index: regress/usr.sbin/rpki-client/Makefile.inc
===================================================================
RCS file: /cvs/src/regress/usr.sbin/rpki-client/Makefile.inc,v
retrieving revision 1.9
diff -u -p -r1.9 Makefile.inc
--- regress/usr.sbin/rpki-client/Makefile.inc 1 Apr 2021 06:47:18 -0000
1.9
+++ regress/usr.sbin/rpki-client/Makefile.inc 3 May 2021 13:53:44 -0000
@@ -40,14 +40,23 @@ mft_gen.c: mft.c
cat $> >> [email protected]
mv -f [email protected] $@
-CLEANFILES += mft_gen.c mft_gen.c.tmp
+# Provide missing prototypes for OpenSSL
+roa_gen.c: roa.c
+ echo '#include <openssl/asn1.h>\n' > [email protected]
+ echo 'int ASN1_time_parse(const char *, size_t, struct tm *, int);' \
+ >> [email protected]
+ echo 'int ASN1_time_tm_cmp(struct tm *, struct tm *);' >> [email protected]
+ cat $> >> [email protected]
+ mv -f [email protected] $@
+
+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
Index: regress/usr.sbin/rpki-client/test-roa.c
===================================================================
RCS file: /cvs/src/regress/usr.sbin/rpki-client/test-roa.c,v
retrieving revision 1.10
diff -u -p -r1.10 test-roa.c
--- regress/usr.sbin/rpki-client/test-roa.c 29 Mar 2021 15:47:34 -0000
1.10
+++ regress/usr.sbin/rpki-client/test-roa.c 3 May 2021 13:53:44 -0000
@@ -32,6 +32,14 @@
#include "test-common.c"
+#ifndef ASN1error
+void
+ASN1error(int err)
+{
+ ASN1err(0, err);
+}
+#endif
+
int verbose;
static void
Index: regress/usr.sbin/rpki-client/openssl11/Makefile
===================================================================
RCS file: /cvs/src/regress/usr.sbin/rpki-client/openssl11/Makefile,v
retrieving revision 1.2
diff -u -p -r1.2 Makefile
--- regress/usr.sbin/rpki-client/openssl11/Makefile 9 Nov 2020 16:05:10
-0000 1.2
+++ regress/usr.sbin/rpki-client/openssl11/Makefile 3 May 2021 13:53:44
-0000
@@ -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}/..