From: job Date: Tue, 30 Aug 2022 18:56:49 +0000 (+0000) Subject: Add support for ASPA objects (draft-ietf-sidrops-aspa-profile-10) X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=a29ddfd5ea76f5c564c0bf78f48af3e0cc656c0d;p=openbsd Add support for ASPA objects (draft-ietf-sidrops-aspa-profile-10) ASPA objects are published in the RPKI and can be used to detect and mitigate BGP route leaks. Validated ASPA Payloads are visible through filemode (-f) and the JSON output format (-j). With feedback from tb@ OK claudio@ tb@ --- diff --git a/usr.sbin/rpki-client/Makefile b/usr.sbin/rpki-client/Makefile index a27dcba8bb0..a7198aa4874 100644 --- a/usr.sbin/rpki-client/Makefile +++ b/usr.sbin/rpki-client/Makefile @@ -1,8 +1,8 @@ -# $OpenBSD: Makefile,v 1.25 2022/05/09 17:02:34 job Exp $ +# $OpenBSD: Makefile,v 1.26 2022/08/30 18:56:49 job Exp $ PROG= rpki-client -SRCS= as.c cert.c cms.c crl.c encoding.c filemode.c gbr.c http.c io.c ip.c \ - log.c main.c mft.c mkdir.c output.c output-bgpd.c output-bird.c \ +SRCS= as.c aspa.c cert.c cms.c crl.c encoding.c filemode.c gbr.c http.c io.c \ + ip.c log.c main.c mft.c mkdir.c output.c output-bgpd.c output-bird.c \ output-csv.c output-json.c parser.c print.c repo.c roa.c rrdp.c \ rrdp_delta.c rrdp_notification.c rrdp_snapshot.c rrdp_util.c \ rsc.c rsync.c tal.c validate.c x509.c diff --git a/usr.sbin/rpki-client/aspa.c b/usr.sbin/rpki-client/aspa.c new file mode 100644 index 00000000000..2bd528cc142 --- /dev/null +++ b/usr.sbin/rpki-client/aspa.c @@ -0,0 +1,440 @@ +/* $OpenBSD: aspa.c,v 1.1 2022/08/30 18:56:49 job Exp $ */ +/* + * Copyright (c) 2022 Job Snijders + * Copyright (c) 2022 Theo Buehler + * Copyright (c) 2019 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "extern.h" + +/* + * Parse results and data of the ASPA object. + */ +struct parse { + const char *fn; /* ASPA file name */ + struct aspa *res; /* results */ +}; + +extern ASN1_OBJECT *aspa_oid; + +/* + * Types and templates for ASPA eContent draft-ietf-sidrops-aspa-profile-08 + */ + +typedef struct { + ASN1_INTEGER *providerASID; + ASN1_OCTET_STRING *afiLimit; +} ProviderAS; + +DECLARE_STACK_OF(ProviderAS); + +#ifndef DEFINE_STACK_OF +#define sk_ProviderAS_num(sk) SKM_sk_num(ProviderAS, (sk)) +#define sk_ProviderAS_value(sk, i) SKM_sk_value(ProviderAS, (sk), (i)) +#endif + +ASN1_SEQUENCE(ProviderAS) = { + ASN1_SIMPLE(ProviderAS, providerASID, ASN1_INTEGER), + ASN1_OPT(ProviderAS, afiLimit, ASN1_OCTET_STRING), +} ASN1_SEQUENCE_END(ProviderAS); + +typedef struct { + ASN1_INTEGER *version; + ASN1_INTEGER *customerASID; + STACK_OF(ProviderAS) *providers; +} ASProviderAttestation; + +ASN1_SEQUENCE(ASProviderAttestation) = { + ASN1_IMP_OPT(ASProviderAttestation, version, ASN1_INTEGER, 0), + ASN1_SIMPLE(ASProviderAttestation, customerASID, ASN1_INTEGER), + ASN1_SEQUENCE_OF(ASProviderAttestation, providers, ProviderAS), +} ASN1_SEQUENCE_END(ASProviderAttestation); + +DECLARE_ASN1_FUNCTIONS(ASProviderAttestation); +IMPLEMENT_ASN1_FUNCTIONS(ASProviderAttestation); + +/* + * Parse the ProviderASSet sequence. + * Return zero on failure, non-zero on success. + */ +static int +aspa_parse_providers(struct parse *p, const STACK_OF(ProviderAS) *providers) +{ + ProviderAS *pa; + struct aspa_provider provider; + size_t providersz, i; + + memset(&provider, 0, sizeof(provider)); + + if ((providersz = sk_ProviderAS_num(providers)) == 0) { + warnx("%s: ASPA: ProviderASSet needs at least one entry", + p->fn); + return 0; + } + + if (providersz >= MAX_ASPA_PROVIDERS) { + warnx("%s: ASPA: too many providers (more than %d)", p->fn, + MAX_ASPA_PROVIDERS); + return 0; + } + + p->res->providers = calloc(providersz, sizeof(provider)); + if (p->res->providers == NULL) + err(1, NULL); + + for (i = 0; i < providersz; i++) { + pa = sk_ProviderAS_value(providers, i); + + if (!as_id_parse(pa->providerASID, &provider.as)) { + warnx("%s: ASPA: malformed ProviderAS", p->fn); + return 0; + } + + if (p->res->custasid == provider.as) { + warnx("%s: ASPA: CustomerASID can't also be Provider", + p->fn); + return 0; + } + + if (i > 0) { + if (p->res->providers[i - 1].as > provider.as) { + warnx("%s: ASPA: invalid ProviderASSet order", + p->fn); + return 0; + } + if (p->res->providers[i - 1].as == provider.as) { + warnx("%s: ASPA: duplicate ProviderAS", p->fn); + return 0; + } + } + + if (pa->afiLimit != NULL && !ip_addr_afi_parse(p->fn, + pa->afiLimit, &provider.afi)) { + warnx("%s: ASPA: invalid afiLimit", p->fn); + return 0; + } + + p->res->providers[p->res->providersz++] = provider; + } + + return 1; +} + +/* + * Parse the eContent of an ASPA file. + * Returns zero on failure, non-zero on success. + */ +static int +aspa_parse_econtent(const unsigned char *d, size_t dsz, struct parse *p) +{ + ASProviderAttestation *aspa; + int rc = 0; + + if ((aspa = d2i_ASProviderAttestation(NULL, &d, dsz)) == NULL) { + cryptowarnx("%s: ASPA: failed to parse ASProviderAttestation", + p->fn); + goto out; + } + + if (!valid_econtent_version(p->fn, aspa->version)) + goto out; + + if (!as_id_parse(aspa->customerASID, &p->res->custasid)) { + warnx("%s: malformed CustomerASID", p->fn); + goto out; + } + + if (!aspa_parse_providers(p, aspa->providers)) + goto out; + + rc = 1; + out: + ASProviderAttestation_free(aspa); + return rc; +} + +/* + * Parse a full ASPA file. + * Returns the payload or NULL if the file was malformed. + */ +struct aspa * +aspa_parse(X509 **x509, const char *fn, const unsigned char *der, size_t len) +{ + struct parse p; + size_t cmsz; + unsigned char *cms; + const ASN1_TIME *at; + struct cert *cert = NULL; + int rc = 0; + + memset(&p, 0, sizeof(struct parse)); + p.fn = fn; + + cms = cms_parse_validate(x509, fn, der, len, aspa_oid, &cmsz); + if (cms == NULL) + return NULL; + + if ((p.res = calloc(1, sizeof(*p.res))) == NULL) + err(1, NULL); + + if (!x509_get_aia(*x509, fn, &p.res->aia)) + goto out; + if (!x509_get_aki(*x509, fn, &p.res->aki)) + goto out; + if (!x509_get_ski(*x509, fn, &p.res->ski)) + goto out; + if (p.res->aia == NULL || p.res->aki == NULL || p.res->ski == NULL) { + warnx("%s: RFC 6487 section 4.8: " + "missing AIA, AKI or SKI X509 extension", fn); + goto out; + } + + if (X509_get_ext_by_NID(*x509, NID_sbgp_ipAddrBlock, -1) != -1) { + warnx("%s: superfluous IP Resources extension present", fn); + goto out; + } + + at = X509_get0_notAfter(*x509); + if (at == NULL) { + warnx("%s: X509_get0_notAfter failed", fn); + goto out; + } + if (x509_get_time(at, &p.res->expires) == -1) { + warnx("%s: ASN1_time_parse failed", fn); + goto out; + } + + if (!aspa_parse_econtent(cms, cmsz, &p)) + goto out; + + if ((cert = cert_parse_ee_cert(fn, *x509)) == NULL) + goto out; + + p.res->valid = valid_aspa(fn, cert, p.res); + + rc = 1; + out: + if (rc == 0) { + aspa_free(p.res); + p.res = NULL; + X509_free(*x509); + *x509 = NULL; + } + free(cms); + return p.res; +} + +/* + * Free an ASPA pointer. + * Safe to call with NULL. + */ +void +aspa_free(struct aspa *p) +{ + if (p == NULL) + return; + + free(p->aia); + free(p->aki); + free(p->ski); + free(p->providers); + free(p); +} + +/* + * Serialise parsed ASPA content. + * See aspa_read() for the reader on the other side. + */ +void +aspa_buffer(struct ibuf *b, const struct aspa *p) +{ + io_simple_buffer(b, &p->valid, sizeof(p->valid)); + io_simple_buffer(b, &p->custasid, sizeof(p->custasid)); + io_simple_buffer(b, &p->expires, sizeof(p->expires)); + + io_simple_buffer(b, &p->providersz, sizeof(size_t)); + io_simple_buffer(b, p->providers, + p->providersz * sizeof(p->providers[0])); + + io_str_buffer(b, p->aia); + io_str_buffer(b, p->aki); + io_str_buffer(b, p->ski); +} + +/* + * Read parsed ASPA content from descriptor. + * See aspa_buffer() for writer. + * Result must be passed to aspa_free(). + */ +struct aspa * +aspa_read(struct ibuf *b) +{ + struct aspa *p; + + if ((p = calloc(1, sizeof(struct aspa))) == NULL) + err(1, NULL); + + io_read_buf(b, &p->valid, sizeof(p->valid)); + io_read_buf(b, &p->custasid, sizeof(p->custasid)); + io_read_buf(b, &p->expires, sizeof(p->expires)); + + io_read_buf(b, &p->providersz, sizeof(size_t)); + if ((p->providers = calloc(p->providersz, + sizeof(struct aspa_provider))) == NULL) + err(1, NULL); + io_read_buf(b, p->providers, p->providersz * sizeof(p->providers[0])); + + io_read_str(b, &p->aia); + io_read_str(b, &p->aki); + io_read_str(b, &p->ski); + assert(p->aia && p->aki && p->ski); + + return p; +} + +/* + * draft-ietf-sidrops-8210bis section 5.12 states: + * + * "The router MUST see at most one ASPA for a given AFI from a cache for + * a particular Customer ASID active at any time. As a number of conditions + * in the global RPKI may present multiple valid ASPA RPKI records for a + * single customer to a particular RP cache, this places a burden on the + * cache to form the union of multiple ASPA records it has received from + * the global RPKI into one RPKI-To-Router (RTR) ASPA PDU." + * + * The above described 'burden' (which is specific to RTR) is resolved in + * insert_vap() and aspa_insert_vaps() functions below. + * + * XXX: for bgpd(8), ASPA config injection (via /var/db/rpki-client/openbgpd) + * we probably want to undo the 'burden solving' and compress into implicit + * AFIs. + */ + +/* + * If the CustomerASID (CAS) showed up before, append the ProviderAS (PAS); + * otherwise create a new entry in the RB tree. + * Ensure there are no duplicates in the 'providers' array. + * Always compare 'expires': use the soonest expiration moment. + */ +static void +insert_vap(struct vap_tree *tree, uint32_t cas, uint32_t pas, time_t expires, + enum afi afi) +{ + struct vap *v, *found; + size_t i; + + if ((v = malloc(sizeof(*v))) == NULL) + err(1, NULL); + v->afi = afi; + v->custasid = cas; + v->expires = expires; + + if ((found = RB_INSERT(vap_tree, tree, v)) == NULL) { + if ((v->providers = malloc(sizeof(uint32_t))) == NULL) + err(1, NULL); + + v->providers[0] = pas; + v->providersz = 1; + + return; + } + + free(v); + + if (found->expires > expires) + found->expires = expires; + + for (i = 0; i < found->providersz; i++) { + if (found->providers[i] == pas) + return; + } + + found->providers = reallocarray(found->providers, + found->providersz + 1, sizeof(uint32_t)); + if (found->providers == NULL) + err(1, NULL); + found->providers[found->providersz++] = pas; +} + +/* + * Add each ProviderAS entry into the Validated ASPA Providers (VAP) tree. + * Updates "vaps" to be the total number of VAPs, and "uniqs" to be the + * pre-'AFI explosion' deduplicated count. + */ +void +aspa_insert_vaps(struct vap_tree *tree, struct aspa *aspa, size_t *vaps, + size_t *uniqs) +{ + size_t i; + uint32_t cas, pas; + time_t expires; + + cas = aspa->custasid; + expires = aspa->expires; + + *uniqs += aspa->providersz; + + for (i = 0; i < aspa->providersz; i++) { + pas = aspa->providers[i].as; + + switch (aspa->providers[i].afi) { + case AFI_IPV4: + insert_vap(tree, cas, pas, expires, AFI_IPV4); + (*vaps)++; + break; + case AFI_IPV6: + insert_vap(tree, cas, pas, expires, AFI_IPV6); + (*vaps)++; + break; + default: + insert_vap(tree, cas, pas, expires, AFI_IPV4); + insert_vap(tree, cas, pas, expires, AFI_IPV6); + *vaps += 2; + break; + } + } +} + +static inline int +vapcmp(struct vap *a, struct vap *b) +{ + if (a->afi > b->afi) + return 1; + if (a->afi < b->afi) + return -1; + + if (a->custasid > b->custasid) + return 1; + if (a->custasid < b->custasid) + return -1; + + return 0; +} + +RB_GENERATE(vap_tree, vap, entry, vapcmp); diff --git a/usr.sbin/rpki-client/extern.h b/usr.sbin/rpki-client/extern.h index 803083815f6..f2c493c962f 100644 --- a/usr.sbin/rpki-client/extern.h +++ b/usr.sbin/rpki-client/extern.h @@ -1,4 +1,4 @@ -/* $OpenBSD: extern.h,v 1.150 2022/08/19 12:45:53 tb Exp $ */ +/* $OpenBSD: extern.h,v 1.151 2022/08/30 18:56:49 job Exp $ */ /* * Copyright (c) 2019 Kristaps Dzonsons * @@ -181,6 +181,7 @@ enum rtype { RTYPE_REPO, RTYPE_FILE, RTYPE_RSC, + RTYPE_ASPA, }; enum location { @@ -282,6 +283,45 @@ struct gbr { char *ski; /* SKI */ }; +struct aspa_provider { + uint32_t as; + enum afi afi; +}; + +/* + * A single ASPA record + */ +struct aspa { + int valid; /* contained in parent auth */ + int talid; /* TAL the ASPA is chained up to */ + char *aia; /* AIA */ + char *aki; /* AKI */ + char *ski; /* SKI */ + uint32_t custasid; /* the customerASID */ + struct aspa_provider *providers; /* the providers */ + size_t providersz; /* number of providers */ + time_t expires; /* NotAfter of the ASPA EE cert */ +}; + +/* + * A Validated ASPA Payload (VAP) tree element. + * To ease transformation, this struct mimicks ASPA RTR PDU structure. + */ +struct vap { + RB_ENTRY(vap) entry; + enum afi afi; + uint32_t custasid; + uint32_t *providers; + size_t providersz; + time_t expires; +}; + +/* + * Tree of VAPs sorted by afi, custasid, and provideras. + */ +RB_HEAD(vap_tree, vap); +RB_PROTOTYPE(vap_tree, vap, entry, vapcmp); + /* * A single VRP element (including ASID) */ @@ -432,6 +472,11 @@ struct stats { size_t rrdp_fails; /* failed rrdp repositories */ size_t crls; /* revocation lists */ size_t gbrs; /* ghostbuster records */ + size_t aspas; /* ASPA objects */ + size_t aspas_fail; /* ASPA objects failing syntactic parse */ + size_t aspas_invalid; /* ASPAs with invalid customerASID */ + size_t vaps; /* total number of Validated ASPA Payloads */ + size_t vaps_uniqs; /* total number of unique VAPs */ size_t vrps; /* total number of vrps */ size_t uniqs; /* number of unique vrps */ size_t del_files; /* number of files removed in cleanup */ @@ -496,6 +541,14 @@ void rsc_free(struct rsc *); struct rsc *rsc_parse(X509 **, const char *, const unsigned char *, size_t); +void aspa_buffer(struct ibuf *, const struct aspa *); +void aspa_free(struct aspa *); +void aspa_insert_vaps(struct vap_tree *, struct aspa *, size_t *, + size_t *); +struct aspa *aspa_parse(X509 **, const char *, const unsigned char *, + size_t); +struct aspa *aspa_read(struct ibuf *); + /* crl.c */ struct crl *crl_parse(const char *, const unsigned char *, size_t); struct crl *crl_get(struct crl_tree *, const struct auth *); @@ -519,6 +572,7 @@ int valid_x509(char *, X509_STORE_CTX *, X509 *, struct auth *, struct crl *, int); int valid_rsc(const char *, struct cert *, struct rsc *); int valid_econtent_version(const char *, const ASN1_INTEGER *); +int valid_aspa(const char *, struct cert *, struct aspa *); /* Working with CMS. */ unsigned char *cms_parse_validate(X509 **, const char *, @@ -664,6 +718,7 @@ void mft_print(const X509 *, const struct mft *); void roa_print(const X509 *, const struct roa *); void gbr_print(const X509 *, const struct gbr *); void rsc_print(const X509 *, const struct rsc *); +void aspa_print(const X509 *, const struct aspa *); /* Output! */ @@ -674,20 +729,20 @@ extern int outformats; #define FORMAT_JSON 0x08 int outputfiles(struct vrp_tree *v, struct brk_tree *b, - struct stats *); + struct vap_tree *, struct stats *); int outputheader(FILE *, struct stats *); int output_bgpd(FILE *, struct vrp_tree *, struct brk_tree *, - struct stats *); + struct vap_tree *, struct stats *); int output_bird1v4(FILE *, struct vrp_tree *, struct brk_tree *, - struct stats *); + struct vap_tree *, struct stats *); int output_bird1v6(FILE *, struct vrp_tree *, struct brk_tree *, - struct stats *); + struct vap_tree *, struct stats *); int output_bird2(FILE *, struct vrp_tree *, struct brk_tree *, - struct stats *); + struct vap_tree *, struct stats *); int output_csv(FILE *, struct vrp_tree *, struct brk_tree *, - struct stats *); + struct vap_tree *, struct stats *); int output_json(FILE *, struct vrp_tree *, struct brk_tree *, - struct stats *); + struct vap_tree *, struct stats *); void logx(const char *fmt, ...) __attribute__((format(printf, 1, 2))); @@ -723,6 +778,9 @@ int mkpathat(int, const char *); /* Maximum number of FileAndHash entries per manifest. */ #define MAX_MANIFEST_ENTRIES 100000 +/* Maximum number of Providers per ASPA object. */ +#define MAX_ASPA_PROVIDERS 10000 + /* Maximum depth of the RPKI tree. */ #define MAX_CERT_DEPTH 12 diff --git a/usr.sbin/rpki-client/filemode.c b/usr.sbin/rpki-client/filemode.c index 95993b05f98..5ebb6e00d9e 100644 --- a/usr.sbin/rpki-client/filemode.c +++ b/usr.sbin/rpki-client/filemode.c @@ -1,4 +1,4 @@ -/* $OpenBSD: filemode.c,v 1.12 2022/08/25 18:12:05 job Exp $ */ +/* $OpenBSD: filemode.c,v 1.13 2022/08/30 18:56:49 job Exp $ */ /* * Copyright (c) 2019 Claudio Jeker * Copyright (c) 2019 Kristaps Dzonsons @@ -268,6 +268,7 @@ proc_parser_file(char *file, unsigned char *buf, size_t len) struct gbr *gbr = NULL; struct tal *tal = NULL; struct rsc *rsc = NULL; + struct aspa *aspa = NULL; char *aia = NULL, *aki = NULL; char filehash[SHA256_DIGEST_LENGTH]; char *hash; @@ -367,6 +368,14 @@ proc_parser_file(char *file, unsigned char *buf, size_t len) aia = rsc->aia; aki = rsc->aki; break; + case RTYPE_ASPA: + aspa = aspa_parse(&x509, file, buf, len); + if (aspa == NULL) + break; + aspa_print(x509, aspa); + aia = aspa->aia; + aki = aspa->aki; + break; default: printf("%s: unsupported file type\n", file); break; @@ -392,10 +401,19 @@ proc_parser_file(char *file, unsigned char *buf, size_t len) c = crl_get(&crlt, a); if ((status = valid_x509(file, ctx, x509, a, c, 0))) { - if (type == RTYPE_ROA) + switch (type) { + case RTYPE_ROA: status = roa->valid; - else if (type == RTYPE_RSC) + break; + case RTYPE_RSC: status = rsc->valid; + break; + case RTYPE_ASPA: + status = aspa->valid; + break; + default: + break; + } } if (status) printf("OK"); @@ -450,6 +468,7 @@ proc_parser_file(char *file, unsigned char *buf, size_t len) gbr_free(gbr); tal_free(tal); rsc_free(rsc); + aspa_free(aspa); } /* diff --git a/usr.sbin/rpki-client/main.c b/usr.sbin/rpki-client/main.c index f6959bdc6b8..d67925b89d3 100644 --- a/usr.sbin/rpki-client/main.c +++ b/usr.sbin/rpki-client/main.c @@ -1,4 +1,4 @@ -/* $OpenBSD: main.c,v 1.213 2022/08/29 18:28:35 tb Exp $ */ +/* $OpenBSD: main.c,v 1.214 2022/08/30 18:56:49 job Exp $ */ /* * Copyright (c) 2021 Claudio Jeker * Copyright (c) 2019 Kristaps Dzonsons @@ -475,13 +475,14 @@ queue_add_from_cert(const struct cert *cert) */ static void entity_process(struct ibuf *b, struct stats *st, struct vrp_tree *tree, - struct brk_tree *brktree) + struct brk_tree *brktree, struct vap_tree *vaptree) { enum rtype type; struct tal *tal; struct cert *cert; struct mft *mft; struct roa *roa; + struct aspa *aspa; char *file; int c; @@ -568,6 +569,21 @@ entity_process(struct ibuf *b, struct stats *st, struct vrp_tree *tree, break; case RTYPE_FILE: break; + case RTYPE_ASPA: + st->aspas++; + io_read_buf(b, &c, sizeof(c)); + if (c == 0) { + st->aspas_fail++; + break; + } + aspa = aspa_read(b); + if (aspa->valid) + aspa_insert_vaps(vaptree, aspa, &st->vaps, + &st->vaps_uniqs); + else + st->aspas_invalid++; + aspa_free(aspa); + break; default: errx(1, "unknown entity type %d", type); } @@ -791,6 +807,7 @@ main(int argc, char *argv[]) const char *skiplistfile = NULL; struct vrp_tree vrps = RB_INITIALIZER(&vrps); struct brk_tree brks = RB_INITIALIZER(&brks); + struct vap_tree vaps = RB_INITIALIZER(&vaps); struct rusage ru; struct timeval start_time, now_time; @@ -1157,7 +1174,7 @@ main(int argc, char *argv[]) if ((pfd[0].revents & POLLIN)) { b = io_buf_read(proc, &procbuf); if (b != NULL) { - entity_process(b, &stats, &vrps, &brks); + entity_process(b, &stats, &vrps, &brks, &vaps); ibuf_free(b); } } @@ -1240,7 +1257,7 @@ main(int argc, char *argv[]) if (fchdir(outdirfd) == -1) err(1, "fchdir output dir"); - if (outputfiles(&vrps, &brks, &stats)) + if (outputfiles(&vrps, &brks, &vaps, &stats)) rc = 1; printf("Processing time %lld seconds " @@ -1251,6 +1268,8 @@ main(int argc, char *argv[]) printf("Skiplist entries: %zu\n", stats.skiplistentries); printf("Route Origin Authorizations: %zu (%zu failed parse, %zu invalid)\n", stats.roas, stats.roas_fail, stats.roas_invalid); + printf("AS Provider Attestations: %zu (%zu failed parse, %zu invalid)\n", + stats.aspas, stats.aspas_fail, stats.aspas_invalid); printf("BGPsec Router Certificates: %zu\n", stats.brks); printf("Certificates: %zu (%zu invalid)\n", stats.certs, stats.certs_fail); @@ -1264,6 +1283,7 @@ main(int argc, char *argv[]) printf("Cleanup: removed %zu files, %zu directories, %zu superfluous\n", stats.del_files, stats.del_dirs, stats.extra_files); printf("VRP Entries: %zu (%zu unique)\n", stats.vrps, stats.uniqs); + printf("VAP Entries: %zu (%zu unique)\n", stats.vaps, stats.vaps_uniqs); /* Memory cleanup. */ repo_free(); diff --git a/usr.sbin/rpki-client/mft.c b/usr.sbin/rpki-client/mft.c index 64477b10eb4..8631071e753 100644 --- a/usr.sbin/rpki-client/mft.c +++ b/usr.sbin/rpki-client/mft.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mft.c,v 1.73 2022/08/18 15:20:27 job Exp $ */ +/* $OpenBSD: mft.c,v 1.74 2022/08/30 18:56:49 job Exp $ */ /* * Copyright (c) 2022 Theo Buehler * Copyright (c) 2019 Kristaps Dzonsons @@ -166,6 +166,8 @@ rtype_from_file_extension(const char *fn) return RTYPE_GBR; if (strcasecmp(fn + sz - 4, ".sig") == 0) return RTYPE_RSC; + if (strcasecmp(fn + sz - 4, ".asa") == 0) + return RTYPE_ASPA; return RTYPE_INVALID; } @@ -205,6 +207,7 @@ rtype_from_mftfile(const char *fn) case RTYPE_CRL: case RTYPE_GBR: case RTYPE_ROA: + case RTYPE_ASPA: return type; default: return RTYPE_INVALID; diff --git a/usr.sbin/rpki-client/output-bgpd.c b/usr.sbin/rpki-client/output-bgpd.c index ff033142398..54c00e53c6f 100644 --- a/usr.sbin/rpki-client/output-bgpd.c +++ b/usr.sbin/rpki-client/output-bgpd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: output-bgpd.c,v 1.23 2021/10/11 16:50:03 job Exp $ */ +/* $OpenBSD: output-bgpd.c,v 1.24 2022/08/30 18:56:49 job Exp $ */ /* * Copyright (c) 2019 Kristaps Dzonsons * @@ -21,7 +21,7 @@ int output_bgpd(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks, - struct stats *st) + struct vap_tree *vaps, struct stats *st) { struct vrp *v; diff --git a/usr.sbin/rpki-client/output-bird.c b/usr.sbin/rpki-client/output-bird.c index fa4211c9870..be8e04d5c1e 100644 --- a/usr.sbin/rpki-client/output-bird.c +++ b/usr.sbin/rpki-client/output-bird.c @@ -1,4 +1,4 @@ -/* $OpenBSD: output-bird.c,v 1.14 2022/05/15 16:43:34 tb Exp $ */ +/* $OpenBSD: output-bird.c,v 1.15 2022/08/30 18:56:49 job Exp $ */ /* * Copyright (c) 2019 Claudio Jeker * Copyright (c) 2020 Robert Scheck @@ -22,7 +22,7 @@ int output_bird1v4(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks, - struct stats *st) + struct vap_tree *vaps, struct stats *st) { extern const char *bird_tablename; struct vrp *v; @@ -51,7 +51,7 @@ output_bird1v4(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks, int output_bird1v6(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks, - struct stats *st) + struct vap_tree *vaps, struct stats *st) { extern const char *bird_tablename; struct vrp *v; @@ -80,7 +80,7 @@ output_bird1v6(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks, int output_bird2(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks, - struct stats *st) + struct vap_tree *vaps, struct stats *st) { extern const char *bird_tablename; struct vrp *v; diff --git a/usr.sbin/rpki-client/output-csv.c b/usr.sbin/rpki-client/output-csv.c index 80cc0e78901..6338b7cad7c 100644 --- a/usr.sbin/rpki-client/output-csv.c +++ b/usr.sbin/rpki-client/output-csv.c @@ -1,4 +1,4 @@ -/* $OpenBSD: output-csv.c,v 1.12 2021/11/04 11:32:55 claudio Exp $ */ +/* $OpenBSD: output-csv.c,v 1.13 2022/08/30 18:56:49 job Exp $ */ /* * Copyright (c) 2019 Claudio Jeker * @@ -21,7 +21,7 @@ int output_csv(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks, - struct stats *st) + struct vap_tree *vaps, struct stats *st) { struct vrp *v; diff --git a/usr.sbin/rpki-client/output-json.c b/usr.sbin/rpki-client/output-json.c index bb404eff613..51d03c31f0f 100644 --- a/usr.sbin/rpki-client/output-json.c +++ b/usr.sbin/rpki-client/output-json.c @@ -1,4 +1,4 @@ -/* $OpenBSD: output-json.c,v 1.26 2022/05/15 16:43:34 tb Exp $ */ +/* $OpenBSD: output-json.c,v 1.27 2022/08/30 18:56:49 job Exp $ */ /* * Copyright (c) 2019 Claudio Jeker * @@ -46,6 +46,9 @@ outputheader_json(FILE *out, struct stats *st) "\t\t\"roas\": %zu,\n" "\t\t\"failedroas\": %zu,\n" "\t\t\"invalidroas\": %zu,\n" + "\t\t\"aspas\": %zu,\n" + "\t\t\"failedaspas\": %zu,\n" + "\t\t\"invalidaspas\": %zu,\n" "\t\t\"bgpsec_pubkeys\": %zu,\n" "\t\t\"certificates\": %zu,\n" "\t\t\"invalidcertificates\": %zu,\n" @@ -55,6 +58,7 @@ outputheader_json(FILE *out, struct stats *st) hn, tbuf, (long long)st->elapsed_time.tv_sec, (long long)st->user_time.tv_sec, (long long)st->system_time.tv_sec, st->roas, st->roas_fail, st->roas_invalid, + st->aspas, st->aspas_fail, st->aspas_invalid, st->brks, st->certs, st->certs_fail, st->tals, talsz - st->tals) < 0) return -1; @@ -76,6 +80,8 @@ outputheader_json(FILE *out, struct stats *st) "\t\t\"repositories\": %zu,\n" "\t\t\"vrps\": %zu,\n" "\t\t\"uniquevrps\": %zu,\n" + "\t\t\"vaps\": %zu,\n" + "\t\t\"uniquevaps\": %zu,\n" "\t\t\"cachedir_del_files\": %zu,\n" "\t\t\"cachedir_superfluous_files\": %zu,\n" "\t\t\"cachedir_del_dirs\": %zu\n" @@ -85,14 +91,81 @@ outputheader_json(FILE *out, struct stats *st) st->gbrs, st->repos, st->vrps, st->uniqs, + st->vaps, st->vaps_uniqs, st->del_files, st->extra_files, st->del_dirs) < 0) return -1; return 0; } +static int +print_vap(FILE *out, struct vap *v) +{ + size_t i; + + if (fprintf(out, "\t\t\t{ \"customer_asid\": %u, \"providers\": [", + v->custasid) < 0) + return -1; + for (i = 0; i < v->providersz; i++) { + if (fprintf(out, "%u", v->providers[i]) < 0) + return -1; + if (i + 1 < v->providersz) + if (fprintf(out, ", ") < 0) + return -1; + } + if (fprintf(out, "], \"expires\": %lld }", (long long)v->expires) < 0) + return -1; + + return 0; +} + +static int +output_aspa(FILE *out, struct vap_tree *vaps) +{ + struct vap *v; + int first; + + if (fprintf(out, "\n\t],\n\n\t\"provider_authorizations\": {\n" + "\t\t\"ipv4\": [\n") < 0) + return -1; + + first = 1; + RB_FOREACH(v, vap_tree, vaps) + if (v->afi == AFI_IPV4) { + if (!first) { + if (fprintf(out, ",\n") < 0) + return -1; + } + first = 0; + if (print_vap(out, v)) + return -1; + } + + if (fprintf(out, "\n\t\t],\n\t\t\"ipv6\": [\n") < 0) + return -1; + + first = 1; + RB_FOREACH(v, vap_tree, vaps) { + if (v->afi == AFI_IPV6) { + if (!first) { + if (fprintf(out, ",\n") < 0) + return -1; + } + first = 0; + if (print_vap(out, v)) + return -1; + } + } + + if (fprintf(out, "\n\t\t]\n\t}\n") < 0) + return -1; + + return 0; +} + + int output_json(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks, - struct stats *st) + struct vap_tree *vaps, struct stats *st) { char buf[64]; struct vrp *v; @@ -140,7 +213,11 @@ output_json(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks, return -1; } - if (fprintf(out, "\n\t]\n}\n") < 0) + if (output_aspa(out, vaps) < 0) return -1; + + if (fprintf(out, "\n}\n") < 0) + return -1; + return 0; } diff --git a/usr.sbin/rpki-client/output.c b/usr.sbin/rpki-client/output.c index fc22af28d8d..aa995a0be3e 100644 --- a/usr.sbin/rpki-client/output.c +++ b/usr.sbin/rpki-client/output.c @@ -1,4 +1,4 @@ -/* $OpenBSD: output.c,v 1.26 2022/04/20 15:29:24 tb Exp $ */ +/* $OpenBSD: output.c,v 1.27 2022/08/30 18:56:49 job Exp $ */ /* * Copyright (c) 2019 Theo de Raadt * @@ -65,7 +65,7 @@ static const struct outputs { int format; char *name; int (*fn)(FILE *, struct vrp_tree *, struct brk_tree *, - struct stats *); + struct vap_tree *, struct stats *); } outputs[] = { { FORMAT_OPENBGPD, "openbgpd", output_bgpd }, { FORMAT_BIRD, "bird1v4", output_bird1v4 }, @@ -83,7 +83,8 @@ static void sig_handler(int); static void set_signal_handler(void); int -outputfiles(struct vrp_tree *v, struct brk_tree *b, struct stats *st) +outputfiles(struct vrp_tree *v, struct brk_tree *b, struct vap_tree *a, + struct stats *st) { int i, rc = 0; @@ -102,7 +103,7 @@ outputfiles(struct vrp_tree *v, struct brk_tree *b, struct stats *st) rc = 1; continue; } - if ((*outputs[i].fn)(fout, v, b, st) != 0) { + if ((*outputs[i].fn)(fout, v, b, a, st) != 0) { warn("output for %s format failed", outputs[i].name); fclose(fout); output_cleantmp(); diff --git a/usr.sbin/rpki-client/parser.c b/usr.sbin/rpki-client/parser.c index e6a9fc9faec..f6f05ae73cf 100644 --- a/usr.sbin/rpki-client/parser.c +++ b/usr.sbin/rpki-client/parser.c @@ -1,4 +1,4 @@ -/* $OpenBSD: parser.c,v 1.74 2022/08/19 12:45:53 tb Exp $ */ +/* $OpenBSD: parser.c,v 1.75 2022/08/30 18:56:49 job Exp $ */ /* * Copyright (c) 2019 Claudio Jeker * Copyright (c) 2019 Kristaps Dzonsons @@ -483,6 +483,43 @@ proc_parser_gbr(char *file, const unsigned char *der, size_t len) gbr_free(gbr); } +/* + * Parse an ASPA object + */ +static struct aspa * +proc_parser_aspa(char *file, const unsigned char *der, size_t len) +{ + struct aspa *aspa; + struct auth *a; + struct crl *crl; + X509 *x509; + + if ((aspa = aspa_parse(&x509, file, der, len)) == NULL) + return NULL; + + a = valid_ski_aki(file, &auths, aspa->ski, aspa->aki); + crl = crl_get(&crlt, a); + + if (!valid_x509(file, ctx, x509, a, crl, 0)) { + X509_free(x509); + aspa_free(aspa); + return NULL; + } + X509_free(x509); + + aspa->talid = a->cert->talid; + + if (crl != NULL && aspa->expires > crl->expires) + aspa->expires = crl->expires; + + for (; a != NULL; a = a->parent) { + if (aspa->expires > a->cert->expires) + aspa->expires = a->cert->expires; + } + + return aspa; +} + /* * Load the file specified by the entity information. */ @@ -514,6 +551,7 @@ parse_entity(struct entityq *q, struct msgbuf *msgq) struct cert *cert; struct mft *mft; struct roa *roa; + struct aspa *aspa; struct ibuf *b; unsigned char *f; size_t flen; @@ -599,6 +637,16 @@ parse_entity(struct entityq *q, struct msgbuf *msgq) io_str_buffer(b, file); proc_parser_gbr(file, f, flen); break; + case RTYPE_ASPA: + file = parse_load_file(entp, &f, &flen); + io_str_buffer(b, file); + aspa = proc_parser_aspa(file, f, flen); + c = (aspa != NULL); + io_simple_buffer(b, &c, sizeof(int)); + if (aspa != NULL) + aspa_buffer(b, aspa); + aspa_free(aspa); + break; default: errx(1, "unhandled entity type %d", entp->type); } diff --git a/usr.sbin/rpki-client/print.c b/usr.sbin/rpki-client/print.c index 7f7bbb4b6dc..95db07b33b0 100644 --- a/usr.sbin/rpki-client/print.c +++ b/usr.sbin/rpki-client/print.c @@ -1,4 +1,4 @@ -/* $OpenBSD: print.c,v 1.14 2022/07/14 13:24:56 job Exp $ */ +/* $OpenBSD: print.c,v 1.15 2022/08/30 18:56:49 job Exp $ */ /* * Copyright (c) 2021 Claudio Jeker * Copyright (c) 2019 Kristaps Dzonsons @@ -568,3 +568,52 @@ rsc_print(const X509 *x, const struct rsc *p) if (outformats & FORMAT_JSON) printf("\t],\n"); } + +void +aspa_print(const X509 *x, const struct aspa *p) +{ + size_t i; + + if (outformats & FORMAT_JSON) { + printf("\t\"type\": \"aspa\",\n"); + printf("\t\"ski\": \"%s\",\n", pretty_key_id(p->ski)); + x509_print(x); + printf("\t\"aki\": \"%s\",\n", pretty_key_id(p->aki)); + printf("\t\"aia\": \"%s\",\n", p->aia); + printf("\t\"customer_asid\": %u,\n", p->custasid); + printf("\t\"provider_set\": [\n"); + for (i = 0; i < p->providersz; i++) { + printf("\t\t{ \"asid\": %u", p->providers[i].as); + if (p->providers[i].afi == AFI_IPV4) + printf(", \"afi_limit\": \"ipv4\""); + if (p->providers[i].afi == AFI_IPV6) + printf(", \"afi_limit\": \"ipv6\""); + printf(" }"); + if (i + 1 < p->providersz) + printf(","); + printf("\n"); + } + printf("\t],\n"); + } else { + printf("Subject key identifier: %s\n", pretty_key_id(p->ski)); + x509_print(x); + printf("Authority key identifier: %s\n", pretty_key_id(p->aki)); + printf("Authority info access: %s\n", p->aia); + printf("Customer AS: %u\n", p->custasid); + printf("Provider Set:\n"); + for (i = 0; i < p->providersz; i++) { + printf("%5zu: AS: %d", i + 1, p->providers[i].as); + switch (p->providers[i].afi) { + case AFI_IPV4: + printf(" (IPv4 only)"); + break; + case AFI_IPV6: + printf(" (IPv6 only)"); + break; + default: + break; + } + printf("\n"); + } + } +} diff --git a/usr.sbin/rpki-client/roa.c b/usr.sbin/rpki-client/roa.c index 6c3aaa3f6fe..a147997e70e 100644 --- a/usr.sbin/rpki-client/roa.c +++ b/usr.sbin/rpki-client/roa.c @@ -1,4 +1,4 @@ -/* $OpenBSD: roa.c,v 1.50 2022/08/19 12:45:53 tb Exp $ */ +/* $OpenBSD: roa.c,v 1.51 2022/08/30 18:56:49 job Exp $ */ /* * Copyright (c) 2022 Theo Buehler * Copyright (c) 2019 Kristaps Dzonsons @@ -400,6 +400,8 @@ vrpcmp(struct vrp *a, struct vrp *b) if (rv) return rv; break; + default: + break; } /* a smaller prefixlen is considered bigger, e.g. /8 vs /10 */ if (a->addr.prefixlen < b->addr.prefixlen) diff --git a/usr.sbin/rpki-client/rpki-client.8 b/usr.sbin/rpki-client/rpki-client.8 index 57fdcbb6f07..59dc93080c1 100644 --- a/usr.sbin/rpki-client/rpki-client.8 +++ b/usr.sbin/rpki-client/rpki-client.8 @@ -1,4 +1,4 @@ -.\" $OpenBSD: rpki-client.8,v 1.70 2022/08/25 18:12:05 job Exp $ +.\" $OpenBSD: rpki-client.8,v 1.71 2022/08/30 18:56:49 job Exp $ .\" .\" Copyright (c) 2019 Kristaps Dzonsons .\" @@ -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: August 25 2022 $ +.Dd $Mdocdate: August 30 2022 $ .Dt RPKI-CLIENT 8 .Os .Sh NAME @@ -301,6 +301,8 @@ Certification Requests. Resource Public Key Infrastructure (RPKI) Trust Anchor Locator. .It draft-ietf-sidrops-rpki-rsc-08 A profile for Resource Public Key Infrastructure (RPKI) Signed Checklists (RSC). +.It draft-ietf-sidrops-aspa-profile-10 +A Profile for Autonomous System Provider Authorization (ASPA). .El .Sh HISTORY .Nm diff --git a/usr.sbin/rpki-client/validate.c b/usr.sbin/rpki-client/validate.c index cc6d9511a13..2638e38ab22 100644 --- a/usr.sbin/rpki-client/validate.c +++ b/usr.sbin/rpki-client/validate.c @@ -1,4 +1,4 @@ -/* $OpenBSD: validate.c,v 1.41 2022/08/19 12:45:53 tb Exp $ */ +/* $OpenBSD: validate.c,v 1.42 2022/08/30 18:56:49 job Exp $ */ /* * Copyright (c) 2019 Kristaps Dzonsons * @@ -533,3 +533,20 @@ valid_econtent_version(const char *fn, const ASN1_INTEGER *aint) return 0; } } + +/* + * Validate the ASPA: check that the customerASID is contained. + * Returns 1 if valid, 0 otherwise. + */ +int +valid_aspa(const char *fn, struct cert *cert, struct aspa *aspa) +{ + + if (as_check_covered(aspa->custasid, aspa->custasid, + cert->as, cert->asz) > 0) + return 1; + + warnx("%s: ASPA: uncovered Customer ASID: %u", fn, aspa->custasid); + + return 0; +} diff --git a/usr.sbin/rpki-client/x509.c b/usr.sbin/rpki-client/x509.c index fd90a1e4000..d3e65d8c1ea 100644 --- a/usr.sbin/rpki-client/x509.c +++ b/usr.sbin/rpki-client/x509.c @@ -1,4 +1,4 @@ -/* $OpenBSD: x509.c,v 1.47 2022/07/28 16:03:19 tb Exp $ */ +/* $OpenBSD: x509.c,v 1.48 2022/08/30 18:56:49 job Exp $ */ /* * Copyright (c) 2022 Theo Buehler * Copyright (c) 2021 Claudio Jeker @@ -44,6 +44,7 @@ ASN1_OBJECT *msg_dgst_oid; /* pkcs-9 id-messageDigest */ ASN1_OBJECT *sign_time_oid; /* pkcs-9 id-signingTime */ ASN1_OBJECT *bin_sign_time_oid; /* pkcs-9 id-aa-binarySigningTime */ ASN1_OBJECT *rsc_oid; /* id-ct-signedChecklist */ +ASN1_OBJECT *aspa_oid; /* id-ct-ASPA */ void x509_init_oid(void) @@ -81,6 +82,9 @@ x509_init_oid(void) if ((rsc_oid = OBJ_txt2obj("1.2.840.113549.1.9.16.1.48", 1)) == NULL) errx(1, "OBJ_txt2obj for %s failed", "1.2.840.113549.1.9.16.1.48"); + if ((aspa_oid = OBJ_txt2obj("1.2.840.113549.1.9.16.1.49", 1)) == NULL) + errx(1, "OBJ_txt2obj for %s failed", + "1.2.840.113549.1.9.16.1.49"); } /*