-# $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
--- /dev/null
+/* $OpenBSD: aspa.c,v 1.1 2022/08/30 18:56:49 job Exp $ */
+/*
+ * Copyright (c) 2022 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 <stdarg.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 "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);
-/* $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 <kristaps@bsd.lv>
*
RTYPE_REPO,
RTYPE_FILE,
RTYPE_RSC,
+ RTYPE_ASPA,
};
enum location {
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)
*/
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 */
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 *);
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 *,
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! */
#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)));
/* 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
-/* $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 <claudio@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
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;
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;
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");
gbr_free(gbr);
tal_free(tal);
rsc_free(rsc);
+ aspa_free(aspa);
}
/*
-/* $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 <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 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;
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);
}
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;
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);
}
}
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 "
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);
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();
-/* $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 <tb@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
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;
}
case RTYPE_CRL:
case RTYPE_GBR:
case RTYPE_ROA:
+ case RTYPE_ASPA:
return type;
default:
return RTYPE_INVALID;
-/* $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 <kristaps@bsd.lv>
*
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;
-/* $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 <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 stats *st)
+ struct vap_tree *vaps, 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 stats *st)
+ struct vap_tree *vaps, 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 stats *st)
+ struct vap_tree *vaps, struct stats *st)
{
extern const char *bird_tablename;
struct vrp *v;
-/* $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 <claudio@openbsd.org>
*
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;
-/* $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 <claudio@openbsd.org>
*
"\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"
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;
"\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"
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;
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;
}
-/* $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 <deraadt@openbsd.org>
*
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 },
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;
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();
-/* $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 <claudio@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
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.
*/
struct cert *cert;
struct mft *mft;
struct roa *roa;
+ struct aspa *aspa;
struct ibuf *b;
unsigned char *f;
size_t flen;
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);
}
-/* $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 <claudio@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
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");
+ }
+ }
+}
-/* $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 <tb@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
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)
-.\" $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 <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: August 25 2022 $
+.Dd $Mdocdate: August 30 2022 $
.Dt RPKI-CLIENT 8
.Os
.Sh NAME
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
-/* $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 <kristaps@bsd.lv>
*
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;
+}
-/* $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 <tb@openbsd.org>
* Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
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)
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");
}
/*