-# $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 \
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
-/* $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 <kristaps@bsd.lv>
*
RTYPE_ASPA,
RTYPE_TAK,
RTYPE_GEOFEED,
+ RTYPE_SPL,
};
enum location {
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.
*/
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)
*/
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 {
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);
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 *,
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);
#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)));
-/* $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 <claudio@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
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;
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)
case RTYPE_RSC:
status = rsc->valid;
break;
+ case RTYPE_SPL:
+ status = spl->valid;
default:
break;
}
case RTYPE_RSC:
rsc_print(x509, rsc);
break;
+ case RTYPE_SPL:
+ spl_print(x509, spl);
+ break;
case RTYPE_TAK:
tak_print(x509, tak);
break;
-/* $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 <claudio@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
*/
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;
struct mft *mft;
struct roa *roa;
struct aspa *aspa;
+ struct spl *spl;
struct repo *rp;
char *file;
time_t mtime;
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:
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
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;
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);
}
}
}
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 "
"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);
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();
-/* $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 <tb@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
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;
}
case RTYPE_GBR:
case RTYPE_ROA:
case RTYPE_ASPA:
+ case RTYPE_SPL:
case RTYPE_TAK:
return type;
default:
-/* $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 <kristaps@bsd.lv>
*
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;
-/* $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 <claudio@openbsd.org>
* Copyright (c) 2020 Robert Scheck <robert@fedoraproject.org>
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;
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;
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;
-/* $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 <claudio@openbsd.org>
*
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;
-/* $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 <claudio@openbsd.org>
*
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);
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);
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);
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();
}
-/* $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 <claudio@openbsd.org>
*
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
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 };
-/* $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 <deraadt@openbsd.org>
*
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 },
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;
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();
-/* $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 <claudio@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
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.
struct aspa *aspa;
struct gbr *gbr;
struct tak *tak;
+ struct spl *spl;
struct ibuf *b;
unsigned char *f;
time_t mtime, crlmtime;
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,
-/* $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 <claudio@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
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)
{
-/* $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 <claudio@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
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;
-.\" $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 <kristaps@bsd.lv>
.\"
.\" 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
.%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
--- /dev/null
+/* $OpenBSD: spl.c,v 1.1 2024/02/22 12:49:42 job Exp $ */
+/*
+ * Copyright (c) 2024 Job Snijders <job@fastly.com>
+ * Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
+ * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <assert.h>
+#include <err.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <openssl/asn1.h>
+#include <openssl/asn1t.h>
+#include <openssl/stack.h>
+#include <openssl/safestack.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+
+#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);
-/* $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 <kristaps@bsd.lv>
*
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.
-/* $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 <tb@openbsd.org>
* Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
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;
.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