From: Job Snijders Subject: rpki-client: implement erik object decoders & printers To: tech@openbsd.org Date: Sun, 16 Aug 2026 13:38:54 +0000 Dear all, This changeset implements DER decoders & printers for the data structures specified in draft-ietf-sidrops-rpki-erik-protocol-07 with the IANA codepoints. Example objects from a live relay: ErikIndex: https://miso.sobornost.net/.well-known/erik/index/rpki.ripe.net ErikSegmentIndex: https://miso.sobornost.net/.well-known/erik/segmentindex/rpki.ripe.net ErikPartition: https://miso.sobornost.net/.well-known/ni/sha-256/zWDTqZw7ABhCtFYKr5RWodoIiUgxGLdvw2JBNf7dS5M While we still are some way off from a native syncing client implementation, having these printers as debugging & inspection utility will help as a step towards that. OK? Feedback? Kind regards, Job Index: Makefile =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/Makefile,v diff -u -p -r1.41 Makefile --- Makefile 22 Jun 2026 21:25:44 -0000 1.41 +++ Makefile 16 Aug 2026 13:33:34 -0000 @@ -11,6 +11,7 @@ SRCS+= cms.c SRCS+= constraints.c SRCS+= crl.c SRCS+= encoding.c +SRCS+= erik.c SRCS+= filemode.c SRCS+= http.c SRCS+= io.c Index: erik.c =================================================================== RCS file: erik.c diff -N erik.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ erik.c 16 Aug 2026 13:33:34 -0000 @@ -0,0 +1,542 @@ +/* $OpenBSD$ */ +/* + * Copyright (c) 2026 Job Snijders + * + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "extern.h" +#include "rpki-asn1.h" + +/* + * Erik obj definitions in draft-ietf-sidrops-rpki-erik-protocol-07, section 3. + */ + +ASN1_ITEM_EXP EInd_ContentInfo_it; +ASN1_ITEM_EXP ErikIndex_it; +ASN1_ITEM_EXP PartitionRef_it; +ASN1_ITEM_EXP EPar_ContentInfo_it; +ASN1_ITEM_EXP ErikPartition_it; +ASN1_ITEM_EXP ManifestRef_it; +ASN1_ITEM_EXP ESI_ContentInfo_it; +ASN1_ITEM_EXP ErikSegmentIndex_it; +ASN1_ITEM_EXP SegmentRef_it; + +ASN1_SEQUENCE(EInd_ContentInfo) = { + ASN1_SIMPLE(EInd_ContentInfo, contentType, ASN1_OBJECT), + ASN1_EXP(EInd_ContentInfo, content, ErikIndex, 0), +} ASN1_SEQUENCE_END(EInd_ContentInfo); + +IMPLEMENT_ASN1_FUNCTIONS(EInd_ContentInfo); + +ASN1_SEQUENCE(ErikIndex) = { + ASN1_EXP_OPT(ErikIndex, version, ASN1_INTEGER, 0), + ASN1_SIMPLE(ErikIndex, indexScope, ASN1_IA5STRING), + ASN1_SIMPLE(ErikIndex, indexTime, ASN1_GENERALIZEDTIME), + ASN1_SIMPLE(ErikIndex, hashAlg, X509_ALGOR), + ASN1_SEQUENCE_OF(ErikIndex, partitionList, PartitionRef), +} ASN1_SEQUENCE_END(ErikIndex); + +IMPLEMENT_ASN1_FUNCTIONS(ErikIndex); + +ASN1_SEQUENCE(PartitionRef) = { + ASN1_SIMPLE(PartitionRef, hash, ASN1_OCTET_STRING), + ASN1_SIMPLE(PartitionRef, size, ASN1_INTEGER), +} ASN1_SEQUENCE_END(PartitionRef); + +ASN1_SEQUENCE(EPar_ContentInfo) = { + ASN1_SIMPLE(EPar_ContentInfo, contentType, ASN1_OBJECT), + ASN1_EXP(EPar_ContentInfo, content, ErikPartition, 0), +} ASN1_SEQUENCE_END(EPar_ContentInfo); + +IMPLEMENT_ASN1_FUNCTIONS(EPar_ContentInfo); + +ASN1_SEQUENCE(ErikPartition) = { + ASN1_EXP_OPT(ErikPartition, version, ASN1_INTEGER, 0), + ASN1_SIMPLE(ErikPartition, partitionTime, ASN1_GENERALIZEDTIME), + ASN1_SIMPLE(ErikPartition, hashAlg, X509_ALGOR), + ASN1_SEQUENCE_OF(ErikPartition, manifestList, ManifestRef), +} ASN1_SEQUENCE_END(ErikPartition); + +IMPLEMENT_ASN1_FUNCTIONS(ErikPartition); + +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_SIMPLE(ManifestRef, thisUpdate, ASN1_GENERALIZEDTIME), + ASN1_SEQUENCE_OF(ManifestRef, locations, ACCESS_DESCRIPTION), +} ASN1_SEQUENCE_END(ManifestRef); + +ASN1_SEQUENCE(ESI_ContentInfo) = { + ASN1_SIMPLE(ESI_ContentInfo, contentType, ASN1_OBJECT), + ASN1_EXP(ESI_ContentInfo, content, ErikSegmentIndex, 0), +} ASN1_SEQUENCE_END(ESI_ContentInfo); + +IMPLEMENT_ASN1_FUNCTIONS(ESI_ContentInfo); + +ASN1_SEQUENCE(ErikSegmentIndex) = { + ASN1_EXP_OPT(ErikSegmentIndex, version, ASN1_INTEGER, 0), + ASN1_SIMPLE(ErikSegmentIndex, segmentScope, ASN1_IA5STRING), + ASN1_SIMPLE(ErikSegmentIndex, segmentIndexTime, ASN1_GENERALIZEDTIME), + ASN1_SIMPLE(ErikSegmentIndex, hashAlg, X509_ALGOR), + ASN1_SEQUENCE_OF(ErikSegmentIndex, segmentList, SegmentRef), +} ASN1_SEQUENCE_END(ErikSegmentIndex); + +IMPLEMENT_ASN1_FUNCTIONS(ErikSegmentIndex); + +ASN1_SEQUENCE(SegmentRef) = { + ASN1_SIMPLE(SegmentRef, segment, ASN1_GENERALIZEDTIME), + ASN1_SIMPLE(SegmentRef, index, ASN1_OCTET_STRING), +} ASN1_SEQUENCE_END(SegmentRef); + +static int +erik_parse_pref(const char *fn, struct eind *eind, const PartitionRef *pr) +{ + struct partref *partref; + const unsigned char *data; + int num, length, rc = 0; + uint64_t size = 0; + + num = eind->parts_num++; + partref = &eind->parts[num]; + + data = ASN1_STRING_get0_data(pr->hash); + length = ASN1_STRING_length(pr->hash); + if (length != SHA256_DIGEST_LENGTH) { + warnx("%s: PartitionRef #%d corrupted: invalid hash", fn, num); + goto out; + } + memcpy(partref->hash, data, length); + + if (!ASN1_INTEGER_get_uint64(&size, pr->size)) { + warnx("%s: PartitionRef #%d corrupted: size parse", fn, num); + goto out; + } + if (size < 100 || size > MAX_FILE_SIZE) { + warnx("%s: PartifionRef #%d corrupted: size", fn, num); + goto out; + } + partref->size = size; + + rc = 1; + out: + return rc; +} + +struct eind * +erik_index_parse(const char *fn, const unsigned char *der, size_t len) +{ + const unsigned char *oder; + EInd_ContentInfo *ci = NULL; + ErikIndex *eind_asn1 = NULL; + const ASN1_OBJECT *oid; + struct eind *eind = NULL; + const unsigned char *data; + int i, length, nid, ptype, rc = 0; + const PartitionRef *pr; + + if (der == NULL) + return NULL; + + oder = der; + if ((ci = d2i_EInd_ContentInfo(NULL, &der, len)) == NULL) { + warnx("%s: d2i_EInd_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, eind_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.55", fn, buf); + goto out; + } + + eind_asn1 = ci->content; + + if (!valid_econtent_version(fn, eind_asn1->version, 0)) + goto out; + + X509_ALGOR_get0(&oid, &ptype, NULL, eind_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 ((eind = calloc(1, sizeof(*eind))) == NULL) + err(1, NULL); + + data = ASN1_STRING_get0_data(eind_asn1->indexScope); + length = ASN1_STRING_length(eind_asn1->indexScope); + + if (!valid_uri(data, length, NULL)) { + warnx("%s: invalid indexScope", fn); + goto out; + } + + if ((eind->scope = strndup(data, length)) == NULL) + err(1, NULL); + + if (!x509_get_generalized_time(fn, "ErikIndex indexTime", + eind_asn1->indexTime, &eind->itime)) + goto out; + + if (sk_PartitionRef_num(eind_asn1->partitionList) <= 0) { + warnx("%s: corrupt partitionList", fn); + goto out; + } + + if (sk_PartitionRef_num(eind_asn1->partitionList) > 256) { + warnx("%s: too many partitions in partitionList", fn); + goto out; + } + + eind->parts = calloc(sk_PartitionRef_num(eind_asn1->partitionList), + sizeof(eind->parts[0])); + if (eind->parts == NULL) + err(1, NULL); + + for (i = 0; i < sk_PartitionRef_num(eind_asn1->partitionList); i++) { + pr = sk_PartitionRef_value(eind_asn1->partitionList, i); + if (!erik_parse_pref(fn, eind, pr)) + goto out; + /* XXX: add uniqueness constraint? */ + } + + rc = 1; + out: + EInd_ContentInfo_free(ci); + + if (rc == 0) { + eind_free(eind); + eind = NULL; + } + + return eind; +} + +static int +erik_parse_mftref(const char *fn, struct epar *epar, const ManifestRef *mr) +{ + struct mftref *mftref; + const unsigned char *data; + int num, length, rc = 0; + uint64_t size = 0; + const ACCESS_DESCRIPTION *ad; + + num = epar->mftrefs_num++; + mftref = &epar->mftrefs[num]; + + data = ASN1_STRING_get0_data(mr->hash); + length = ASN1_STRING_length(mr->hash); + if (length != SHA256_DIGEST_LENGTH) { + warnx("%s: ManifestRef #%d corrupted: invalid hash", fn, num); + goto out; + } + memcpy(mftref->hash, data, length); + + if (!ASN1_INTEGER_get_uint64(&size, mr->size)) { + warnx("%s: ManifestRef #%d corrupted: size parse", fn, num); + goto out; + } + if (size < 1000 || size > MAX_FILE_SIZE) { + warnx("%s: ManifestRef #%d corrupted: size", fn, num); + goto out; + } + mftref->size = size; + + data = ASN1_STRING_get0_data(mr->aki); + length = ASN1_STRING_length(mr->aki); + if (length != SHA_DIGEST_LENGTH) { + warnx("%s: ManifestRef #%d corrupted: invalid aki", fn, num); + goto out; + } + memcpy(mftref->aki, data, length); + + mftref->seqnum = x509_convert_seqnum(fn, "ManifestRef seqnum", + mr->manifestNumber); + if (mftref->seqnum == NULL) + goto out; + + if (!x509_get_generalized_time(fn, "ManifestRef thisUpdate", + mr->thisUpdate, &mftref->thisupdate)) + goto out; + + if (sk_ACCESS_DESCRIPTION_num(mr->locations) != 1) { + warnx("%s: ManifestRef #%d corrupted: unexpected number of " + "locations", fn, num); + goto out; + } + ad = sk_ACCESS_DESCRIPTION_value(mr->locations, 0); + if (!x509_location(fn, "SIA: signedObject", ad->location, &mftref->sia)) + goto out; + + rc = 1; + out: + return rc; +} + +struct epar * +erik_part_parse(const char *fn, const unsigned char *der, size_t len) +{ + const unsigned char *oder; + EPar_ContentInfo *ci = NULL; + ErikPartition *epar_asn1 = NULL; + const ASN1_OBJECT *oid; + struct epar *epar = NULL; + int i, nid, ptype, rc = 0; + const ManifestRef *mr; + + if (der == NULL) + return NULL; + + oder = der; + if ((ci = d2i_EPar_ContentInfo(NULL, &der, len)) == NULL) { + warnx("%s: d2i_EPar_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, epar_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.56", fn, buf); + goto out; + } + + epar_asn1 = ci->content; + + if (!valid_econtent_version(fn, epar_asn1->version, 0)) + goto out; + + X509_ALGOR_get0(&oid, &ptype, NULL, epar_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 ((epar = calloc(1, sizeof(*epar))) == NULL) + err(1, NULL); + + if (!x509_get_generalized_time(fn, "ErikPartition partitionTime", + epar_asn1->partitionTime, &epar->ptime)) + goto out; + + if (sk_ManifestRef_num(epar_asn1->manifestList) <= 0) { + warnx("%s: corrupt manifestList", fn); + goto out; + } + + epar->mftrefs = calloc(sk_ManifestRef_num(epar_asn1->manifestList), + sizeof(epar->mftrefs[0])); + if (epar->mftrefs == NULL) + err(1, NULL); + + for (i = 0; i < sk_ManifestRef_num(epar_asn1->manifestList); i++) { + mr = sk_ManifestRef_value(epar_asn1->manifestList, i); + if (!erik_parse_mftref(fn, epar, mr)) + goto out; + /* XXX: add uniqueness constraint? */ + } + + rc = 1; + out: + EPar_ContentInfo_free(ci); + + if (rc == 0) { + epar_free(epar); + epar = NULL; + } + + return epar; +} + +static int +erik_parse_segref(const char *fn, struct esi *esi, const SegmentRef *sr) +{ + struct segref *segref; + const unsigned char *data; + int num, length, rc = 0; + + num = esi->segrefs_num++; + segref = &esi->segrefs[num]; + + if (!x509_get_generalized_time(fn, "SegmentRef segment", + sr->segment, &segref->segment)) + goto out; + + data = ASN1_STRING_get0_data(sr->index); + length = ASN1_STRING_length(sr->index); + if (length != SHA256_DIGEST_LENGTH) { + warnx("%s: SegmentRef #%d corrupted: invalid index", fn, num); + goto out; + } + memcpy(segref->index, data, length); + + rc = 1; + out: + return rc; +} + +struct esi * +erik_segindex_parse(const char *fn, const unsigned char *der, size_t len) +{ + const unsigned char *oder; + ESI_ContentInfo *ci = NULL; + ErikSegmentIndex *esi_asn1 = NULL; + const ASN1_OBJECT *oid; + struct esi *esi = NULL; + const unsigned char *data; + int i, length, nid, ptype, rc = 0; + const SegmentRef *sr; + + if (der == NULL) + return NULL; + + oder = der; + if ((ci = d2i_ESI_ContentInfo(NULL, &der, len)) == NULL) { + warnx("%s: d2i_ESI_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, esi_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.59", fn, buf); + goto out; + } + + esi_asn1 = ci->content; + + if (!valid_econtent_version(fn, esi_asn1->version, 0)) + goto out; + + X509_ALGOR_get0(&oid, &ptype, NULL, esi_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 ((esi = calloc(1, sizeof(*esi))) == NULL) + err(1, NULL); + + data = ASN1_STRING_get0_data(esi_asn1->segmentScope); + length = ASN1_STRING_length(esi_asn1->segmentScope); + if (!valid_uri(data, length, NULL)) { + warnx("%s: invalid segmentScope", fn); + goto out; + } + if ((esi->scope = strndup(data, length)) == NULL) + err(1, NULL); + + if (!x509_get_generalized_time(fn, "ErikSegmentIndex segmentIndexTime", + esi_asn1->segmentIndexTime, &esi->stime)) + goto out; + + if (sk_SegmentRef_num(esi_asn1->segmentList) <= 0) { + warnx("%s: corrupt segmentList", fn); + goto out; + } + + esi->segrefs = calloc(sk_SegmentRef_num(esi_asn1->segmentList), + sizeof(esi->segrefs[0])); + if (esi->segrefs == NULL) + err(1, NULL); + + for (i = 0; i < sk_SegmentRef_num(esi_asn1->segmentList); i++) { + sr = sk_SegmentRef_value(esi_asn1->segmentList, i); + if (!erik_parse_segref(fn, esi, sr)) + goto out; + /* XXX: add uniqueness constraint? */ + } + + rc = 1; + out: + ESI_ContentInfo_free(ci); + + if (rc == 0) { + esi_free(esi); + esi = NULL; + } + + return esi; +} + +void +eind_free(struct eind *eind) +{ + if (eind == NULL) + return; + + free(eind->parts); + free(eind); +} + +void +epar_free(struct epar *epar) +{ + size_t i; + + if (epar == NULL) + return; + + for (i = 0; i < epar->mftrefs_num; i++) { + free(epar->mftrefs[i].seqnum); + free(epar->mftrefs[i].sia); + } + + free(epar->mftrefs); + free(epar); +} + +void +esi_free(struct esi *esi) +{ + if (esi == NULL) + return; + + free(esi->segrefs); + free(esi); +} Index: extern.h =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/extern.h,v diff -u -p -r1.290 extern.h --- extern.h 15 Jul 2026 07:53:06 -0000 1.290 +++ extern.h 16 Aug 2026 13:33:35 -0000 @@ -234,6 +234,9 @@ enum rtype { RTYPE_SPL, RTYPE_CCR, RTYPE_GZ, + RTYPE_EIND, + RTYPE_EPAR, + RTYPE_ESI, }; enum location { @@ -290,6 +293,48 @@ struct mft { }; /* + * Internal datastructures for Erik objects + */ +struct partref { + unsigned char hash[SHA256_DIGEST_LENGTH]; + size_t size; +}; + +struct eind { + char *scope; + time_t itime; + size_t parts_num; + struct partref *parts; +}; + +struct mftref { + unsigned char hash[SHA256_DIGEST_LENGTH]; + size_t size; + unsigned char aki[SHA_DIGEST_LENGTH]; + char *seqnum; + time_t thisupdate; + char *sia; +}; + +struct epar { + time_t ptime; + size_t mftrefs_num; + struct mftref *mftrefs; +}; + +struct segref { + time_t segment; + unsigned char index[SHA256_DIGEST_LENGTH]; +}; + +struct esi { + char *scope; + time_t stime; + size_t segrefs_num; + struct segref *segrefs; +}; + +/* * An IP address prefix for a given ROA. * This encodes the maximum length, AFI (v6/v4), and address. * FIXME: are the min/max necessary or just used in one place? @@ -732,6 +777,9 @@ extern ASN1_OBJECT *aspa_oid; extern ASN1_OBJECT *tak_oid; extern ASN1_OBJECT *spl_oid; extern ASN1_OBJECT *ccr_oid; +extern ASN1_OBJECT *eind_oid; +extern ASN1_OBJECT *epar_oid; +extern ASN1_OBJECT *esi_oid; extern int verbose; extern int noop; @@ -1066,6 +1114,19 @@ void ccr_insert_roa(struct ccr_vrp_tree void ccr_insert_tas(struct ccr_tas_tree *, const struct cert *); void ccr_insert_mft_sub(struct ccr_mft_tree *, const struct cert *); void serialize_ccr_content(struct validation_data *); + +/* + * Erik Synchronisation protocol objects + */ +struct eind *erik_index_parse(const char *, const unsigned char *, size_t); +struct epar *erik_part_parse(const char *, const unsigned char *, size_t); +struct esi *erik_segindex_parse(const char *, const unsigned char *, size_t); +void eind_free(struct eind *); +void epar_free(struct epar *); +void esi_free(struct esi *); +void eind_print(struct eind *); +void epar_print(struct epar *); +void esi_print(struct esi *); void logx(const char *fmt, ...) __attribute__((format(printf, 1, 2))); Index: filemode.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/filemode.c,v diff -u -p -r1.86 filemode.c --- filemode.c 9 Jul 2026 11:39:19 -0000 1.86 +++ filemode.c 16 Aug 2026 13:33:35 -0000 @@ -351,6 +351,18 @@ rtype_from_der(const char *fn, const uns rtype = RTYPE_CCR; goto out; } + if (OBJ_cmp(obj, eind_oid) == 0) { + rtype = RTYPE_EIND; + goto out; + } + if (OBJ_cmp(obj, epar_oid) == 0) { + rtype = RTYPE_EPAR; + goto out; + } + if (OBJ_cmp(obj, esi_oid) == 0) { + rtype = RTYPE_ESI; + goto out; + } } if (CMS_get0_SignerInfos(cms) == NULL) { @@ -420,6 +432,9 @@ proc_parser_file(char *file, unsigned ch struct cert *cert = NULL; struct ccr *ccr = NULL; struct crl *crl = NULL; + struct eind *eind = NULL; + struct epar *epar = NULL; + struct esi *esi = NULL; struct mft *mft = NULL; struct roa *roa = NULL; struct rsc *rsc = NULL; @@ -530,6 +545,24 @@ proc_parser_file(char *file, unsigned ch break; crl_print(crl); break; + case RTYPE_EIND: + eind = erik_index_parse(file, buf, len); + if (eind == NULL) + break; + eind_print(eind); + break; + case RTYPE_EPAR: + epar = erik_part_parse(file, buf, len); + if (epar == NULL) + break; + epar_print(epar); + break; + case RTYPE_ESI: + esi = erik_segindex_parse(file, buf, len); + if (esi == NULL) + break; + esi_print(esi); + break; case RTYPE_MFT: mft = mft_parse(&cert, file, -1, buf, len); if (mft == NULL) @@ -730,6 +763,9 @@ proc_parser_file(char *file, unsigned ch cert_free(cert); ccr_free(ccr); crl_free(crl); + eind_free(eind); + epar_free(epar); + esi_free(esi); mft_free(mft); roa_free(roa); rsc_free(rsc); Index: print.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/print.c,v diff -u -p -r1.79 print.c --- print.c 7 Jul 2026 13:38:54 -0000 1.79 +++ print.c 16 Aug 2026 13:33:35 -0000 @@ -1092,3 +1092,135 @@ ccr_print(struct ccr *ccr) if (ccr->brks_hash != NULL) print_ccr_rkstate(ccr); } + +void +eind_print(struct eind *eind) +{ + size_t i; + char *hash; + struct partref *pr; + + if (outformats & FORMAT_JSON) { + json_do_string("type", "erikindex"); + json_do_int("indextime", eind->itime); + json_do_string("indexscope", eind->scope); + } else { + printf("Index time: %s\n", + time2str(eind->itime)); + printf("Index scope: %s\n", eind->scope); + printf("Partitions: "); + } + + if (outformats & FORMAT_JSON) + json_do_array("partitions"); + for (i = 0; i < eind->parts_num; i++) { + pr = &eind->parts[i]; + + if (base64_encode(pr->hash, sizeof(pr->hash), &hash) == -1) + errx(1, "base64_encode failure"); + + if (outformats & FORMAT_JSON) { + json_do_object("partition", 1); + json_do_string("hash", hash); + json_do_uint("size", pr->size); + json_do_end(); + } else { + if (i > 0) + printf("%26s", ""); + printf("%zu: hash: %s (size: %zu)\n", i + 1, hash, + pr->size); + } + free(hash); + } + if (outformats & FORMAT_JSON) + json_do_end(); +} + +void +epar_print(struct epar *epar) +{ + size_t i; + struct mftref *mr; + char *aki, *hash; + + if (outformats & FORMAT_JSON) { + json_do_string("type", "erikpart"); + json_do_int("partitiontime", epar->ptime); + } else { + printf("Partition time: %s\n", + time2str(epar->ptime)); + printf("Manifest references: "); + } + + if (outformats & FORMAT_JSON) + json_do_array("manifestrefs"); + for (i = 0; i < epar->mftrefs_num; i++) { + mr = &epar->mftrefs[i]; + + aki = hex_encode(mr->aki, SHA_DIGEST_LENGTH); + + if (base64_encode(mr->hash, sizeof(mr->hash), &hash) == -1) + errx(1, "base64_encode failure"); + + if (outformats & FORMAT_JSON) { + json_do_object("manifestrefs", 1); + json_do_string("hash", hash); + json_do_uint("size", mr->size); + json_do_string("aki", aki); + json_do_end(); + } else { + if (i > 0) + printf("%26s", ""); + printf("%zu: hash:%s size:%zu aki:%s seqnum:%s " + "thisupdate:%lld sia:%s\n", i + 1, hash, + mr->size, aki, mr->seqnum, + (long long)mr->thisupdate, mr->sia); + } + free(aki); + free(hash); + } + if (outformats & FORMAT_JSON) + json_do_end(); +} + +void +esi_print(struct esi *esi) +{ + size_t i; + struct segref *sr; + char *hash; + + if (outformats & FORMAT_JSON) { + json_do_string("type", "eriksegmentindex"); + json_do_int("segmentindextime", esi->stime); + } else { + printf("Segment index time: %s\n", + time2str(esi->stime)); + printf("Segment references: "); + } + + if (outformats & FORMAT_JSON) + json_do_array("segmentrefs"); + for (i = 0; i < esi->segrefs_num; i++) { + sr = &esi->segrefs[i]; + + if (base64_encode(sr->index, sizeof(sr->index), &hash) == -1) + errx(1, "base64_encode failure"); + + if (outformats & FORMAT_JSON) { + json_do_object("segmentrefs", 1); + json_do_uint("segment", sr->segment); + json_do_string("index", hash); + json_do_end(); + } else { + if (i > 0) + printf("%26s", ""); + printf("%zu: segment:%lld index:%s (%s)\n", i + 1, + (long long)sr->segment, hash, + time2str(sr->segment)); + } + free(hash); + } + if (outformats & FORMAT_JSON) + json_do_end(); +} Index: rpki-asn1.h =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/rpki-asn1.h,v diff -u -p -r1.14 rpki-asn1.h --- rpki-asn1.h 7 Apr 2026 11:52:21 -0000 1.14 +++ rpki-asn1.h 16 Aug 2026 13:33:35 -0000 @@ -235,6 +235,112 @@ DECLARE_ASN1_FUNCTIONS(ContentInfo); /* + * Erik Synchronisation Protocol objects + * reference: draft-ietf-sidrops-rpki-erik-protocol-07 + */ + +extern ASN1_ITEM_EXP EInd_ContentInfo_it; +extern ASN1_ITEM_EXP EPar_ContentInfo_it; +extern ASN1_ITEM_EXP ESI_ContentInfo_it; +extern ASN1_ITEM_EXP ErikIndex_it; +extern ASN1_ITEM_EXP ErikPartition_it; +extern ASN1_ITEM_EXP ErikSegmentIndex_it; +extern ASN1_ITEM_EXP ManifestRef_it; +extern ASN1_ITEM_EXP PartitionRef_it; +extern ASN1_ITEM_EXP SegmentRef_it; + +typedef struct { + ASN1_OCTET_STRING *hash; + ASN1_INTEGER *size; +} PartitionRef; + +DECLARE_STACK_OF(PartitionRef); + +#ifndef DEFINE_STACK_OF +#define sk_PartitionRef_num(sk) SKM_sk_num(PartitionRef, (sk)) +#define sk_PartitionRef_value(sk, i) SKM_sk_value(PartitionRef, (sk), (i)) +#endif + +typedef struct { + ASN1_INTEGER *version; + ASN1_IA5STRING *indexScope; + ASN1_GENERALIZEDTIME *indexTime; + X509_ALGOR *hashAlg; + STACK_OF(PartitionRef) *partitionList; +} ErikIndex; + +DECLARE_ASN1_FUNCTIONS(ErikIndex); + +typedef struct { + ASN1_OBJECT *contentType; + ErikIndex *content; +} EInd_ContentInfo;; + +DECLARE_ASN1_FUNCTIONS(EInd_ContentInfo); + +typedef struct { + ASN1_OCTET_STRING *hash; + ASN1_INTEGER *size; + ASN1_OCTET_STRING *aki; + ASN1_INTEGER *manifestNumber; + ASN1_GENERALIZEDTIME *thisUpdate; + STACK_OF(ACCESS_DESCRIPTION) *locations; +} ManifestRef; + +DECLARE_STACK_OF(ManifestRef); + +#ifndef DEFINE_STACK_OF +#define sk_ManifestRef_num(sk) SKM_sk_num(ManifestRef, (sk)) +#define sk_ManifestRef_value(sk, i) SKM_sk_value(ManifestRef, (sk), (i)) +#endif + +typedef struct { + ASN1_INTEGER *version; + ASN1_GENERALIZEDTIME *partitionTime; + X509_ALGOR *hashAlg; + STACK_OF(ManifestRef) *manifestList; +} ErikPartition; + +DECLARE_ASN1_FUNCTIONS(ErikPartition); + +typedef struct { + ASN1_OBJECT *contentType; + ErikPartition *content; +} EPar_ContentInfo;; + +DECLARE_ASN1_FUNCTIONS(EPar_ContentInfo); + +typedef struct { + ASN1_GENERALIZEDTIME *segment; + ASN1_OCTET_STRING *index; +} SegmentRef; + +DECLARE_STACK_OF(SegmentRef); + +#ifndef DEFINE_STACK_OF +#define sk_SegmentRef_num(sk) SKM_sk_num(SegmentRef, (sk)) +#define sk_SegmentRef_value(sk, i) SKM_sk_value(SegmentRef, (sk), (i)) +#endif + +typedef struct { + ASN1_INTEGER *version; + ASN1_IA5STRING *segmentScope; + ASN1_GENERALIZEDTIME *segmentIndexTime; + X509_ALGOR *hashAlg; + STACK_OF(SegmentRef) *segmentList; +} ErikSegmentIndex; + +DECLARE_ASN1_FUNCTIONS(ErikSegmentIndex); + +typedef struct { + ASN1_OBJECT *contentType; + ErikSegmentIndex *content; +} ESI_ContentInfo;; + +DECLARE_ASN1_FUNCTIONS(ESI_ContentInfo); + + +/* * RPKI Manifest * reference: RFC 9286. */ Index: x509.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/x509.c,v diff -u -p -r1.133 x509.c --- x509.c 21 Jun 2026 19:23:56 -0000 1.133 +++ x509.c 16 Aug 2026 13:33:35 -0000 @@ -47,6 +47,9 @@ ASN1_OBJECT *aspa_oid; /* id-ct-ASPA */ ASN1_OBJECT *tak_oid; /* id-ct-SignedTAL */ ASN1_OBJECT *spl_oid; /* id-ct-signedPrefixList */ ASN1_OBJECT *ccr_oid; /* id-ct-rpkiCanonicalCacheRepresentation */ +ASN1_OBJECT *eind_oid; /* id-ct-rpkiErikIndex */ +ASN1_OBJECT *epar_oid; /* id-ct-rpkiErikPartition */ +ASN1_OBJECT *esi_oid; /* id-ct-rpkiErikSegmentIndex */ static const struct { const char *oid; @@ -119,6 +122,18 @@ static const struct { { .oid = "1.2.840.113549.1.9.16.1.54", .ptr = &ccr_oid, + }, + { + .oid = "1.2.840.113549.1.9.16.1.55", + .ptr = &eind_oid, + }, + { + .oid = "1.2.840.113549.1.9.16.1.56", + .ptr = &epar_oid, + }, + { + .oid = "1.2.840.113549.1.9.16.1.59", + .ptr = &esi_oid, }, };