Add an 'expires' column to CSV & JSON output
authorjob <job@openbsd.org>
Thu, 6 May 2021 17:03:57 +0000 (17:03 +0000)
committerjob <job@openbsd.org>
Thu, 6 May 2021 17:03:57 +0000 (17:03 +0000)
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
regress/usr.sbin/rpki-client/openssl11/Makefile
regress/usr.sbin/rpki-client/test-roa.c
usr.sbin/rpki-client/extern.h
usr.sbin/rpki-client/output-csv.c
usr.sbin/rpki-client/output-json.c
usr.sbin/rpki-client/parser.c
usr.sbin/rpki-client/roa.c
usr.sbin/rpki-client/rpki-client.8

index 5aa3cf9..5b2e6b5 100644 (file)
@@ -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 <openssl/asn1.h>\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
 
index 9481fa8..87b8334 100644 (file)
@@ -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}/..
index 2f3c77b..da22e4b 100644 (file)
@@ -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 <kristaps@bsd.lv>
  *
 
 #include "test-common.c"
 
+#ifndef ASN1error
+void
+ASN1error(int err)
+{
+       ASN1err(0, err);
+}
+#endif
+
 int verbose;
 
 static void
index 1323c33..f6a7a76 100644 (file)
@@ -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 <kristaps@bsd.lv>
  *
@@ -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
index 26033eb..971672b 100644 (file)
@@ -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 <claudio@openbsd.org>
  *
@@ -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;
index 8ca411a..d1cc32b 100644 (file)
@@ -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 <claudio@openbsd.org>
  *
@@ -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;
        }
 
index 7ba4221..1fc86c2 100644 (file)
@@ -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 <claudio@openbsd.org>
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -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;
 }
 
index f9e34f0..236df32 100644 (file)
@@ -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 <kristaps@bsd.lv>
  *
@@ -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)++;
        }
 }
index e0f5f88..d35e79e 100644 (file)
@@ -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 <kristaps@bsd.lv>
 .\"
@@ -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