Add support for ASPA objects (draft-ietf-sidrops-aspa-profile-10)
authorjob <job@openbsd.org>
Tue, 30 Aug 2022 18:56:49 +0000 (18:56 +0000)
committerjob <job@openbsd.org>
Tue, 30 Aug 2022 18:56:49 +0000 (18:56 +0000)
ASPA objects are published in the RPKI and can be used to detect and
mitigate BGP route leaks. Validated ASPA Payloads are visible through
filemode (-f) and the JSON output format (-j).

With feedback from tb@

OK claudio@ tb@

17 files changed:
usr.sbin/rpki-client/Makefile
usr.sbin/rpki-client/aspa.c [new file with mode: 0644]
usr.sbin/rpki-client/extern.h
usr.sbin/rpki-client/filemode.c
usr.sbin/rpki-client/main.c
usr.sbin/rpki-client/mft.c
usr.sbin/rpki-client/output-bgpd.c
usr.sbin/rpki-client/output-bird.c
usr.sbin/rpki-client/output-csv.c
usr.sbin/rpki-client/output-json.c
usr.sbin/rpki-client/output.c
usr.sbin/rpki-client/parser.c
usr.sbin/rpki-client/print.c
usr.sbin/rpki-client/roa.c
usr.sbin/rpki-client/rpki-client.8
usr.sbin/rpki-client/validate.c
usr.sbin/rpki-client/x509.c

index a27dcba..a7198aa 100644 (file)
@@ -1,8 +1,8 @@
-#      $OpenBSD: Makefile,v 1.25 2022/05/09 17:02:34 job Exp $
+#      $OpenBSD: Makefile,v 1.26 2022/08/30 18:56:49 job Exp $
 
 PROG=  rpki-client
-SRCS=  as.c cert.c cms.c crl.c encoding.c filemode.c gbr.c http.c io.c ip.c \
-       log.c main.c mft.c mkdir.c output.c output-bgpd.c output-bird.c \
+SRCS=  as.c aspa.c cert.c cms.c crl.c encoding.c filemode.c gbr.c http.c io.c \
+       ip.c log.c main.c mft.c mkdir.c output.c output-bgpd.c output-bird.c \
        output-csv.c output-json.c parser.c print.c repo.c roa.c rrdp.c \
        rrdp_delta.c rrdp_notification.c rrdp_snapshot.c rrdp_util.c \
        rsc.c rsync.c tal.c validate.c x509.c
diff --git a/usr.sbin/rpki-client/aspa.c b/usr.sbin/rpki-client/aspa.c
new file mode 100644 (file)
index 0000000..2bd528c
--- /dev/null
@@ -0,0 +1,440 @@
+/*     $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);
index 8030838..f2c493c 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: extern.h,v 1.150 2022/08/19 12:45:53 tb Exp $ */
+/*     $OpenBSD: extern.h,v 1.151 2022/08/30 18:56:49 job Exp $ */
 /*
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
  *
@@ -181,6 +181,7 @@ enum rtype {
        RTYPE_REPO,
        RTYPE_FILE,
        RTYPE_RSC,
+       RTYPE_ASPA,
 };
 
 enum location {
@@ -282,6 +283,45 @@ struct gbr {
        char            *ski; /* SKI */
 };
 
+struct aspa_provider {
+       uint32_t         as;
+       enum afi         afi;
+};
+
+/*
+ * A single ASPA record
+ */
+struct aspa {
+       int                      valid; /* contained in parent auth */
+       int                      talid; /* TAL the ASPA is chained up to */
+       char                    *aia; /* AIA */
+       char                    *aki; /* AKI */
+       char                    *ski; /* SKI */
+       uint32_t                 custasid; /* the customerASID */
+       struct aspa_provider    *providers; /* the providers */
+       size_t                   providersz; /* number of providers */
+       time_t                   expires; /* NotAfter of the ASPA EE cert */
+};
+
+/*
+ * A Validated ASPA Payload (VAP) tree element.
+ * To ease transformation, this struct mimicks ASPA RTR PDU structure.
+ */
+struct vap {
+       RB_ENTRY(vap)            entry;
+       enum afi                 afi;
+       uint32_t                 custasid;
+       uint32_t                *providers;
+       size_t                   providersz;
+       time_t                   expires;
+};
+
+/*
+ * Tree of VAPs sorted by afi, custasid, and provideras.
+ */
+RB_HEAD(vap_tree, vap);
+RB_PROTOTYPE(vap_tree, vap, entry, vapcmp);
+
 /*
  * A single VRP element (including ASID)
  */
@@ -432,6 +472,11 @@ struct stats {
        size_t   rrdp_fails; /* failed rrdp repositories */
        size_t   crls; /* revocation lists */
        size_t   gbrs; /* ghostbuster records */
+       size_t   aspas; /* ASPA objects */
+       size_t   aspas_fail; /* ASPA objects failing syntactic parse */
+       size_t   aspas_invalid; /* ASPAs with invalid customerASID */
+       size_t   vaps; /* total number of Validated ASPA Payloads */
+       size_t   vaps_uniqs; /* total number of unique VAPs */
        size_t   vrps; /* total number of vrps */
        size_t   uniqs; /* number of unique vrps */
        size_t   del_files; /* number of files removed in cleanup */
@@ -496,6 +541,14 @@ void                rsc_free(struct rsc *);
 struct rsc     *rsc_parse(X509 **, const char *, const unsigned char *,
                    size_t);
 
+void            aspa_buffer(struct ibuf *, const struct aspa *);
+void            aspa_free(struct aspa *);
+void            aspa_insert_vaps(struct vap_tree *, struct aspa *, size_t *,
+                   size_t *);
+struct aspa    *aspa_parse(X509 **, const char *, const unsigned char *,
+                   size_t);
+struct aspa    *aspa_read(struct ibuf *);
+
 /* crl.c */
 struct crl     *crl_parse(const char *, const unsigned char *, size_t);
 struct crl     *crl_get(struct crl_tree *, const struct auth *);
@@ -519,6 +572,7 @@ int          valid_x509(char *, X509_STORE_CTX *, X509 *, struct auth *,
                    struct crl *, int);
 int             valid_rsc(const char *, struct cert *, struct rsc *);
 int             valid_econtent_version(const char *, const ASN1_INTEGER *);
+int             valid_aspa(const char *, struct cert *, struct aspa *);
 
 /* Working with CMS. */
 unsigned char  *cms_parse_validate(X509 **, const char *,
@@ -664,6 +718,7 @@ void                 mft_print(const X509 *, const struct mft *);
 void            roa_print(const X509 *, const struct roa *);
 void            gbr_print(const X509 *, const struct gbr *);
 void            rsc_print(const X509 *, const struct rsc *);
+void            aspa_print(const X509 *, const struct aspa *);
 
 /* Output! */
 
@@ -674,20 +729,20 @@ extern int         outformats;
 #define FORMAT_JSON    0x08
 
 int             outputfiles(struct vrp_tree *v, struct brk_tree *b,
-                   struct stats *);
+                   struct vap_tree *, struct stats *);
 int             outputheader(FILE *, struct stats *);
 int             output_bgpd(FILE *, struct vrp_tree *, struct brk_tree *,
-                   struct stats *);
+                   struct vap_tree *, struct stats *);
 int             output_bird1v4(FILE *, struct vrp_tree *, struct brk_tree *,
-                   struct stats *);
+                   struct vap_tree *, struct stats *);
 int             output_bird1v6(FILE *, struct vrp_tree *, struct brk_tree *,
-                   struct stats *);
+                   struct vap_tree *, struct stats *);
 int             output_bird2(FILE *, struct vrp_tree *, struct brk_tree *,
-                   struct stats *);
+                   struct vap_tree *, struct stats *);
 int             output_csv(FILE *, struct vrp_tree *, struct brk_tree *,
-                   struct stats *);
+                   struct vap_tree *, struct stats *);
 int             output_json(FILE *, struct vrp_tree *, struct brk_tree *,
-                   struct stats *);
+                   struct vap_tree *, struct stats *);
 
 void           logx(const char *fmt, ...)
                    __attribute__((format(printf, 1, 2)));
@@ -723,6 +778,9 @@ int mkpathat(int, const char *);
 /* Maximum number of FileAndHash entries per manifest. */
 #define MAX_MANIFEST_ENTRIES   100000
 
+/* Maximum number of Providers per ASPA object. */
+#define MAX_ASPA_PROVIDERS     10000
+
 /* Maximum depth of the RPKI tree. */
 #define MAX_CERT_DEPTH         12
 
index 95993b0..5ebb6e0 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: filemode.c,v 1.12 2022/08/25 18:12:05 job Exp $ */
+/*     $OpenBSD: filemode.c,v 1.13 2022/08/30 18:56:49 job Exp $ */
 /*
  * Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -268,6 +268,7 @@ proc_parser_file(char *file, unsigned char *buf, size_t len)
        struct gbr *gbr = NULL;
        struct tal *tal = NULL;
        struct rsc *rsc = NULL;
+       struct aspa *aspa = NULL;
        char *aia = NULL, *aki = NULL;
        char filehash[SHA256_DIGEST_LENGTH];
        char *hash;
@@ -367,6 +368,14 @@ proc_parser_file(char *file, unsigned char *buf, size_t len)
                aia = rsc->aia;
                aki = rsc->aki;
                break;
+       case RTYPE_ASPA:
+               aspa = aspa_parse(&x509, file, buf, len);
+               if (aspa == NULL)
+                       break;
+               aspa_print(x509, aspa);
+               aia = aspa->aia;
+               aki = aspa->aki;
+               break;
        default:
                printf("%s: unsupported file type\n", file);
                break;
@@ -392,10 +401,19 @@ proc_parser_file(char *file, unsigned char *buf, size_t len)
                c = crl_get(&crlt, a);
 
                if ((status = valid_x509(file, ctx, x509, a, c, 0))) {
-                       if (type == RTYPE_ROA)
+                       switch (type) {
+                       case RTYPE_ROA:
                                status = roa->valid;
-                       else if (type == RTYPE_RSC)
+                               break;
+                       case RTYPE_RSC:
                                status = rsc->valid;
+                               break;
+                       case RTYPE_ASPA:
+                               status = aspa->valid;
+                               break;
+                       default:
+                               break;
+                       }
                }
                if (status)
                        printf("OK");
@@ -450,6 +468,7 @@ proc_parser_file(char *file, unsigned char *buf, size_t len)
        gbr_free(gbr);
        tal_free(tal);
        rsc_free(rsc);
+       aspa_free(aspa);
 }
 
 /*
index f6959bd..d67925b 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: main.c,v 1.213 2022/08/29 18:28:35 tb Exp $ */
+/*     $OpenBSD: main.c,v 1.214 2022/08/30 18:56:49 job Exp $ */
 /*
  * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -475,13 +475,14 @@ queue_add_from_cert(const struct cert *cert)
  */
 static void
 entity_process(struct ibuf *b, struct stats *st, struct vrp_tree *tree,
-    struct brk_tree *brktree)
+    struct brk_tree *brktree, struct vap_tree *vaptree)
 {
        enum rtype       type;
        struct tal      *tal;
        struct cert     *cert;
        struct mft      *mft;
        struct roa      *roa;
+       struct aspa     *aspa;
        char            *file;
        int              c;
 
@@ -568,6 +569,21 @@ entity_process(struct ibuf *b, struct stats *st, struct vrp_tree *tree,
                break;
        case RTYPE_FILE:
                break;
+       case RTYPE_ASPA:
+               st->aspas++;
+               io_read_buf(b, &c, sizeof(c));
+               if (c == 0) {
+                       st->aspas_fail++;
+                       break;
+               }
+               aspa = aspa_read(b);
+               if (aspa->valid)
+                       aspa_insert_vaps(vaptree, aspa, &st->vaps,
+                           &st->vaps_uniqs);
+               else
+                       st->aspas_invalid++;
+               aspa_free(aspa);
+               break;
        default:
                errx(1, "unknown entity type %d", type);
        }
@@ -791,6 +807,7 @@ main(int argc, char *argv[])
        const char      *skiplistfile = NULL;
        struct vrp_tree  vrps = RB_INITIALIZER(&vrps);
        struct brk_tree  brks = RB_INITIALIZER(&brks);
+       struct vap_tree  vaps = RB_INITIALIZER(&vaps);
        struct rusage    ru;
        struct timeval   start_time, now_time;
 
@@ -1157,7 +1174,7 @@ main(int argc, char *argv[])
                if ((pfd[0].revents & POLLIN)) {
                        b = io_buf_read(proc, &procbuf);
                        if (b != NULL) {
-                               entity_process(b, &stats, &vrps, &brks);
+                               entity_process(b, &stats, &vrps, &brks, &vaps);
                                ibuf_free(b);
                        }
                }
@@ -1240,7 +1257,7 @@ main(int argc, char *argv[])
        if (fchdir(outdirfd) == -1)
                err(1, "fchdir output dir");
 
-       if (outputfiles(&vrps, &brks, &stats))
+       if (outputfiles(&vrps, &brks, &vaps, &stats))
                rc = 1;
 
        printf("Processing time %lld seconds "
@@ -1251,6 +1268,8 @@ main(int argc, char *argv[])
        printf("Skiplist entries: %zu\n", stats.skiplistentries);
        printf("Route Origin Authorizations: %zu (%zu failed parse, %zu invalid)\n",
            stats.roas, stats.roas_fail, stats.roas_invalid);
+       printf("AS Provider Attestations: %zu (%zu failed parse, %zu invalid)\n",
+           stats.aspas, stats.aspas_fail, stats.aspas_invalid);
        printf("BGPsec Router Certificates: %zu\n", stats.brks);
        printf("Certificates: %zu (%zu invalid)\n",
            stats.certs, stats.certs_fail);
@@ -1264,6 +1283,7 @@ main(int argc, char *argv[])
        printf("Cleanup: removed %zu files, %zu directories, %zu superfluous\n",
            stats.del_files, stats.del_dirs, stats.extra_files);
        printf("VRP Entries: %zu (%zu unique)\n", stats.vrps, stats.uniqs);
+       printf("VAP Entries: %zu (%zu unique)\n", stats.vaps, stats.vaps_uniqs);
 
        /* Memory cleanup. */
        repo_free();
index 64477b1..8631071 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: mft.c,v 1.73 2022/08/18 15:20:27 job Exp $ */
+/*     $OpenBSD: mft.c,v 1.74 2022/08/30 18:56:49 job Exp $ */
 /*
  * Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -166,6 +166,8 @@ rtype_from_file_extension(const char *fn)
                return RTYPE_GBR;
        if (strcasecmp(fn + sz - 4, ".sig") == 0)
                return RTYPE_RSC;
+       if (strcasecmp(fn + sz - 4, ".asa") == 0)
+               return RTYPE_ASPA;
 
        return RTYPE_INVALID;
 }
@@ -205,6 +207,7 @@ rtype_from_mftfile(const char *fn)
        case RTYPE_CRL:
        case RTYPE_GBR:
        case RTYPE_ROA:
+       case RTYPE_ASPA:
                return type;
        default:
                return RTYPE_INVALID;
index ff03314..54c00e5 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: output-bgpd.c,v 1.23 2021/10/11 16:50:03 job Exp $ */
+/*     $OpenBSD: output-bgpd.c,v 1.24 2022/08/30 18:56:49 job Exp $ */
 /*
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
  *
@@ -21,7 +21,7 @@
 
 int
 output_bgpd(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
-    struct stats *st)
+    struct vap_tree *vaps, struct stats *st)
 {
        struct vrp      *v;
 
index fa4211c..be8e04d 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: output-bird.c,v 1.14 2022/05/15 16:43:34 tb Exp $ */
+/*     $OpenBSD: output-bird.c,v 1.15 2022/08/30 18:56:49 job Exp $ */
 /*
  * Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
  * Copyright (c) 2020 Robert Scheck <robert@fedoraproject.org>
@@ -22,7 +22,7 @@
 
 int
 output_bird1v4(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
-    struct stats *st)
+    struct vap_tree *vaps, struct stats *st)
 {
        extern          const char *bird_tablename;
        struct vrp      *v;
@@ -51,7 +51,7 @@ output_bird1v4(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
 
 int
 output_bird1v6(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
-    struct stats *st)
+    struct vap_tree *vaps, struct stats *st)
 {
        extern          const char *bird_tablename;
        struct vrp      *v;
@@ -80,7 +80,7 @@ output_bird1v6(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
 
 int
 output_bird2(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
-    struct stats *st)
+    struct vap_tree *vaps, struct stats *st)
 {
        extern          const char *bird_tablename;
        struct vrp      *v;
index 80cc0e7..6338b7c 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: output-csv.c,v 1.12 2021/11/04 11:32:55 claudio Exp $ */
+/*     $OpenBSD: output-csv.c,v 1.13 2022/08/30 18:56:49 job Exp $ */
 /*
  * Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
  *
@@ -21,7 +21,7 @@
 
 int
 output_csv(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
-    struct stats *st)
+    struct vap_tree *vaps, struct stats *st)
 {
        struct vrp      *v;
 
index bb404ef..51d03c3 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: output-json.c,v 1.26 2022/05/15 16:43:34 tb Exp $ */
+/*     $OpenBSD: output-json.c,v 1.27 2022/08/30 18:56:49 job Exp $ */
 /*
  * Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
  *
@@ -46,6 +46,9 @@ outputheader_json(FILE *out, struct stats *st)
            "\t\t\"roas\": %zu,\n"
            "\t\t\"failedroas\": %zu,\n"
            "\t\t\"invalidroas\": %zu,\n"
+           "\t\t\"aspas\": %zu,\n"
+           "\t\t\"failedaspas\": %zu,\n"
+           "\t\t\"invalidaspas\": %zu,\n"
            "\t\t\"bgpsec_pubkeys\": %zu,\n"
            "\t\t\"certificates\": %zu,\n"
            "\t\t\"invalidcertificates\": %zu,\n"
@@ -55,6 +58,7 @@ outputheader_json(FILE *out, struct stats *st)
            hn, tbuf, (long long)st->elapsed_time.tv_sec,
            (long long)st->user_time.tv_sec, (long long)st->system_time.tv_sec,
            st->roas, st->roas_fail, st->roas_invalid,
+           st->aspas, st->aspas_fail, st->aspas_invalid,
            st->brks, st->certs, st->certs_fail,
            st->tals, talsz - st->tals) < 0)
                return -1;
@@ -76,6 +80,8 @@ outputheader_json(FILE *out, struct stats *st)
            "\t\t\"repositories\": %zu,\n"
            "\t\t\"vrps\": %zu,\n"
            "\t\t\"uniquevrps\": %zu,\n"
+           "\t\t\"vaps\": %zu,\n"
+           "\t\t\"uniquevaps\": %zu,\n"
            "\t\t\"cachedir_del_files\": %zu,\n"
            "\t\t\"cachedir_superfluous_files\": %zu,\n"
            "\t\t\"cachedir_del_dirs\": %zu\n"
@@ -85,14 +91,81 @@ outputheader_json(FILE *out, struct stats *st)
            st->gbrs,
            st->repos,
            st->vrps, st->uniqs,
+           st->vaps, st->vaps_uniqs,
            st->del_files, st->extra_files, st->del_dirs) < 0)
                return -1;
        return 0;
 }
 
+static int
+print_vap(FILE *out, struct vap *v)
+{
+       size_t i;
+
+       if (fprintf(out, "\t\t\t{ \"customer_asid\": %u, \"providers\": [",
+           v->custasid) < 0)
+               return -1;
+       for (i = 0; i < v->providersz; i++) {
+               if (fprintf(out, "%u", v->providers[i]) < 0)
+                       return -1;
+               if (i + 1 < v->providersz)
+                       if (fprintf(out, ", ") < 0)
+                               return -1;
+       }
+       if (fprintf(out, "], \"expires\": %lld }", (long long)v->expires) < 0)
+               return -1;
+
+       return 0;
+}
+
+static int
+output_aspa(FILE *out, struct vap_tree *vaps)
+{
+       struct vap      *v;
+       int              first;
+
+       if (fprintf(out, "\n\t],\n\n\t\"provider_authorizations\": {\n"
+           "\t\t\"ipv4\": [\n") < 0)
+               return -1;
+
+       first = 1;
+       RB_FOREACH(v, vap_tree, vaps)
+               if (v->afi == AFI_IPV4) {
+                       if (!first) {
+                               if (fprintf(out, ",\n") < 0)
+                                       return -1;
+                       }
+                       first = 0;
+                       if (print_vap(out, v))
+                               return -1;
+               }
+
+       if (fprintf(out, "\n\t\t],\n\t\t\"ipv6\": [\n") < 0)
+               return -1;
+
+       first = 1;
+       RB_FOREACH(v, vap_tree, vaps) {
+               if (v->afi == AFI_IPV6) {
+                       if (!first) {
+                               if (fprintf(out, ",\n") < 0)
+                                       return -1;
+                       }
+                       first = 0;
+                       if (print_vap(out, v))
+                               return -1;
+               }
+       }
+
+       if (fprintf(out, "\n\t\t]\n\t}\n") < 0)
+               return -1;
+       
+       return 0;
+}
+
+
 int
 output_json(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
-    struct stats *st)
+    struct vap_tree *vaps, struct stats *st)
 {
        char             buf[64];
        struct vrp      *v;
@@ -140,7 +213,11 @@ output_json(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
                        return -1;
        }
 
-       if (fprintf(out, "\n\t]\n}\n") < 0)
+       if (output_aspa(out, vaps) < 0)
                return -1;
+
+       if (fprintf(out, "\n}\n") < 0)
+                return -1;
+
        return 0;
 }
index fc22af2..aa995a0 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: output.c,v 1.26 2022/04/20 15:29:24 tb Exp $ */
+/*     $OpenBSD: output.c,v 1.27 2022/08/30 18:56:49 job Exp $ */
 /*
  * Copyright (c) 2019 Theo de Raadt <deraadt@openbsd.org>
  *
@@ -65,7 +65,7 @@ static const struct outputs {
        int      format;
        char    *name;
        int     (*fn)(FILE *, struct vrp_tree *, struct brk_tree *,
-                   struct stats *);
+                   struct vap_tree *, struct stats *);
 } outputs[] = {
        { FORMAT_OPENBGPD, "openbgpd", output_bgpd },
        { FORMAT_BIRD, "bird1v4", output_bird1v4 },
@@ -83,7 +83,8 @@ static void    sig_handler(int);
 static void     set_signal_handler(void);
 
 int
-outputfiles(struct vrp_tree *v, struct brk_tree *b, struct stats *st)
+outputfiles(struct vrp_tree *v, struct brk_tree *b, struct vap_tree *a,
+    struct stats *st)
 {
        int i, rc = 0;
 
@@ -102,7 +103,7 @@ outputfiles(struct vrp_tree *v, struct brk_tree *b, struct stats *st)
                        rc = 1;
                        continue;
                }
-               if ((*outputs[i].fn)(fout, v, b, st) != 0) {
+               if ((*outputs[i].fn)(fout, v, b, a, st) != 0) {
                        warn("output for %s format failed", outputs[i].name);
                        fclose(fout);
                        output_cleantmp();
index e6a9fc9..f6f05ae 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: parser.c,v 1.74 2022/08/19 12:45:53 tb Exp $ */
+/*     $OpenBSD: parser.c,v 1.75 2022/08/30 18:56:49 job Exp $ */
 /*
  * Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -483,6 +483,43 @@ proc_parser_gbr(char *file, const unsigned char *der, size_t len)
        gbr_free(gbr);
 }
 
+/*
+ * Parse an ASPA object
+ */
+static struct aspa *
+proc_parser_aspa(char *file, const unsigned char *der, size_t len)
+{
+       struct aspa             *aspa;
+       struct auth             *a;
+       struct crl              *crl;
+       X509                    *x509;
+
+       if ((aspa = aspa_parse(&x509, file, der, len)) == NULL)
+               return NULL;
+
+       a = valid_ski_aki(file, &auths, aspa->ski, aspa->aki);
+       crl = crl_get(&crlt, a);
+
+       if (!valid_x509(file, ctx, x509, a, crl, 0)) {
+               X509_free(x509);
+               aspa_free(aspa);
+               return NULL;
+       }
+       X509_free(x509);
+
+       aspa->talid = a->cert->talid;
+
+       if (crl != NULL && aspa->expires > crl->expires)
+               aspa->expires = crl->expires;
+
+       for (; a != NULL; a = a->parent) {
+               if (aspa->expires > a->cert->expires)
+                       aspa->expires = a->cert->expires;
+       }
+
+       return aspa;
+}
+
 /*
  * Load the file specified by the entity information.
  */
@@ -514,6 +551,7 @@ parse_entity(struct entityq *q, struct msgbuf *msgq)
        struct cert     *cert;
        struct mft      *mft;
        struct roa      *roa;
+       struct aspa     *aspa;
        struct ibuf     *b;
        unsigned char   *f;
        size_t           flen;
@@ -599,6 +637,16 @@ parse_entity(struct entityq *q, struct msgbuf *msgq)
                        io_str_buffer(b, file);
                        proc_parser_gbr(file, f, flen);
                        break;
+               case RTYPE_ASPA:
+                       file = parse_load_file(entp, &f, &flen);
+                       io_str_buffer(b, file);
+                       aspa = proc_parser_aspa(file, f, flen);
+                       c = (aspa != NULL);
+                       io_simple_buffer(b, &c, sizeof(int));
+                       if (aspa != NULL)
+                               aspa_buffer(b, aspa);
+                       aspa_free(aspa);
+                       break;
                default:
                        errx(1, "unhandled entity type %d", entp->type);
                }
index 7f7bbb4..95db07b 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: print.c,v 1.14 2022/07/14 13:24:56 job Exp $ */
+/*     $OpenBSD: print.c,v 1.15 2022/08/30 18:56:49 job Exp $ */
 /*
  * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -568,3 +568,52 @@ rsc_print(const X509 *x, const struct rsc *p)
        if (outformats & FORMAT_JSON)
                printf("\t],\n");
 }
+
+void
+aspa_print(const X509 *x, const struct aspa *p)
+{
+       size_t  i;
+
+       if (outformats & FORMAT_JSON) {
+               printf("\t\"type\": \"aspa\",\n");
+               printf("\t\"ski\": \"%s\",\n", pretty_key_id(p->ski));
+               x509_print(x);
+               printf("\t\"aki\": \"%s\",\n", pretty_key_id(p->aki));
+               printf("\t\"aia\": \"%s\",\n", p->aia);
+               printf("\t\"customer_asid\": %u,\n", p->custasid);
+               printf("\t\"provider_set\": [\n");
+               for (i = 0; i < p->providersz; i++) {
+                       printf("\t\t{ \"asid\": %u", p->providers[i].as);
+                       if (p->providers[i].afi == AFI_IPV4)
+                               printf(", \"afi_limit\": \"ipv4\"");
+                       if (p->providers[i].afi == AFI_IPV6)
+                               printf(", \"afi_limit\": \"ipv6\"");
+                       printf(" }");
+                       if (i + 1 < p->providersz)
+                               printf(",");
+                       printf("\n");   
+               }
+               printf("\t],\n");
+       } else {
+               printf("Subject key identifier: %s\n", pretty_key_id(p->ski));
+               x509_print(x);
+               printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
+               printf("Authority info access: %s\n", p->aia);
+               printf("Customer AS: %u\n", p->custasid);
+               printf("Provider Set:\n");
+               for (i = 0; i < p->providersz; i++) {
+                       printf("%5zu: AS: %d", i + 1, p->providers[i].as);
+                       switch (p->providers[i].afi) {
+                       case AFI_IPV4:
+                               printf(" (IPv4 only)");
+                               break;
+                       case AFI_IPV6:
+                               printf(" (IPv6 only)");
+                               break;
+                       default:
+                               break;
+                       }
+                       printf("\n");
+               }
+       }
+}
index 6c3aaa3..a147997 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: roa.c,v 1.50 2022/08/19 12:45:53 tb Exp $ */
+/*     $OpenBSD: roa.c,v 1.51 2022/08/30 18:56:49 job Exp $ */
 /*
  * Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -400,6 +400,8 @@ vrpcmp(struct vrp *a, struct vrp *b)
                if (rv)
                        return rv;
                break;
+       default:
+               break;
        }
        /* a smaller prefixlen is considered bigger, e.g. /8 vs /10 */
        if (a->addr.prefixlen < b->addr.prefixlen)
index 57fdcbb..59dc930 100644 (file)
@@ -1,4 +1,4 @@
-.\"    $OpenBSD: rpki-client.8,v 1.70 2022/08/25 18:12:05 job Exp $
+.\"    $OpenBSD: rpki-client.8,v 1.71 2022/08/30 18:56:49 job Exp $
 .\"
 .\" Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
 .\"
@@ -14,7 +14,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: August 25 2022 $
+.Dd $Mdocdate: August 30 2022 $
 .Dt RPKI-CLIENT 8
 .Os
 .Sh NAME
@@ -301,6 +301,8 @@ Certification Requests.
 Resource Public Key Infrastructure (RPKI) Trust Anchor Locator.
 .It draft-ietf-sidrops-rpki-rsc-08
 A profile for Resource Public Key Infrastructure (RPKI) Signed Checklists (RSC).
+.It draft-ietf-sidrops-aspa-profile-10
+A Profile for Autonomous System Provider Authorization (ASPA).
 .El
 .Sh HISTORY
 .Nm
index cc6d951..2638e38 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: validate.c,v 1.41 2022/08/19 12:45:53 tb Exp $ */
+/*     $OpenBSD: validate.c,v 1.42 2022/08/30 18:56:49 job Exp $ */
 /*
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
  *
@@ -533,3 +533,20 @@ valid_econtent_version(const char *fn, const ASN1_INTEGER *aint)
                return 0;
        }
 }
+
+/*
+ * Validate the ASPA: check that the customerASID is contained.
+ * Returns 1 if valid, 0 otherwise.
+ */
+int
+valid_aspa(const char *fn, struct cert *cert, struct aspa *aspa)
+{
+
+       if (as_check_covered(aspa->custasid, aspa->custasid,
+           cert->as, cert->asz) > 0)
+               return 1;
+
+       warnx("%s: ASPA: uncovered Customer ASID: %u", fn, aspa->custasid);
+
+       return 0;
+}
index fd90a1e..d3e65d8 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: x509.c,v 1.47 2022/07/28 16:03:19 tb Exp $ */
+/*     $OpenBSD: x509.c,v 1.48 2022/08/30 18:56:49 job Exp $ */
 /*
  * Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
  * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
@@ -44,6 +44,7 @@ ASN1_OBJECT   *msg_dgst_oid;  /* pkcs-9 id-messageDigest */
 ASN1_OBJECT    *sign_time_oid; /* pkcs-9 id-signingTime */
 ASN1_OBJECT    *bin_sign_time_oid;     /* pkcs-9 id-aa-binarySigningTime */
 ASN1_OBJECT    *rsc_oid;       /* id-ct-signedChecklist */
+ASN1_OBJECT    *aspa_oid;      /* id-ct-ASPA */
 
 void
 x509_init_oid(void)
@@ -81,6 +82,9 @@ x509_init_oid(void)
        if ((rsc_oid = OBJ_txt2obj("1.2.840.113549.1.9.16.1.48", 1)) == NULL)
                errx(1, "OBJ_txt2obj for %s failed",
                    "1.2.840.113549.1.9.16.1.48");
+       if ((aspa_oid = OBJ_txt2obj("1.2.840.113549.1.9.16.1.49", 1)) == NULL)
+               errx(1, "OBJ_txt2obj for %s failed",
+                   "1.2.840.113549.1.9.16.1.49");
 }
 
 /*