From d4be4cde0cbd41ee77eddbc4e2967ff15e75cb90 Mon Sep 17 00:00:00 2001 From: job Date: Thu, 22 Feb 2024 12:49:42 +0000 Subject: [PATCH] Add support for RPKI Signed Prefix Lists Signed Prefix List are a CMS protected content type for use with the RPKI to carry the complete list of prefixes which an Autonomous System may originate to all or any of its routing peers. The validation of a Signed Prefix List confirms that the holder of the listed ASN produced the object, and that this list is a current, accurate and complete description of address prefixes that may be announced into the routing system originated by this AS. https://datatracker.ietf.org/doc/html/draft-ietf-sidrops-rpki-prefixlist with and OK claudio@ tb@ --- usr.sbin/rpki-client/Makefile | 4 +- usr.sbin/rpki-client/extern.h | 82 ++++- usr.sbin/rpki-client/filemode.c | 17 +- usr.sbin/rpki-client/main.c | 35 +- usr.sbin/rpki-client/mft.c | 5 +- usr.sbin/rpki-client/output-bgpd.c | 4 +- usr.sbin/rpki-client/output-bird.c | 8 +- usr.sbin/rpki-client/output-csv.c | 4 +- usr.sbin/rpki-client/output-json.c | 28 +- usr.sbin/rpki-client/output-ometric.c | 16 +- usr.sbin/rpki-client/output.c | 8 +- usr.sbin/rpki-client/parser.c | 51 ++- usr.sbin/rpki-client/print.c | 56 ++- usr.sbin/rpki-client/repo.c | 26 +- usr.sbin/rpki-client/rpki-client.8 | 10 +- usr.sbin/rpki-client/spl.c | 487 ++++++++++++++++++++++++++ usr.sbin/rpki-client/validate.c | 18 +- usr.sbin/rpki-client/x509.c | 7 +- 18 files changed, 826 insertions(+), 40 deletions(-) create mode 100644 usr.sbin/rpki-client/spl.c diff --git a/usr.sbin/rpki-client/Makefile b/usr.sbin/rpki-client/Makefile index edb66b69757..25db8469197 100644 --- a/usr.sbin/rpki-client/Makefile +++ b/usr.sbin/rpki-client/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.33 2023/10/13 12:06:49 job Exp $ +# $OpenBSD: Makefile,v 1.34 2024/02/22 12:49:42 job Exp $ PROG= rpki-client SRCS= as.c aspa.c cert.c cms.c constraints.c crl.c encoding.c filemode.c \ @@ -6,7 +6,7 @@ SRCS= as.c aspa.c cert.c cms.c constraints.c crl.c encoding.c filemode.c \ output.c output-bgpd.c output-bird.c output-csv.c output-json.c \ output-ometric.c parser.c print.c repo.c rfc3779.c roa.c \ rrdp.c rrdp_delta.c rrdp_notification.c rrdp_snapshot.c rrdp_util.c \ - rsc.c rsync.c tak.c tal.c validate.c x509.c + rsc.c rsync.c spl.c tak.c tal.c validate.c x509.c MAN= rpki-client.8 LDADD+= -lexpat -ltls -lssl -lcrypto -lutil -lz diff --git a/usr.sbin/rpki-client/extern.h b/usr.sbin/rpki-client/extern.h index 10235441c0b..0aedd75aba1 100644 --- a/usr.sbin/rpki-client/extern.h +++ b/usr.sbin/rpki-client/extern.h @@ -1,4 +1,4 @@ -/* $OpenBSD: extern.h,v 1.207 2024/02/21 12:48:25 tb Exp $ */ +/* $OpenBSD: extern.h,v 1.208 2024/02/22 12:49:42 job Exp $ */ /* * Copyright (c) 2019 Kristaps Dzonsons * @@ -178,6 +178,7 @@ enum rtype { RTYPE_ASPA, RTYPE_TAK, RTYPE_GEOFEED, + RTYPE_SPL, }; enum location { @@ -280,6 +281,34 @@ struct rsc { time_t expires; /* when the signature path expires */ }; +/* + * An IP address prefix in a given SignedPrefixList. + */ +struct spl_pfx { + enum afi afi; + struct ip_addr prefix; +}; + +/* + * An SPL, draft-ietf-sidrops-rpki-prefixlist + * This consists of an ASID and its IP prefixes. + */ +struct spl { + uint32_t asid; + struct spl_pfx *pfxs; + size_t pfxsz; + int talid; + char *aia; + char *aki; + char *sia; + char *ski; + time_t signtime; /* CMS signing-time attribute */ + time_t notbefore; /* EE cert's Not Before */ + time_t notafter; /* EE cert's Not After */ + time_t expires; /* when the certification path expires */ + int valid; +}; + /* * Datastructure representing the TAKey sequence inside TAKs. */ @@ -409,6 +438,26 @@ struct vrp { RB_HEAD(vrp_tree, vrp); RB_PROTOTYPE(vrp_tree, vrp, entry, vrpcmp); +/* + * Validated SignedPrefixList Payload + * A single VSP element (including ASID) + * draft-ietf-sidrops-rpki-prefixlist + */ +struct vsp { + RB_ENTRY(vsp) entry; + uint32_t asid; + struct spl_pfx *prefixes; + size_t prefixesz; + time_t expires; + int talid; + unsigned int repoid; +}; +/* + * Tree of VSP sorted by asid + */ +RB_HEAD(vsp_tree, vsp); +RB_PROTOTYPE(vsp_tree, vsp, entry, vspcmp); + /* * A single BGPsec Router Key (including ASID) */ @@ -561,6 +610,11 @@ struct repotalstats { uint32_t vaps_pas; /* total number of providers */ uint32_t vrps; /* total number of Validated ROA Payloads */ uint32_t vrps_uniqs; /* number of unique vrps */ + uint32_t spls; /* signed prefix list */ + uint32_t spls_fail; /* failing syntactic parse */ + uint32_t spls_invalid; /* invalid asid */ + uint32_t vsps; /* total number of Validated SPL Payloads */ + uint32_t vsps_uniqs; /* number of unique vsps */ }; struct repostats { @@ -637,6 +691,14 @@ struct roa *roa_read(struct ibuf *); void roa_insert_vrps(struct vrp_tree *, struct roa *, struct repo *); +void spl_buffer(struct ibuf *, const struct spl *); +void spl_free(struct spl *); +struct spl *spl_parse(X509 **, const char *, int, const unsigned char *, + size_t); +struct spl *spl_read(struct ibuf *); +void spl_insert_vsps(struct vsp_tree *, struct spl *, + struct repo *); + void gbr_free(struct gbr *); struct gbr *gbr_parse(X509 **, const char *, int, const unsigned char *, size_t); @@ -690,6 +752,7 @@ int valid_aspa(const char *, struct cert *, struct aspa *); int valid_geofeed(const char *, struct cert *, struct geofeed *); int valid_uuid(const char *); int valid_ca_pkey(const char *, EVP_PKEY *); +int valid_spl(const char *, struct cert *, struct spl *); /* Working with CMS. */ unsigned char *cms_parse_validate(X509 **, const char *, @@ -870,6 +933,7 @@ void rsc_print(const X509 *, const struct rsc *); void aspa_print(const X509 *, const struct aspa *); void tak_print(const X509 *, const struct tak *); void geofeed_print(const X509 *, const struct geofeed *); +void spl_print(const X509 *, const struct spl *); /* Missing RFC 3779 API */ IPAddrBlocks *IPAddrBlocks_new(void); @@ -885,22 +949,22 @@ extern int outformats; #define FORMAT_OMETRIC 0x10 int outputfiles(struct vrp_tree *v, struct brk_tree *b, - struct vap_tree *, struct stats *); + struct vap_tree *, struct vsp_tree *, struct stats *); int outputheader(FILE *, struct stats *); int output_bgpd(FILE *, struct vrp_tree *, struct brk_tree *, - struct vap_tree *, struct stats *); + struct vap_tree *, struct vsp_tree *, struct stats *); int output_bird1v4(FILE *, struct vrp_tree *, struct brk_tree *, - struct vap_tree *, struct stats *); + struct vap_tree *, struct vsp_tree *, struct stats *); int output_bird1v6(FILE *, struct vrp_tree *, struct brk_tree *, - struct vap_tree *, struct stats *); + struct vap_tree *, struct vsp_tree *, struct stats *); int output_bird2(FILE *, struct vrp_tree *, struct brk_tree *, - struct vap_tree *, struct stats *); + struct vap_tree *, struct vsp_tree *, struct stats *); int output_csv(FILE *, struct vrp_tree *, struct brk_tree *, - struct vap_tree *, struct stats *); + struct vap_tree *, struct vsp_tree *, struct stats *); int output_json(FILE *, struct vrp_tree *, struct brk_tree *, - struct vap_tree *, struct stats *); + struct vap_tree *, struct vsp_tree *, struct stats *); int output_ometric(FILE *, struct vrp_tree *, struct brk_tree *, - struct vap_tree *, struct stats *); + struct vap_tree *, struct vsp_tree *, struct stats *); void logx(const char *fmt, ...) __attribute__((format(printf, 1, 2))); diff --git a/usr.sbin/rpki-client/filemode.c b/usr.sbin/rpki-client/filemode.c index 7532b68c363..5daf03b72f3 100644 --- a/usr.sbin/rpki-client/filemode.c +++ b/usr.sbin/rpki-client/filemode.c @@ -1,4 +1,4 @@ -/* $OpenBSD: filemode.c,v 1.37 2024/01/23 09:32:57 job Exp $ */ +/* $OpenBSD: filemode.c,v 1.38 2024/02/22 12:49:42 job Exp $ */ /* * Copyright (c) 2019 Claudio Jeker * Copyright (c) 2019 Kristaps Dzonsons @@ -296,6 +296,7 @@ proc_parser_file(char *file, unsigned char *buf, size_t len) struct mft *mft = NULL; struct roa *roa = NULL; struct rsc *rsc = NULL; + struct spl *spl = NULL; struct tak *tak = NULL; struct tal *tal = NULL; char *aia = NULL, *aki = NULL; @@ -422,6 +423,15 @@ proc_parser_file(char *file, unsigned char *buf, size_t len) expires = &rsc->expires; notafter = &rsc->notafter; break; + case RTYPE_SPL: + spl = spl_parse(&x509, file, -1, buf, len); + if (spl == NULL) + break; + aia = spl->aia; + aki = spl->aki; + expires = &spl->expires; + notafter = &spl->notafter; + break; case RTYPE_TAK: tak = tak_parse(&x509, file, -1, buf, len); if (tak == NULL) @@ -464,6 +474,8 @@ proc_parser_file(char *file, unsigned char *buf, size_t len) case RTYPE_RSC: status = rsc->valid; break; + case RTYPE_SPL: + status = spl->valid; default: break; } @@ -523,6 +535,9 @@ proc_parser_file(char *file, unsigned char *buf, size_t len) case RTYPE_RSC: rsc_print(x509, rsc); break; + case RTYPE_SPL: + spl_print(x509, spl); + break; case RTYPE_TAK: tak_print(x509, tak); break; diff --git a/usr.sbin/rpki-client/main.c b/usr.sbin/rpki-client/main.c index 6f51e0fac75..933494a2540 100644 --- a/usr.sbin/rpki-client/main.c +++ b/usr.sbin/rpki-client/main.c @@ -1,4 +1,4 @@ -/* $OpenBSD: main.c,v 1.250 2024/02/21 12:48:25 tb Exp $ */ +/* $OpenBSD: main.c,v 1.251 2024/02/22 12:49:42 job Exp $ */ /* * Copyright (c) 2021 Claudio Jeker * Copyright (c) 2019 Kristaps Dzonsons @@ -557,7 +557,8 @@ 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 vap_tree *vaptree) + struct brk_tree *brktree, struct vap_tree *vaptree, + struct vsp_tree *vsptree) { enum rtype type; struct tal *tal; @@ -565,6 +566,7 @@ entity_process(struct ibuf *b, struct stats *st, struct vrp_tree *tree, struct mft *mft; struct roa *roa; struct aspa *aspa; + struct spl *spl; struct repo *rp; char *file; time_t mtime; @@ -665,6 +667,19 @@ entity_process(struct ibuf *b, struct stats *st, struct vrp_tree *tree, repo_stat_inc(rp, talid, type, STYPE_INVALID); aspa_free(aspa); break; + case RTYPE_SPL: + io_read_buf(b, &c, sizeof(c)); + if (c == 0) { + repo_stat_inc(rp, talid, type, STYPE_FAIL); + break; + } + spl = spl_read(b); + if (spl->valid) + spl_insert_vsps(vsptree, spl, rp); + else + repo_stat_inc(rp, talid, type, STYPE_INVALID); + spl_free(spl); + break; case RTYPE_TAK: break; case RTYPE_FILE: @@ -755,6 +770,11 @@ sum_stats(const struct repo *rp, const struct repotalstats *in, void *arg) out->vaps += in->vaps; out->vaps_uniqs += in->vaps_uniqs; out->vaps_pas += in->vaps_pas; + out->spls += in->spls; + out->spls_fail += in->spls_fail; + out->spls_invalid += in->spls_invalid; + out->vsps += in->vsps; + out->vsps_uniqs += in->vsps_uniqs; } static void @@ -947,6 +967,7 @@ main(int argc, char *argv[]) const char *errs, *name; const char *skiplistfile = NULL; struct vrp_tree vrps = RB_INITIALIZER(&vrps); + struct vsp_tree vsps = RB_INITIALIZER(&vsps); struct brk_tree brks = RB_INITIALIZER(&brks); struct vap_tree vaps = RB_INITIALIZER(&vaps); struct rusage ru; @@ -1341,7 +1362,8 @@ 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, &vaps); + entity_process(b, &stats, &vrps, &brks, &vaps, + &vsps); ibuf_free(b); } } @@ -1434,7 +1456,7 @@ main(int argc, char *argv[]) } repo_stats_collect(sum_repostats, &stats.repo_stats); - if (outputfiles(&vrps, &brks, &vaps, &stats)) + if (outputfiles(&vrps, &brks, &vaps, &vsps, &stats)) rc = 1; printf("Processing time %lld seconds " @@ -1451,6 +1473,9 @@ main(int argc, char *argv[]) "invalid)\n", stats.repo_tal_stats.aspas, stats.repo_tal_stats.aspas_fail, stats.repo_tal_stats.aspas_invalid); + printf("Signed Prefix Lists: %u (%u failed parse, %u invalid)\n", + stats.repo_tal_stats.spls, stats.repo_tal_stats.spls_fail, + stats.repo_tal_stats.spls_invalid); printf("BGPsec Router Certificates: %u\n", stats.repo_tal_stats.brks); printf("Certificates: %u (%u invalid)\n", stats.repo_tal_stats.certs, stats.repo_tal_stats.certs_fail); @@ -1470,6 +1495,8 @@ main(int argc, char *argv[]) stats.repo_tal_stats.vrps_uniqs); printf("VAP Entries: %u (%u unique)\n", stats.repo_tal_stats.vaps, stats.repo_tal_stats.vaps_uniqs); + printf("VSP Entries: %u (%u unique)\n", stats.repo_tal_stats.vsps, + stats.repo_tal_stats.vsps_uniqs); /* Memory cleanup. */ repo_free(); diff --git a/usr.sbin/rpki-client/mft.c b/usr.sbin/rpki-client/mft.c index bc3fb930b91..8f48a881d71 100644 --- a/usr.sbin/rpki-client/mft.c +++ b/usr.sbin/rpki-client/mft.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mft.c,v 1.111 2024/02/21 09:17:06 tb Exp $ */ +/* $OpenBSD: mft.c,v 1.112 2024/02/22 12:49:42 job Exp $ */ /* * Copyright (c) 2022 Theo Buehler * Copyright (c) 2019 Kristaps Dzonsons @@ -121,6 +121,8 @@ rtype_from_file_extension(const char *fn) return RTYPE_TAK; if (strcasecmp(fn + sz - 4, ".csv") == 0) return RTYPE_GEOFEED; + if (strcasecmp(fn + sz - 4, ".spl") == 0) + return RTYPE_SPL; return RTYPE_INVALID; } @@ -162,6 +164,7 @@ rtype_from_mftfile(const char *fn) case RTYPE_GBR: case RTYPE_ROA: case RTYPE_ASPA: + case RTYPE_SPL: case RTYPE_TAK: return type; default: diff --git a/usr.sbin/rpki-client/output-bgpd.c b/usr.sbin/rpki-client/output-bgpd.c index 1207f46d51b..7bf47d30450 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.28 2023/06/26 18:39:53 job Exp $ */ +/* $OpenBSD: output-bgpd.c,v 1.29 2024/02/22 12:49:42 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 vap_tree *vaps, struct stats *st) + struct vap_tree *vaps, struct vsp_tree *vsps, struct stats *st) { struct vrp *vrp; struct vap *vap; diff --git a/usr.sbin/rpki-client/output-bird.c b/usr.sbin/rpki-client/output-bird.c index 344f28d2724..fca66050797 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.18 2023/05/30 12:14:48 claudio Exp $ */ +/* $OpenBSD: output-bird.c,v 1.19 2024/02/22 12:49:42 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 vap_tree *vaps, struct stats *st) + struct vap_tree *vaps, struct vsp_tree *vsps, 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 vap_tree *vaps, struct stats *st) + struct vap_tree *vaps, struct vsp_tree *vsps, 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 vap_tree *vaps, struct stats *st) + struct vap_tree *vaps, struct vsp_tree *vsps, 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 6338b7cad7c..7e46c44d4cb 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.13 2022/08/30 18:56:49 job Exp $ */ +/* $OpenBSD: output-csv.c,v 1.14 2024/02/22 12:49:42 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 vap_tree *vaps, struct stats *st) + struct vap_tree *vaps, struct vsp_tree *vsps, 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 9c8f3f2ba6c..8ff1e7f6243 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.42 2024/02/13 20:41:22 job Exp $ */ +/* $OpenBSD: output-json.c,v 1.43 2024/02/22 12:49:42 job Exp $ */ /* * Copyright (c) 2019 Claudio Jeker * @@ -47,6 +47,9 @@ outputheader_json(struct stats *st) json_do_int("roas", st->repo_tal_stats.roas); json_do_int("failedroas", st->repo_tal_stats.roas_fail); json_do_int("invalidroas", st->repo_tal_stats.roas_invalid); + json_do_int("spls", st->repo_tal_stats.spls); + json_do_int("failedspls", st->repo_tal_stats.spls_fail); + json_do_int("invalidspls", st->repo_tal_stats.spls_invalid); json_do_int("aspas", st->repo_tal_stats.aspas); json_do_int("failedaspas", st->repo_tal_stats.aspas_fail); json_do_int("invalidaspas", st->repo_tal_stats.aspas_invalid); @@ -69,6 +72,8 @@ outputheader_json(struct stats *st) json_do_int("repositories", st->repos); json_do_int("vrps", st->repo_tal_stats.vrps); json_do_int("uniquevrps", st->repo_tal_stats.vrps_uniqs); + json_do_int("vsps", st->repo_tal_stats.vsps); + json_do_int("uniquevsps", st->repo_tal_stats.vsps_uniqs); json_do_int("vaps", st->repo_tal_stats.vaps); json_do_int("uniquevaps", st->repo_tal_stats.vaps_uniqs); json_do_int("cachedir_del_files", st->repo_stats.del_files); @@ -109,11 +114,13 @@ output_aspa(struct vap_tree *vaps) int output_json(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks, - struct vap_tree *vaps, struct stats *st) + struct vap_tree *vaps, struct vsp_tree *vsps, struct stats *st) { char buf[64]; struct vrp *v; struct brk *b; + struct vsp *vsp; + size_t i; json_do_start(out); outputheader_json(st); @@ -147,5 +154,22 @@ output_json(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks, if (!excludeaspa) output_aspa(vaps); + json_do_array("signedprefixlists"); + RB_FOREACH(vsp, vsp_tree, vsps) { + json_do_object("vsp", 1); + json_do_int("origin_as", vsp->asid); + json_do_array("prefixes"); + for (i = 0; i < vsp->prefixesz; i++) { + ip_addr_print(&vsp->prefixes[i].prefix, + vsp->prefixes[i].afi, buf, sizeof(buf)); + json_do_string("prefix", buf); + } + json_do_end(); + json_do_int("expires", vsp->expires); + json_do_string("ta", taldescs[vsp->talid]); + json_do_end(); + } + json_do_end(); + return json_do_finish(); } diff --git a/usr.sbin/rpki-client/output-ometric.c b/usr.sbin/rpki-client/output-ometric.c index 1addf16728c..b2775e159d5 100644 --- a/usr.sbin/rpki-client/output-ometric.c +++ b/usr.sbin/rpki-client/output-ometric.c @@ -1,4 +1,4 @@ -/* $OpenBSD: output-ometric.c,v 1.7 2024/02/13 20:41:22 job Exp $ */ +/* $OpenBSD: output-ometric.c,v 1.8 2024/02/22 12:49:42 job Exp $ */ /* * Copyright (c) 2022 Claudio Jeker * @@ -82,6 +82,18 @@ set_common_stats(const struct repotalstats *in, struct ometric *metric, OKV("type", "state"), OKV("vap", "unique"), ol); ometric_set_int_with_labels(metric, in->vaps_pas, OKV("type", "state"), OKV("vap providers", "total"), ol); + + ometric_set_int_with_labels(metric, in->spls, + OKV("type", "state"), OKV("spl", "valid"), ol); + ometric_set_int_with_labels(metric, in->spls_fail, + OKV("type", "state"), OKV("spl", "failed parse"), ol); + ometric_set_int_with_labels(metric, in->spls_invalid, + OKV("type", "state"), OKV("spl", "invalid"), ol); + + ometric_set_int_with_labels(metric, in->vsps, + OKV("type", "state"), OKV("vsp", "total"), ol); + ometric_set_int_with_labels(metric, in->vsps_uniqs, + OKV("type", "state"), OKV("vsp", "unique"), ol); } static void @@ -146,7 +158,7 @@ repo_stats(const struct repo *rp, const struct repostats *in, void *arg) int output_ometric(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks, - struct vap_tree *vaps, struct stats *st) + struct vap_tree *vaps, struct vsp_tree *vsps, struct stats *st) { struct olabels *ol; const char *keys[4] = { "nodename", "domainname", "release", NULL }; diff --git a/usr.sbin/rpki-client/output.c b/usr.sbin/rpki-client/output.c index e875698ebdf..e4725d66a65 100644 --- a/usr.sbin/rpki-client/output.c +++ b/usr.sbin/rpki-client/output.c @@ -1,4 +1,4 @@ -/* $OpenBSD: output.c,v 1.32 2024/02/03 14:30:47 job Exp $ */ +/* $OpenBSD: output.c,v 1.33 2024/02/22 12:49:42 job Exp $ */ /* * Copyright (c) 2019 Theo de Raadt * @@ -64,7 +64,7 @@ static const struct outputs { int format; char *name; int (*fn)(FILE *, struct vrp_tree *, struct brk_tree *, - struct vap_tree *, struct stats *); + struct vap_tree *, struct vsp_tree *, struct stats *); } outputs[] = { { FORMAT_OPENBGPD, "openbgpd", output_bgpd }, { FORMAT_BIRD, "bird1v4", output_bird1v4 }, @@ -84,7 +84,7 @@ static void set_signal_handler(void); int outputfiles(struct vrp_tree *v, struct brk_tree *b, struct vap_tree *a, - struct stats *st) + struct vsp_tree *p, struct stats *st) { int i, rc = 0; @@ -103,7 +103,7 @@ outputfiles(struct vrp_tree *v, struct brk_tree *b, struct vap_tree *a, rc = 1; continue; } - if ((*outputs[i].fn)(fout, v, b, a, st) != 0) { + if ((*outputs[i].fn)(fout, v, b, a, p, 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 e1ef9b973fd..62ebcade65e 100644 --- a/usr.sbin/rpki-client/parser.c +++ b/usr.sbin/rpki-client/parser.c @@ -1,4 +1,4 @@ -/* $OpenBSD: parser.c,v 1.128 2024/02/03 14:30:47 job Exp $ */ +/* $OpenBSD: parser.c,v 1.129 2024/02/22 12:49:42 job Exp $ */ /* * Copyright (c) 2019 Claudio Jeker * Copyright (c) 2019 Kristaps Dzonsons @@ -157,6 +157,41 @@ proc_parser_roa(char *file, const unsigned char *der, size_t len, return roa; } +/* + * Parse and validate a draft-ietf-sidrops-rpki-prefixlist SPL. + * Returns the spl on success, NULL on failure. + */ +static struct spl * +proc_parser_spl(char *file, const unsigned char *der, size_t len, + const struct entity *entp) +{ + struct spl *spl; + struct auth *a; + struct crl *crl; + X509 *x509; + const char *errstr; + + if ((spl = spl_parse(&x509, file, entp->talid, der, len)) == NULL) + return NULL; + + a = valid_ski_aki(file, &auths, spl->ski, spl->aki, entp->mftaki); + crl = crl_get(&crlt, a); + + if (!valid_x509(file, ctx, x509, a, crl, &errstr)) { + warnx("%s: %s", file, errstr); + X509_free(x509); + spl_free(spl); + return NULL; + } + X509_free(x509); + + spl->talid = a->cert->talid; + + spl->expires = x509_find_expires(spl->notafter, a, &crlt); + + return spl; +} + /* * Check all files and their hashes in a MFT structure. * Return zero on failure, non-zero on success. @@ -681,6 +716,7 @@ parse_entity(struct entityq *q, struct msgbuf *msgq) struct aspa *aspa; struct gbr *gbr; struct tak *tak; + struct spl *spl; struct ibuf *b; unsigned char *f; time_t mtime, crlmtime; @@ -822,6 +858,19 @@ parse_entity(struct entityq *q, struct msgbuf *msgq) io_simple_buffer(b, &mtime, sizeof(mtime)); tak_free(tak); break; + case RTYPE_SPL: + file = parse_load_file(entp, &f, &flen); + io_str_buffer(b, file); + spl = proc_parser_spl(file, f, flen, entp); + if (spl != NULL) + mtime = spl->signtime; + io_simple_buffer(b, &mtime, sizeof(mtime)); + c = (spl != NULL); + io_simple_buffer(b, &c, sizeof(int)); + if (spl != NULL) + spl_buffer(b, spl); + spl_free(spl); + break; case RTYPE_CRL: default: file = parse_filepath(entp->repoid, entp->path, diff --git a/usr.sbin/rpki-client/print.c b/usr.sbin/rpki-client/print.c index 1e5503b2a1c..ac3852a482b 100644 --- a/usr.sbin/rpki-client/print.c +++ b/usr.sbin/rpki-client/print.c @@ -1,4 +1,4 @@ -/* $OpenBSD: print.c,v 1.49 2024/02/16 05:18:29 tb Exp $ */ +/* $OpenBSD: print.c,v 1.50 2024/02/22 12:49:42 job Exp $ */ /* * Copyright (c) 2021 Claudio Jeker * Copyright (c) 2019 Kristaps Dzonsons @@ -507,6 +507,60 @@ roa_print(const X509 *x, const struct roa *p) json_do_end(); } +void +spl_print(const X509 *x, const struct spl *s) +{ + char buf[128]; + size_t i; + + if (outformats & FORMAT_JSON) { + json_do_string("type", "spl"); + json_do_string("ski", pretty_key_id(s->ski)); + x509_print(x); + json_do_string("aki", pretty_key_id(s->aki)); + json_do_string("aia", s->aia); + json_do_string("sia", s->sia); + if (s->signtime != 0) + json_do_int("signing_time", s->signtime); + json_do_int("valid_since", s->notbefore); + json_do_int("valid_until", s->notafter); + if (s->expires) + json_do_int("expires", s->expires); + json_do_int("asid", s->asid); + } else { + printf("Subject key identifier: %s\n", pretty_key_id(s->ski)); + x509_print(x); + printf("Authority key identifier: %s\n", pretty_key_id(s->aki)); + printf("Authority info access: %s\n", s->aia); + printf("Subject info access: %s\n", s->sia); + if (s->signtime != 0) + printf("Signing time: %s\n", + time2str(s->signtime)); + printf("SPL not before: %s\n", + time2str(s->notbefore)); + printf("SPL not after: %s\n", time2str(s->notafter)); + printf("asID: %u\n", s->asid); + printf("Originated IP Prefixes: "); + } + + if (outformats & FORMAT_JSON) + json_do_array("prefixes"); + for (i = 0; i < s->pfxsz; i++) { + ip_addr_print(&s->pfxs[i].prefix, s->pfxs[i].afi, buf, + sizeof(buf)); + + if (outformats & FORMAT_JSON) { + json_do_string("prefix", buf); + } else { + if (i > 0) + printf("%26s", ""); + printf("%s\n", buf); + } + } + if (outformats & FORMAT_JSON) + json_do_end(); +} + void gbr_print(const X509 *x, const struct gbr *p) { diff --git a/usr.sbin/rpki-client/repo.c b/usr.sbin/rpki-client/repo.c index f1bf22ba5c5..60354de7f0e 100644 --- a/usr.sbin/rpki-client/repo.c +++ b/usr.sbin/rpki-client/repo.c @@ -1,4 +1,4 @@ -/* $OpenBSD: repo.c,v 1.52 2024/02/03 14:30:47 job Exp $ */ +/* $OpenBSD: repo.c,v 1.53 2024/02/22 12:49:42 job Exp $ */ /* * Copyright (c) 2021 Claudio Jeker * Copyright (c) 2019 Kristaps Dzonsons @@ -1480,6 +1480,30 @@ repo_stat_inc(struct repo *rp, int talid, enum rtype type, enum stype subtype) break; } break; + case RTYPE_SPL: + switch (subtype) { + case STYPE_OK: + rp->stats[talid].spls++; + break; + case STYPE_FAIL: + rp->stats[talid].spls_fail++; + break; + case STYPE_INVALID: + rp->stats[talid].spls_invalid++; + break; + case STYPE_TOTAL: + rp->stats[talid].vsps++; + break; + case STYPE_UNIQUE: + rp->stats[talid].vsps_uniqs++; + break; + case STYPE_DEC_UNIQUE: + rp->stats[talid].vsps_uniqs--; + break; + default: + break; + } + break; case RTYPE_CRL: rp->stats[talid].crls++; break; diff --git a/usr.sbin/rpki-client/rpki-client.8 b/usr.sbin/rpki-client/rpki-client.8 index a7d2d0e27a8..d3b1aa8b264 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.100 2024/01/31 17:19:02 job Exp $ +.\" $OpenBSD: rpki-client.8,v 1.101 2024/02/22 12:49:42 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: January 31 2024 $ +.Dd $Mdocdate: February 22 2024 $ .Dt RPKI-CLIENT 8 .Os .Sh NAME @@ -451,6 +451,12 @@ agreement regarding ARIN service restrictions. .%U https://datatracker.ietf.org/doc/html/draft-spaghetti-sidrops-rrdp-desynchronization-00 .%D Jan, 2024 .Re +.Pp +.Rs +.%T A profile for Signed Prefix Lists for Use in the Resource Public Key Infrastructure (RPKI) +.%U https://datatracker.ietf.org/doc/html/draft-ietf-sidrops-rpki-prefixlist-02 +.%D Jan, 2024 +.Re .Sh HISTORY .Nm first appeared in diff --git a/usr.sbin/rpki-client/spl.c b/usr.sbin/rpki-client/spl.c new file mode 100644 index 00000000000..fdb5cdf172e --- /dev/null +++ b/usr.sbin/rpki-client/spl.c @@ -0,0 +1,487 @@ +/* $OpenBSD: spl.c,v 1.1 2024/02/22 12:49:42 job Exp $ */ +/* + * Copyright (c) 2024 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" + +extern ASN1_OBJECT *spl_oid; + +/* + * Types and templates for the SPL eContent. + */ + +ASN1_ITEM_EXP AddressFamilyPrefixes_it; +ASN1_ITEM_EXP SignedPrefixList_it; + +DECLARE_STACK_OF(ASN1_BIT_STRING); + +typedef struct { + ASN1_OCTET_STRING *addressFamily; + STACK_OF(ASN1_BIT_STRING) *addressPrefixes; +} AddressFamilyPrefixes; + +DECLARE_STACK_OF(AddressFamilyPrefixes); + +ASN1_SEQUENCE(AddressFamilyPrefixes) = { + ASN1_SIMPLE(AddressFamilyPrefixes, addressFamily, ASN1_OCTET_STRING), + ASN1_SEQUENCE_OF(AddressFamilyPrefixes, addressPrefixes, + ASN1_BIT_STRING), +} ASN1_SEQUENCE_END(AddressFamilyPrefixes); + +#ifndef DEFINE_STACK_OF +#define sk_ASN1_BIT_STRING_num(st) SKM_sk_num(ASN1_BIT_STRING, (st)) +#define sk_ASN1_BIT_STRING_value(st, i) SKM_sk_value(ASN1_BIT_STRING, (st), (i)) + +#define sk_AddressFamilyPrefixes_num(st) \ + SKM_sk_num(AddressFamilyPrefixes, (st)) +#define sk_AddressFamilyPrefixes_value(st, i) \ + SKM_sk_value(AddressFamilyPrefixes, (st), (i)) +#endif + +typedef struct { + ASN1_INTEGER *version; + ASN1_INTEGER *asid; + STACK_OF(AddressFamilyPrefixes) *prefixBlocks; +} SignedPrefixList; + +ASN1_SEQUENCE(SignedPrefixList) = { + ASN1_EXP_OPT(SignedPrefixList, version, ASN1_INTEGER, 0), + ASN1_SIMPLE(SignedPrefixList, asid, ASN1_INTEGER), + ASN1_SEQUENCE_OF(SignedPrefixList, prefixBlocks, AddressFamilyPrefixes) +} ASN1_SEQUENCE_END(SignedPrefixList); + +DECLARE_ASN1_FUNCTIONS(SignedPrefixList); +IMPLEMENT_ASN1_FUNCTIONS(SignedPrefixList); + +/* + * Comparator to help sorting elements in SPL prefixBlocks and VSPs. + * Returns -1 if 'a' should precede 'b', 1 if 'b' should precede 'a', + * or '0' if a and b are equal. + */ +static int +prefix_cmp(enum afi afi, const struct ip_addr *a, const struct ip_addr *b) +{ + int cmp; + + switch (afi) { + case AFI_IPV4: + cmp = memcmp(&a->addr, &b->addr, 4); + if (cmp < 0) + return -1; + if (cmp > 0) + return 1; + break; + case AFI_IPV6: + cmp = memcmp(&a->addr, &b->addr, 16); + if (cmp < 0) + return -1; + if (cmp > 0) + return 1; + break; + default: + break; + } + + if (a->prefixlen < b->prefixlen) + return -1; + if (a->prefixlen > b->prefixlen) + return 1; + + return 0; +} + +/* + * Parses the eContent section of a SPL file, + * draft-ietf-sidrops-rpki-prefixlist-02 section 3. + * Returns zero on failure, non-zero on success. + */ +static int +spl_parse_econtent(const char *fn, struct spl *spl, const unsigned char *d, + size_t dsz) +{ + const unsigned char *oder; + SignedPrefixList *spl_asn1; + const AddressFamilyPrefixes *afp; + const STACK_OF(ASN1_BIT_STRING) *prefixes; + const ASN1_BIT_STRING *prefix_asn1; + int afpsz, prefixesz; + enum afi afi; + struct ip_addr ip_addr; + struct spl_pfx *prefix; + int ipv4_seen = 0, ipv6_seen = 0; + int i, j, rc = 0; + + oder = d; + if ((spl_asn1 = d2i_SignedPrefixList(NULL, &d, dsz)) == NULL) { + warnx("%s: RFC 6482 section 3: failed to parse " + "SignedPrefixList", fn); + goto out; + } + if (d != oder + dsz) { + warnx("%s: %td bytes trailing garbage in eContent", fn, + oder + dsz - d); + goto out; + } + + if (!valid_econtent_version(fn, spl_asn1->version, 0)) + goto out; + + if (!as_id_parse(spl_asn1->asid, &spl->asid)) { + warnx("%s: asid: malformed AS identifier", fn); + goto out; + } + + afpsz = sk_AddressFamilyPrefixes_num(spl_asn1->prefixBlocks); + if (afpsz < 0 || afpsz > 2) { + warnx("%s: unexpected number of AddressFamilyAddressPrefixes" + "(got %d, expected 0, 1, or 2)", fn, afpsz); + goto out; + } + + for (i = 0; i < afpsz; i++) { + struct ip_addr *prev_ip_addr = NULL; + + afp = sk_AddressFamilyPrefixes_value(spl_asn1->prefixBlocks, i); + prefixes = afp->addressPrefixes; + prefixesz = sk_ASN1_BIT_STRING_num(afp->addressPrefixes); + + if (prefixesz == 0) { + warnx("%s: empty AddressFamilyAddressPrefixes", fn); + goto out; + } + if (spl->pfxsz + prefixesz >= MAX_IP_SIZE) { + warnx("%s: too many addressPrefixes entries", fn); + goto out; + } + + if (!ip_addr_afi_parse(fn, afp->addressFamily, &afi)) + goto out; + + switch (afi) { + case AFI_IPV4: + if (ipv4_seen++ > 0) { + warnx("%s: addressFamilyIPv4 appeared twice", + fn); + goto out; + } + if (ipv6_seen > 0) { + warnx("%s: invalid sorting, IPv6 before IPv4", + fn); + goto out; + } + break; + case AFI_IPV6: + if (ipv6_seen++ > 0) { + warnx("%s: addressFamilyIPv6 appeared twice", + fn); + goto out; + } + } + + spl->pfxs = recallocarray(spl->pfxs, spl->pfxsz, + spl->pfxsz + prefixesz, sizeof(struct spl_pfx)); + if (spl->pfxs == NULL) + err(1, NULL); + + for (j = 0; j < prefixesz; j++) { + prefix_asn1 = sk_ASN1_BIT_STRING_value(prefixes, j); + + if (!ip_addr_parse(prefix_asn1, afi, fn, &ip_addr)) + goto out; + + if (j > 0 && + prefix_cmp(afi, prev_ip_addr, &ip_addr) != -1) { + warnx("%s: invalid addressPrefixes sorting", fn); + goto out; + } + + prefix = &spl->pfxs[spl->pfxsz++]; + prefix->prefix = ip_addr; + prefix->afi = afi; + prev_ip_addr = &prefix->prefix; + } + } + + rc = 1; + out: + SignedPrefixList_free(spl_asn1); + return rc; +} + +/* + * Parse a full Signed Prefix List file. + * Returns the SPL, or NULL if the object was malformed. + */ +struct spl * +spl_parse(X509 **x509, const char *fn, int talid, const unsigned char *der, + size_t len) +{ + struct spl *spl; + size_t cmsz; + unsigned char *cms; + struct cert *cert = NULL; + time_t signtime = 0; + int rc = 0; + + cms = cms_parse_validate(x509, fn, der, len, spl_oid, &cmsz, &signtime); + if (cms == NULL) + return NULL; + + if ((spl = calloc(1, sizeof(*spl))) == NULL) + err(1, NULL); + spl->signtime = signtime; + + if (!x509_get_aia(*x509, fn, &spl->aia)) + goto out; + if (!x509_get_aki(*x509, fn, &spl->aki)) + goto out; + if (!x509_get_sia(*x509, fn, &spl->sia)) + goto out; + if (!x509_get_ski(*x509, fn, &spl->ski)) + goto out; + if (spl->aia == NULL || spl->aki == NULL || spl->sia == NULL || + spl->ski == NULL) { + warnx("%s: RFC 6487 section 4.8: " + "missing AIA, AKI, SIA, or SKI X509 extension", fn); + goto out; + } + + if (!x509_get_notbefore(*x509, fn, &spl->notbefore)) + goto out; + if (!x509_get_notafter(*x509, fn, &spl->notafter)) + goto out; + + if (!spl_parse_econtent(fn, spl, cms, cmsz)) + goto out; + + if (x509_any_inherits(*x509)) { + warnx("%s: inherit elements not allowed in EE cert", fn); + goto out; + } + + if ((cert = cert_parse_ee_cert(fn, talid, *x509)) == NULL) + goto out; + + if (cert->asz == 0) { + warnx("%s: AS Resources extension missing", fn); + goto out; + } + + if (cert->ipsz > 0) { + warnx("%s: superfluous IP Resources extension present", fn); + goto out; + } + + /* + * If the SPL isn't valid, we accept it anyway and depend upon + * the code around spl_read() to check the "valid" field itself. + */ + spl->valid = valid_spl(fn, cert, spl); + + rc = 1; + out: + if (rc == 0) { + spl_free(spl); + spl = NULL; + X509_free(*x509); + *x509 = NULL; + } + cert_free(cert); + free(cms); + return spl; +} + +void +spl_free(struct spl *s) +{ + if (s == NULL) + return; + + free(s->aia); + free(s->aki); + free(s->sia); + free(s->ski); + free(s->pfxs); + free(s); +} + +/* + * Serialize parsed SPL content. + * See spl_read() for reader. + */ +void +spl_buffer(struct ibuf *b, const struct spl *s) +{ + io_simple_buffer(b, &s->valid, sizeof(s->valid)); + io_simple_buffer(b, &s->asid, sizeof(s->asid)); + io_simple_buffer(b, &s->talid, sizeof(s->talid)); + io_simple_buffer(b, &s->pfxsz, sizeof(s->pfxsz)); + io_simple_buffer(b, &s->expires, sizeof(s->expires)); + + io_simple_buffer(b, s->pfxs, s->pfxsz * sizeof(s->pfxs[0])); + + io_str_buffer(b, s->aia); + io_str_buffer(b, s->aki); + io_str_buffer(b, s->ski); +} + +/* + * Read parsed SPL content from descriptor. + * See spl_buffer() for writer. + * Result must be passed to spl_free(). + */ +struct spl * +spl_read(struct ibuf *b) +{ + struct spl *s; + + if ((s = calloc(1, sizeof(struct spl))) == NULL) + err(1, NULL); + + io_read_buf(b, &s->valid, sizeof(s->valid)); + io_read_buf(b, &s->asid, sizeof(s->asid)); + io_read_buf(b, &s->talid, sizeof(s->talid)); + io_read_buf(b, &s->pfxsz, sizeof(s->pfxsz)); + io_read_buf(b, &s->expires, sizeof(s->expires)); + + if ((s->pfxs = calloc(s->pfxsz, sizeof(struct spl_pfx))) == NULL) + err(1, NULL); + io_read_buf(b, s->pfxs, s->pfxsz * sizeof(s->pfxs[0])); + + io_read_str(b, &s->aia); + io_read_str(b, &s->aki); + io_read_str(b, &s->ski); + assert(s->aia && s->aki && s->ski); + + return s; +} + +static int +spl_pfx_cmp(const struct spl_pfx *a, const struct spl_pfx *b) +{ + if (a->afi > b->afi) + return 1; + if (a->afi < b->afi) + return -1; + + return prefix_cmp(a->afi, &a->prefix, &b->prefix); +} + +static void +insert_vsp(struct vsp *vsp, size_t idx, struct spl_pfx *pfx) +{ + if (idx < vsp->prefixesz) + memmove(vsp->prefixes + idx + 1, vsp->prefixes + idx, + (vsp->prefixesz - idx) * sizeof(*vsp->prefixes)); + vsp->prefixes[idx] = *pfx; + vsp->prefixesz++; +} + +/* + * Add each prefix in the SPL into the VSP tree. + * Updates "vsps" to be the number of VSPs and "uniqs" to be the unique + * number of prefixes. + */ +void +spl_insert_vsps(struct vsp_tree *tree, struct spl *spl, struct repo *rp) +{ + struct vsp *vsp, *found; + size_t i, j; + int cmp; + + if ((vsp = calloc(1, sizeof(*vsp))) == NULL) + err(1, NULL); + + vsp->asid = spl->asid; + vsp->talid = spl->talid; + vsp->expires = spl->expires; + if (rp != NULL) + vsp->repoid = repo_id(rp); + + if ((found = RB_INSERT(vsp_tree, tree, vsp)) != NULL) { + /* already exists */ + if (found->expires < vsp->expires) { + /* adjust unique count */ + repo_stat_inc(repo_byid(found->repoid), + found->talid, RTYPE_SPL, STYPE_DEC_UNIQUE); + found->expires = vsp->expires; + found->talid = vsp->talid; + found->repoid = vsp->repoid; + repo_stat_inc(rp, vsp->talid, RTYPE_SPL, + STYPE_UNIQUE); + } + free(vsp); + vsp = found; + } else + repo_stat_inc(rp, vsp->talid, RTYPE_SPL, STYPE_UNIQUE); + repo_stat_inc(rp, spl->talid, RTYPE_SPL, STYPE_TOTAL); + + /* merge content of multiple SPLs */ + vsp->prefixes = reallocarray(vsp->prefixes, + vsp->prefixesz + spl->pfxsz, sizeof(struct spl_pfx)); + if (vsp->prefixes == NULL) + err(1, NULL); + + /* + * Merge all data from the new SPL at hand into 'vsp': loop over + * all SPL->pfxs, and insert them in the right place in + * vsp->prefixes while keeping the order of the array. + */ + for (i = 0, j = 0; i < spl->pfxsz; ) { + cmp = -1; + if (j == vsp->prefixesz || + (cmp = spl_pfx_cmp(&spl->pfxs[i], &vsp->prefixes[j])) < 0) { + insert_vsp(vsp, j, &spl->pfxs[i]); + i++; + } else if (cmp == 0) + i++; + + if (j < vsp->prefixesz) + j++; + } +} + +/* + * Comparison function for the RB tree + */ +static inline int +vspcmp(const struct vsp *a, const struct vsp *b) +{ + if (a->asid > b->asid) + return 1; + if (a->asid < b->asid) + return -1; + + return 0; +} + +RB_GENERATE(vsp_tree, vsp, entry, vspcmp); diff --git a/usr.sbin/rpki-client/validate.c b/usr.sbin/rpki-client/validate.c index c0c4ee5e96e..5d4656cb556 100644 --- a/usr.sbin/rpki-client/validate.c +++ b/usr.sbin/rpki-client/validate.c @@ -1,4 +1,4 @@ -/* $OpenBSD: validate.c,v 1.71 2024/02/01 15:11:38 tb Exp $ */ +/* $OpenBSD: validate.c,v 1.72 2024/02/22 12:49:42 job Exp $ */ /* * Copyright (c) 2019 Kristaps Dzonsons * @@ -194,6 +194,22 @@ valid_roa(const char *fn, struct cert *cert, struct roa *roa) return 1; } +/* + * Validate our SPL: check that the asID is contained in the end-entity + * certificate's resources. + * Returns 1 if valid, 0 otherwise. + */ +int +valid_spl(const char *fn, struct cert *cert, struct spl *spl) +{ + if (as_check_covered(spl->asid, spl->asid, cert->as, cert->asz) > 0) + return 1; + + warnx("%s: SPL: uncovered ASID: %u", fn, spl->asid); + + return 0; +} + /* * Validate a file by verifying the SHA256 hash of that file. * The file to check is passed as a file descriptor. diff --git a/usr.sbin/rpki-client/x509.c b/usr.sbin/rpki-client/x509.c index c9e340b6c19..5646eb7154d 100644 --- a/usr.sbin/rpki-client/x509.c +++ b/usr.sbin/rpki-client/x509.c @@ -1,4 +1,4 @@ -/* $OpenBSD: x509.c,v 1.80 2024/02/16 05:18:29 tb Exp $ */ +/* $OpenBSD: x509.c,v 1.81 2024/02/22 12:49:42 job Exp $ */ /* * Copyright (c) 2022 Theo Buehler * Copyright (c) 2021 Claudio Jeker @@ -44,6 +44,7 @@ ASN1_OBJECT *rsc_oid; /* id-ct-signedChecklist */ ASN1_OBJECT *aspa_oid; /* id-ct-ASPA */ ASN1_OBJECT *tak_oid; /* id-ct-SignedTAL */ ASN1_OBJECT *geofeed_oid; /* id-ct-geofeedCSVwithCRLF */ +ASN1_OBJECT *spl_oid; /* id-ct-signedPrefixList */ static const struct { const char *oid; @@ -117,6 +118,10 @@ static const struct { .oid = "1.2.840.113549.1.9.16.1.50", .ptr = &tak_oid, }, + { + .oid = "1.2.840.113549.1.9.16.1.51", + .ptr = &spl_oid, + }, }; void -- 2.20.1