From: Job Snijders Subject: Re: rpki-client: encode Trust Anchor SubjectKeyIdentifiers into CCR output To: Theo Buehler Cc: tech@openbsd.org Date: Sat, 6 Sep 2025 07:38:08 +0000 Here is a variant that is more consistent with the other *State fields: $ cat /var/db/rpki-client/rpki.ccr | der2ascii | tail -n 16 | head -n 12 [4] { SEQUENCE { SEQUENCE { OCTET_STRING { `0b9cca90dd0d7a8a37666b19217fe0d84037b7a2` } OCTET_STRING { `13d4f24f9a9fcd98db36f930631808c88f3974bc` } OCTET_STRING { `e8552b1fd6d1a4f7e404c6d8e5680d1ebc163fc3` } OCTET_STRING { `eb680f38f5d6c71bb4b106b8bd06585012da31b6` } OCTET_STRING { `fc8a9cb3ed184e17d30eea1e0fa7615ce4b1af47` } } OCTET_STRING { `b9ba66b2bcd54e4812249f60ed2de9357670cc48ff848f1bc35f5986703de71f` } } } Index: ccr.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/ccr.c,v diff -u -p -r1.2 ccr.c --- ccr.c 23 Aug 2025 11:16:50 -0000 1.2 +++ ccr.c 6 Sep 2025 07:29:11 -0000 @@ -41,15 +41,10 @@ * BEGIN * * IMPORTS - * CONTENT-TYPE, Digest, DigestAlgorithmIdentifier + * CONTENT-TYPE, Digest, DigestAlgorithmIdentifier, SubjectKeyIdentifier * 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) } + * pkcs-9(9) smime(16) modules(0) id-mod-cms-2009(58) } * * -- in [draft-spaghetti-sidrops-rpki-erik-protocol-01] * -- https://sobornost.net/~job/draft-spaghetti-sidrops-rpki-erik-protocol.html @@ -62,6 +57,7 @@ * 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 @@ -78,11 +74,13 @@ * mfts [1] ManifestState OPTIONAL, * vrps [2] ROAPayloadState OPTIONAL, * vaps [3] ASPAPayloadState OPTIONAL, + * tas [4] TrustAnchorState OPTIONAL, * ... } - * -- at least one of mfts, vrps or vaps MUST be present + * -- at least one of mfts, vrps, vaps, or tas MUST be present * ( WITH COMPONENTS { ..., mfts PRESENT } | * WITH COMPONENTS { ..., vrps PRESENT } | - * WITH COMPONENTS { ..., vaps PRESENT } ) + * WITH COMPONENTS { ..., vaps PRESENT } | + * WITH COMPONENTS { ..., tas PRESENT } ) * * ManifestState ::= SEQUENCE { * mftrefs SEQUENCE OF ManifestRef, @@ -105,6 +103,10 @@ * asID ASID * providers SEQUENCE (SIZE(1..MAX)) OF ASID } * + * TrustAnchorState ::= SEQUENCE { + * taskis SEQUENCE (SIZE(1..MAX)) OF SubjectKeyIdentifier, + * hash Digest } + * * END */ @@ -116,6 +118,8 @@ ASN1_ITEM_EXP ROAPayloadSets_it; ASN1_ITEM_EXP ROAPayloadSet_it; ASN1_ITEM_EXP ASPAPayloadSets_it; ASN1_ITEM_EXP ASPAPayloadSet_it; +ASN1_ITEM_EXP TASubjectKeyIdentifiers_it; +ASN1_ITEM_EXP SubjectKeyIdentifier_it; /* * Can't use CMS_ContentInfo since it is not backed by a public struct @@ -136,6 +140,7 @@ ASN1_SEQUENCE(CanonicalCacheRepresentati ASN1_EXP_OPT(CanonicalCacheRepresentation, mfts, ManifestState, 1), ASN1_EXP_OPT(CanonicalCacheRepresentation, vrps, ROAPayloadState, 2), ASN1_EXP_OPT(CanonicalCacheRepresentation, vaps, ASPAPayloadState, 3), + ASN1_EXP_OPT(CanonicalCacheRepresentation, tas, TrustAnchorState, 4), } ASN1_SEQUENCE_END(CanonicalCacheRepresentation); IMPLEMENT_ASN1_FUNCTIONS(CanonicalCacheRepresentation); @@ -208,6 +213,24 @@ ASN1_SEQUENCE(ASPAPayloadSet) = { IMPLEMENT_ASN1_FUNCTIONS(ASPAPayloadSet); +IMPLEMENT_ASN1_FUNCTIONS(SubjectKeyIdentifier); + +ASN1_SEQUENCE(TrustAnchorState) = { + ASN1_SEQUENCE_OF(TrustAnchorState, taskis, ASN1_OCTET_STRING), + ASN1_SIMPLE(TrustAnchorState, hash, ASN1_OCTET_STRING), +} ASN1_SEQUENCE_END(TrustAnchorState); + +IMPLEMENT_ASN1_FUNCTIONS(TrustAnchorState); + +ASN1_ITEM_TEMPLATE(TASubjectKeyIdentifiers) = + ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_SEQUENCE_OF, 0, tas, + ASN1_OCTET_STRING) +ASN1_ITEM_TEMPLATE_END(TASubjectKeyIdentifiers) + +IMPLEMENT_ASN1_ENCODE_FUNCTIONS_fname(TASubjectKeyIdentifiers, + TASubjectKeyIdentifiers, TASubjectKeyIdentifiers); + + static void asn1int_set_seqnum(ASN1_INTEGER *aint, const char *seqnum) { @@ -456,6 +479,44 @@ generate_aspapayloadstate(struct validat return vaps; } +static void +append_ta(STACK_OF(SubjectKeyIdentifier) *taskis, struct ccr_taski *taski) +{ + SubjectKeyIdentifier *ski; + + if ((ski = SubjectKeyIdentifier_new()) == NULL) + errx(1, "SubjectKeyIdentifier_new"); + + if (!ASN1_OCTET_STRING_set(ski, taski->keyid, sizeof(taski->keyid))) + errx(1, "ASN1_OCTET_STRING_set"); + + if ((sk_SubjectKeyIdentifier_push(taskis, ski)) <= 0) + errx(1, "sk_SubjectKeyIdentifier_push"); +} + +static TrustAnchorState * +generate_trustanchorstate(struct validation_data *vd) +{ + TrustAnchorState *tas; + struct ccr_taski *taski; + + if ((tas = TrustAnchorState_new()) == NULL) + errx(1, "TrustAnchorState_new"); + + RB_FOREACH(taski, ccr_taski_tree, &vd->ccr.taskis) { + append_ta(tas->taskis, taski); + } + + hash_asn1_item(tas->hash, ASN1_ITEM_rptr(TASubjectKeyIdentifiers), + tas->taskis); + + if (base64_encode(tas->hash->data, tas->hash->length, + &vd->ccr.tas_hash) == -1) + errx(1, "base64_encode"); + + return tas; +} + static CanonicalCacheRepresentation * generate_ccr(struct validation_data *vd) { @@ -481,6 +542,9 @@ generate_ccr(struct validation_data *vd) if ((ccr->vaps = generate_aspapayloadstate(vd)) == NULL) errx(1, "generate_aspapayloadstate"); + if ((ccr->tas = generate_trustanchorstate(vd)) == NULL) + errx(1, "generate_trustanchorstate"); + return ccr; } @@ -520,6 +584,30 @@ serialize_ccr_content(struct validation_ ContentInfo_free(ci); } +static inline int +ccr_taski_cmp(const struct ccr_taski *a, const struct ccr_taski *b) +{ + return memcmp(a->keyid, b->keyid, SHA_DIGEST_LENGTH); +} + +RB_GENERATE(ccr_taski_tree, ccr_taski, entry, ccr_taski_cmp); + +void +ccr_insert_taski(struct ccr_taski_tree *tree, const struct cert *cert) +{ + struct ccr_taski *taski; + + assert(cert->aki == NULL && cert->aia == NULL); + + if ((taski = calloc(1, sizeof(*taski))) == NULL) + err(1, NULL); + + if ((hex_decode(cert->ski, taski->keyid, sizeof(taski->keyid))) != 0) + err(1, NULL); + + if (RB_INSERT(ccr_taski_tree, tree, taski) != NULL) + errx(1, "CCR TASKI tree corrupted"); +} static inline int ccr_mft_cmp(const struct ccr_mft *a, const struct ccr_mft *b) Index: extern.h =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/extern.h,v diff -u -p -r1.260 extern.h --- extern.h 24 Aug 2025 12:17:12 -0000 1.260 +++ extern.h 6 Sep 2025 07:29:12 -0000 @@ -476,12 +476,22 @@ RB_PROTOTYPE(ccr_mft_tree, ccr_mft, entr RB_HEAD(ccr_vrp_tree, vrp); RB_PROTOTYPE(ccr_vrp_tree, vrp, entry, ccr_vrp_cmp); +struct ccr_taski { + RB_ENTRY(ccr_taski) entry; + unsigned char keyid[SHA_DIGEST_LENGTH]; +}; + +RB_HEAD(ccr_taski_tree, ccr_taski); +RB_PROTOTYPE(ccr_taski_tree, ccr_taski, entry, ccr_taski_cmp); + struct ccr { struct ccr_mft_tree mfts; struct ccr_vrp_tree vrps; + struct ccr_taski_tree taskis; char *mfts_hash; char *vrps_hash; char *vaps_hash; + char *tas_hash; unsigned char *der; size_t der_len; }; @@ -1018,6 +1028,7 @@ int output_ccr_der(FILE *, struct vali */ void ccr_insert_mft(struct ccr_mft_tree *, const struct mft *); void ccr_insert_roa(struct ccr_vrp_tree *, const struct roa *); +void ccr_insert_taski(struct ccr_taski_tree *, const struct cert *); void serialize_ccr_content(struct validation_data *); void logx(const char *fmt, ...) Index: main.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/main.c,v diff -u -p -r1.296 main.c --- main.c 5 Sep 2025 17:38:03 -0000 1.296 +++ main.c 6 Sep 2025 07:29:12 -0000 @@ -634,6 +634,8 @@ entity_process(struct ibuf *b, struct va cert = cert_read(b); switch (cert->purpose) { case CERT_PURPOSE_TA: + ccr_insert_taski(&vd->ccr.taskis, cert); + /* FALLTHROUGH */ case CERT_PURPOSE_CA: queue_add_from_cert(cert, &vd->ncas); break; @@ -1025,6 +1027,7 @@ main(int argc, char *argv[]) RB_INIT(&vd.ncas); RB_INIT(&vd.ccr.mfts); RB_INIT(&vd.ccr.vrps); + RB_INIT(&vd.ccr.taskis); /* If started as root, priv-drop to _rpki-client */ if (getuid() == 0) { Index: rpki-asn1.h =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/rpki-asn1.h,v diff -u -p -r1.2 rpki-asn1.h --- rpki-asn1.h 23 Aug 2025 09:13:14 -0000 1.2 +++ rpki-asn1.h 6 Sep 2025 07:29:12 -0000 @@ -53,6 +53,8 @@ extern ASN1_ITEM_EXP ROAPayloadSets_it; extern ASN1_ITEM_EXP ROAPayloadSet_it; extern ASN1_ITEM_EXP ASPAPayloadSets_it; extern ASN1_ITEM_EXP ASPAPayloadSet_it; +extern ASN1_ITEM_EXP TASubjectKeyIdentifiers_it; +extern ASN1_ITEM_EXP SubjectKeyIdentifier_it; typedef struct { ASN1_OCTET_STRING *hash; @@ -133,6 +135,28 @@ typedef struct { DECLARE_ASN1_FUNCTIONS(ASPAPayloadState); +typedef ASN1_OCTET_STRING SubjectKeyIdentifier; + +DECLARE_ASN1_FUNCTIONS(SubjectKeyIdentifier); + +DECLARE_STACK_OF(SubjectKeyIdentifier); + +#ifndef DEFINE_STACK_OF +#define sk_SubjectKeyIdentifier_push(st, i) \ + SKM_sk_push(SubjectKeyIdentifier, (st), (i)) +#endif + +typedef STACK_OF(SubjectKeyIdentifier) TASubjectKeyIdentifiers; + +DECLARE_ASN1_FUNCTIONS(TASubjectKeyIdentifiers); + +typedef struct { + STACK_OF(SubjectKeyIdentifier) *taskis; + ASN1_OCTET_STRING *hash; +} TrustAnchorState; + +DECLARE_ASN1_FUNCTIONS(TrustAnchorState); + typedef struct { ASN1_INTEGER *version; ASN1_OBJECT *hashAlg; @@ -140,6 +164,7 @@ typedef struct { ManifestState *mfts; ROAPayloadState *vrps; ASPAPayloadState *vaps; + TrustAnchorState *tas; } CanonicalCacheRepresentation; DECLARE_ASN1_FUNCTIONS(CanonicalCacheRepresentation);