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}/..

Reply via email to