Download raw body.
rpki-client: Canonical Cache Representation
Dear all,
I'd like to introduce Canonical Cache Representation, CCR for short. CCR
is a lean DER-encoded output that represents various aspects of the
state of the RPKI validated cache. CCR primarily exists to support and
improve RPKI analysis, diagnostic, and monitoring functions.
The CCR file format is described in ASN.1 notation in ccr.c. The format
description will likely later on move into an internet-draft, because
I'd very much like to be able to easily compare different validator
instances/implementations using an interopable format.
How CCR works at a high level: each current RPKI Manifest represents the
current state of a given Certification Authority's caRepository.
Therefor, the hash of the Manifest object's file contents indirectly
represents the caRepository contents. In turn, a hash calculated over a
list of all those aforementioned hashes represents the entire
on-the-wire form of the validated cache.
Additionally, having rpki-client itself emit the list of current manifest
filenames (SIAs) and their respective hashes (which rpki-client collects
_anyway_) also saves me from having to issue a fairly expensive commands
(such as "find /var/cache/rpki-client/* -type f | xargs sha256") after
each run.
The CCR file also contains a deduplicated & merged representation of all
validated ROA+ASPA payloads. This is the same data a BGP router would
ingest after sending a "Reset Query" to a RTR server. A hash over all
VRPs/VAPs represents the current _effective_ state of the RPKI. Being
able to easily differentiate between 'routine' changes in the ecosystem
(CRL resigning) and changes that might impact route decision making
seems useful to me.
Example (snipped) stdout output of an rpki-client invocation:
...
New files moved into validated cache: 283
CCR manifest hash: nzMbydW1rjwufeFots7lQ0wWpo6Srn9GpVMkQqMBb+I=
CCR VRPs hash: c0OsIXEGEkpK7tu8gYRhB+ZR2uifAEyio0zvyPlkD/Y=
CCR VAPs hash: 6CgIGm6OCBiPq8m1rRAeust4sGrl6QXcMEcsGRlbLKE=
Cleanup: removed 3 files, 251 directories
Repository cleanup: kept 5842 and removed 5 superfluous files
VRP Entries: 740014 (733698 unique)
VAP Entries: 112 (111 unique, 0 overflowed)
...
The hashes also are available via the OpenBGPD/BIRD/JSON outputs.
After this I'll work to add CCR file decoding support to filemode.
Kind regards,
Job
Index: Makefile
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/Makefile,v
diff -u -p -r1.36 Makefile
--- Makefile 14 Jul 2025 20:42:21 -0000 1.36
+++ Makefile 20 Aug 2025 11:17:20 -0000
@@ -4,6 +4,7 @@ PROG= rpki-client
SRCS+= as.c
SRCS+= aspa.c
+SRCS+= ccr.c
SRCS+= cert.c
SRCS+= cms.c
SRCS+= constraints.c
Index: ccr.c
===================================================================
RCS file: ccr.c
diff -N ccr.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ ccr.c 20 Aug 2025 11:17:20 -0000
@@ -0,0 +1,676 @@
+/* $OpenBSD$ */
+/*
+ * 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/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 "extern.h"
+#include "rpki-asn1.h"
+
+/*
+ * RpkiCanonicalCacheRepresentation-2025
+ * { iso(1) member-body(2) us(840) rsadsi(113549)
+ * pkcs(1) pkcs9(9) smime(16) mod(0) id-mod-rpkiCCR-2025(TBD) }
+ *
+ * DEFINITIONS EXPLICIT TAGS ::=
+ * BEGIN
+ *
+ * IMPORTS
+ * CONTENT-TYPE, Digest, DigestAlgorithmIdentifier
+ * FROM CryptographicMessageSyntax-2010 -- in [RFC6268]
+ * { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1)
+ * pkcs-9(9) smime(16) modules(0) id-mod-cms-2009(58) };
+ *
+ * KeyIdentifier
+ * FROM PKIX1Implicit88 -- in [RFC5280]
+ * { iso(1) identified-organization(3) dod(6) internet(1) security(5)
+ * mechanisms(5) pkix(7) id-mod(0) id-pkix1-implicit(19) }
+ *
+ * -- in [draft-spaghetti-sidrops-rpki-erik-protocol-01]
+ * -- https://sobornost.net/~job/draft-spaghetti-sidrops-rpki-erik-protocol.html
+ * ManifestRef
+ * FROM RpkiErikPartition-2025
+ * { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1)
+ * pkcs9(9) smime(16) mod(0) id-mod-rpkiErikPartition-2025(TBD) }
+ *
+ * ASID, ROAIPAddressFamily
+ * FROM RPKI-ROA-2023 -- in [RFC9582]
+ * { so(1) member-body(2) us(840) rsadsi(113549) pkcs(1)
+ * pkcs9(9) smime(16) mod(0) id-mod-rpkiROA-2023(75) }
+ *
+ * ct-rpkiCanonicalCacheRepresentation CONTENT-TYPE ::=
+ * { TYPE RpkiCanonicalCacheRepresentation
+ * IDENTIFIED BY id-ct-rpkiCanonicalCacheRepresentation }
+ *
+ * id-ct-rpkiCanonicalCacheRepresentation OBJECT IDENTIFIER ::=
+ * { iso(1) identified-organization(3) dod(6) internet(1) private(4)
+ * enterprise(1) snijders(41948) ccr(825) }
+ *
+ * RpkiCanonicalCacheRepresentation ::= SEQUENCE {
+ * version [0] INTEGER DEFAULT 0,
+ * hashAlg DigestAlgorithmIdentifier,
+ * producedAt GeneralizedTime,
+ * mfts [1] ManifestState OPTIONAL,
+ * vrps [2] ROAPayloadState OPTIONAL,
+ * vaps [3] ASPAPayloadState OPTIONAL,
+ * ... }
+ * -- at least one of mfts or vrps MUST be present
+ * ( WITH COMPONENTS { ..., mfts PRESENT } |
+ * WITH COMPONENTS { ..., vrps PRESENT } |
+ * WITH COMPONENTS { ..., vaps PRESENT } )
+ *
+ * ManifestState ::= SEQUENCE {
+ * mftrefs SEQUENCE OF ManifestRef,
+ * mostRecentUpdate GeneralizedTime,
+ * hash Digest OPTIONAL }
+ *
+ * ROAPayloadState ::= SEQUENCE {
+ * rps SEQUENCE OF ROAPayloadSet,
+ * hash Digest OPTIONAL }
+ *
+ * ROAPayloadSet ::= SEQUENCE {
+ * asID ASID,
+ * ipAddrBlocks SEQUENCE (SIZE(1..2)) OF ROAIPAddressFamily }
+ *
+ * ASPAPayloadState ::= SEQUENCE {
+ * aps SEQUENCE OF ASPAPayloadSet,
+ * hash Digest OPTIONAL }
+ *
+ * ASPAPayloadSet ::= SEQUENCE {
+ * asID ASID
+ * providers SEQUENCE (SIZE(1..MAX)) OF ASID }
+ *
+ * END
+ */
+
+ASN1_ITEM_EXP ManifestRef_it;
+ASN1_ITEM_EXP ManifestRefs_it;
+ASN1_ITEM_EXP ROAPayloadSet_it;
+ASN1_ITEM_EXP ROAPayloadSets_it;
+ASN1_ITEM_EXP ASPAPayloadSet_it;
+ASN1_ITEM_EXP ASPAPayloadSets_it;
+ASN1_ITEM_EXP CanonicalCacheRepresentation_it;
+ASN1_ITEM_EXP CONTENT_TYPE_it;
+
+ASN1_SEQUENCE(ManifestRef) = {
+ ASN1_SIMPLE(ManifestRef, hash, ASN1_OCTET_STRING),
+ ASN1_SIMPLE(ManifestRef, size, ASN1_INTEGER),
+ ASN1_SIMPLE(ManifestRef, aki, ASN1_OCTET_STRING),
+ ASN1_SIMPLE(ManifestRef, manifestNumber, ASN1_INTEGER),
+ ASN1_SEQUENCE_OF(ManifestRef, location, ACCESS_DESCRIPTION),
+} ASN1_SEQUENCE_END(ManifestRef);
+
+IMPLEMENT_ASN1_FUNCTIONS(ManifestRef);
+
+ASN1_ITEM_TEMPLATE(ManifestRefs) =
+ ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_SEQUENCE_OF, 0, mftrefs, ManifestRef)
+ASN1_ITEM_TEMPLATE_END(ManifestRefs)
+
+IMPLEMENT_ASN1_ENCODE_FUNCTIONS_fname(ManifestRefs, ManifestRefs, ManifestRefs);
+
+ASN1_SEQUENCE(ManifestState) = {
+ ASN1_SEQUENCE_OF(ManifestState, mftrefs, ManifestRef),
+ ASN1_SIMPLE(ManifestState, mostRecentUpdate, ASN1_GENERALIZEDTIME),
+ ASN1_OPT(ManifestState, hash, ASN1_OCTET_STRING),
+} ASN1_SEQUENCE_END(ManifestState);
+
+IMPLEMENT_ASN1_FUNCTIONS(ManifestState);
+
+ASN1_SEQUENCE(ROAPayloadSet) = {
+ ASN1_SIMPLE(ROAPayloadSet, asID, ASN1_INTEGER),
+ ASN1_SEQUENCE_OF(ROAPayloadSet, ipAddrBlocks, ROAIPAddressFamily),
+} ASN1_SEQUENCE_END(ROAPayloadSet);
+
+IMPLEMENT_ASN1_FUNCTIONS(ROAIPAddress);
+IMPLEMENT_ASN1_FUNCTIONS(ROAIPAddressFamily);
+IMPLEMENT_ASN1_FUNCTIONS(ROAPayloadSet);
+
+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(ROAPayloadState) = {
+ ASN1_SEQUENCE_OF(ROAPayloadState, rps, ROAPayloadSet),
+ ASN1_OPT(ROAPayloadState, hash, ASN1_OCTET_STRING),
+} ASN1_SEQUENCE_END(ROAPayloadState);
+
+IMPLEMENT_ASN1_FUNCTIONS(ROAPayloadState);
+
+ASN1_SEQUENCE(ASPAPayloadSet) = {
+ ASN1_SIMPLE(ASPAPayloadSet, asID, ASN1_INTEGER),
+ ASN1_SEQUENCE_OF(ASPAPayloadSet, providers, ASN1_INTEGER),
+} ASN1_SEQUENCE_END(ASPAPayloadSet);
+
+IMPLEMENT_ASN1_FUNCTIONS(ASPAPayloadSet);
+
+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(ASPAPayloadState) = {
+ ASN1_SEQUENCE_OF(ASPAPayloadState, aps, ASPAPayloadSet),
+ ASN1_OPT(ASPAPayloadState, hash, ASN1_OCTET_STRING),
+} ASN1_SEQUENCE_END(ASPAPayloadState);
+
+IMPLEMENT_ASN1_FUNCTIONS(ASPAPayloadState);
+
+ASN1_SEQUENCE(CanonicalCacheRepresentation) = {
+ ASN1_EXP_OPT(CanonicalCacheRepresentation, version, ASN1_INTEGER, 0),
+ ASN1_SIMPLE(CanonicalCacheRepresentation, hashAlg, ASN1_OBJECT),
+ 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_SEQUENCE_END(CanonicalCacheRepresentation);
+
+IMPLEMENT_ASN1_FUNCTIONS(CanonicalCacheRepresentation);
+
+ASN1_SEQUENCE(CONTENT_TYPE) = {
+ ASN1_SIMPLE(CONTENT_TYPE, oid, ASN1_OBJECT),
+ ASN1_EXP(CONTENT_TYPE, content, ASN1_OCTET_STRING, 0),
+} ASN1_SEQUENCE_END(CONTENT_TYPE);
+
+IMPLEMENT_ASN1_FUNCTIONS(CONTENT_TYPE);
+
+
+static ASN1_INTEGER *
+seqnum_to_asn1int(const char *seqnum)
+{
+ BIGNUM *bn = NULL;
+ ASN1_INTEGER *aint;
+
+ if (!BN_hex2bn(&bn, seqnum))
+ errx(1, "BN_hex2bn");
+
+ if ((aint = BN_to_ASN1_INTEGER(bn, NULL)) == NULL)
+ errx(1, "BN_to_ASN1_INTEGER");
+
+ BN_free(bn);
+ return aint;
+}
+
+static STACK_OF(ACCESS_DESCRIPTION) *
+sia_to_ad(const char *sia)
+{
+ STACK_OF(ACCESS_DESCRIPTION) *sad = NULL;
+ ACCESS_DESCRIPTION *ad = NULL;
+
+ if ((sad = sk_ACCESS_DESCRIPTION_new_null()) == NULL)
+ errx(1, "sk_ACCESS_DESCRIPTION_new_null");
+
+ if ((ad = ACCESS_DESCRIPTION_new()) == NULL)
+ errx(1, "ACCESS_DESCRIPTION_new");
+ assert(ad->method != NULL);
+ assert(ad->location != NULL);
+
+ ASN1_OBJECT_free(ad->method);
+ if ((ad->method = OBJ_nid2obj(NID_signedObject)) == NULL)
+ errx(1, "OBJ_nid2obj");
+
+ GENERAL_NAME_free(ad->location);
+ if ((ad->location = a2i_GENERAL_NAME(NULL, NULL, NULL, GEN_URI, sia,
+ 0)) == NULL)
+ errx(1, "a2i_GENERAL_NAME");
+
+ if (sk_ACCESS_DESCRIPTION_push(sad, ad) != 1)
+ errx(1, "sk_ACCESS_DESCRIPTION_push");
+
+ return sad;
+}
+
+int
+output_ccr(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 ManifestState *
+generate_manifeststate(struct ccr *ccr)
+{
+ ManifestState *ms;
+ ManifestRef *mftref;
+ struct ccr_mft *cm;
+ time_t most_recent_update = 0;
+ unsigned char *out = NULL;
+ int out_len;
+ char hash[SHA256_DIGEST_LENGTH];
+
+ if ((ms = ManifestState_new()) == NULL)
+ errx(1, "ManifestState_new");
+
+ if ((ms->mftrefs = sk_ManifestRef_new_null()) == NULL)
+ errx(1, "sk_ManifestRef_new_null");
+
+ RB_FOREACH(cm, ccr_mft_tree, &ccr->mfts) {
+ if ((mftref = ManifestRef_new()) == NULL)
+ errx(1, "ManifestRef_new");
+
+ if (ASN1_STRING_set(mftref->hash, cm->hash,
+ SHA256_DIGEST_LENGTH) != 1)
+ errx(1, "ASN1_STRING_set");
+
+ if (ASN1_STRING_set(mftref->aki, cm->aki, sizeof(cm->aki)) != 1)
+ errx(1, "ASN1_STRING_set");
+
+ if (!ASN1_INTEGER_set_uint64(mftref->size, cm->size))
+ errx(1, "ASN1_INTEGER_set_uint64");
+
+ mftref->manifestNumber = seqnum_to_asn1int(cm->seqnum);
+
+ mftref->location = sia_to_ad(cm->sia);
+
+ if (sk_ManifestRef_push(ms->mftrefs, mftref) <= 0)
+ errx(1, "sk_ManifestRef_push");
+
+ if (cm->thisupdate > most_recent_update)
+ most_recent_update = cm->thisupdate;
+ }
+
+ if ((ms->mostRecentUpdate = ASN1_GENERALIZEDTIME_set(NULL,
+ most_recent_update)) == NULL)
+ errx(1, "ASN1_GENERALIZEDTIME_set");
+
+ if ((out_len = i2d_ManifestRefs(ms->mftrefs, &out)) <= 0)
+ errx(1, "i2d_ManifestRefs");
+
+ if (!EVP_Digest(out, out_len, hash, NULL, EVP_sha256(), NULL))
+ errx(1, "EVP_Digest");
+
+ if ((ms->hash = ASN1_STRING_new()) == NULL)
+ errx(1, "ASN1_STRING_new");
+
+ if (ASN1_STRING_set(ms->hash, hash, SHA256_DIGEST_LENGTH) != 1)
+ errx(1, "ASN1_STRING_set ms->hash");
+
+ if (base64_encode(hash, SHA256_DIGEST_LENGTH, &ccr->mfts_hash))
+ errx(1, "base64_encode");
+
+ free(out);
+
+ return ms;
+}
+
+static ROAIPAddress *
+vrp_to_bs(struct vrp *vrp)
+{
+ ROAIPAddress *ripa;
+ ASN1_BIT_STRING *addr;
+ unsigned addr_bit_len, addr_byte_len;
+
+ if ((ripa = ROAIPAddress_new()) == NULL)
+ errx(1, "ROAIPAddress_new");
+
+ if ((addr = ASN1_BIT_STRING_new()) == NULL)
+ errx(1, "ASN1_BIT_STRING_new");
+
+ addr_byte_len = (vrp->addr.prefixlen + 7) / 8;
+
+ if (!ASN1_BIT_STRING_set(addr, vrp->addr.addr, addr_byte_len))
+ errx(1, "ASN1_BIT_STRING_set");
+
+ addr->flags &= ~7;
+ addr->flags |= ASN1_STRING_FLAG_BITS_LEFT;
+
+ addr_bit_len = vrp->addr.prefixlen % 8;
+ if (addr_bit_len > 0) {
+ addr->data[addr_byte_len - 1] &= ~(0xFF >> addr_bit_len);
+ addr->flags |= 8 - addr_bit_len;
+ }
+
+ ripa->address = addr;
+
+ 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");
+ }
+
+ return ripa;
+}
+
+static ROAPayloadState *
+generate_roapayloadstate(struct validation_data *vd)
+{
+ ROAPayloadState *vrps;
+ struct vrp *vrp;
+ int curr_asn = -1, prev_asn = -1;
+ ROAPayloadSet *rp;
+ ROAIPAddressFamily *ripaf;
+ enum afi prev_afi = 0, curr_afi = 0;
+ unsigned char afibuf[2];
+ ROAIPAddress *ripa;
+ unsigned char *out = NULL;
+ int out_len;
+ char hash[SHA256_DIGEST_LENGTH];
+
+ if ((vrps = ROAPayloadState_new()) == NULL)
+ errx(1, "ROAPayloadState_new");
+
+ if ((vrps->rps = sk_ROAPayloadSet_new_null()) == NULL)
+ errx(1, "sk_ROAPayloadSet_new_null");
+
+ RB_FOREACH(vrp, ccr_vrp_tree, &vd->ccr.vrps) {
+ prev_asn = curr_asn;
+ curr_asn = vrp->asid;
+
+ if (prev_asn != curr_asn) {
+ if ((rp = ROAPayloadSet_new()) == NULL)
+ errx(1, "ROAPayloadSet_new");
+
+ if (sk_ROAPayloadSet_push(vrps->rps, rp) <= 0)
+ errx(1, "sk_ROAPayloadSet_push");
+
+ if (!ASN1_INTEGER_set_uint64(rp->asID, vrp->asid))
+ errx(1, "ASN1_INTEGER_set_uint64");
+
+ if ((rp->ipAddrBlocks =
+ sk_ROAIPAddressFamily_new_null()) == NULL)
+ errx(1, "sk_ROAIPAddressFamily_new");
+
+ }
+
+ prev_afi = curr_afi;
+ curr_afi = vrp->afi;
+
+ if (prev_afi != curr_afi || prev_asn != curr_asn) {
+ if ((ripaf = ROAIPAddressFamily_new()) == NULL)
+ errx(1, "ROAIPAddressFamily_new");
+
+ if (sk_ROAIPAddressFamily_push(rp->ipAddrBlocks,
+ ripaf) <= 0)
+ errx(1, "sk_ROAIPAddressFamily_push");
+
+ afibuf[0] = (vrp->afi >> 8) & 0xFF;
+ afibuf[1] = (vrp->afi ) & 0xFF;
+ if (!ASN1_OCTET_STRING_set(ripaf->addressFamily, afibuf,
+ sizeof(afibuf)))
+ errx(1, "ASN1_OCTET_STRING_set");
+
+ ripaf->addresses = sk_ROAIPAddress_new_null();
+ if (ripaf->addresses == NULL)
+ errx(1, "sk_ROAIPAddress_new_null");
+ }
+
+ ripa = vrp_to_bs(vrp);
+
+ if (sk_ROAIPAddress_push(ripaf->addresses, ripa) <= 0)
+ errx(1, "sk_ROAIPAddress_push");
+ }
+
+ if ((out_len = i2d_ROAPayloadSets(vrps->rps, &out)) <= 0)
+ errx(1, "i2d_ROAPayloadSets");
+
+ if (!EVP_Digest(out, out_len, hash, NULL, EVP_sha256(), NULL))
+ errx(1, "EVP_Digest");
+
+ if ((vrps->hash = ASN1_STRING_new()) == NULL)
+ errx(1, "ASN1_STRING_new");
+
+ if (ASN1_STRING_set(vrps->hash, hash, SHA256_DIGEST_LENGTH) != 1)
+ errx(1, "ASN1_STRING_set vrps->hash");
+
+ if (base64_encode(hash, SHA256_DIGEST_LENGTH, &vd->ccr.vrps_hash))
+ errx(1, "base64_encode");
+
+ free(out);
+
+ return vrps;
+}
+
+static ASPAPayloadState *
+generate_aspapayloadstate(struct validation_data *vd)
+{
+ ASPAPayloadState *vaps;
+ ASPAPayloadSet *ap;
+ struct vap *vap;
+ unsigned char *out = NULL;
+ size_t i;
+ int out_len;
+ char hash[SHA256_DIGEST_LENGTH];
+
+ if ((vaps = ASPAPayloadState_new()) == NULL)
+ errx(1, "ASPAPayloadState_new");
+
+ if ((vaps->aps = sk_ASPAPayloadSet_new_null()) == NULL)
+ errx(1, "sk_ASPAPayloadSet_new_null");
+
+ RB_FOREACH(vap, vap_tree, &vd->vaps) {
+ if ((ap = ASPAPayloadSet_new()) == NULL)
+ errx(1, "ASPAPayloadSet_new");
+
+ if ((sk_ASPAPayloadSet_push(vaps->aps, ap)) <= 0)
+ errx(1, "sk_ASPAPayloadSet_push");
+
+ if (!ASN1_INTEGER_set_uint64(ap->asID, vap->custasid))
+ errx(1, "ASN1_INTEGER_set_uint64");
+
+ if ((ap->providers = sk_ASN1_INTEGER_new_null()) == NULL)
+ errx(1, "sk_ASN1_INTEGER_new_null");
+
+ for (i = 0; i < vap->num_providers; i++) {
+ ASN1_INTEGER *ai;
+
+ if ((ai = ASN1_INTEGER_new()) == NULL)
+ errx(1, "ASN1_INTEGER");
+
+ 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 ((out_len = i2d_ASPAPayloadSets(vaps->aps, &out)) <= 0)
+ errx(1, "i2d_ASPAPayloadSets");
+
+ if (!EVP_Digest(out, out_len, hash, NULL, EVP_sha256(), NULL))
+ errx(1, "EVP_Digest");
+
+ if ((vaps->hash = ASN1_STRING_new()) == NULL)
+ errx(1, "ASN1_STRING_new");
+
+ if (ASN1_STRING_set(vaps->hash, hash, SHA256_DIGEST_LENGTH) != 1)
+ errx(1, "ASN1_STRING_set vaps->hash");
+
+ if (base64_encode(hash, SHA256_DIGEST_LENGTH, &vd->ccr.vaps_hash))
+ errx(1, "base64_encode");
+
+ free(out);
+
+ return vaps;
+}
+
+void
+generate_ccr(struct validation_data *vd)
+{
+ CanonicalCacheRepresentation *ccr = NULL;
+ time_t now = get_current_time();
+ CONTENT_TYPE *ct = NULL;
+ unsigned char *out = NULL;
+ int out_len;
+
+ if ((ccr = CanonicalCacheRepresentation_new()) == NULL)
+ errx(1, "CanonicalCacheRepresentation_new");
+
+ if ((ccr->hashAlg = OBJ_nid2obj(NID_sha256)) == NULL)
+ errx(1, "OBJ_nid2obj");
+
+ if ((ccr->producedAt = ASN1_GENERALIZEDTIME_set(NULL, now)) == NULL)
+ errx(1, "ASN1_GENERALIZEDTIME_set");
+
+ if ((ccr->mfts = generate_manifeststate(&vd->ccr)) == 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 ((out_len = i2d_CanonicalCacheRepresentation(ccr, &out)) <= 0)
+ err(1, "i2d_CanonicalCacheRepresentation");
+
+ CanonicalCacheRepresentation_free(ccr);
+
+ if ((ct = CONTENT_TYPE_new()) == NULL)
+ errx(1, "CONTENT_TYPE_new");
+
+ /*
+ * At some point the below PEN OID should be replaced by one from IANA.
+ */
+ if ((ct->oid = OBJ_txt2obj("1.3.6.1.4.1.41948.825", 1)) == NULL)
+ errx(1, "OBJ_txt2obj");
+
+ if ((ct->content = ASN1_STRING_new()) == NULL)
+ errx(1, "ASN1_STRING_new");
+
+ if (ASN1_STRING_set(ct->content, out, out_len) != 1)
+ errx(1, "ASN1_STRING_set");
+
+ vd->ccr.der = NULL;
+ if ((vd->ccr.der_len = i2d_CONTENT_TYPE(ct, &vd->ccr.der)) <= 0)
+ errx(1, "i2d_CONTENT_TYPE");
+
+ CONTENT_TYPE_free(ct);
+}
+
+
+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);
+
+void
+ccr_insert_mft(struct ccr_mft_tree *tree, const struct mft *mft)
+{
+ struct ccr_mft *ccr_mft;
+
+ if ((ccr_mft = calloc(1, sizeof(*ccr_mft))) == NULL)
+ err(1, NULL);
+
+ 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, SHA256_DIGEST_LENGTH);
+
+ 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;
+
+ RB_INSERT(ccr_vrp_tree, tree, 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:
+ 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);
Index: extern.h
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/extern.h,v
diff -u -p -r1.257 extern.h
--- extern.h 14 Aug 2025 15:12:00 -0000 1.257
+++ extern.h 20 Aug 2025 11:17:20 -0000
@@ -233,6 +233,7 @@ struct mft {
char *sia; /* SIA signedObject */
char *crl; /* CRL file name */
unsigned char mfthash[SHA256_DIGEST_LENGTH];
+ size_t mftsize;
unsigned char crlhash[SHA256_DIGEST_LENGTH];
time_t signtime; /* CMS signing-time attribute */
time_t thisupdate; /* from the eContent */
@@ -459,12 +460,40 @@ struct brk {
RB_HEAD(brk_tree, brk);
RB_PROTOTYPE(brk_tree, brk, entry, brkcmp);
+struct ccr_mft {
+ RB_ENTRY(ccr_mft) entry;
+ char hash[SHA256_DIGEST_LENGTH];
+#define SHA1_DIGEST_LENGTH 20
+ char aki[SHA1_DIGEST_LENGTH];
+ size_t size;
+ time_t thisupdate;
+ char *seqnum;
+ char *sia;
+};
+
+RB_HEAD(ccr_mft_tree, ccr_mft);
+RB_PROTOTYPE(ccr_mft_tree, ccr_mft, entry, ccr_mft_cmp);
+
+RB_HEAD(ccr_vrp_tree, vrp);
+RB_PROTOTYPE(ccr_vrp_tree, vrp, entry, ccr_vrp_cmp);
+
+struct ccr {
+ struct ccr_mft_tree mfts;
+ struct ccr_vrp_tree vrps;
+ char *mfts_hash;
+ char *vrps_hash;
+ char *vaps_hash;
+ unsigned char *der;
+ size_t der_len;
+};
+
struct validation_data {
struct vrp_tree vrps;
struct brk_tree brks;
struct vap_tree vaps;
struct vsp_tree vsps;
struct nca_tree ncas;
+ struct ccr ccr;
};
/*
@@ -973,15 +1002,24 @@ extern int outformats;
#define FORMAT_CSV 0x04
#define FORMAT_JSON 0x08
#define FORMAT_OMETRIC 0x10
+#define FORMAT_CCR 0x20
int outputfiles(struct validation_data *, struct stats *);
-int outputheader(FILE *, struct stats *);
+int outputheader(FILE *, struct validation_data *, struct stats *);
int output_bgpd(FILE *, struct validation_data *, struct stats *);
int output_bird(FILE *, struct validation_data *, struct stats *);
int output_csv(FILE *, struct validation_data *, struct stats *);
int output_json(FILE *, struct validation_data *, struct stats *);
int output_ometric(FILE *, struct validation_data *,
struct stats *);
+int output_ccr(FILE *, struct validation_data *, struct stats *);
+
+/*
+ * Canonical Cache Representation
+ */
+void ccr_insert_mft(struct ccr_mft_tree *, const struct mft *);
+void ccr_insert_roa(struct ccr_vrp_tree *, const struct roa *);
+void generate_ccr(struct validation_data *);
void logx(const char *fmt, ...)
__attribute__((format(printf, 1, 2)));
Index: main.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/main.c,v
diff -u -p -r1.293 main.c
--- main.c 14 Aug 2025 15:12:00 -0000 1.293
+++ main.c 20 Aug 2025 11:17:21 -0000
@@ -658,6 +658,7 @@ entity_process(struct ibuf *b, struct va
repo_stat_inc(rp, talid, type, STYPE_SEQNUM_GAP);
queue_add_from_mft(mft);
cert_remove_nca(&vd->ncas, mft->certid, rp);
+ ccr_insert_mft(&vd->ccr.mfts, mft);
mft_free(mft);
break;
case RTYPE_CRL:
@@ -671,9 +672,10 @@ entity_process(struct ibuf *b, struct va
break;
}
roa = roa_read(b);
- if (roa->valid)
+ if (roa->valid) {
roa_insert_vrps(&vd->vrps, roa, rp);
- else
+ ccr_insert_roa(&vd->ccr.vrps, roa);
+ } else
repo_stat_inc(rp, talid, type, STYPE_INVALID);
roa_free(roa);
break;
@@ -1021,6 +1023,8 @@ main(int argc, char *argv[])
RB_INIT(&vd.vaps);
RB_INIT(&vd.vsps);
RB_INIT(&vd.ncas);
+ RB_INIT(&vd.ccr.mfts);
+ RB_INIT(&vd.ccr.vrps);
/* If started as root, priv-drop to _rpki-client */
if (getuid() == 0) {
@@ -1164,6 +1168,7 @@ main(int argc, char *argv[])
err(1, "output directory %s", outputdir);
if (outformats == 0)
outformats = FORMAT_OPENBGPD;
+ outformats |= FORMAT_CCR;
}
check_fs_size(cachefd, cachedir);
@@ -1540,6 +1545,8 @@ main(int argc, char *argv[])
}
repo_stats_collect(sum_repostats, &stats.repo_stats);
+ generate_ccr(&vd);
+
if (outputfiles(&vd, &stats))
rc = 1;
@@ -1578,6 +1585,9 @@ main(int argc, char *argv[])
printf("Repositories: %u\n", stats.repos);
printf("New files moved into validated cache: %u\n",
stats.repo_stats.new_files);
+ printf("CCR manifest hash: %s\n", vd.ccr.mfts_hash);
+ printf("CCR VRPs hash: %s\n", vd.ccr.vrps_hash);
+ printf("CCR VAPs hash: %s\n", vd.ccr.vaps_hash);
printf("Cleanup: removed %u files, %u directories\n"
"Repository cleanup: kept %u and removed %u superfluous files\n",
stats.repo_stats.del_files, stats.repo_stats.del_dirs,
Index: mft.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/mft.c,v
diff -u -p -r1.128 mft.c
--- mft.c 19 Aug 2025 11:30:20 -0000 1.128
+++ mft.c 20 Aug 2025 11:17:21 -0000
@@ -408,6 +408,7 @@ mft_parse(struct cert **out_cert, const
if ((mft = calloc(1, sizeof(*mft))) == NULL)
err(1, NULL);
mft->signtime = signtime;
+ mft->mftsize = len;
if ((mft->aki = strdup(cert->aki)) == NULL)
err(1, NULL);
@@ -498,6 +499,11 @@ mft_buffer(struct ibuf *b, const struct
io_opt_str_buffer(b, p->path);
io_str_buffer(b, p->aki);
+ io_str_buffer(b, p->seqnum);
+ io_str_buffer(b, p->sia);
+ io_simple_buffer(b, &p->thisupdate, sizeof(p->thisupdate));
+ io_simple_buffer(b, p->mfthash, sizeof(p->mfthash));
+ io_simple_buffer(b, &p->mftsize, sizeof(p->mftsize));
io_simple_buffer(b, &p->filesz, sizeof(size_t));
for (i = 0; i < p->filesz; i++) {
@@ -530,6 +536,11 @@ mft_read(struct ibuf *b)
io_read_opt_str(b, &p->path);
io_read_str(b, &p->aki);
+ io_read_str(b, &p->seqnum);
+ io_read_str(b, &p->sia);
+ io_read_buf(b, &p->thisupdate, sizeof(p->thisupdate));
+ io_read_buf(b, &p->mfthash, sizeof(p->mfthash));
+ io_read_buf(b, &p->mftsize, sizeof(p->mftsize));
io_read_buf(b, &p->filesz, sizeof(size_t));
if (p->filesz == 0)
Index: output-bgpd.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/output-bgpd.c,v
diff -u -p -r1.34 output-bgpd.c
--- output-bgpd.c 8 Jul 2025 14:19:21 -0000 1.34
+++ output-bgpd.c 20 Aug 2025 11:17:21 -0000
@@ -26,7 +26,7 @@ output_bgpd(FILE *out, struct validation
struct vap *vap;
size_t i;
- if (outputheader(out, st) < 0)
+ if (outputheader(out, vd, st) < 0)
return -1;
if (fprintf(out, "roa-set {\n") < 0)
Index: output-bird.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/output-bird.c,v
diff -u -p -r1.24 output-bird.c
--- output-bird.c 8 Jul 2025 14:19:21 -0000 1.24
+++ output-bird.c 20 Aug 2025 11:17:21 -0000
@@ -31,7 +31,7 @@ output_bird(FILE *out, struct validation
if (fprintf(out, "# For BIRD %s\n#\n", excludeaspa ? "2" : "2.16+") < 0)
return -1;
- if (outputheader(out, st) < 0)
+ if (outputheader(out, vd, st) < 0)
return -1;
if (fprintf(out, "\ndefine force_roa_table_update = %lld;\n\n"
Index: output-json.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/output-json.c,v
diff -u -p -r1.54 output-json.c
--- output-json.c 8 Jul 2025 14:19:21 -0000 1.54
+++ output-json.c 20 Aug 2025 11:17:21 -0000
@@ -24,7 +24,7 @@
#include "json.h"
static void
-outputheader_json(struct stats *st)
+outputheader_json(struct validation_data *vd, struct stats *st)
{
char hn[NI_MAXHOST], tbuf[26];
struct tm *tp;
@@ -85,6 +85,9 @@ outputheader_json(struct stats *st)
json_do_int("cachedir_superfluous_files", st->repo_stats.extra_files);
json_do_int("cachedir_del_superfluous_files",
st->repo_stats.del_extra_files);
+ json_do_string("ccr_mfts_hash", vd->ccr.mfts_hash);
+ json_do_string("ccr_vrps_hash", vd->ccr.vrps_hash);
+ json_do_string("ccr_vaps_hash", vd->ccr.vaps_hash);
json_do_end();
}
@@ -153,7 +156,7 @@ output_json(FILE *out, struct validation
struct nonfunc_ca *nca;
json_do_start(out);
- outputheader_json(st);
+ outputheader_json(vd, st);
json_do_array("roas");
RB_FOREACH(v, vrp_tree, &vd->vrps) {
Index: output.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/output.c,v
diff -u -p -r1.41 output.c
--- output.c 8 Jul 2025 14:19:21 -0000 1.41
+++ output.c 20 Aug 2025 11:17:21 -0000
@@ -70,6 +70,7 @@ static const struct outputs {
{ FORMAT_CSV, "csv", output_csv },
{ FORMAT_JSON, "json", output_json },
{ FORMAT_OMETRIC, "metrics", output_ometric },
+ { FORMAT_CCR, "rpki.ccr", output_ccr },
{ 0, NULL, NULL }
};
@@ -238,7 +239,7 @@ set_signal_handler(void)
}
int
-outputheader(FILE *out, struct stats *st)
+outputheader(FILE *out, struct validation_data *vd, struct stats *st)
{
char hn[NI_MAXHOST], tbuf[80];
struct tm *tp;
@@ -275,11 +276,15 @@ outputheader(FILE *out, struct stats *st
if (fprintf(out,
" ]\n"
+ "# CCR manifest hash: %s\n"
+ "# CCR validated ROA payloads hash: %s\n"
+ "# CCR validated ASPA payloads hash: %s\n"
"# Manifests: %u (%u failed parse)\n"
"# Certificate revocation lists: %u\n"
"# Ghostbuster records: %u\n"
"# Repositories: %u\n"
"# VRP Entries: %u (%u unique)\n",
+ vd->ccr.mfts_hash, vd->ccr.vrps_hash, vd->ccr.vaps_hash,
st->repo_tal_stats.mfts, st->repo_tal_stats.mfts_fail,
st->repo_tal_stats.crls,
st->repo_tal_stats.gbrs,
Index: rpki-asn1.h
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/rpki-asn1.h,v
diff -u -p -r1.1 rpki-asn1.h
--- rpki-asn1.h 19 Aug 2025 11:30:20 -0000 1.1
+++ rpki-asn1.h 20 Aug 2025 11:17:21 -0000
@@ -41,6 +41,121 @@ DECLARE_ASN1_FUNCTIONS(ASProviderAttesta
/*
+ * Canonical Cache Representation (CCR)
+ * reference: TBD
+ */
+
+extern ASN1_ITEM_EXP ManifestRef_it;
+extern ASN1_ITEM_EXP ManifestRefs_it;
+extern ASN1_ITEM_EXP ROAPayloadSet_it;
+extern ASN1_ITEM_EXP ROAPayloadSets_it;
+extern ASN1_ITEM_EXP ASPAPayloadSet_it;
+extern ASN1_ITEM_EXP ASPAPayloadSets_it;
+extern ASN1_ITEM_EXP CanonicalCacheRepresentation_it;
+extern ASN1_ITEM_EXP CONTENT_TYPE_it;
+
+typedef struct {
+ ASN1_OCTET_STRING *hash;
+ ASN1_INTEGER *size;
+ ASN1_OCTET_STRING *aki;
+ ASN1_INTEGER *manifestNumber;
+ STACK_OF(ACCESS_DESCRIPTION) *location;
+} ManifestRef;
+
+DECLARE_STACK_OF(ManifestRef);
+
+#ifndef DEFINE_STACK_OF
+#define sk_ManifestRef_new_null() SKM_sk_new_null(ManifestRef)
+#define sk_ManifestRef_num(st) SKM_sk_num(ManifestRef, (st))
+#define sk_ManifestRef_push(st, i) SKM_sk_push(ManifestRef, (st), (i))
+#endif
+
+DECLARE_ASN1_FUNCTIONS(ManifestRef);
+
+typedef STACK_OF(ManifestRef) ManifestRefs;
+
+DECLARE_ASN1_FUNCTIONS(ManifestRefs);
+
+typedef struct {
+ STACK_OF(ManifestRef) *mftrefs;
+ ASN1_GENERALIZEDTIME *mostRecentUpdate;
+ ASN1_OCTET_STRING *hash;
+} ManifestState;
+
+DECLARE_ASN1_FUNCTIONS(ManifestState);
+
+typedef struct {
+ ASN1_INTEGER *asID;
+ STACK_OF(ROAIPAddressFamily) *ipAddrBlocks;
+} ROAPayloadSet;
+
+DECLARE_STACK_OF(ROAPayloadSet);
+
+#ifndef DEFINE_STACK_OF
+#define sk_ROAPayloadSet_new_null() SKM_sk_new_null(ROAPayloadSet)
+#define sk_ROAPayloadSet_num(st) SKM_sk_num(ROAPayloadSet, (st))
+#define sk_ROAPayloadSet_push(st, i) SKM_sk_push(ROAPayloadSet, (st), (i))
+#endif
+
+DECLARE_ASN1_FUNCTIONS(ROAPayloadSet);
+
+typedef STACK_OF(ROAPayloadSet) ROAPayloadSets;
+
+DECLARE_ASN1_FUNCTIONS(ROAPayloadSets);
+
+typedef struct {
+ STACK_OF(ROAPayloadSet) *rps;
+ ASN1_OCTET_STRING *hash;
+} ROAPayloadState;
+
+DECLARE_ASN1_FUNCTIONS(ROAPayloadState);
+
+typedef struct {
+ ASN1_INTEGER *asID;
+ STACK_OF(ASN1_INTEGER) *providers;
+} ASPAPayloadSet;
+
+DECLARE_STACK_OF(ASPAPayloadSet);
+
+#ifndef DEFINE_STACK_OF
+#define sk_ASPAPayloadSet_new_null() SKM_sk_new_null(ASPAPayloadSet)
+#define sk_ASPAPayloadSet_num(st) SKM_sk_num(ASPAPayloadSet, (st))
+#define sk_ASPAPayloadSet_push(st, i) SKM_sk_push(ASPAPayloadSet, (st), (i))
+#endif
+
+DECLARE_ASN1_FUNCTIONS(ASPAPayloadSet);
+
+typedef STACK_OF(ASPAPayloadSet) ASPAPayloadSets;
+
+DECLARE_ASN1_FUNCTIONS(ASPAPayloadSets);
+
+typedef struct {
+ STACK_OF(ASPAPayloadSet) *aps;
+ ASN1_OCTET_STRING *hash;
+} ASPAPayloadState;
+
+DECLARE_ASN1_FUNCTIONS(ASPAPayloadState);
+
+typedef struct {
+ ASN1_INTEGER *version;
+ ASN1_OBJECT *hashAlg;
+ ASN1_GENERALIZEDTIME *producedAt;
+ ManifestState *mfts;
+ ROAPayloadState *vrps;
+ ASPAPayloadState *vaps;
+} CanonicalCacheRepresentation;
+
+DECLARE_ASN1_FUNCTIONS(CanonicalCacheRepresentation);
+
+typedef struct {
+ ASN1_OBJECT *oid;
+ ASN1_OCTET_STRING *content;
+} CONTENT_TYPE;
+
+DECLARE_ASN1_FUNCTIONS(CONTENT_TYPE);
+
+
+/*
* RPKI Manifest
* reference: RFC 9286.
*/
@@ -91,20 +206,30 @@ typedef struct {
ASN1_INTEGER *maxLength;
} ROAIPAddress;
+DECLARE_ASN1_FUNCTIONS(ROAIPAddress);
DECLARE_STACK_OF(ROAIPAddress);
+#ifndef DEFINE_STACK_OF
+#define sk_ROAIPAddress_new_null() SKM_sk_new_null(ROAIPAddress)
+#define sk_ROAIPAddress_num(st) SKM_sk_num(ROAIPAddress, (st))
+#define sk_ROAIPAddress_push(st, i) SKM_sk_push(ROAIPAddress, (st), (i))
+#define sk_ROAIPAddress_value(st, i) SKM_sk_value(ROAIPAddress, (st), (i))
+#endif
+
typedef struct {
ASN1_OCTET_STRING *addressFamily;
STACK_OF(ROAIPAddress) *addresses;
} ROAIPAddressFamily;
+DECLARE_ASN1_FUNCTIONS(ROAIPAddressFamily);
DECLARE_STACK_OF(ROAIPAddressFamily);
#ifndef DEFINE_STACK_OF
-#define sk_ROAIPAddress_num(st) SKM_sk_num(ROAIPAddress, (st))
-#define sk_ROAIPAddress_value(st, i) SKM_sk_value(ROAIPAddress, (st), (i))
-
-#define sk_ROAIPAddressFamily_num(st) SKM_sk_num(ROAIPAddressFamily, (st))
+#define sk_ROAIPAddressFamily_new_null() \
+ SKM_sk_new_null(ROAIPAddressFamily)
+#define sk_ROAIPAddressFamily_num(st) SKM_sk_num(ROAIPAddressFamily, (st))
+#define sk_ROAIPAddressFamily_push(st, i) \
+ SKM_sk_push(ROAIPAddressFamily, (st), (i))
#define sk_ROAIPAddressFamily_value(st, i) \
SKM_sk_value(ROAIPAddressFamily, (st), (i))
#endif
Index: rpki-client.8
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/rpki-client.8,v
diff -u -p -r1.127 rpki-client.8
--- rpki-client.8 2 Aug 2025 13:24:16 -0000 1.127
+++ rpki-client.8 20 Aug 2025 11:17:21 -0000
@@ -318,6 +318,8 @@ default skiplist file, unless
is specified.
.It Pa /var/cache/rpki-client
cached repository data.
+.It Pa /var/db/rpki-client/rpki.ccr
+DER-encoded canonical cache representation file.
.It Pa /var/db/rpki-client/openbgpd
default roa-set output file.
.El
rpki-client: Canonical Cache Representation