root/usr.sbin/rpki-client/ccr.c
/*      $OpenBSD: ccr.c,v 1.43 2026/07/20 10:02:55 job Exp $ */
/*
 * Copyright (c) 2025 Job Snijders <job@openbsd.org>
 *
 * 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 <sys/queue.h>
#include <sys/socket.h>
#include <sys/tree.h>

#include <assert.h>
#include <err.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"
#include "rpki-asn1.h"

/*
 * CCR definition in draft-ietf-sidrops-rpki-ccr-01, section 3.
 */

ASN1_ITEM_EXP ContentInfo_it;
ASN1_ITEM_EXP CanonicalCacheRepresentation_it;
ASN1_ITEM_EXP ManifestInstances_it;
ASN1_ITEM_EXP ManifestInstance_it;
ASN1_ITEM_EXP ROAPayloadSets_it;
ASN1_ITEM_EXP ROAPayloadSet_it;
ASN1_ITEM_EXP ASPAPayloadSets_it;
ASN1_ITEM_EXP ASPAPayloadSet_it;
ASN1_ITEM_EXP SubjectKeyIdentifiers_it;
ASN1_ITEM_EXP SubjectKeyIdentifier_it;
ASN1_ITEM_EXP RouterKeySets_it;
ASN1_ITEM_EXP RouterKeySet_it;
ASN1_ITEM_EXP RouterKey_it;

ASN1_SEQUENCE(ContentInfo) = {
        ASN1_SIMPLE(ContentInfo, contentType, ASN1_OBJECT),
        ASN1_EXP(ContentInfo, content, CanonicalCacheRepresentation, 0),
} ASN1_SEQUENCE_END(ContentInfo);

IMPLEMENT_ASN1_FUNCTIONS(ContentInfo);

ASN1_SEQUENCE(CanonicalCacheRepresentation) = {
        ASN1_EXP_OPT(CanonicalCacheRepresentation, version, ASN1_INTEGER, 0),
        ASN1_SIMPLE(CanonicalCacheRepresentation, hashAlg, X509_ALGOR),
        ASN1_SIMPLE(CanonicalCacheRepresentation, producedAt,
            ASN1_GENERALIZEDTIME),
        ASN1_EXP_OPT(CanonicalCacheRepresentation, mfts, ManifestState, 1),
        ASN1_EXP_OPT(CanonicalCacheRepresentation, vrps, ROAPayloadState, 2),
        ASN1_EXP_OPT(CanonicalCacheRepresentation, vaps, ASPAPayloadState, 3),
        ASN1_EXP_OPT(CanonicalCacheRepresentation, tas, TrustAnchorState, 4),
        ASN1_EXP_OPT(CanonicalCacheRepresentation, rks, RouterKeyState, 5),
} ASN1_SEQUENCE_END(CanonicalCacheRepresentation);

IMPLEMENT_ASN1_FUNCTIONS(CanonicalCacheRepresentation);

ASN1_SEQUENCE(ManifestState) = {
        ASN1_SEQUENCE_OF(ManifestState, mis, ManifestInstance),
        ASN1_SIMPLE(ManifestState, mostRecentUpdate, ASN1_GENERALIZEDTIME),
        ASN1_SIMPLE(ManifestState, hash, ASN1_OCTET_STRING),
} ASN1_SEQUENCE_END(ManifestState);

IMPLEMENT_ASN1_FUNCTIONS(ManifestState);

ASN1_ITEM_TEMPLATE(ManifestInstances) =
    ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_SEQUENCE_OF, 0, mis, ManifestInstance)
ASN1_ITEM_TEMPLATE_END(ManifestInstances);

IMPLEMENT_ASN1_ENCODE_FUNCTIONS_fname(ManifestInstances, ManifestInstances,
    ManifestInstances);

ASN1_SEQUENCE(ManifestInstance) = {
        ASN1_SIMPLE(ManifestInstance, hash, ASN1_OCTET_STRING),
        ASN1_SIMPLE(ManifestInstance, size, ASN1_INTEGER),
        ASN1_SIMPLE(ManifestInstance, aki, ASN1_OCTET_STRING),
        ASN1_SIMPLE(ManifestInstance, manifestNumber, ASN1_INTEGER),
        ASN1_SIMPLE(ManifestInstance, thisUpdate, ASN1_GENERALIZEDTIME),
        ASN1_SEQUENCE_OF(ManifestInstance, locations, ACCESS_DESCRIPTION),
        ASN1_SEQUENCE_OF_OPT(ManifestInstance, subordinates,
            SubjectKeyIdentifier),
} ASN1_SEQUENCE_END(ManifestInstance);

IMPLEMENT_ASN1_FUNCTIONS(ManifestInstance);

ASN1_SEQUENCE(ROAPayloadState) = {
        ASN1_SEQUENCE_OF(ROAPayloadState, rps, ROAPayloadSet),
        ASN1_SIMPLE(ROAPayloadState, hash, ASN1_OCTET_STRING),
} ASN1_SEQUENCE_END(ROAPayloadState);

IMPLEMENT_ASN1_FUNCTIONS(ROAPayloadState);

ASN1_ITEM_TEMPLATE(ROAPayloadSets) =
    ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_SEQUENCE_OF, 0, rps, ROAPayloadSet)
ASN1_ITEM_TEMPLATE_END(ROAPayloadSets);

IMPLEMENT_ASN1_ENCODE_FUNCTIONS_fname(ROAPayloadSets, ROAPayloadSets,
    ROAPayloadSets);

ASN1_SEQUENCE(ROAPayloadSet) = {
        ASN1_SIMPLE(ROAPayloadSet, asID, ASN1_INTEGER),
        ASN1_SEQUENCE_OF(ROAPayloadSet, ipAddrBlocks, ROAIPAddressFamily),
} ASN1_SEQUENCE_END(ROAPayloadSet);

IMPLEMENT_ASN1_FUNCTIONS(ROAPayloadSet);
IMPLEMENT_ASN1_FUNCTIONS(ROAIPAddressFamily);
IMPLEMENT_ASN1_FUNCTIONS(ROAIPAddress);

ASN1_SEQUENCE(ASPAPayloadState) = {
        ASN1_SEQUENCE_OF(ASPAPayloadState, aps, ASPAPayloadSet),
        ASN1_SIMPLE(ASPAPayloadState, hash, ASN1_OCTET_STRING),
} ASN1_SEQUENCE_END(ASPAPayloadState);

IMPLEMENT_ASN1_FUNCTIONS(ASPAPayloadState);

ASN1_ITEM_TEMPLATE(ASPAPayloadSets) =
    ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_SEQUENCE_OF, 0, aps, ASPAPayloadSet)
ASN1_ITEM_TEMPLATE_END(ASPAPayloadSets)

IMPLEMENT_ASN1_ENCODE_FUNCTIONS_fname(ASPAPayloadSets, ASPAPayloadSets,
    ASPAPayloadSets);

ASN1_SEQUENCE(ASPAPayloadSet) = {
        ASN1_SIMPLE(ASPAPayloadSet, asID, ASN1_INTEGER),
        ASN1_SEQUENCE_OF(ASPAPayloadSet, providers, ASN1_INTEGER),
} ASN1_SEQUENCE_END(ASPAPayloadSet);

IMPLEMENT_ASN1_FUNCTIONS(ASPAPayloadSet);

IMPLEMENT_ASN1_TYPE_ex(SubjectKeyIdentifier, ASN1_OCTET_STRING, 0)
IMPLEMENT_ASN1_FUNCTIONS(SubjectKeyIdentifier);

ASN1_SEQUENCE(TrustAnchorState) = {
        ASN1_SEQUENCE_OF(TrustAnchorState, skis, SubjectKeyIdentifier),
        ASN1_SIMPLE(TrustAnchorState, hash, ASN1_OCTET_STRING),
} ASN1_SEQUENCE_END(TrustAnchorState);

IMPLEMENT_ASN1_FUNCTIONS(TrustAnchorState);

ASN1_ITEM_TEMPLATE(SubjectKeyIdentifiers) =
        ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_SEQUENCE_OF, 0, tas,
            SubjectKeyIdentifier)
ASN1_ITEM_TEMPLATE_END(SubjectKeyIdentifiers);

IMPLEMENT_ASN1_ENCODE_FUNCTIONS_fname(SubjectKeyIdentifiers,
    SubjectKeyIdentifiers, SubjectKeyIdentifiers);

ASN1_SEQUENCE(RouterKeyState) = {
        ASN1_SEQUENCE_OF(RouterKeyState, rksets, RouterKeySet),
        ASN1_SIMPLE(RouterKeyState, hash, ASN1_OCTET_STRING),
} ASN1_SEQUENCE_END(RouterKeyState);

IMPLEMENT_ASN1_FUNCTIONS(RouterKeyState);

ASN1_ITEM_TEMPLATE(RouterKeySets) =
    ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_SEQUENCE_OF, 0, rks, RouterKeySet)
ASN1_ITEM_TEMPLATE_END(RouterKeySets);

IMPLEMENT_ASN1_ENCODE_FUNCTIONS_fname(RouterKeySets, RouterKeySets,
    RouterKeySets);

ASN1_SEQUENCE(RouterKeySet) = {
        ASN1_SIMPLE(RouterKeySet, asID, ASN1_INTEGER),
        ASN1_SEQUENCE_OF(RouterKeySet, routerKeys, RouterKey),
} ASN1_SEQUENCE_END(RouterKeySet);

IMPLEMENT_ASN1_FUNCTIONS(RouterKeySet);

ASN1_SEQUENCE(RouterKey) = {
        ASN1_SIMPLE(RouterKey, ski, SubjectKeyIdentifier),
        ASN1_SIMPLE(RouterKey, spki, X509_PUBKEY),
} ASN1_SEQUENCE_END(RouterKey);

IMPLEMENT_ASN1_FUNCTIONS(RouterKey);

static char *
hex_encode_asn1_string(const ASN1_STRING *str)
{
        return hex_encode(ASN1_STRING_get0_data(str), ASN1_STRING_length(str));
}

static int
base64_encode_asn1_string(const ASN1_OCTET_STRING *astr, char **out)
{
        const unsigned char *data = ASN1_STRING_get0_data(astr);
        int length = ASN1_STRING_length(astr);

        return base64_encode(data, length, out) == 0;
}

static int
copy_asn1_string(const ASN1_STRING *astr, unsigned char *buf, size_t len)
{
        const unsigned char *data = ASN1_STRING_get0_data(astr);
        int length = ASN1_STRING_length(astr);

        if (length < 0 || (size_t)length != len)
                return 0;

        memcpy(buf, data, length);

        return 1;
}

static void
hash_asn1_item(ASN1_OCTET_STRING *astr, const ASN1_ITEM *it, void *val)
{
        unsigned char hash[SHA256_DIGEST_LENGTH];

        if (!ASN1_item_digest(it, EVP_sha256(), val, hash, NULL))
                errx(1, "ASN1_item_digest");

        if (!ASN1_OCTET_STRING_set(astr, hash, sizeof(hash)))
                errx(1, "ASN1_OCTET_STRING_set");
}

static char *
validate_asn1_hash(const char *fn, const char *descr,
    const ASN1_OCTET_STRING *hash, const ASN1_ITEM *it, void *val)
{
        ASN1_OCTET_STRING *astr = NULL;
        char *b64 = NULL;

        if ((astr = ASN1_OCTET_STRING_new()) == NULL)
                errx(1, "ASN1_OCTET_STRING_new");

        hash_asn1_item(astr, it, val);

        if (ASN1_OCTET_STRING_cmp(hash, astr) != 0) {
                warnx("%s: corrupted %s state", fn, descr);
                goto out;
        }

        if (!base64_encode_asn1_string(astr, &b64))
                errx(1, "base64_encode_asn1_string");

 out:
        ASN1_OCTET_STRING_free(astr);
        return b64;
}

static void
asn1int_set_seqnum(ASN1_INTEGER *aint, const char *seqnum)
{
        BIGNUM *bn = NULL;

        if (!BN_hex2bn(&bn, seqnum))
                errx(1, "BN_hex2bn");

        if (BN_to_ASN1_INTEGER(bn, aint) == NULL)
                errx(1, "BN_to_ASN1_INTEGER");

        BN_free(bn);
}

static void
location_add_sia(STACK_OF(ACCESS_DESCRIPTION) *sad, const char *sia)
{
        ACCESS_DESCRIPTION *ad = NULL;

        if ((ad = ACCESS_DESCRIPTION_new()) == NULL)
                errx(1, "ACCESS_DESCRIPTION_new");

        ASN1_OBJECT_free(ad->method);
        if ((ad->method = OBJ_dup(signedobj_oid)) == NULL)
                errx(1, "OBJ_dup");

        GENERAL_NAME_free(ad->location);
        ad->location = a2i_GENERAL_NAME(NULL, NULL, NULL, GEN_URI, sia, 0);
        if (ad->location == NULL)
                errx(1, "a2i_GENERAL_NAME");

        if (sk_ACCESS_DESCRIPTION_push(sad, ad) <= 0)
                errx(1, "sk_ACCESS_DESCRIPTION_push");
}

static int
ski_cmp(const SubjectKeyIdentifier *const *a, const SubjectKeyIdentifier *const *b)
{
        return ASN1_OCTET_STRING_cmp(*a, *b);
}

static void
append_cached_manifest(STACK_OF(ManifestInstance) *mis, struct ccr_mft *cm)
{
        ManifestInstance *mi;
        struct ccr_mft_sub_ski *sub;
        SubjectKeyIdentifier *ski;

        if ((mi = ManifestInstance_new()) == NULL)
                errx(1, "ManifestInstance_new");

        if (!ASN1_OCTET_STRING_set(mi->hash, cm->hash, sizeof(cm->hash)))
                errx(1, "ASN1_OCTET_STRING_set");

        if (!ASN1_OCTET_STRING_set(mi->aki, cm->aki, sizeof(cm->aki)))
                errx(1, "ASN1_OCTET_STRING_set");

        if (!ASN1_INTEGER_set_uint64(mi->size, cm->size))
                errx(1, "ASN1_INTEGER_set_uint64");

        asn1int_set_seqnum(mi->manifestNumber, cm->seqnum);

        if (ASN1_GENERALIZEDTIME_set(mi->thisUpdate, cm->thisupdate) == NULL)
                errx(1, "ASN1_GENERALIZEDTIME_set");

        location_add_sia(mi->locations, cm->sia);

        if (SIMPLEQ_EMPTY(&cm->subordinates))
                goto done;

        if ((mi->subordinates = sk_SubjectKeyIdentifier_new(ski_cmp)) == NULL)
                err(1, NULL);

        SIMPLEQ_FOREACH(sub, &cm->subordinates, entry) {
                if ((ski = SubjectKeyIdentifier_new()) == NULL)
                        err(1, NULL);

                if (!ASN1_OCTET_STRING_set(ski, sub->ski, sizeof(sub->ski)))
                        errx(1, "ASN1_OCTET_STRING_set");

                if (sk_SubjectKeyIdentifier_push(mi->subordinates, ski) <= 0)
                        errx(1, "sk_SubjectKeyIdentifier_push");
        }

        sk_SubjectKeyIdentifier_sort(mi->subordinates);

 done:
        if (sk_ManifestInstance_push(mis, mi) <= 0)
                errx(1, "sk_ManifestInstance_push");
}

static ManifestState *
generate_manifeststate(struct validation_data *vd)
{
        struct ccr *ccr = &vd->ccr;
        ManifestState *ms;
        struct ccr_mft *cm;
        time_t most_recent_update = 0;

        if ((ms = ManifestState_new()) == NULL)
                errx(1, "ManifestState_new");

        RB_FOREACH(cm, ccr_mft_tree, &ccr->mfts) {
                append_cached_manifest(ms->mis, cm);

                if (cm->thisupdate > most_recent_update)
                        most_recent_update = cm->thisupdate;
        }

        if (ASN1_GENERALIZEDTIME_set(ms->mostRecentUpdate,
            most_recent_update) == NULL)
                errx(1, "ASN1_GENERALIZEDTIME_set");

        hash_asn1_item(ms->hash, ASN1_ITEM_rptr(ManifestInstances), ms->mis);

        if (!base64_encode_asn1_string(ms->hash, &ccr->mfts_hash))
                errx(1, "base64_encode_asn1_string");

        return ms;
}

static void
append_cached_vrp(STACK_OF(ROAIPAddress) *addresses, struct vrp *vrp)
{
        ROAIPAddress *ripa;
        int num_bits, num_bytes;
        uint8_t unused_bits;

        if ((ripa = ROAIPAddress_new()) == NULL)
                errx(1, "ROAIPAddress_new");

        num_bytes = (vrp->addr.prefixlen + 7) / 8;
        num_bits = vrp->addr.prefixlen % 8;

        unused_bits = 0;
        if (num_bits > 0)
                unused_bits = 8 - num_bits;

        if (!ASN1_BIT_STRING_set1(ripa->address, vrp->addr.addr, num_bytes,
            unused_bits))
                errx(1, "ASN1_BIT_STRING_set1");

        if (vrp->maxlength > vrp->addr.prefixlen) {
                if ((ripa->maxLength = ASN1_INTEGER_new()) == NULL)
                        errx(1, "ASN1_INTEGER_new");

                if (!ASN1_INTEGER_set_uint64(ripa->maxLength, vrp->maxlength))
                        errx(1, "ASN1_INTEGER_set_uint64");
        }

        if (sk_ROAIPAddress_push(addresses, ripa) <= 0)
                errx(1, "sk_ROAIPAddress_push");
}

static ROAPayloadState *
generate_roapayloadstate(struct validation_data *vd)
{
        struct ccr *ccr = &vd->ccr;
        ROAPayloadState *vrps;
        struct vrp *prev, *vrp;
        ROAPayloadSet *rp = NULL;
        ROAIPAddressFamily *ripaf = NULL;
        unsigned char afibuf[2];

        if ((vrps = ROAPayloadState_new()) == NULL)
                errx(1, "ROAPayloadState_new");

        prev = NULL;
        RB_FOREACH(vrp, ccr_vrp_tree, &ccr->vrps) {
                if (prev == NULL || prev->asid != vrp->asid) {
                        if ((rp = ROAPayloadSet_new()) == NULL)
                                errx(1, "ROAPayloadSet_new");

                        if (!ASN1_INTEGER_set_uint64(rp->asID, vrp->asid))
                                errx(1, "ASN1_INTEGER_set_uint64");

                        if (sk_ROAPayloadSet_push(vrps->rps, rp) <= 0)
                                errx(1, "sk_ROAPayloadSet_push");
                }

                if (prev == NULL || prev->asid != vrp->asid ||
                    prev->afi != vrp->afi) {
                        if ((ripaf = ROAIPAddressFamily_new()) == NULL)
                                errx(1, "ROAIPAddressFamily_new");

                        /* vrp->afi is 1 or 2. */
                        assert(vrp->afi == AFI_IPV4 || vrp->afi == AFI_IPV6);

                        afibuf[0] = 0;
                        afibuf[1] = vrp->afi;
                        if (!ASN1_OCTET_STRING_set(ripaf->addressFamily, afibuf,
                            sizeof(afibuf)))
                                errx(1, "ASN1_OCTET_STRING_set");

                        if (sk_ROAIPAddressFamily_push(rp->ipAddrBlocks,
                            ripaf) <= 0)
                                errx(1, "sk_ROAIPAddressFamily_push");
                }

                append_cached_vrp(ripaf->addresses, vrp);
                prev = vrp;
        }

        hash_asn1_item(vrps->hash, ASN1_ITEM_rptr(ROAPayloadSets), vrps->rps);

        if (!base64_encode_asn1_string(vrps->hash, &ccr->vrps_hash))
                errx(1, "base64_encode_asn1_string");

        return vrps;
}

static void
append_cached_aspa(STACK_OF(ASPAPayloadSet) *aps, struct vap *vap)
{
        ASPAPayloadSet *ap;
        size_t i;

        if ((ap = ASPAPayloadSet_new()) == NULL)
                errx(1, "ASPAPayloadSet_new");

        if (!ASN1_INTEGER_set_uint64(ap->asID, vap->custasid))
                errx(1, "ASN1_INTEGER_set_uint64");

        for (i = 0; i < vap->num_providers; i++) {
                ASN1_INTEGER *ai;

                if ((ai = ASN1_INTEGER_new()) == NULL)
                        errx(1, "ASN1_INTEGER_new");

                if (!ASN1_INTEGER_set_uint64(ai, vap->providers[i]))
                        errx(1, "ASN1_INTEGER_set_uint64");

                if ((sk_ASN1_INTEGER_push(ap->providers, ai)) <= 0)
                        errx(1, "sk_ASN1_INTEGER_push");
        }

        if ((sk_ASPAPayloadSet_push(aps, ap)) <= 0)
                errx(1, "sk_ASPAPayloadSet_push");
}

static ASPAPayloadState *
generate_aspapayloadstate(struct validation_data *vd)
{
        struct ccr *ccr = &vd->ccr;
        ASPAPayloadState *vaps;
        struct vap *vap;

        if ((vaps = ASPAPayloadState_new()) == NULL)
                errx(1, "ASPAPayloadState_new");

        RB_FOREACH(vap, vap_tree, &vd->vaps) {
                if (!vap->overflowed) {
                        append_cached_aspa(vaps->aps, vap);
                }
        }

        hash_asn1_item(vaps->hash, ASN1_ITEM_rptr(ASPAPayloadSets), vaps->aps);

        if (!base64_encode_asn1_string(vaps->hash, &ccr->vaps_hash))
                errx(1, "base64_encode_asn1_string");

        return vaps;
}

static void
append_tas_ski(STACK_OF(SubjectKeyIdentifier) *skis, struct ccr_tas_ski *cts)
{
        SubjectKeyIdentifier *ski;

        if ((ski = SubjectKeyIdentifier_new()) == NULL)
                errx(1, "SubjectKeyIdentifier_new");

        if (!ASN1_OCTET_STRING_set(ski, cts->keyid, sizeof(cts->keyid)))
                errx(1, "ASN1_OCTET_STRING_set");

        if ((sk_SubjectKeyIdentifier_push(skis, ski)) <= 0)
                errx(1, "sk_SubjectKeyIdentifier_push");
}

static TrustAnchorState *
generate_trustanchorstate(struct validation_data *vd)
{
        TrustAnchorState *tas;
        struct ccr_tas_ski *cts;

        if ((tas = TrustAnchorState_new()) == NULL)
                errx(1, "TrustAnchorState_new");

        RB_FOREACH(cts, ccr_tas_tree, &vd->ccr.tas) {
                append_tas_ski(tas->skis, cts);
        }

        hash_asn1_item(tas->hash, ASN1_ITEM_rptr(SubjectKeyIdentifiers),
            tas->skis);

        if (!base64_encode_asn1_string(tas->hash, &vd->ccr.tas_hash))
                errx(1, "base64_encode_asn1_string");

        return tas;
}

static RouterKeyState *
generate_routerkeystate(struct validation_data *vd)
{
        RouterKeyState *rks;
        RouterKeySet *rkset = NULL;
        RouterKey *rk;
        struct brk *brk, *prev;
        unsigned char *pk_der = NULL;
        size_t pk_len;
        const unsigned char *der;

        if ((rks = RouterKeyState_new()) == NULL)
                errx(1, "RouterKeyState_new");

        prev = NULL;
        RB_FOREACH(brk, brk_tree, &vd->brks) {
                static char hbuf[SHA_DIGEST_LENGTH] = { 0 };

                if (prev == NULL || prev->asid != brk->asid) {
                        if ((rkset = RouterKeySet_new()) == NULL)
                                errx(1, "RouterKeySet_new");

                        if (!ASN1_INTEGER_set_uint64(rkset->asID, brk->asid))
                                errx(1, "ASN1_INTEGER_set_uint64");

                        if (sk_RouterKeySet_push(rks->rksets, rkset) <= 0)
                                errx(1, "sk_RouterKeySet_push");
                }

                if ((rk = RouterKey_new()) == NULL)
                        errx(1, "RouterKey_new");

                if (hex_decode(brk->ski, hbuf, sizeof(hbuf)) == -1)
                        errx(1, "hex_decode brk pubkey corrupted");

                if (!ASN1_OCTET_STRING_set(rk->ski, hbuf, sizeof(hbuf)))
                        errx(1, "ASN1_OCTET_STRING_set");

                if (base64_decode(brk->pubkey, strlen(brk->pubkey), &pk_der,
                    &pk_len) == -1)
                        errx(1, "base64_decode");

                X509_PUBKEY_free(rk->spki);
                der = pk_der;
                if ((rk->spki = d2i_X509_PUBKEY(NULL, &der, pk_len)) == NULL)
                        errx(1, "d2i_X509_PUBKEY");
                if (der != pk_der + pk_len)
                        errx(1, "brk pubkey corrupted");
                free(pk_der);
                pk_der = NULL;

                if ((sk_RouterKey_push(rkset->routerKeys, rk)) <= 0)
                        errx(1, "sk_RouterKey_push");

                prev = brk;
        }

        hash_asn1_item(rks->hash, ASN1_ITEM_rptr(RouterKeySets), rks->rksets);

        if (!base64_encode_asn1_string(rks->hash, &vd->ccr.brks_hash))
                errx(1, "base64_encode_asn1_string");

        return rks;
}

static CanonicalCacheRepresentation *
generate_ccr(struct validation_data *vd)
{
        CanonicalCacheRepresentation *ccr = NULL;
        ASN1_OBJECT *oid;

        if ((ccr = CanonicalCacheRepresentation_new()) == NULL)
                errx(1, "CanonicalCacheRepresentation_new");

        if ((oid = OBJ_nid2obj(NID_sha256)) == NULL)
                errx(1, "OBJ_nid2obj");

        if (!X509_ALGOR_set0(ccr->hashAlg, oid, V_ASN1_UNDEF, NULL))
                errx(1, "X509_ALGOR_set0");

        if (ASN1_GENERALIZEDTIME_set(ccr->producedAt, vd->buildtime) == NULL)
                errx(1, "ASN1_GENERALIZEDTIME_set");

        if ((ccr->mfts = generate_manifeststate(vd)) == NULL)
                errx(1, "generate_manifeststate");

        if ((ccr->vrps = generate_roapayloadstate(vd)) == NULL)
                errx(1, "generate_roapayloadstate");

        if ((ccr->vaps = generate_aspapayloadstate(vd)) == NULL)
                errx(1, "generate_aspapayloadstate");

        if ((ccr->tas = generate_trustanchorstate(vd)) == NULL)
                errx(1, "generate_trustanchorstate");

        if ((ccr->rks = generate_routerkeystate(vd)) == NULL)
                errx(1, "generate_routerkeystate");

        return ccr;
}

void
serialize_ccr_content(struct validation_data *vd)
{
        ContentInfo *ci = NULL;
        int ci_der_len;

        if ((ci = ContentInfo_new()) == NULL)
                errx(1, "ContentInfo_new");

        ASN1_OBJECT_free(ci->contentType);
        if ((ci->contentType = OBJ_dup(ccr_oid)) == NULL)
                errx(1, "OBJ_dup");

        CanonicalCacheRepresentation_free(ci->content);
        ci->content = generate_ccr(vd);

        vd->ccr.der = NULL;
        if ((ci_der_len = i2d_ContentInfo(ci, &vd->ccr.der)) <= 0)
                errx(1, "i2d_ContentInfo");
        vd->ccr.der_len = ci_der_len;

        ContentInfo_free(ci);
}

static inline int
ccr_tas_ski_cmp(const struct ccr_tas_ski *a, const struct ccr_tas_ski *b)
{
        return memcmp(a->keyid, b->keyid, SHA_DIGEST_LENGTH);
}

RB_GENERATE(ccr_tas_tree, ccr_tas_ski, entry, ccr_tas_ski_cmp);

void
ccr_insert_tas(struct ccr_tas_tree *tree, const struct cert *cert)
{
        struct ccr_tas_ski *cts;

        assert(cert->purpose == CERT_PURPOSE_TA);

        if ((cts = calloc(1, sizeof(*cts))) == NULL)
                err(1, NULL);

        if ((hex_decode(cert->ski, cts->keyid, sizeof(cts->keyid))) != 0)
                errx(1, "hex_decode");

        if (RB_INSERT(ccr_tas_tree, tree, cts) != NULL)
                errx(1, "multiple TALs with the same key are not supported");
}

void
ccr_insert_mft_sub(struct ccr_mft_tree *tree, const struct cert *cert)
{
        struct ccr_mft *m, needle = { 0 };
        struct ccr_mft_sub_ski *sub;

        assert(cert->purpose == CERT_PURPOSE_CA);

        memcpy(needle.hash, cert->mfthash, sizeof(cert->mfthash));

        if ((m = RB_FIND(ccr_mft_tree, tree, &needle)) == NULL)
                errx(1, "RB_FIND ccr_mft_tree");

        if ((sub = calloc(1, sizeof(*sub))) == NULL)
                err(1, NULL);

        if (hex_decode(cert->ski, sub->ski, sizeof(sub->ski)) != 0)
                errx(1, "hex_decode");

        SIMPLEQ_INSERT_TAIL(&m->subordinates, sub, entry);
}

static inline int
ccr_mft_cmp(const struct ccr_mft *a, const struct ccr_mft *b)
{
        return memcmp(a->hash, b->hash, SHA256_DIGEST_LENGTH);
}

RB_GENERATE(ccr_mft_tree, ccr_mft, entry, ccr_mft_cmp);

static struct ccr_mft *
ccr_mft_new(void)
{
        struct ccr_mft *ccr_mft = NULL;

        if ((ccr_mft = calloc(1, sizeof(*ccr_mft))) == NULL)
                err(1, NULL);

        SIMPLEQ_INIT(&ccr_mft->subordinates);

        return ccr_mft;
}

void
ccr_insert_mft(struct ccr_mft_tree *tree, const struct mft *mft)
{
        struct ccr_mft *ccr_mft;

        ccr_mft = ccr_mft_new();

        if (hex_decode(mft->aki, ccr_mft->aki, sizeof(ccr_mft->aki)) != 0)
                errx(1, "hex_decode");

        if ((ccr_mft->sia = strdup(mft->sia)) == NULL)
                err(1, NULL);

        if ((ccr_mft->seqnum = strdup(mft->seqnum)) == NULL)
                err(1, NULL);

        memcpy(ccr_mft->hash, mft->mfthash, sizeof(ccr_mft->hash));

        ccr_mft->size = mft->mftsize;
        ccr_mft->thisupdate = mft->thisupdate;

        if (RB_INSERT(ccr_mft_tree, tree, ccr_mft) != NULL)
                errx(1, "CCR MFT tree corrupted");
}

void
ccr_insert_roa(struct ccr_vrp_tree *tree, const struct roa *roa)
{
        struct vrp *vrp;
        size_t i;

        for (i = 0; i < roa->num_ips; i++) {
                if ((vrp = calloc(1, sizeof(*vrp))) == NULL)
                        err(1, NULL);

                vrp->asid = roa->asid;
                vrp->afi = roa->ips[i].afi;
                vrp->addr = roa->ips[i].addr;
                vrp->maxlength = roa->ips[i].maxlength;

                if (RB_INSERT(ccr_vrp_tree, tree, vrp) != NULL)
                        free(vrp);
        }
}

/*
 * Total ordering modeled after RFC 9582, section 4.3.3.
 */
static inline int
ccr_vrp_cmp(const struct vrp *a, const struct vrp *b)
{
        int rv;

        if (a->asid > b->asid)
                return 1;
        if (a->asid < b->asid)
                return -1;

        if (a->afi > b->afi)
                return 1;
        if (a->afi < b->afi)
                return -1;

        switch (a->afi) {
        case AFI_IPV4:
                rv = memcmp(&a->addr.addr, &b->addr.addr, 4);
                if (rv)
                        return rv;
                break;
        case AFI_IPV6:
                rv = memcmp(&a->addr.addr, &b->addr.addr, 16);
                if (rv)
                        return rv;
                break;
        default:
                abort();
                break;
        }

        if (a->addr.prefixlen > b->addr.prefixlen)
                return 1;
        if (a->addr.prefixlen < b->addr.prefixlen)
                return -1;

        if (a->maxlength > b->maxlength)
                return 1;
        if (a->maxlength < b->maxlength)
                return -1;

        return 0;
}

RB_GENERATE(ccr_vrp_tree, vrp, entry, ccr_vrp_cmp);

int
output_ccr_der(FILE *out, struct validation_data *vd, struct stats *st)
{
        if (fwrite(vd->ccr.der, vd->ccr.der_len, 1, out) != 1)
                err(1, "fwrite");

        return 0;
}

static void
ccr_mft_free(struct ccr_mft *ccr_mft)
{
        struct ccr_mft_sub_ski *sub_ski;

        if (ccr_mft == NULL)
                return;

        while (!SIMPLEQ_EMPTY(&ccr_mft->subordinates)) {
                sub_ski = SIMPLEQ_FIRST(&ccr_mft->subordinates);
                SIMPLEQ_REMOVE_HEAD(&ccr_mft->subordinates, entry);
                free(sub_ski);
        }

        free(ccr_mft->seqnum);
        free(ccr_mft->sia);
        free(ccr_mft);
}

static void
ccr_mfts_free(struct ccr_mft_tree *mfts)
{
        struct ccr_mft *ccr_mft, *tmp_ccr_mft;

        RB_FOREACH_SAFE(ccr_mft, ccr_mft_tree, mfts, tmp_ccr_mft) {
                RB_REMOVE(ccr_mft_tree, mfts, ccr_mft);
                ccr_mft_free(ccr_mft);
        }
}

static void
ccr_vrps_free(struct ccr_vrp_tree *vrps)
{
        struct vrp *vrp, *tmp_vrp;

        RB_FOREACH_SAFE(vrp, ccr_vrp_tree, vrps, tmp_vrp) {
                RB_REMOVE(ccr_vrp_tree, vrps, vrp);
                free(vrp);
        }
}

static void
vap_free(struct vap *vap)
{
        if (vap == NULL)
                return;

        free(vap->providers);
        free(vap);
}

static void
ccr_vaps_free(struct vap_tree *vaps)
{
        struct vap *vap, *tmp_vap;

        RB_FOREACH_SAFE(vap, vap_tree, vaps, tmp_vap) {
                RB_REMOVE(vap_tree, vaps, vap);
                vap_free(vap);
        }
}

static void
ccr_tas_free(struct ccr_tas_tree *tas)
{
        struct ccr_tas_ski *cts, *tmp_cts;

        RB_FOREACH_SAFE(cts, ccr_tas_tree, tas, tmp_cts) {
                RB_REMOVE(ccr_tas_tree, tas, cts);
                free(cts);
        }
}

static void
brk_free(struct brk *brk)
{
        if (brk == NULL)
                return;

        free(brk->ski);
        free(brk->pubkey);
        free(brk);
}

static void
ccr_brks_free(struct brk_tree *brks)
{
        struct brk *brk, *tmp_brk;

        RB_FOREACH_SAFE(brk, brk_tree, brks, tmp_brk) {
                RB_REMOVE(brk_tree, brks, brk);
                brk_free(brk);
        }
}

void
ccr_free(struct ccr *ccr)
{
        if (ccr == NULL)
                return;

        ccr_mfts_free(&ccr->mfts);
        ccr_vrps_free(&ccr->vrps);
        ccr_vaps_free(&ccr->vaps);
        ccr_tas_free(&ccr->tas);
        ccr_brks_free(&ccr->brks);

        free(ccr->mfts_hash);
        free(ccr->vrps_hash);
        free(ccr->vaps_hash);
        free(ccr->tas_hash);
        free(ccr->brks_hash);
        free(ccr->der);
        free(ccr);
}

static int
parse_mft_instances(const char *fn, struct ccr *ccr,
    const STACK_OF(ManifestInstance) *mis)
{
        ManifestInstance *mi;
        struct ccr_mft *ccr_mft = NULL, *prev;
        int i, j, instances_num, sub_num;
        const ACCESS_DESCRIPTION *ad;
        const SubjectKeyIdentifier *s;
        struct ccr_mft_sub_ski *sub = NULL;
        int rc = 0;
        uint64_t size = 0;

        instances_num = sk_ManifestInstance_num(mis);

        RB_INIT(&ccr->mfts);

        prev = NULL;
        for (i = 0; i < instances_num; i++) {
                ccr_mft = ccr_mft_new();

                mi = sk_ManifestInstance_value(mis, i);

                if (!copy_asn1_string(mi->hash,
                    ccr_mft->hash, sizeof(ccr_mft->hash))) {
                        warnx("%s: manifest instance #%d corrupted", fn, i);
                        goto out;
                }

                if (prev != NULL) {
                        if (ccr_mft_cmp(ccr_mft, prev) <= 0) {
                                warnx("%s: misordered ManifestInstances", fn);
                                goto out;
                        }
                }

                if (!copy_asn1_string(mi->aki,
                    ccr_mft->aki, sizeof(ccr_mft->aki))) {
                        warnx("%s: manifest instance #%d corrupted: aki",
                            fn, i);
                        goto out;
                }

                if (!ASN1_INTEGER_get_uint64(&size, mi->size)) {
                        warnx("%s: manifest instance #%d corrupted: size parse",
                            fn, i);
                        goto out;
                }
                if (size < 1000 || size > MAX_FILE_SIZE) {
                        warnx("%s: manifest instance #%d corrupted: size",
                            fn, i);
                        goto out;
                }
                ccr_mft->size = size;

                ccr_mft->seqnum = x509_convert_seqnum(fn, "manifest number",
                    mi->manifestNumber);
                if (ccr_mft->seqnum == NULL)
                        goto out;

                if (!x509_get_generalized_time(fn, "ManifestInstance "
                    "thisUpdate", mi->thisUpdate, &ccr_mft->thisupdate))
                        goto out;

                if (sk_ACCESS_DESCRIPTION_num(mi->locations) != 1) {
                        warnx("%s: manifest instance #%d corrupted: unexpected"
                            " number of locations", fn, i);
                        goto out;
                }

                ad = sk_ACCESS_DESCRIPTION_value(mi->locations, 0);

                if (!x509_location(fn, "SIA: signedObject", ad->location,
                    &ccr_mft->sia))
                        goto out;

                sub_num = sk_SubjectKeyIdentifier_num(mi->subordinates);
                if (sub_num <= 0)
                        goto insert;

                for (j = 0; j < sub_num; j++) {
                        if ((sub = calloc(1, sizeof(*sub))) == NULL)
                                err(1, NULL);

                        s = sk_SubjectKeyIdentifier_value(mi->subordinates, j);
                        if (!copy_asn1_string(s, sub->ski, sizeof(sub->ski))) {
                                warnx("%s: manifest instance #%d corrupted: "
                                    "subordinates ski", fn, i);
                                goto out;
                        }
                        SIMPLEQ_INSERT_TAIL(&ccr_mft->subordinates, sub, entry);
                        sub = NULL;
                }

 insert:
                if (RB_INSERT(ccr_mft_tree, &ccr->mfts, ccr_mft) != NULL) {
                        warnx("%s: manifest state corrupted", fn);
                        goto out;
                }

                prev = ccr_mft;
                ccr_mft = NULL;
        }

        rc = 1;
 out:
        ccr_mft_free(ccr_mft);
        free(sub);
        return rc;
}

static int
parse_manifeststate(const char *fn, struct ccr *ccr, const ManifestState *state)
{
        int rc = 0;

        ccr->mfts_hash = validate_asn1_hash(fn, "ManifestState", state->hash,
            ASN1_ITEM_rptr(ManifestInstances), state->mis);
        if (ccr->mfts_hash == NULL)
                goto out;

        if (!x509_get_generalized_time(fn, "CCR mostRecentUpdate",
            state->mostRecentUpdate, &ccr->most_recent_update))
                goto out;

        if (sk_ManifestInstance_num(state->mis) == 0 &&
            ccr->most_recent_update != 0) {
                warnx("%s: invalid ManifestState mostRecentUpdate", fn);
                goto out;
        }

        if (!parse_mft_instances(fn, ccr, state->mis))
                goto out;

        rc = 1;
 out:
        return rc;
}

static int
parse_roa_addresses(const char *fn, struct ccr *ccr, uint32_t asid, enum afi afi,
    const STACK_OF(ROAIPAddress) *addrs)
{
        const ROAIPAddress *r;
        struct vrp *prev, *vrp = NULL;
        uint64_t maxlen;
        int addrs_num, i, rc = 0;

        if ((addrs_num = sk_ROAIPAddress_num(addrs)) <= 0) {
                warnx("%s: missing ROAIPAddress", fn);
                goto out;
        }

        prev = NULL;
        for (i = 0; i < addrs_num; i++) {
                r = sk_ROAIPAddress_value(addrs, i);

                if ((vrp = calloc(1, sizeof(*vrp))) == NULL)
                        err(1, NULL);

                vrp->asid = asid;
                vrp->afi = afi;

                if (!ip_addr_parse(r->address, afi, fn, &vrp->addr)) {
                        warnx("%s: invalid address in ROAPayload", fn);
                        goto out;
                }

                maxlen = vrp->addr.prefixlen;
                if (r->maxLength != NULL) {
                        if (!ASN1_INTEGER_get_uint64(&maxlen, r->maxLength)) {
                                warnx("%s: ASN1_INTEGER_get_uint64 failed", fn);
                                goto out;
                        }
                        if (vrp->addr.prefixlen > maxlen) {
                                warnx("%s: invalid maxLength", fn);
                                goto out;
                        }
                        if (maxlen > ((afi == AFI_IPV4) ? 32 : 128)) {
                                warnx("%s: maxLength too large", fn);
                                goto out;
                        }
                        vrp->maxlength = maxlen;
                }

                if (prev != NULL) {
                        if (ccr_vrp_cmp(vrp, prev) <= 0) {
                                warnx("%s: misordered ROAIPAddressFamily", fn);
                                goto out;
                        }
                }

                if ((RB_INSERT(ccr_vrp_tree, &ccr->vrps, vrp)) != NULL) {
                        warnx("%s: duplicate ROAIPAddress", fn);
                        goto out;
                }

                prev = vrp;
                vrp = NULL;
        }

        rc = 1;
 out:
        free(vrp);
        return rc;
}

static int
parse_roa_ipaddrb(const char *fn, struct ccr *ccr, uint32_t asid,
    const STACK_OF(ROAIPAddressFamily) *ipaddrblocks)
{
        const ROAIPAddressFamily *ripaf;
        enum afi afi;
        int ipv4_seen = 0, ipv6_seen = 0;
        int i, rc = 0, ipb_num;

        ipb_num = sk_ROAIPAddressFamily_num(ipaddrblocks);
        if (ipb_num != 1 && ipb_num != 2) {
                warnx("%s: unexpected ipAddrBlocks count for AS %u", fn, asid);
                goto out;
        }

        for (i = 0; i < ipb_num; i++) {
                ripaf = sk_ROAIPAddressFamily_value(ipaddrblocks, i);

                if (!ip_addr_afi_parse(fn, ripaf->addressFamily, &afi)) {
                        warnx("%s: invalid afi for AS %u", fn, asid);
                        goto out;
                }

                switch (afi) {
                case AFI_IPV4:
                        if (ipv6_seen > 0) {
                                warnx("%s: misordered IPv4 addressFamily for AS"
                                    " %u", fn, asid);
                                goto out;
                        }
                        if (ipv4_seen++ > 0) {
                                warnx("%s: IPv4 addressFamily duplicate for AS"
                                    " %u", fn, asid);
                                goto out;
                        }
                        break;
                case AFI_IPV6:
                        if (ipv6_seen++ > 0) {
                                warnx("%s: IPv6 addressFamily duplicate for AS"
                                    " %u", fn, asid);
                                goto out;
                        }
                        break;
                }

                if (!parse_roa_addresses(fn, ccr, asid, afi, ripaf->addresses))
                        goto out;
        }

        rc = 1;
 out:
        return rc;
}

static int
parse_roa_payloads(const char *fn, struct ccr *ccr,
    const STACK_OF(ROAPayloadSet) *rps)
{
        ROAPayloadSet *rp;
        int i, rc = 0, rps_num;

        rps_num = sk_ROAPayloadSet_num(rps);

        RB_INIT(&ccr->vrps);

        for (i = 0; i < rps_num; i++) {
                uint32_t asid;

                rp = sk_ROAPayloadSet_value(rps, i);

                if (!as_id_parse(rp->asID, &asid)) {
                        warnx("%s: malformed asID in ROAPayloadSet", fn);
                        goto out;
                }

                if (!parse_roa_ipaddrb(fn, ccr, asid, rp->ipAddrBlocks))
                        goto out;
        }

        rc = 1;
 out:
        return rc;
}

static int
parse_roastate(const char *fn, struct ccr *ccr, const ROAPayloadState *state)
{
        int rc = 0;

        ccr->vrps_hash = validate_asn1_hash(fn, "ROAPayloadState", state->hash,
            ASN1_ITEM_rptr(ROAPayloadSets), state->rps);
        if (ccr->vrps_hash == NULL)
                goto out;

        if (!parse_roa_payloads(fn, ccr, state->rps))
                goto out;

        rc = 1;
 out:
        return rc;
}

static int
parse_aspa_providers(const char *fn, struct ccr *ccr, uint32_t asid,
    STACK_OF(ASN1_INTEGER) *providers)
{
        struct vap *vap = NULL;
        ASN1_INTEGER *aint;
        uint32_t prev = 0, provider;
        int i, p_num, rc = 0;

        if ((p_num = sk_ASN1_INTEGER_num(providers)) <= 0) {
                warnx("%s: AS %u ASPAPayloadSet providers missing", fn, asid);
                goto out;
        }

        if ((vap = calloc(1, sizeof(*vap))) == NULL)
                err(1, NULL);

        vap->custasid = asid;
        vap->num_providers = p_num;

        if ((vap->providers = calloc(p_num, sizeof(vap->providers[0]))) == NULL)
                err(1, NULL);

        for (i = 0; i < p_num; i++) {
                aint = sk_ASN1_INTEGER_value(providers, i);

                if (!as_id_parse(aint, &provider)) {
                        warnx("%s: AS %u malformed ASPA provider", fn, asid);
                        goto out;
                }

                if (i > 0) {
                        if (provider <= prev) {
                                warnx("%s: AS %u misordered providers", fn,
                                    asid);
                                goto out;
                        }
                }

                vap->providers[i] = provider;
                prev = provider;
        }

        if ((RB_INSERT(vap_tree, &ccr->vaps, vap)) != NULL) {
                warnx("%s: duplicate ASPAPayloadSet", fn);
                goto out;
        }
        vap = NULL;

        rc = 1;
 out:
        vap_free(vap);
        return rc;
}

static int
parse_aspa_payloads(const char *fn, struct ccr *ccr,
    const STACK_OF(ASPAPayloadSet) *aps)
{
        ASPAPayloadSet *a;
        uint32_t asid, prev = 0;
        int i, rc = 0, aps_num;

        aps_num = sk_ASPAPayloadSet_num(aps);

        RB_INIT(&ccr->vaps);

        for (i = 0; i < aps_num; i++) {
                a = sk_ASPAPayloadSet_value(aps, i);

                if (!as_id_parse(a->asID, &asid)) {
                        warnx("%s: malformed asID in ASPAPayloadSet", fn);
                        goto out;
                }

                if (i > 0) {
                        if (asid <= prev) {
                                warnx("%s: ASPAPayloadState misordered", fn);
                                goto out;
                        }
                }

                if (!parse_aspa_providers(fn, ccr, asid, a->providers))
                        goto out;

                prev = asid;
        }

        rc = 1;
 out:
        return rc;
}

static int
parse_aspastate(const char *fn, struct ccr *ccr, const ASPAPayloadState *state)
{
        int rc = 0;

        ccr->vaps_hash = validate_asn1_hash(fn, "ASPAPayloadState", state->hash,
            ASN1_ITEM_rptr(ASPAPayloadSets), state->aps);
        if (ccr->vaps_hash == NULL)
                goto out;

        if (!parse_aspa_payloads(fn, ccr, state->aps))
                goto out;

        rc = 1;
 out:
        return rc;
}

static int
parse_tas_skis(const char *fn, struct ccr *ccr,
    const STACK_OF(SubjectKeyIdentifier) *skis)
{
        SubjectKeyIdentifier *ski;
        struct ccr_tas_ski *cts = NULL, *prev;
        int i, rc = 0, skis_num;

        if ((skis_num = sk_SubjectKeyIdentifier_num(skis)) <= 0) {
                warnx("%s: missing TrustAnchorState SubjectKeyIdentifier", fn);
                goto out;
        }

        RB_INIT(&ccr->tas);

        prev = NULL;
        for (i = 0; i < skis_num; i++) {
                if ((cts = calloc(1, sizeof(*cts))) == NULL)
                        err(1, NULL);

                ski = sk_SubjectKeyIdentifier_value(skis, i);

                if (!copy_asn1_string(ski, cts->keyid, sizeof(cts->keyid))) {
                        warnx("%s: TAS SKI #%d corrupted", fn, i);
                        goto out;
                }

                if (prev != NULL) {
                        if (ccr_tas_ski_cmp(cts, prev) <= 0) {
                                warnx("%s: misordered TrustAnchorState", fn);
                                goto out;
                        }
                }

                if (RB_INSERT(ccr_tas_tree, &ccr->tas, cts) != NULL) {
                        warnx("%s: trust anchor state corrupted", fn);
                        goto out;
                }

                prev = cts;
                cts = NULL;
        }

        rc = 1;
 out:
        free(cts);
        return rc;
}

static int
parse_tastate(const char *fn, struct ccr *ccr, const TrustAnchorState *state)
{
        int rc = 0;

        ccr->tas_hash = validate_asn1_hash(fn, "TrustAnchorState", state->hash,
            ASN1_ITEM_rptr(SubjectKeyIdentifiers), state->skis);
        if (ccr->tas_hash == NULL)
                goto out;

        if (!parse_tas_skis(fn, ccr, state->skis))
                goto out;

        rc = 1;
 out:
        return rc;
}

static int
parse_routerkeys(const char *fn, struct ccr *ccr, uint32_t asid,
    STACK_OF(RouterKey) *routerkeys)
{
        RouterKey *rk;
        struct brk *brk = NULL, *prev;
        int i, rk_num, rc = 0;

        if ((rk_num = sk_RouterKey_num(routerkeys)) <= 0) {
                warnx("%s: missing RouterKey", fn);
                goto out;
        }

        prev = NULL;
        for (i = 0; i < rk_num; i++) {
                unsigned char *der;
                int der_len;

                if ((brk = calloc(1, sizeof(*brk))) == NULL)
                        err(1, NULL);

                brk->asid = asid;

                rk = sk_RouterKey_value(routerkeys, i);

                if (ASN1_STRING_length(rk->ski) != SHA_DIGEST_LENGTH) {
                        warnx("%s: AS %u RouterKey SKI corrupted", fn, asid);
                        goto out;
                }

                brk->ski = hex_encode_asn1_string(rk->ski);

                der = NULL;
                if ((der_len = i2d_X509_PUBKEY(rk->spki, &der)) <= 0) {
                        warnx("%s: AS %u RouterKey SPKI corrupted", fn, asid);
                        goto out;
                }

                if (base64_encode(der, der_len, &brk->pubkey) == -1)
                        errx(1, "base64_encode");

                free(der);

                if (prev != NULL) {
                        if (strcmp(brk->ski, prev->ski) <= 0) {
                                warnx("%s: misordered RouterKey", fn);
                                goto out;
                        }
                }

                if ((RB_INSERT(brk_tree, &ccr->brks, brk)) != NULL) {
                        warnx("%s; duplicate RouterKey", fn);
                        goto out;
                }

                prev = brk;
                brk = NULL;
        }

        rc = 1;
 out:
        brk_free(brk);
        return rc;
}

static int
parse_rksets(const char *fn, struct ccr *ccr, STACK_OF(RouterKeySet) *rksets)
{
        RouterKeySet *rkset;
        uint32_t asid, prev = 0;
        int i, rc = 0, rksets_num;

        rksets_num = sk_RouterKeySet_num(rksets);

        RB_INIT(&ccr->brks);

        for (i = 0; i < rksets_num; i++) {
                rkset = sk_RouterKeySet_value(rksets, i);

                if (!as_id_parse(rkset->asID, &asid)) {
                        warnx("%s: malformed asID in RouterKeySet", fn);
                        goto out;
                }

                if (i > 0) {
                        if (asid <= prev) {
                                warnx("%s: AS %u misordered routerkeyset", fn,
                                    asid);
                                goto out;
                        }
                }

                if (!parse_routerkeys(fn, ccr, asid, rkset->routerKeys))
                        goto out;

                prev = asid;
        }

        rc = 1;
 out:
        return rc;
}

static int
parse_rkstate(const char *fn, struct ccr *ccr, const RouterKeyState *state)
{
        int rc = 0;

        ccr->brks_hash = validate_asn1_hash(fn, "RouterKeyState", state->hash,
            ASN1_ITEM_rptr(RouterKeySets), state->rksets);
        if (ccr->brks_hash == NULL)
                goto out;

        if (!parse_rksets(fn, ccr, state->rksets))
                goto out;

        rc = 1;
 out:
        return rc;
}

struct ccr *
ccr_parse(const char *fn, const unsigned char *der, size_t len)
{
        const unsigned char *oder;
        ContentInfo *ci = NULL;
        CanonicalCacheRepresentation *ccr_asn1 = NULL;
        const ASN1_OBJECT *oid;
        struct ccr *ccr = NULL;
        int nid, ptype, rc = 0;

        if (der == NULL)
                return NULL;

        oder = der;
        if ((ci = d2i_ContentInfo(NULL, &der, len)) == NULL) {
                warnx("%s: d2i_ContentInfo", fn);
                goto out;
        }
        if (der != oder + len) {
                warnx("%s: %td bytes trailing garbage", fn, oder + len - der);
                goto out;
        }

        if (OBJ_cmp(ci->contentType, ccr_oid) != 0) {
                char buf[128];

                OBJ_obj2txt(buf, sizeof(buf), ci->contentType, 1);
                warnx("%s: unexpected OID: got %s, want 1.2.840.113549.1.9.16.1.54",
                    fn, buf);
                goto out;
        }

        ccr_asn1 = ci->content;

        if (!valid_econtent_version(fn, ccr_asn1->version, 0))
                goto out;

        X509_ALGOR_get0(&oid, &ptype, NULL, ccr_asn1->hashAlg);
        if ((nid = OBJ_obj2nid(oid)) != NID_sha256 || ptype != V_ASN1_UNDEF) {
                warnx("%s: hashAlg: want SHA256 object without parameters "
                    "have %s with parameter type %d", fn, nid2str(nid), ptype);
                goto out;
        }

        if ((ccr = calloc(1, sizeof(*ccr))) == NULL)
                err(1, NULL);

        if (!x509_get_generalized_time(fn, "CCR producedAt",
            ccr_asn1->producedAt, &ccr->producedat))
                goto out;

        if (ccr_asn1->mfts == NULL && ccr_asn1->vrps == NULL &&
            ccr_asn1->vaps == NULL && ccr_asn1->tas == NULL &&
            ccr_asn1->rks == NULL) {
                warnx("%s: must contain at least one state component", fn);
                goto out;
        }

        if (ccr_asn1->mfts != NULL) {
                if (!parse_manifeststate(fn, ccr, ccr_asn1->mfts))
                        goto out;
        }

        if (ccr_asn1->vrps != NULL) {
                if (!parse_roastate(fn, ccr, ccr_asn1->vrps))
                        goto out;
        }

        if (ccr_asn1->vaps != NULL) {
                if (!parse_aspastate(fn, ccr, ccr_asn1->vaps))
                        goto out;
        }

        if (ccr_asn1->tas != NULL) {
                if (!parse_tastate(fn, ccr, ccr_asn1->tas))
                        goto out;
        }

        if (ccr_asn1->rks != NULL) {
                if (!parse_rkstate(fn, ccr, ccr_asn1->rks))
                        goto out;
        }

        rc = 1;
 out:
        ContentInfo_free(ci);

        if (rc == 0) {
                ccr_free(ccr);
                ccr = NULL;
        }

        return ccr;
}