Index | Thread | Search

From:
Job Snijders <job@openbsd.org>
Subject:
Re: rpki-client: encode Trust Anchor SubjectKeyIdentifiers into CCR output
To:
Theo Buehler <tb@theobuehler.org>
Cc:
tech@openbsd.org
Date:
Sat, 6 Sep 2025 07:38:08 +0000

Download raw body.

Thread
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);