Add rudimentary support for BGPsec router certificates
authorjob <job@openbsd.org>
Tue, 5 Oct 2021 11:20:46 +0000 (11:20 +0000)
committerjob <job@openbsd.org>
Tue, 5 Oct 2021 11:20:46 +0000 (11:20 +0000)
OK claudio@

usr.sbin/rpki-client/cert.c
usr.sbin/rpki-client/extern.h
usr.sbin/rpki-client/main.c
usr.sbin/rpki-client/x509.c

index 3ac117f..979995f 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: cert.c,v 1.32 2021/09/09 14:15:49 claudio Exp $ */
+/*     $OpenBSD: cert.c,v 1.33 2021/10/05 11:20:46 job Exp $ */
 /*
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
  *
@@ -1040,6 +1040,8 @@ cert_parse_inner(X509 **xp, const char *fn, int ta)
                        break;
                case NID_subject_key_identifier:
                        break;
+               case NID_ext_key_usage:
+                       break;
                default:
                        /* {
                                char objn[64];
@@ -1059,12 +1061,12 @@ cert_parse_inner(X509 **xp, const char *fn, int ta)
                p.res->aia = x509_get_aia(x, p.fn);
                p.res->crl = x509_get_crl(x, p.fn);
        }
+       p.res->purpose = x509_get_purpose(x, p.fn);
 
        /* Validation on required fields. */
 
        if (p.res->ski == NULL) {
-               warnx("%s: RFC 6487 section 8.4.2: "
-                   "missing SKI", p.fn);
+               warnx("%s: RFC 6487 section 8.4.2: missing SKI", p.fn);
                goto out;
        }
 
@@ -1106,11 +1108,22 @@ cert_parse_inner(X509 **xp, const char *fn, int ta)
                goto out;
        }
 
-       if (p.res->mft == NULL) {
-               warnx("%s: RFC 6487 section 4.8.8: "
-                   "missing SIA", p.fn);
+       if (p.res->ipsz > 0 &&
+           p.res->purpose == CERT_PURPOSE_BGPSEC_ROUTER) {
+               warnx("%s: BGPsec Router Certificate must not have RFC 3779 IP "
+                   "Addresses", p.fn);
+               goto out;
+       }
+
+       if (p.res->purpose == CERT_PURPOSE_CA && p.res->mft == NULL) {
+               warnx("%s: RFC 6487 section 4.8.8: missing SIA", p.fn);
                goto out;
        }
+
+       /*
+        * XXX: also add opposite check: is any SIA present?
+        */
+
        if (X509_up_ref(x) == 0)
                errx(1, "king bula");
 
@@ -1232,6 +1245,7 @@ cert_buffer(struct ibuf *b, const struct cert *p)
        size_t   i;
 
        io_simple_buffer(b, &p->valid, sizeof(int));
+       io_simple_buffer(b, &p->purpose, sizeof(enum cert_purpose));
        io_simple_buffer(b, &p->ipsz, sizeof(size_t));
        for (i = 0; i < p->ipsz; i++)
                cert_ip_buffer(b, &p->ips[i]);
@@ -1239,7 +1253,6 @@ cert_buffer(struct ibuf *b, const struct cert *p)
        io_simple_buffer(b, &p->asz, sizeof(size_t));
        for (i = 0; i < p->asz; i++)
                cert_as_buffer(b, &p->as[i]);
-
        io_str_buffer(b, p->mft);
        io_str_buffer(b, p->notify);
        io_str_buffer(b, p->repo);
@@ -1294,6 +1307,7 @@ cert_read(int fd)
                err(1, NULL);
 
        io_simple_read(fd, &p->valid, sizeof(int));
+       io_simple_read(fd, &p->purpose, sizeof(enum cert_purpose));
        io_simple_read(fd, &p->ipsz, sizeof(size_t));
        p->ips = calloc(p->ipsz, sizeof(struct cert_ip));
        if (p->ips == NULL)
@@ -1309,7 +1323,7 @@ cert_read(int fd)
                cert_as_read(fd, &p->as[i]);
 
        io_str_read(fd, &p->mft);
-       assert(p->mft);
+       assert(p->mft != NULL || p->purpose == CERT_PURPOSE_BGPSEC_ROUTER);
        io_str_read(fd, &p->notify);
        io_str_read(fd, &p->repo);
        io_str_read(fd, &p->crl);
index d0fd3d1..04804a5 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: extern.h,v 1.67 2021/09/09 14:15:49 claudio Exp $ */
+/*     $OpenBSD: extern.h,v 1.68 2021/10/05 11:20:46 job Exp $ */
 /*
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
  *
@@ -101,6 +101,11 @@ struct cert_ip {
        };
 };
 
+enum cert_purpose {
+       CERT_PURPOSE_CA = 1,
+       CERT_PURPOSE_BGPSEC_ROUTER
+};
+
 /*
  * Parsed components of a validated X509 certificate stipulated by RFC
  * 6847 and further (within) by RFC 3779.
@@ -119,6 +124,7 @@ struct cert {
        char            *aia; /* AIA (or NULL, for trust anchor) */
        char            *aki; /* AKI (or NULL, for trust anchor) */
        char            *ski; /* SKI */
+       enum cert_purpose        purpose; /* Certificate Purpose (BGPSec or CA) */
        int              valid; /* validated resources */
        X509            *x509; /* the cert */
 };
@@ -351,6 +357,8 @@ struct      stats {
        size_t   uniqs; /* number of unique vrps */
        size_t   del_files; /* number of files removed in cleanup */
        size_t   del_dirs; /* number of directories removed in cleanup */
+       size_t   bgpsec_routers; /* number of BGPsec Router certs */
+       size_t   bgpsec_invalids; /* invalid bgpsec router certs */
        char    *talnames;
        struct timeval  elapsed_time;
        struct timeval  user_time;
@@ -521,6 +529,7 @@ char                *x509_get_aki(X509 *, int, const char *);
 char           *x509_get_ski(X509 *, const char *);
 char           *x509_get_crl(X509 *, const char *);
 char           *x509_crl_get_aki(X509_CRL *, const char *);
+enum cert_purpose       x509_get_purpose(X509 *, const char *);
 
 /* Output! */
 
index ff6713d..8ebaaa5 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: main.c,v 1.145 2021/08/30 16:05:55 job Exp $ */
+/*     $OpenBSD: main.c,v 1.146 2021/10/05 11:20:46 job Exp $ */
 /*
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
  *
@@ -497,14 +497,22 @@ entity_process(int proc, struct stats *st, struct vrp_tree *tree)
                        break;
                }
                cert = cert_read(proc);
-               if (cert->valid) {
-                       /*
-                        * Process the revocation list from the
-                        * certificate *first*, since it might mark that
-                        * we're revoked and then we don't want to
-                        * process the MFT.
-                        */
-                       queue_add_from_cert(cert);
+               if (cert->purpose == CERT_PURPOSE_CA) {
+                       if (cert->valid) {
+                               /*
+                                * Process the revocation list from the
+                                * certificate *first*, since it might mark that
+                                * we're revoked and then we don't want to
+                                * process the MFT.
+                                */
+                               queue_add_from_cert(cert);
+                       } else
+                               st->certs_invalid++;
+               } else if (cert->purpose == CERT_PURPOSE_BGPSEC_ROUTER) {
+                       if (cert->valid)
+                               st->bgpsec_routers++;
+                       else
+                               st->bgpsec_invalids++;
                } else
                        st->certs_invalid++;
                cert_free(cert);
@@ -1158,6 +1166,8 @@ main(int argc, char *argv[])
            stats.mfts, stats.mfts_fail, stats.mfts_stale);
        logx("Certificate revocation lists: %zu", stats.crls);
        logx("Ghostbuster records: %zu", stats.gbrs);
+       logx("BGPsec Router Certificates: %zu (%zu invalid)",
+           stats.bgpsec_routers, stats.bgpsec_invalids);
        logx("Repositories: %zu", stats.repos);
        logx("Cleanup: removed %zu files, %zu directories",
            stats.del_files, stats.del_dirs);
index 385f1ac..ffd3938 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: x509.c,v 1.21 2021/04/01 06:43:23 claudio Exp $ */
+/*     $OpenBSD: x509.c,v 1.22 2021/10/05 11:20:46 job Exp $ */
 /*
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
  *
 
 #include "extern.h"
 
+static ASN1_OBJECT     *bgpsec_oid;    /* id-kp-bgpsec-router */
+
+static void
+init_oid(void)
+{
+       if ((bgpsec_oid = OBJ_txt2obj("1.3.6.1.5.5.7.3.30", 1)) == NULL)
+               errx(1, "OBJ_txt2obj for %s failed", "1.3.6.1.5.5.7.3.30");
+}
+
 /*
  * Parse X509v3 authority key identifier (AKI), RFC 6487 sec. 4.8.3.
  * Returns the AKI or NULL if it could not be parsed.
@@ -124,6 +133,51 @@ out:
        return res;
 }
 
+/*
+ * Check the certificate's purpose: CA or BGPsec Router.
+ * Return a member of enum cert_purpose.
+ */
+enum cert_purpose
+x509_get_purpose(X509 *x, const char *fn)
+{
+       EXTENDED_KEY_USAGE              *eku = NULL;
+       int                              crit;
+       enum cert_purpose                purpose = 0;
+
+       if (X509_check_ca(x) == 1) {
+               purpose = CERT_PURPOSE_CA;
+               goto out;
+       }
+
+       eku = X509_get_ext_d2i(x, NID_ext_key_usage, &crit, NULL);
+       if (eku == NULL) {
+               warnx("%s: EKU: extension missing", fn);
+               goto out;
+       }
+       if (crit != 0) {
+               warnx("%s: EKU: extension must not be marked critical", fn);
+               goto out;
+       }
+       if (sk_ASN1_OBJECT_num(eku) != 1) {
+               warnx("%s: EKU: expected 1 purpose, have %d", fn,
+                   sk_ASN1_OBJECT_num(eku));
+               goto out;
+       }
+
+       if (bgpsec_oid == NULL)
+               init_oid();
+
+       if (OBJ_cmp(bgpsec_oid, sk_ASN1_OBJECT_value(eku, 0)) == 0) {
+               purpose = CERT_PURPOSE_BGPSEC_ROUTER;
+               goto out;
+       }
+
+ out:
+       EXTENDED_KEY_USAGE_free(eku);
+       return purpose;
+}
+
+
 /*
  * Parse the Authority Information Access (AIA) extension
  * See RFC 6487, section 4.8.7 for details.