Re: rpki-client: implement erik object decoders & printers
Theo Buehler <[email protected]>
| Newsgroups | gmane.os.openbsd.tech |
|---|---|
| Message-ID | <[email protected]> |
On Sun, Aug 16, 2026 at 01:38:54PM +0000, Job Snijders wrote: > 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? Here's a first pass. I know you're just following the existing design but unless you expect to need all this in extern.h, I think it is really not great to have all these erik internals exposted in extern.h only for erik.c and print.c to be able to share them. Can we stop doing this and merge the print.c bits into erik.c? I expect that the other object types will eventually follow the same path. On naming: I don't like the eind, epar and esi names. The first has index abbreviated to ind, and the third has it abbreviated to i. Similarly, you use p, par, part, or parts for partitions, and then there is pr, partref and pref. It's all rather inconsistently done and thus harder to read than necessary. There are a few helpers that could be split out and simplify the existing and the new code. That should be done in preparatory steps before we land this (we should probably cut a release before doing any of this, though). > > 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 <[email protected]> > + * > + * 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. I would omit -07. This will get out of sync very soon > + */ > + > +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); Leave it like this for now, but... Instead of adding three more ContentInfo parsers, we should probably consider if we want to add a parser with an ASN1_ADB table dispatcher. This would then have five instances: 1. RFC 6488 signedOjbect 2. ccr 3.-5. the three erik objects: index, partition, segment index As a side effect of this, we could get rid of all the terrifying madness reachable from libcrypto/cms. I have to think about this... > + > +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); It is somewhat annoying that this is the same thing as a CCR manifest instance, except there are no SKIs. Quite a bit of code that cannot be reused because of this. > + > +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]; I'm not terribly fond of this. partref and num feel like things that want to be passed in instead of eind. The caller knows how many partrefs there are. This function doesn't (I know we have worse things than this in append_as() and append_ip()). > + > + 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; no cleanup, so goto out could just be return 0; and rc could go away > + } > + memcpy(partref->hash, data, length); in ccr.c we have a copy_asn1_string() helper. This could be used here as well. (and in all the other places where you use memcpy()). > + > + 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); the warning could give a hint that size is not in [100, MAX_FILE_SIZE] > + 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; > + } Seems it's time to add a helper for the SHA-256 DigestAlgorithmIdentifier to x509.c (ccr, cms, and rsc could use this): int x509_algor_is_sha256(const char *fn, const X509_ALGOR *alg) { ... } and we don't check the absence of parameters in cms.c, which we probably should. Such a helper would fix this. > + > + 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); Not sure, but we might want to split a helper out of x509_location() for this ASN1_STRING -> valid_uri -> strndup dance. The fewer explicit calls to these inconvenient ASN1_STRING accessors we have the better. > + > + 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])); I think sk_PartitionRef_num(eind_asn1->partitionList) wants to be stored in a local variable for brevity and readabaility: num_prefs = sk_PartitionRef_num(eind_asn1->partitionList); if (num_prefs < 1 || num_prefs > 255) { warnx("%s: number of partitions %d not in [1, 255]", fn, num_prefs); goto out; } if ((eind->parts = calloc(num_prefs, sizeof(eind->parts[0]))) == NULL) err(1, NULL); > + 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) { Let's not add more instances of this dumb idiom (I know the other parse functions have this, but that will go away). Either drop rc and use two exit paths: EInd_ContentInfo_free(ci); return eind; out: EInd_ContentInfo_free(ci); eind_free(eind); return NULL; } or if you really want a single exit, use a meaningful extra variable rather than the rc which isn't a return code. struct eind *ret_eind = NULL; ... ret_eind = eind; eind = NULL; out: EInd_ContentInfo_free(ci); eind_free(eind); return ret_eind; } > + 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]; like for the eind parsing above. pass in mftref and 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; Again, there's no cleanup here, so you could just return 0; > + } > + memcpy(mftref->hash, data, length); copy_asn1_string() > + > + 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); hint that size is out of explicit range? > + 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); copy_asn1_string() > + > + 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; > + } no upper bound? > + > + 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) { avoid rc > + 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]; same as above, segref and num should probably be passed in rather than esi > + > + 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; > + } no upper bound? > + > + 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) { avoid rc > + esi_free(esi); > + esi = NULL; > + } > + > + return esi; > +} > + > +void > +eind_free(struct eind *eind) > +{ > + if (eind == NULL) > + return; > + free(eind->scope); > + 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->scope); > + 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; par vs p > + 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; s vs seg > +}; > + > +/* > * 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 -07 needed? > + */ > + > +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, > }, > }; > >