Download raw body.
rpki-client: implement erik object decoders & printers
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 <job@bsd.nl>
+ *
+ * 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 <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"
+
+/*
+ * 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,
},
};
rpki-client: implement erik object decoders & printers