On Sat, Aug 13, 2022 at 01:06:38AM +0000, Job Snijders wrote:
> Dear all,
> 
> The below changeset extends rpki-client(8) to validate and emit
> Autonomous System Provider Authorizations (ASPAs) in JSON format for
> consumption in other routing stacks.
> 
> The ASPA CMS protected content type is specified here:
> https://datatracker.ietf.org/doc/html/draft-ietf-sidrops-aspa-profile
> 
> How to verify BGP UPDATES using ASPA objects is described here:
> https://datatracker.ietf.org/doc/html/draft-ietf-sidrops-aspa-verification
> (Note: aspa-profile and aspa-verification really are two different beasts)
> 
> A test suite to generate conforming and non-conforming ASPA objects is
> available at: https://github.com/benmaddison/rpki-aspa-test-data
> Another testbed was outlined here:
> https://mailarchive.ietf.org/arch/msg/sidrops/DvRqMbOXkhiBDdUCTJU89L4q6LY
> 
> The rpki-client(8) '-j' (/var/db/rpki-client/json) output is formatted
> such that transformation into draft-ietf-sidrops-8210bis § 5.12
> RPKI-To-Router PDUs should be easy. However, for future direct
> disk-to-bgpd(8) dissemination of ASPA objects; we probably can come up
> with something more efficient :-)

This brings me to one very basic question for which I couldn't find an
answer in the above links: how will ASPAs usually be distributed?

> Example output from "rpki-client -f":
> 
>     File: 
> rpki.example.net/rpki/TA/ca-case-multi-afi-true/2338bd30cba1ff30fe3cf930824f39b45569d458de356729cb0121400ee3616a.asa
>     Hash identifier: P+IG54iYS6VLlZmnIacSks0bZwHxItkIHPt5YjI4y58=
>     Subject key identifier: 
> CB:B8:19:D8:E0:FD:8E:0E:10:83:D6:11:6B:2C:CD:92:28:8F:F8:C9
>     Certificate serial: 01
>     Authority key identifier: 
> 3C:E8:78:93:CE:9C:7F:41:53:0D:D0:E8:BB:CA:B0:03:7F:69:43:74
>     Authority info access: 
> rsync://rpki.example.net/rpki/TA/ca-case-multi-afi-true.cer
>     Customer AS: 65004
>     Provider Set:
>         1: AS: 65005
>         2: AS: 65006 (IPv4 only)
>         3: AS: 65007 (IPv6 only)
>     Validation: OK
> 
> OK? Feedback?

Could you please rebase your diff on top of -current so that you won't
undo recent pledge and unveil changes to main.c?

This looks pretty good already. I have many comments inline that should
all be easy to address. Two points up front so they don't get lost in
the noise:

1) what will need more work are aspa_insert_vaps() and insert_vap().

Looking up the same two struct vap (depending on the AFI) using
RB_INSERT(), walking the array of providers, and reallocating()
over and over again is not ideal.

Probably aspa_insert_vaps() should fill two arrays (one for each AFI) of
size providersz with the respective providerASes up front. Then we can
shrink them to their actual sizes and hang them off the two vap for this
customer AS. If there is more than one ASPA for that customer AS, then
this gets a bit trickier, but this is fine as it should rarely happen.
Another approach would be to use some linked list gymnastics.

I'm fine with keeping things mostly as they are for now and address this
in tree.

2) bikeshed material: the use of AFI_BOTH = 3 as part of enum afi.

This is a bit of an ugly hack: according to IANA, AFI number 3 is NSAP.
We use enum afi to check the on-the-wire AFIs, so this squatting is not
ideal.

Since AFI_BOTH isn't really used anyway, I think it would be acceptable
to treat 0 implicitly as AFI_BOTH: don't set aspa_p.afi to AFI_BOTH in
aspa_parse_providers() and replace "case AFI_BOTH:" with "default:" in
aspa_insert_vaps() as you already do in aspa_print(). This matches the
semantics in the draft: if the afi isn't explicitly set to IPv4 or IPv6,
it applies to both.

> 
> Kind regards,
> 
> Job
> 
> Index: Makefile
> ===================================================================
> RCS file: /cvs/src/usr.sbin/rpki-client/Makefile,v
> retrieving revision 1.25
> diff -u -p -r1.25 Makefile
> --- Makefile  9 May 2022 17:02:34 -0000       1.25
> +++ Makefile  13 Aug 2022 00:24:24 -0000
> @@ -1,8 +1,8 @@
>  #    $OpenBSD: Makefile,v 1.25 2022/05/09 17:02:34 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
> Index: aspa.c
> ===================================================================
> RCS file: aspa.c
> diff -N aspa.c
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ aspa.c    13 Aug 2022 00:24:24 -0000
> @@ -0,0 +1,447 @@
> +/*   $OpenBSD: aspa.c,v 1.16 2022/05/11 21:19:06 job Exp $ */
> +/*
> + * Copyright (c) 2022 Job Snijders <j...@fastly.com>

You've copied and adapted Kristaps's and my code, so I think our
copyrights belong here.

> + *
> + * 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     aspa_p;

Any reason not to call "aspa_p" a "provider"?

> +     size_t                   providersz, i, j;
> +
> +     memset(&aspa_p, 0, sizeof(struct aspa_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(struct aspa_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, &aspa_p.as)) {
> +                     warnx("%s: ASPA: malformed ProviderAS", p->fn);
> +                     return 0;
> +             }
> +
> +             if (p->res->custasid == aspa_p.as) {
> +                     warnx("%s: ASPA: CustomerASID can't also be Provider",
> +                         p->fn);
> +                     return 0;
> +             }
> +
> +             if (i > 0)

Add braces

> +                     if (p->res->providers[i - 1].as > aspa_p.as) {

Either check for >= here

> +                             warnx("%s: ASPA: invalid ProviderASSet order",
> +                                 p->fn);
> +                             return 0;
> +                     }

or do this:

                if (i > 0) {
                        if (p->res->providers[i - 1].as > aspa_p.as) {
                                warnx("%s: ASPA: invalid ProviderASSet order",
                                    p->fn);
                                return 0;
                        }
                        if (p->res->providers[i - 1].as == aspa_p.as) {
                                warnx("%s: ASPA: duplicate ProviderAS", p->fn);
                                return 0;
                        }
                }

Then you can remove the for loop below. This avoids quadratic complexity.

> +
> +             for (j = 0; j < i; j++) {
> +                     if (p->res->providers[j].as == aspa_p.as) {
> +                             warnx("%s: ASPA: duplicate ProviderAS", p->fn);
> +                             return 0;
> +                     }
> +             }
> +
> +             if (pa->afiLimit == NULL)
> +                     aspa_p.afi = AFI_BOTH;

As discussed above, maybe drop setting AFI_BOTH

> +             else if (!ip_addr_afi_parse(p->fn, pa->afiLimit, &aspa_p.afi)) {
> +                     warnx("%s: ASPA: invalid afiLimit", p->fn);
> +                     return 0;
> +             }
> +
> +             p->res->providers[p->res->providersz++] = aspa_p;
> +     }
> +
> +     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 (aspa->customerASID == NULL) {

customerASID is not marked optional in the template, so this check
isn't strictly necessary.

> +             warnx("%s: ASPA: customerASID must be present", p->fn);
> +             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;
> +     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;
> +     }

Fine for now. A variant of this check should probably be done in
valid_aspa() what that will look like depends on how we solve the
overclaim thing.

> +
> +     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;
> +
> +     rc = 1;
> + out:
> +     if (rc == 0) {
> +             aspa_free(p.res);
> +             p.res = NULL;
> +             X509_free(*x509);
> +             *x509 = NULL;
> +     }
> +     free(cms);
> +     return p.res;
> +}
> +
> +/*
> + * Free a ASPA pointer.

an

> + * 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 § 5.12 states:

Replace the non-ASCII § mark with "section"

> + *
> + *     "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 int

The only way this can fail is by hitting an err(), so this can be a void
function. You can then omit the error checking in aspa_insert_vaps().

> +insert_vap(struct vap_tree *tree, uint32_t cas, uint32_t pas, time_t expires,
> +    enum afi afi)
> +{
> +     struct vap      *v, *found;
> +     int              append;
> +     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 (found->expires > expires)
> +                     found->expires = expires;
> +
> +             append = 1;
> +             for (i = 0; i < found->providersz; i++)
> +                     if (found->providers[i] == pas) {
> +                             append = 0;
> +                             break;
> +                     }
> +             if (append) {

append is redundant: append == 1 if and only if i == found->providersz.
However, such a check is not needed if you change the break into a
return and hoist free(v). But see below.

> +                     found->providers = reallocarray(found->providers,
> +                         found->providersz + 1, sizeof(uint32_t));
> +                     if (found->providers == NULL)
> +                             err(1, NULL);
> +                     found->providers[found->providersz++] = pas;
> +             }
> +
> +             free(v);
> +     } else {
> +             if ((v->providers = malloc(sizeof(uint32_t))) == NULL)
> +                     err(1, NULL);
> +             v->providers[0] = pas;
> +             v->providersz = 1;
> +     }

I would switch the whole logic around and use early returns. This allows
you to remove two levels of indentation for the most complicated case:

        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;
}

> +
> +     return 1;
> +}
> +
> +/*
> + * 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;
> +

You could calculate uniqs here to avoid incrementing it one-by-one at
the end of the loop:

        *uniqs += aspa->providersz;

> +     for (i = 0; i < aspa->providersz; i++) {
> +             pas = aspa->providers[i].as;
> +
> +             switch (aspa->providers[i].afi) {
> +             case AFI_IPV4:
> +                     if (!insert_vap(tree, cas, pas, expires, AFI_IPV4))
> +                             err(1, NULL);

If you change insert_vap() into a void function, this can be simplified.

> +                     (*vaps)++;
> +                     break;
> +             case AFI_IPV6:
> +                     if (!insert_vap(tree, cas, pas, expires, AFI_IPV6))
> +                             err(1, NULL);
> +                     (*vaps)++;
> +                     break;
> +             case AFI_BOTH:
> +                     if (!insert_vap(tree, cas, pas, expires, AFI_IPV4))
> +                             err(1, NULL);
> +                     (*vaps)++;
> +                     if (!insert_vap(tree, cas, pas, expires, AFI_IPV6))
> +                             err(1, NULL);
> +                     (*vaps)++;

This could become:

                        insert_vap(tree, cas, pas, expires, AFI_IPV4);
                        insert_vap(tree, cas, pas, expires, AFI_IPV6);
                        *vaps += 2;

> +                     break;
> +             }
> +             (*uniqs)++;
> +     }
> +}
> +
> +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: extern.h
> ===================================================================
> RCS file: /cvs/src/usr.sbin/rpki-client/extern.h,v
> retrieving revision 1.147
> diff -u -p -r1.147 extern.h
> --- extern.h  10 Aug 2022 10:27:03 -0000      1.147
> +++ extern.h  13 Aug 2022 00:24:24 -0000
> @@ -74,7 +74,8 @@ struct cert_as {
>   */
>  enum afi {
>       AFI_IPV4 = 1,
> -     AFI_IPV6 = 2
> +     AFI_IPV6 = 2,
> +     AFI_BOTH = 3

As mentioned above, I'd drop AFI_BOTH.

>  };
>  
>  /*
> @@ -283,6 +284,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)
>   */
> @@ -433,6 +473,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 *);
> @@ -518,6 +571,7 @@ int                valid_origin(const char *, const c
>  int           valid_x509(char *, X509_STORE_CTX *, X509 *, struct auth *,
>                   struct crl *, int);
>  int           valid_rsc(const char *, struct auth *, struct rsc *);
> +int           valid_aspa(const char *, struct auth *, struct aspa *);
>  int           valid_econtent_version(const char *, const ASN1_INTEGER *);
>  
>  /* Working with CMS. */
> @@ -664,6 +718,7 @@ void               mft_print(const X509 *, const str
>  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)));
> @@ -719,6 +774,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: filemode.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/rpki-client/filemode.c,v
> retrieving revision 1.7
> diff -u -p -r1.7 filemode.c
> --- filemode.c        11 May 2022 14:42:01 -0000      1.7
> +++ filemode.c        13 Aug 2022 00:24:24 -0000
> @@ -265,6 +265,7 @@ proc_parser_file(char *file, unsigned ch
>       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;
> @@ -366,6 +367,14 @@ proc_parser_file(char *file, unsigned ch
>               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;
> @@ -391,10 +400,19 @@ proc_parser_file(char *file, unsigned ch
>               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 = valid_roa(file, a, roa);
> -                     else if (type == RTYPE_RSC)
> +                             break;
> +                     case RTYPE_RSC:
>                               status = valid_rsc(file, a, rsc);
> +                             break;
> +                     case RTYPE_ASPA:
> +                             status = valid_aspa(file, a, aspa);
> +                             break;
> +                     default:
> +                             break;
> +                     }
>               }
>               if (status)
>                       printf("OK");
> @@ -431,6 +449,7 @@ proc_parser_file(char *file, unsigned ch
>       roa_free(roa);
>       gbr_free(gbr);
>       tal_free(tal);
> +     aspa_free(aspa);
>  }
>  
>  /*
> Index: main.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/rpki-client/main.c,v
> retrieving revision 1.209
> diff -u -p -r1.209 main.c
> --- main.c    4 Aug 2022 13:44:07 -0000       1.209
> +++ main.c    13 Aug 2022 00:24:25 -0000
> @@ -1,4 +1,4 @@
> -/*   $OpenBSD: main.c,v 1.209 2022/08/04 13:44:07 claudio Exp $ */
> +/*   $OpenBSD: main.c,v 1.208 2022/06/27 10:18:27 job Exp $ */
>  /*
>   * Copyright (c) 2021 Claudio Jeker <clau...@openbsd.org>
>   * Copyright (c) 2019 Kristaps Dzonsons <krist...@bsd.lv>
> @@ -475,13 +475,14 @@ queue_add_from_cert(const struct cert *c
>   */
>  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 st
>               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);
>       }
> @@ -793,6 +809,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;
>  
> @@ -1006,7 +1023,8 @@ main(int argc, char *argv[])
>               signal(SIGALRM, suicide);
>       }
>  
> -     if (pledge("stdio rpath wpath cpath fattr sendfd unveil", NULL) == -1)
> +     /* TODO unveil cachedir and outputdir, no other access allowed */
> +     if (pledge("stdio rpath wpath cpath fattr sendfd", NULL) == -1)
>               err(1, "pledge");

The pledge and unveil changes should go away once you rebase.

>  
>       msgbuf_init(&procq);
> @@ -1047,17 +1065,7 @@ main(int argc, char *argv[])
>       if (filemode) {
>               while (*argv != NULL)
>                       queue_add_file(*argv++, RTYPE_FILE, 0);
> -
> -             if (unveil(cachedir, "r") == -1)
> -                     err(1, "unveil cachedir");
> -     } else {
> -             if (unveil(outputdir, "rwc") == -1)
> -                     err(1, "unveil outputdir");
> -             if (unveil(cachedir, "rwc") == -1)
> -                     err(1, "unveil cachedir");
>       }
> -     if (pledge("stdio rpath wpath cpath fattr sendfd", NULL) == -1)
> -             err(1, "unveil");
>  
>       /* change working directory to the cache directory */
>       if (fchdir(cachefd) == -1)
> @@ -1159,7 +1167,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);
>                       }
>               }
> @@ -1242,7 +1250,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 "
> @@ -1253,6 +1261,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);
> @@ -1266,6 +1276,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: output-bgpd.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/rpki-client/output-bgpd.c,v
> retrieving revision 1.23
> diff -u -p -r1.23 output-bgpd.c
> --- output-bgpd.c     11 Oct 2021 16:50:03 -0000      1.23
> +++ output-bgpd.c     13 Aug 2022 00:24:25 -0000
> @@ -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: output-bird.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/rpki-client/output-bird.c,v
> retrieving revision 1.14
> diff -u -p -r1.14 output-bird.c
> --- output-bird.c     15 May 2022 16:43:34 -0000      1.14
> +++ output-bird.c     13 Aug 2022 00:24:25 -0000
> @@ -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_tre
>  
>  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_tre
>  
>  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: output-csv.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/rpki-client/output-csv.c,v
> retrieving revision 1.12
> diff -u -p -r1.12 output-csv.c
> --- output-csv.c      4 Nov 2021 11:32:55 -0000       1.12
> +++ output-csv.c      13 Aug 2022 00:24:25 -0000
> @@ -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: output-json.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/rpki-client/output-json.c,v
> retrieving revision 1.26
> diff -u -p -r1.26 output-json.c
> --- output-json.c     15 May 2022 16:43:34 -0000      1.26
> +++ output-json.c     13 Aug 2022 00:24:25 -0000
> @@ -46,6 +46,9 @@ outputheader_json(FILE *out, struct stat
>           "\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 stat
>           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 stat
>           "\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,80 @@ outputheader_json(FILE *out, struct stat
>           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{ \"custasid\": %u, \"providers\": [",

Isn't custasid an internal name? I'd probably spell it out as
customer_asid or something.

> +         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)

add braces.

> +             if (v->afi == AFI_IPV6) {
> +                     if (!first) {
> +                             if (fprintf(out, ",\n") < 0)
> +                                     return -1;
> +                     }
> +                     first = 0;
> +                             if (print_vap(out, v))
> +                                     return -1;

wrong indent

> +             }
> +
> +     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 +212,11 @@ output_json(FILE *out, struct vrp_tree *
>                       return -1;
>       }
>  
> -     if (fprintf(out, "\n\t]\n}\n") < 0)
> +     if (output_aspa(out, vaps) < 0)
>               return -1;
> +
> +     if (fprintf(out, "\n}\n") < 0)

Was the closing ] above incorrect before?

> +                return -1;
> +
>       return 0;
>  }
> Index: output.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/rpki-client/output.c,v
> retrieving revision 1.26
> diff -u -p -r1.26 output.c
> --- output.c  20 Apr 2022 15:29:24 -0000      1.26
> +++ output.c  13 Aug 2022 00:24:25 -0000
> @@ -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 b
>                       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: parser.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/rpki-client/parser.c,v
> retrieving revision 1.73
> diff -u -p -r1.73 parser.c
> --- parser.c  21 Apr 2022 12:59:03 -0000      1.73
> +++ parser.c  13 Aug 2022 00:24:25 -0000
> @@ -492,6 +492,46 @@ proc_parser_gbr(char *file, const unsign
>  }
>  
>  /*
> + * 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 (valid_aspa(file, a, aspa))

Like ROAs and RSCs, this will permit overclaiming. Fix will be basically
the same.

> +             aspa->valid = 1;
> +
> +     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.
>   */
>  static char *
> @@ -522,6 +562,7 @@ parse_entity(struct entityq *q, struct m
>       struct cert     *cert;
>       struct mft      *mft;
>       struct roa      *roa;
> +     struct aspa     *aspa;
>       struct ibuf     *b;
>       unsigned char   *f;
>       size_t           flen;
> @@ -606,6 +647,16 @@ parse_entity(struct entityq *q, struct m
>                       file = parse_load_file(entp, &f, &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);
> Index: print.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/rpki-client/print.c,v
> retrieving revision 1.14
> diff -u -p -r1.14 print.c
> --- print.c   14 Jul 2022 13:24:56 -0000      1.14
> +++ print.c   13 Aug 2022 00:24:25 -0000
> @@ -568,3 +568,52 @@ rsc_print(const X509 *x, const struct rs
>       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");   

zap trailing whitespace.

> +             }
> +             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: roa.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/rpki-client/roa.c,v
> retrieving revision 1.49
> diff -u -p -r1.49 roa.c
> --- roa.c     10 Aug 2022 14:54:03 -0000      1.49
> +++ roa.c     13 Aug 2022 00:24:25 -0000
> @@ -389,6 +389,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: rpki-client.8
> ===================================================================
> RCS file: /cvs/src/usr.sbin/rpki-client/rpki-client.8,v
> retrieving revision 1.68
> diff -u -p -r1.68 rpki-client.8
> --- rpki-client.8     30 Jun 2022 10:27:52 -0000      1.68
> +++ rpki-client.8     13 Aug 2022 00:24:25 -0000
> @@ -295,6 +295,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: validate.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/rpki-client/validate.c,v
> retrieving revision 1.40
> diff -u -p -r1.40 validate.c
> --- validate.c        10 Jun 2022 10:36:43 -0000      1.40
> +++ validate.c        13 Aug 2022 00:24:25 -0000
> @@ -533,3 +533,20 @@ valid_econtent_version(const char *fn, c
>               return 0;
>       }
>  }
> +
> +/*
> + * Validate the ASPA: check that the customerASID is contained.
> + * Returns 1 if valid, 0 otherwise.
> + */
> +int
> +valid_aspa(const char *fn, struct auth *a, struct aspa *aspa)
> +{
> +
> +     if (valid_as(a, aspa->custasid, aspa->custasid))
> +             return 1;
> +     else

this else is unnecessary. then you can unindent and unwrap the next line

> +             warnx("%s: ASPA: uncovered Customer ASID: %u", fn,
> +                 aspa->custasid);
> +
> +     return 0;
> +}
> Index: x509.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/rpki-client/x509.c,v
> retrieving revision 1.47
> diff -u -p -r1.47 x509.c
> --- x509.c    28 Jul 2022 16:03:19 -0000      1.47
> +++ x509.c    13 Aug 2022 00:24:25 -0000
> @@ -44,6 +44,7 @@ ASN1_OBJECT *msg_dgst_oid;  /* pkcs-9 id-
>  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");
>  }
>  
>  /*
> 

Reply via email to