Index | Thread | Search

From:
Theo Buehler <tb@theobuehler.org>
Subject:
Make CCR a proper CMS citizen
To:
tech@openbsd.org
Date:
Thu, 4 Dec 2025 14:10:18 +0100

Download raw body.

Thread
  • Theo Buehler:

    Make CCR a proper CMS citizen

Using an EncapsulatedContentInfo was a hack which arose out of the
desire of having an OID next to the stuff we really care about.
Russ Housley pointed out that an EncapsulatedContentInfo is never
really used at the top level and that CMS (of course) has a proper
mechanism for this. (The absence of signers also makes this original
choice dubious, see last paragraph before RFC 5652, section 5.2.1.)

So, switch to a ContentInfo and fix another encoding issue, namely
using a naked OID instead of a CMS DigestAlgorithmIdentifier.

In addition to CCR becoming a plain CMS object, the code becomes
simpler overall despite the DigestAlgorithmIdentifier adding some
complexity. The big win is that the opaque OCTET STRING wrapping
the CCR SEQUENCE and the manual DER wrangling can go away.

Russ confirmed by decoding a detailed example that this produces
the appropriate DER matching the changes in the ASN.1 in .

For the review (roughly top to bottom):

- mechanically rename EncapContentInfo (back) to ContentInfo
- embed the CanonicalCacheRepresentation directly in ContentInfo
- make hashAlg an X509_ALGOR, representing an AlgorithmIdentifier
  rather than an ASN1_OBJECT (an OID).
- in generate_ccr() we embed the OID in the X509_ALGOR with no
  parameters V_ASN1_UNDEF, as appropriate for SHA-256
- in serialize_ccr_content() and ccr_parse() get rid of the OCTET
  STRING wrapping dance
- in ccr_parse() unwrap OID and parameters and check they are as
  they should be for SHA-256.

Index: ccr.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/ccr.c,v
diff -u -p -r1.30 ccr.c
--- ccr.c	4 Dec 2025 12:07:01 -0000	1.30
+++ ccr.c	4 Dec 2025 12:19:50 -0000
@@ -29,6 +29,7 @@
 #include <openssl/asn1t.h>
 #include <openssl/stack.h>
 #include <openssl/safestack.h>
+#include <openssl/x509.h>
 
 #include "extern.h"
 #include "rpki-asn1.h"
@@ -37,7 +38,7 @@
  * CCR definition in draft-ietf-sidrops-rpki-ccr-01, section 3.
  */
 
-ASN1_ITEM_EXP EncapContentInfo_it;
+ASN1_ITEM_EXP ContentInfo_it;
 ASN1_ITEM_EXP CanonicalCacheRepresentation_it;
 ASN1_ITEM_EXP ManifestInstances_it;
 ASN1_ITEM_EXP ManifestInstance_it;
@@ -51,16 +52,16 @@ ASN1_ITEM_EXP RouterKeySets_it;
 ASN1_ITEM_EXP RouterKeySet_it;
 ASN1_ITEM_EXP RouterKey_it;
 
-ASN1_SEQUENCE(EncapContentInfo) = {
-	ASN1_SIMPLE(EncapContentInfo, contentType, ASN1_OBJECT),
-	ASN1_EXP(EncapContentInfo, content, ASN1_OCTET_STRING, 0),
-} ASN1_SEQUENCE_END(EncapContentInfo);
+ASN1_SEQUENCE(ContentInfo) = {
+	ASN1_SIMPLE(ContentInfo, contentType, ASN1_OBJECT),
+	ASN1_EXP(ContentInfo, content, CanonicalCacheRepresentation, 0),
+} ASN1_SEQUENCE_END(ContentInfo);
 
-IMPLEMENT_ASN1_FUNCTIONS(EncapContentInfo);
+IMPLEMENT_ASN1_FUNCTIONS(ContentInfo);
 
 ASN1_SEQUENCE(CanonicalCacheRepresentation) = {
 	ASN1_EXP_OPT(CanonicalCacheRepresentation, version, ASN1_INTEGER, 0),
-	ASN1_SIMPLE(CanonicalCacheRepresentation, hashAlg, ASN1_OBJECT),
+	ASN1_SIMPLE(CanonicalCacheRepresentation, hashAlg, X509_ALGOR),
 	ASN1_SIMPLE(CanonicalCacheRepresentation, producedAt,
 	    ASN1_GENERALIZEDTIME),
 	ASN1_EXP_OPT(CanonicalCacheRepresentation, mfts, ManifestState, 1),
@@ -626,14 +627,17 @@ static CanonicalCacheRepresentation *
 generate_ccr(struct validation_data *vd)
 {
 	CanonicalCacheRepresentation *ccr = NULL;
+	ASN1_OBJECT *oid;
 
 	if ((ccr = CanonicalCacheRepresentation_new()) == NULL)
 		errx(1, "CanonicalCacheRepresentation_new");
 
-	ASN1_OBJECT_free(ccr->hashAlg);
-	if ((ccr->hashAlg = OBJ_nid2obj(NID_sha256)) == NULL)
+	if ((oid = OBJ_nid2obj(NID_sha256)) == NULL)
 		errx(1, "OBJ_nid2obj");
 
+	if (!X509_ALGOR_set0(ccr->hashAlg, oid, V_ASN1_UNDEF, NULL))
+		errx(1, "X509_ALGOR_set0");
+
 	if (ASN1_GENERALIZEDTIME_set(ccr->producedAt, vd->buildtime) == NULL)
 		errx(1, "ASN1_GENERALIZEDTIME_set");
 
@@ -658,40 +662,25 @@ generate_ccr(struct validation_data *vd)
 void
 serialize_ccr_content(struct validation_data *vd)
 {
-	CanonicalCacheRepresentation *ccr;
-	EncapContentInfo *ci = NULL;
-	unsigned char *out;
-	int out_len, ci_der_len;
-
-	if ((ci = EncapContentInfo_new()) == NULL)
-		errx(1, "EncapContentInfo_new");
-
-	/*
-	 * At some point the below PEN OID should be replaced by one from IANA.
-	 */
+	ContentInfo *ci = NULL;
+	int ci_der_len;
+
+	if ((ci = ContentInfo_new()) == NULL)
+		errx(1, "ContentInfo_new");
+
 	ASN1_OBJECT_free(ci->contentType);
 	if ((ci->contentType = OBJ_dup(ccr_oid)) == NULL)
 		errx(1, "OBJ_dup");
 
-	ccr = generate_ccr(vd);
-
-	out = NULL;
-	if ((out_len = i2d_CanonicalCacheRepresentation(ccr, &out)) <= 0)
-		errx(1, "i2d_CanonicalCacheRepresentation");
-
-	CanonicalCacheRepresentation_free(ccr);
-
-	if (!ASN1_OCTET_STRING_set(ci->content, out, out_len))
-		errx(1, "ASN1_OCTET_STRING_set");
-
-	free(out);
+	CanonicalCacheRepresentation_free(ci->content);
+	ci->content = generate_ccr(vd);
 
 	vd->ccr.der = NULL;
-	if ((ci_der_len = i2d_EncapContentInfo(ci, &vd->ccr.der)) <= 0)
-		errx(1, "i2d_EncapContentInfo");
+	if ((ci_der_len = i2d_ContentInfo(ci, &vd->ccr.der)) <= 0)
+		errx(1, "i2d_ContentInfo");
 	vd->ccr.der_len = ci_der_len;
 
-	EncapContentInfo_free(ci);
+	ContentInfo_free(ci);
 }
 
 static inline int
@@ -1583,17 +1572,18 @@ struct ccr *
 ccr_parse(const char *fn, const unsigned char *der, size_t len)
 {
 	const unsigned char *oder;
-	EncapContentInfo *ci = NULL;
+	ContentInfo *ci = NULL;
 	CanonicalCacheRepresentation *ccr_asn1 = NULL;
+	const ASN1_OBJECT *oid;
 	struct ccr *ccr = NULL;
-	int nid, rc = 0;
+	int nid, ptype, rc = 0;
 
 	if (der == NULL)
 		return NULL;
 
 	oder = der;
-	if ((ci = d2i_EncapContentInfo(NULL, &der, len)) == NULL) {
-		warnx("%s: d2i_EncapContentInfo", fn);
+	if ((ci = d2i_ContentInfo(NULL, &der, len)) == NULL) {
+		warnx("%s: d2i_ContentInfo", fn);
 		goto out;
 	}
 	if (der != oder + len) {
@@ -1610,26 +1600,15 @@ ccr_parse(const char *fn, const unsigned
 		goto out;
 	}
 
-	der = ASN1_STRING_get0_data(ci->content);
-	len = ASN1_STRING_length(ci->content);
-
-	oder = der;
-	ccr_asn1 = d2i_CanonicalCacheRepresentation(NULL, &der, len);
-	if (ccr_asn1 == NULL) {
-		warnx("%s: d2i_CanonicalCacheRepresentation failed", fn);
-		goto out;
-	}
-	if (der != oder + len) {
-		warnx("%s: %td bytes trailing garbage", fn, oder + len - der);
-		goto out;
-	}
+	ccr_asn1 = ci->content;
 
 	if (!valid_econtent_version(fn, ccr_asn1->version, 0))
 		goto out;
 
-	if ((nid = OBJ_obj2nid(ccr_asn1->hashAlg)) != NID_sha256) {
-		warnx("%s: hashAlg: want SHA256 object, have %s", fn,
-		    nid2str(nid));
+	X509_ALGOR_get0(&oid, &ptype, NULL, ccr_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;
 	}
 
@@ -1674,8 +1653,7 @@ ccr_parse(const char *fn, const unsigned
 
 	rc = 1;
  out:
-	CanonicalCacheRepresentation_free(ccr_asn1);
-	EncapContentInfo_free(ci);
+	ContentInfo_free(ci);
 
 	if (rc == 0) {
 		ccr_free(ccr);
Index: rpki-asn1.h
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/rpki-asn1.h,v
diff -u -p -r1.11 rpki-asn1.h
--- rpki-asn1.h	16 Oct 2025 07:49:36 -0000	1.11
+++ rpki-asn1.h	4 Dec 2025 12:19:50 -0000
@@ -215,7 +215,7 @@ DECLARE_ASN1_FUNCTIONS(RouterKey);
 
 typedef struct {
 	ASN1_INTEGER *version;
-	ASN1_OBJECT *hashAlg;
+	X509_ALGOR *hashAlg;
 	ASN1_GENERALIZEDTIME *producedAt;
 	ManifestState *mfts;
 	ROAPayloadState *vrps;
@@ -228,10 +228,10 @@ DECLARE_ASN1_FUNCTIONS(CanonicalCacheRep
 
 typedef struct {
 	ASN1_OBJECT *contentType;
-	ASN1_OCTET_STRING *content;
-} EncapContentInfo;
+	CanonicalCacheRepresentation *content;
+} ContentInfo;
 
-DECLARE_ASN1_FUNCTIONS(EncapContentInfo);
+DECLARE_ASN1_FUNCTIONS(ContentInfo);
 
 
 /*