From fdfddccf0d0f27b0d97b3cb9d2fd91ffea08a495 Mon Sep 17 00:00:00 2001 From: job Date: Tue, 5 Oct 2021 11:20:46 +0000 Subject: [PATCH] Add rudimentary support for BGPsec router certificates OK claudio@ --- usr.sbin/rpki-client/cert.c | 30 ++++++++++++++----- usr.sbin/rpki-client/extern.h | 11 ++++++- usr.sbin/rpki-client/main.c | 28 ++++++++++++------ usr.sbin/rpki-client/x509.c | 56 ++++++++++++++++++++++++++++++++++- 4 files changed, 106 insertions(+), 19 deletions(-) diff --git a/usr.sbin/rpki-client/cert.c b/usr.sbin/rpki-client/cert.c index 3ac117f437a..979995f8909 100644 --- a/usr.sbin/rpki-client/cert.c +++ b/usr.sbin/rpki-client/cert.c @@ -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 * @@ -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); diff --git a/usr.sbin/rpki-client/extern.h b/usr.sbin/rpki-client/extern.h index d0fd3d1a4d7..04804a52882 100644 --- a/usr.sbin/rpki-client/extern.h +++ b/usr.sbin/rpki-client/extern.h @@ -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 * @@ -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! */ diff --git a/usr.sbin/rpki-client/main.c b/usr.sbin/rpki-client/main.c index ff6713d6296..8ebaaa5c800 100644 --- a/usr.sbin/rpki-client/main.c +++ b/usr.sbin/rpki-client/main.c @@ -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 * @@ -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); diff --git a/usr.sbin/rpki-client/x509.c b/usr.sbin/rpki-client/x509.c index 385f1ace68d..ffd393804d4 100644 --- a/usr.sbin/rpki-client/x509.c +++ b/usr.sbin/rpki-client/x509.c @@ -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 * @@ -28,6 +28,15 @@ #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. -- 2.20.1