From: Theo Buehler Subject: rpki-client: CMS parser rewrite To: tech@openbsd.org Date: Mon, 5 Oct 2026 05:01:51 +0200 This diff removes use of the libcrypto CMS API from rpki-client and replaces it with an implementation using CBS/CBB's ASN.1 capabilities. It does exactly what's needed to parse and validate an RPKI signed object per RFC 6488. It only allows DER in normal mode - EE cert and eContent are still parsed leniently due to the templated ASN.1. The code passes Job's fixture test, so we can parse whatever we should be able to parse. I also checked this against the BBN objects with hacks to be more permissive wrt signtime and AS resources in ROA EE certs. For review, apply the diff, then read cms_parse_validate() and follow the code drilling down into the CMS. That covers all of cms.c except the file mode helper cms_rtype_from_der() at the end. Having RFC 6488 at hand may help; RFC 5652 probably isn't needed. All things listed in RFC 6488 section 3, as updated by RFC 9589, are checked, as should be easy enough to verify. Chaining the EE cert to the TA is still performed by valid_cert() outside of cms.c right now. The changes outside of cms.c are straightforward: the filemode CMS type prober is replaced by cms_rtype_from_der() and cert_parse_ee_cert() becomes like the other cert parsers (except for the talid) because we get the cert's DER directly rather than an already deserialized X509 *. Copious comments in cms.c attempt to make this palatable, although it's ASN.1, so it must be horrible. The AlgorithmIdentifier parameter dances are particularly annoying because every algorithm has its own quirks. I have chosen to discard tags and lengths most of the time, i.e., I use CBS_get_asn1() rather than CBS_get_asn1_element(). This may not be the cleanest thing to do from a purist's perspective, but it avoids annoying duplication. Where things felt ambiguous, I added 'value' in one form or the other. CBS_get_* always advances the input CBS and always decreases its length. We therefore only need to be concerned with ensuring we don't allow trailing garbage anywhere. signed-data is pretty complicated even in the minimal form specified in RFC 6488. Since we no longer use libcrypto with whatever weird things it does in its peculiar approximation of validating the CMS and signatures, we need to do that ourselves. The eContent protection is indirect: 1. The eContentType OID and the SHA-256 hash of the eContentValue (omitting tag and length) are recorded as attributes in the signedAttrs element of the signerInfo. 2. A modified DER encoding of the signedAttrs is what's actually signed with the embedded EE cert's private key. cms_check_rfc6488_signedAttrs() checks 1, cms_validate_rfc6488_object() then checks 2. Detailed references are in the code. I would like to land this and deal with error diagnostics in tree. Index: usr.sbin/rpki-client/cert.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/cert.c,v diff -u -p -r1.244 cert.c --- usr.sbin/rpki-client/cert.c 13 Jul 2026 10:53:12 -0000 1.244 +++ usr.sbin/rpki-client/cert.c 4 Oct 2026 11:27:01 -0000 @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -1540,40 +1541,6 @@ cert_parse_internal(const char *fn, X509 } /* - * Parse an EE cert extracted from a CMS signed object. Store all cert and - * extension data we need later in the returned struct cert. - * Check the certificate's purpose and validate the TA constraints. - * Returns cert on success and NULL on failure. - */ -struct cert * -cert_parse_ee_cert(const char *fn, int talid, X509 *x) -{ - struct cert *cert = NULL; - - if (!X509_up_ref(x)) - goto out; - - if ((cert = cert_parse_internal(fn, x)) == NULL) - goto out; - cert->talid = talid; - - if (cert->purpose != CERT_PURPOSE_EE) { - warnx("%s: expected EE cert, got %s", fn, - purpose2str(cert->purpose)); - goto out; - } - - if (!constraints_validate(fn, cert)) - goto out; - - return cert; - - out: - cert_free(cert); - return NULL; -} - -/* * This is a generic parser for resource certificates and can only do as much * validation as can be extracted from the bare DER. Callers should at least * check the cert->purpose and consider any further validation. @@ -1585,6 +1552,11 @@ cert_deserialize_and_parse(const char *f const unsigned char *oder; X509 *x = NULL; + if (len > INT_MAX) { + warnx("%s: overlong DER", fn); + goto out; + } + oder = der; if ((x = d2i_X509(NULL, &der, len)) == NULL) { warnx("%s: d2i_X509", fn); @@ -1608,6 +1580,38 @@ cert_deserialize_and_parse(const char *f out: cert_free(cert); X509_free(x); + return NULL; +} + +/* + * Parse an EE cert extracted from a CMS signed object. Store all cert and + * extension data we need later in the returned struct cert. + * Check the certificate's purpose and validate the TA constraints. + * Returns cert on success and NULL on failure. + */ +struct cert * +cert_parse_ee_cert(const char *fn, int talid, const unsigned char *der, + size_t len) +{ + struct cert *cert = NULL; + + if ((cert = cert_deserialize_and_parse(fn, der, len)) == NULL) + goto out; + cert->talid = talid; + + if (cert->purpose != CERT_PURPOSE_EE) { + warnx("%s: expected EE cert, got %s", fn, + purpose2str(cert->purpose)); + goto out; + } + + if (!constraints_validate(fn, cert)) + goto out; + + return cert; + + out: + cert_free(cert); return NULL; } Index: usr.sbin/rpki-client/cms.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/cms.c,v diff -u -p -r1.69 cms.c --- usr.sbin/rpki-client/cms.c 11 Sep 2026 06:25:00 -0000 1.69 +++ usr.sbin/rpki-client/cms.c 4 Oct 2026 11:27:01 -0000 @@ -1,6 +1,7 @@ /* $OpenBSD: cms.c,v 1.69 2026/09/11 06:25:00 tb Exp $ */ + /* - * Copyright (c) 2019 Kristaps Dzonsons + * Copyright (c) 2026 Theo Buehler * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,283 +18,747 @@ #include #include -#include +#include #include #include -#include +#include -#include +#include +#include +#include +#include +#include +#include "bytestring.h" #include "extern.h" -#define ASN1_TAG_SEQUENCE 0x30 /* X.690, section 8.9 */ -#define ASN1_LENGTH_INDEFINITE 0x80 /* X.690, section 8.1.3.6.1 */ +/* RFC 5280, 4.1.2.5.1 and 4.1.2.5.2 as well as RFC 5652, 11.3. */ +#define UTCTIME_LEN 15 /* 0x17 0x0d YYMMDDHHMMSSZ */ +#define GENERALIZEDTIME_LEN 17 /* 0x18 0x0f YYYYMMDDHHMMSSZ */ + +struct cms_signedData; + +static int cms_get_encapContentInfo(const char *, struct cms_signedData *, + CBS *, CBS *); + +static int +cbs_oid_value_matches_obj(CBS *oid, const ASN1_OBJECT *obj) +{ + return CBS_mem_equal(oid, OBJ_get0_data(obj), OBJ_length(obj)); +} -extern int filemode; +static int +cbs_oid_value_matches_nid(CBS *oid, int nid) +{ + const ASN1_OBJECT *obj; + + if ((obj = OBJ_nid2obj(nid)) == NULL) + errx(1, "OBJ_nid2obj failed for %s", nid2str(nid)); + + return cbs_oid_value_matches_obj(oid, obj); +} +/* Helper for syntax version of signedData and signerInfo. */ static int -cms_extract_econtent(const char *fn, CMS_ContentInfo *cms, const uint8_t **res, - size_t *rsz) +cbs_integer_value_is_3(CBS *cbs) { - ASN1_OCTET_STRING **os = NULL; + uint8_t u8; - if ((os = CMS_get0_content(cms)) == NULL || *os == NULL) { - warnx("%s: RFC 6488 section 2.1.4: " - "eContent: zero-length content", fn); + if (CBS_len(cbs) != 1) return 0; - } + if (!CBS_peek_u8(cbs, &u8)) + return 0; + + return u8 == 3; +} + +/* + * RFC 5280, 4.1.2.2. + * + * AlgorithmIdentifier ::= SEQUENCE { + * algorithm OBJECT IDENTIFIER, + * parameters ANY DEFINED BY algorithm OPTIONAL } + */ + +static int +cbs_is_sha256_algorithmIdentifier_value(CBS *cbs) +{ + CBS alg, oid, param; + int param_present; + + CBS_dup(cbs, &alg); - if ((*rsz = ASN1_STRING_length(*os)) == 0) { - warnx("%s: RFC 6488 section 2.1.4: " - "eContent: zero-length content", fn); + if (!CBS_get_asn1(&alg, &oid, CBS_ASN1_OBJECT)) return 0; - } - if (*rsz > MAX_FILE_SIZE) { - warnx("%s: overlong eContent of length %zu", fn, *rsz); + /* Allow for faulty encoding coming from the last Millennium. */ + if (!CBS_get_optional_asn1(&alg, ¶m, ¶m_present, CBS_ASN1_NULL)) + return 0; + + if (CBS_len(&alg) != 0) + return 0; + + if (!cbs_oid_value_matches_nid(&oid, NID_sha256)) return 0; - } - *res = ASN1_STRING_get0_data(*os); + /* Long live Postel's Law - cf., e.g., RFC 5754, section 2. */ + if (param_present) + return CBS_len(¶m) == 0; + return 1; } static int -cms_get_signtime(const char *fn, X509_ATTRIBUTE *attr, time_t *signtime) +cbs_is_rfc7935_signatureAlgorithm_value(CBS *cbs) { - const ASN1_TIME *at; - const char *time_str = "UTCTime"; - int time_type = V_ASN1_UTCTIME; - - *signtime = 0; - at = X509_ATTRIBUTE_get0_data(attr, 0, time_type, NULL); - if (at == NULL) { - time_str = "GeneralizedTime"; - time_type = V_ASN1_GENERALIZEDTIME; - at = X509_ATTRIBUTE_get0_data(attr, 0, time_type, NULL); - if (at == NULL) { - warnx("%s: CMS signing-time issue", fn); - return 0; - } - warnx("%s: GeneralizedTime instead of UTCTime", fn); - } + CBS alg, oid, param; + int param_present; + + CBS_dup(cbs, &alg); - if (!x509_get_time(at, signtime)) { - warnx("%s: failed to convert %s", fn, time_str); + if (!CBS_get_asn1(&alg, &oid, CBS_ASN1_OBJECT)) return 0; - } + if (!CBS_get_optional_asn1(&alg, ¶m, ¶m_present, CBS_ASN1_NULL)) + return 0; + + if (CBS_len(&alg) != 0) + return 0; + + /* RFC 4055, 1.2: parameters field MUST contain NULL. */ + if (cbs_oid_value_matches_nid(&oid, NID_rsaEncryption)) + return param_present && CBS_len(¶m) == 0; + + /* + * Need to accept sha256WithRSAEncryption for compat with RFC 6485. + * RFC 4055, 5: parameters MUST be NULL. Implementations MUST accept + * the parameters being absent as well as present. + */ + if (cbs_oid_value_matches_nid(&oid, NID_sha256WithRSAEncryption)) + return !param_present || CBS_len(¶m) == 0; + + if (!experimental) + return 0; + + /* RFC 5758, 3.2: encoding MUST omit parameters. */ + if (cbs_oid_value_matches_nid(&oid, NID_ecdsa_with_SHA256)) + return !param_present; + + return 0; +} + +/* + * RFC 5652, section 3: + * + * ContentInfo ::= SEQUENCE { + * contentType ContentType, + * content [0] EXPLICIT ANY DEFINED BY contentType } + * + * ContentType ::= OBJECT IDENTIFIER + */ + +static int +cms_get_contentInfo(CBS *cbs, CBS *out_contentType, CBS *out_content) +{ + CBS contentInfo; + + if (!CBS_get_asn1(cbs, &contentInfo, CBS_ASN1_SEQUENCE)) + return 0; + + if (!CBS_get_asn1(&contentInfo, out_contentType, CBS_ASN1_OBJECT)) + return 0; + if (!CBS_get_asn1(&contentInfo, out_content, + CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0x00)) + return 0; + + if (CBS_len(&contentInfo) != 0) + return 0; + + /* Don't check CBS_len(&cbs) == 0 to allow for concatenated objects. */ return 1; } +/* + * RFC 5652, section 5.1: + * + * SignedData ::= SEQUENCE { + * version CMSVersion, + * digestAlgorithms DigestAlgorithmIdentifiers, + * encapContentInfo EncapsulatedContentInfo, + * certificates [0] IMPLICIT CertificateSet OPTIONAL, + * crls [1] IMPLICIT RevocationInfoChoices OPTIONAL, + * signerInfos SignerInfos } + * + * DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier + * + * SignerInfos ::= SET OF SignerInfo + */ + +struct cms_signedData { + CBS version; + CBS digestAlgorithms; + CBS encapContentInfo; + CBS certificates; + CBS crls; + CBS signerInfos; + + int certificates_present; + int crls_present; +}; + static int -cms_SignerInfo_check_attributes(const char *fn, const CMS_SignerInfo *si, - time_t *signtime) +cms_get_signedData(CBS *cbs, struct cms_signedData *out) { - char buf[128]; - const ASN1_OBJECT *obj; - int i, nattrs; - int has_ct = 0, has_md = 0, has_st = 0; + CBS signedData; + + memset(out, 0, sizeof(*out)); + + if (!CBS_get_asn1(cbs, &signedData, CBS_ASN1_SEQUENCE)) + return 0; + + if (!CBS_get_asn1(&signedData, &out->version, CBS_ASN1_INTEGER)) + return 0; + if (!CBS_get_asn1(&signedData, &out->digestAlgorithms, CBS_ASN1_SET)) + return 0; + if (!CBS_get_asn1(&signedData, + &out->encapContentInfo, CBS_ASN1_SEQUENCE)) + return 0; + if (!CBS_get_optional_asn1(&signedData, + &out->certificates, &out->certificates_present, + CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0x00)) + return 0; + if (!CBS_get_optional_asn1(&signedData, + &out->crls, &out->crls_present, + CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0x01)) + return 0; + if (!CBS_get_asn1(&signedData, &out->signerInfos, CBS_ASN1_SET)) + return 0; + + if (CBS_len(&signedData) != 0) + return 0; + + if (CBS_len(cbs) != 0) + return 0; + + return 1; +} + +/* + * RFC 5652, 5.3 + * + * SignerInfo ::= SEQUENCE { + * version CMSVersion, + * sid SignerIdentifier, + * digestAlgorithm DigestAlgorithmIdentifier, + * signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL, + * signatureAlgorithm SignatureAlgorithmIdentifier, + * signature SignatureValue, + * unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL } + * + * SignerIdentifier ::= CHOICE { + * issuerAndSerialNumber IssuerAndSerialNumber, + * subjectKeyIdentifier [0] SubjectKeyIdentifier } + * + * SignedAttributes ::= SET SIZE (1..MAX) OF Attribute + * + * UnsignedAttributes ::= SET SIZE (1..MAX) OF Attribute + * + * Attribute ::= SEQUENCE { + * attrType OBJECT IDENTIFIER, + * attrValues SET OF AttributeValue } + * + * AttributeValue ::= ANY + * + * SignatureValue ::= OCTET STRING + */ + +struct cms_signerInfo { + CBS version; + CBS sid; + CBS digestAlgorithm; + CBS signedAttrs; + CBS signatureAlgorithm; + CBS signature; + CBS unsignedAttrs; + + int sid_is_subjectKeyIdentifier; + int signedAttrs_present; + int unsignedAttrs_present; +}; - *signtime = 0; +static int +cms_check_rfc6488_signerInfo_version(CBS *cbs) +{ + return cbs_integer_value_is_3(cbs); +} + +/* + * Time ::= CHOICE { + * utcTime UTCTime, + * generalizedTime GeneralizedTime } + */ + +static int +cbs_is_Time(CBS *cbs, uint8_t *out_tag) +{ + uint8_t tag; - nattrs = CMS_signed_get_attr_count(si); - if (nattrs <= 0) { - warnx("%s: RFC 6488: error extracting signedAttrs", fn); + if (!CBS_peek_u8(cbs, &tag)) return 0; + + *out_tag = tag; + + if (tag == CBS_ASN1_UTCTIME && + CBS_len(cbs) == UTCTIME_LEN) + return 1; + + if (tag == CBS_ASN1_GENERALIZEDTIME && + CBS_len(cbs) == GENERALIZEDTIME_LEN) + return 1; + + return 0; +} + +/* + * RFC 5652, 11.3 SigningTime ::= Time + * Enforce the usual 1950-2049 rule for UTCTime vs GeneralizedTime. + */ +static int +cbs_get_signingtime(CBS *cbs, time_t *out_signingtime) +{ + CBS signingTime; + ASN1_TIME *at = NULL; + char *str = NULL; + uint8_t tag; + int ret = 0; + + if (!cbs_is_Time(cbs, &tag)) + goto out; + if (!CBS_get_asn1(cbs, &signingTime, tag)) + goto out; + if (CBS_len(cbs) != 0) + goto out; + + if (!CBS_strdup(&signingTime, &str)) + goto out; + + if ((at = ASN1_TIME_new()) == NULL) + goto out; + if (!ASN1_TIME_set_string_X509(at, str)) + goto out; + if (!x509_get_time(at, out_signingtime)) + goto out; + + ret = 1; + + out: + ASN1_TIME_free(at); + free(str); + + return ret; +} + +/* + * Helper for checking correct set-of encoding. + * + * X.690, 11.6: + * + * The encodings of the component values of a set-of value shall appear in + * ascending order, the encodings being compared as octet strings with the + * shorter components being padded at their trailing end with 0-octets. + * + * Note: The padding octets are for comparison purposes only and do not + * appear in the encodings. + */ + +static int +cbs_compare_octets(CBS *cbs1, CBS *cbs2, int *out_cmp) +{ + CBS tail; + size_t len; + int cmp, tail_cmp; + + if (CBS_len(cbs1) > CBS_len(cbs2)) { + len = CBS_len(cbs2); + + CBS_dup(cbs1, &tail); + tail_cmp = 1; + } else { + len = CBS_len(cbs1); + + CBS_dup(cbs2, &tail); + tail_cmp = -1; + } + + if ((cmp = memcmp(CBS_data(cbs1), CBS_data(cbs2), len)) != 0) { + if (cmp > 0) + *out_cmp = 1; + else + *out_cmp = -1; + return 1; } - for (i = 0; i < nattrs; i++) { - X509_ATTRIBUTE *attr; - attr = CMS_signed_get_attr(si, i); - if (attr == NULL || X509_ATTRIBUTE_count(attr) != 1) { - warnx("%s: RFC 6488: bad signed attribute encoding", - fn); + if (!CBS_skip(&tail, len)) + return 0; + + while (CBS_len(&tail) > 0) { + uint8_t u8; + + if (!CBS_get_u8(&tail, &u8)) return 0; + + if (u8 > 0) { + *out_cmp = tail_cmp; + return 1; } + } + + *out_cmp = 0; + /* Multisets are valid. Trailing zeroes imply invalid tag or length. */ + return CBS_len(cbs1) == CBS_len(cbs2); +} + +/* + * RFC 6488, 2.1.6.4, as updated by RFC 9589: + * + * The signedAttrs element MUST be present and MUST include the + * content-type, message-digest, and signing-time attributes + * [RFC5652]. Other signed attributes MUST NOT be included. + * + * Check proper DER encoding of the signedAttrs set, verify that + * each attribute value is a single element set, check that the + * encapContentInfo's OID and message digest match the ones in + * the signed attribute values. Parse and return the signingtime. + */ + +static int +cms_check_rfc6488_signedAttrs(const char *fn, CBS *attrs, + struct cms_signedData *sd, time_t *out_signingtime) +{ + CBS cbs; + CBS curr, prev; + CBS eContentType, eContentValue; + int contentType_seen = 0, messageDigest_seen = 0, signingTime_seen = 0; + int cmp; - obj = X509_ATTRIBUTE_get0_object(attr); - if (obj == NULL) { - warnx("%s: RFC 6488: bad signed attribute", fn); + CBS_dup(attrs, &cbs); + + /* + * Enforce correct set-of encoding. While we could merge the two loops, + * it is way simpler to make two passes over the signedAttrs. We test + * for cmp == -1 rather than <= 0 because "[t]he signedAttrs element + * MUST include only a single instance of any particular attribute." + */ + if (!CBS_get_asn1_element(&cbs, &prev, CBS_ASN1_SEQUENCE)) + return 0; + while (CBS_len(&cbs) > 0) { + if (!CBS_get_asn1_element(&cbs, &curr, CBS_ASN1_SEQUENCE)) return 0; - } - if (OBJ_cmp(obj, cnt_type_oid) == 0) { - if (has_ct++ != 0) { - warnx("%s: RFC 6488: duplicate " - "signed attribute", fn); + if (!cbs_compare_octets(&prev, &curr, &cmp)) + return 0; + if (cmp != -1) + return 0; + CBS_dup(&curr, &prev); + } + + CBS_dup(attrs, &cbs); + + if (!cms_get_encapContentInfo(fn, sd, &eContentType, &eContentValue)) + return 0; + + /* + * RFC 6488, 2.1.6.4: signedAttrs + * + * SignedAttributes ::= SET SIZE (1..MAX) OF Attribute + * + * Attribute ::= SEQUENCE { + * attrType OBJECT IDENTIFIER, + * attrValues SET OF AttributeValue } + * + * AttributeValue ::= ANY + */ + while (CBS_len(&cbs) > 0) { + CBS attr, oid, value; + + if (!CBS_get_asn1(&cbs, &attr, CBS_ASN1_SEQUENCE)) + return 0; + + if (!CBS_get_asn1(&attr, &oid, CBS_ASN1_OBJECT)) + return 0; + if (!CBS_get_asn1(&attr, &value, CBS_ASN1_SET)) + return 0; + + if (CBS_len(&attr) != 0) + return 0; + + if (cbs_oid_value_matches_obj(&oid, cnt_type_oid)) { + /* + * 2.1.6.4.1: Content-Type Attribute + * attrValues [...] MUST match the eContentType + */ + CBS contentType; + + if (contentType_seen++ > 0) + return 0; + + if (!CBS_get_asn1(&value, &contentType, + CBS_ASN1_OBJECT)) + return 0; + + if (!CBS_mem_equal(&contentType, + CBS_data(&eContentType), CBS_len(&eContentType))) + return 0; + } else if (cbs_oid_value_matches_obj(&oid, msg_dgst_oid)) { + /* + * 2.1.6.4.2: Message-Digest Attribute + * contains the output of the digest algorithm applied + * to the content being signed + */ + CBS messageDigest; + uint8_t digest[EVP_MAX_MD_SIZE]; + unsigned int len = sizeof(digest); + + if (messageDigest_seen++ > 0) + return 0; + + if (!CBS_get_asn1(&value, &messageDigest, + CBS_ASN1_OCTETSTRING)) return 0; - } - } else if (OBJ_cmp(obj, msg_dgst_oid) == 0) { - if (has_md++ != 0) { - warnx("%s: RFC 6488: duplicate " - "signed attribute", fn); + + if (!EVP_Digest(CBS_data(&eContentValue), + CBS_len(&eContentValue), digest, &len, EVP_sha256(), + NULL)) return 0; - } - } else if (OBJ_cmp(obj, sign_time_oid) == 0) { - if (has_st++ != 0) { - warnx("%s: RFC 6488: duplicate " - "signed attribute", fn); + + if (!CBS_mem_equal(&messageDigest, digest, len)) return 0; - } - if (!cms_get_signtime(fn, attr, signtime)) + } else if (cbs_oid_value_matches_obj(&oid, sign_time_oid)) { + /* + * 2.1.6.4.3: Signing-Time Attribute + * time [...] at which the digital signature was applied + */ + if (signingTime_seen++ > 0) + return 0; + + if (!cbs_get_signingtime(&value, out_signingtime)) return 0; } else { - OBJ_obj2txt(buf, sizeof(buf), obj, 1); - warnx("%s: RFC 6488: " - "CMS has unexpected signed attribute %s", - fn, buf); return 0; } + + /* attrValues MUST consist of only a single AttributeValue. */ + if (CBS_len(&value) != 0) + return 0; } - if (!has_ct || !has_md) { - /* RFC 9589, section 4 */ - warnx("%s: RFC 6488: CMS missing required " - "signed attribute", fn); + return messageDigest_seen && signingTime_seen && contentType_seen; +} + +/* + * RFC 6488, 2.1: the SignerInfos set MUST contain only a single SignerInfo + * object. So we only parse one of these. + */ +static int +cms_get_rfc6488_signerInfo(const char *fn, struct cms_signedData *sd, + struct cms_signerInfo *out, time_t *out_signingtime) +{ + CBS sis, cbs; + + memset(out, 0, sizeof(*out)); + + CBS_dup(&sd->signerInfos, &sis); + + if (!CBS_get_asn1(&sis, &cbs, CBS_ASN1_SEQUENCE)) return 0; - } - if (!has_st) { - /* RFC 9589, section 4 */ - warnx("%s: missing CMS signing-time attribute", fn); + if (!CBS_get_asn1(&cbs, &out->version, CBS_ASN1_INTEGER)) return 0; - } - if (CMS_unsigned_get_attr_count(si) != -1) { - warnx("%s: RFC 6488: CMS has unsignedAttrs", fn); + /* + * RFC 6488, 2.1.6.1: version number MUST be 3. Ensure this right away + * because his simplifies parsing the sid next: per RFC 5652, 5.3 this + * implies that the CHOICE has the subjectKeyIdentifier variant, so we + * can ignore the issuerAndSerialNumber complication. + */ + if (!cms_check_rfc6488_signerInfo_version(&out->version)) return 0; - } - return 1; -} + /* + * RFC 6488, 2.1.6.2: sid. cms_validate_rfc6488_object() checks + * that it matches the EE cert's SKI. + */ + if (!CBS_get_asn1(&cbs, &out->sid, + CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_PRIMITIVE | 0x00)) + return 0; + out->sid_is_subjectKeyIdentifier = 1; -static int -cms_check_SignerInfo(const char *fn, CMS_ContentInfo *cms, - const ASN1_OBJECT *oid, struct cert *cert, time_t *signtime) -{ - char buf[128], obuf[128]; - const ASN1_OBJECT *obj, *octype; - ASN1_OCTET_STRING *kid = NULL; - long version; - STACK_OF(CMS_SignerInfo) *sinfos; - CMS_SignerInfo *si; - X509_ALGOR *pdig, *psig; - int nid; - - /* Should only return NULL if cms is not of type SignedData. */ - if ((sinfos = CMS_get0_SignerInfos(cms)) == NULL) { - if ((obj = CMS_get0_type(cms)) == NULL) { - warnx("%s: RFC 6488: missing content-type", fn); - return 0; - } - OBJ_obj2txt(buf, sizeof(buf), obj, 1); - warnx("%s: RFC 6488: no signerInfo in CMS object of type %s", - fn, buf); + /* + * RFC 6488, 2.1.6.3: digestAlgorithm. + */ + if (!CBS_get_asn1(&cbs, &out->digestAlgorithm, CBS_ASN1_SEQUENCE)) return 0; - } - if (sk_CMS_SignerInfo_num(sinfos) != 1) { - warnx("%s: RFC 6488: CMS has multiple signerInfos", fn); + /* RFC 7935, section 2: The hashing algorithm [...] is SHA-256. */ + if (!cbs_is_sha256_algorithmIdentifier_value(&out->digestAlgorithm)) return 0; - } - si = sk_CMS_SignerInfo_value(sinfos, 0); - if (!CMS_get_version(cms, &version)) { - warnx("%s: Failed to retrieve SignedData version", fn); + /* + * RFC 6488, 2.1.6.4: signedAttrs + */ + if (!CBS_get_optional_asn1(&cbs, &out->signedAttrs, + &out->signedAttrs_present, + CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0x00)) return 0; - } - if (version != 3) { - warnx("%s: SignedData version %ld != 3", fn, version); + if (!out->signedAttrs_present) return 0; - } - if (!CMS_SignerInfo_get_version(si, &version)) { - warnx("%s: Failed to retrieve SignerInfo version", fn); + if (!cms_check_rfc6488_signedAttrs(fn, &out->signedAttrs, sd, + out_signingtime)) return 0; - } - if (version != 3) { - warnx("%s: SignerInfo version %ld != 3", fn, version); + + /* + * RFC 6488, 2.1.6.5: signatureAlgorithm. + */ + if (!CBS_get_asn1(&cbs, &out->signatureAlgorithm, CBS_ASN1_SEQUENCE)) + return 0; + if (!cbs_is_rfc7935_signatureAlgorithm_value(&out->signatureAlgorithm)) return 0; - } - if (!cms_SignerInfo_check_attributes(fn, si, signtime)) + /* + * RFC 6488, 2.1.6.6: signature. + * Checked in cms_validate_rfc6488_object(). + */ + if (!CBS_get_asn1(&cbs, &out->signature, CBS_ASN1_OCTETSTRING)) return 0; - /* Check digest and signature algorithms (RFC 7935) */ - CMS_SignerInfo_get0_algs(si, NULL, NULL, &pdig, &psig); + /* + * RFC 6488, 2.1.6.7: unsignedAttrs MUST be omitted. + */ + if (!CBS_get_optional_asn1(&cbs, &out->unsignedAttrs, + &out->unsignedAttrs_present, + CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0x01)) + return 0; + if (out->unsignedAttrs_present) + return 0; - X509_ALGOR_get0(&obj, NULL, NULL, pdig); - nid = OBJ_obj2nid(obj); - if (nid != NID_sha256) { - warnx("%s: RFC 6488: wrong digest %s, want %s", fn, - nid2str(nid), LN_sha256); + /* No trailing garbage in signerInfo. */ + if (CBS_len(&cbs) != 0) return 0; - } - X509_ALGOR_get0(&obj, NULL, NULL, psig); - nid = OBJ_obj2nid(obj); - /* RFC7935 last paragraph of section 2 specifies the allowed psig */ - if (experimental && nid == NID_ecdsa_with_SHA256) { - if (verbose) - warnx("%s: P-256 support is experimental", fn); - } else if (nid != NID_rsaEncryption && - nid != NID_sha256WithRSAEncryption) { - warnx("%s: RFC 6488: wrong signature algorithm %s, want %s", - fn, nid2str(nid), LN_rsaEncryption); + + /* Only support a single signerInfo. */ + if (CBS_len(&sis) != 0) return 0; - } - /* RFC 6488 section 2.1.3.1: check the object's eContentType. */ + return 1; +} + +/* + * RFC 6488, 2.1.1 + * Check that syntax version number is 3. + */ +static int +cms_check_rfc6488_signedData_version(const char *fn, struct cms_signedData *sd) +{ + return cbs_integer_value_is_3(&sd->version); +} + +/* + * RFC 6488, 2.1.2 + * Exactly one AlgorithmIdentfier, must be SHA-256 per RFC 7935, section 2. + */ +static int +cms_check_rfc6488_digestAlgorithms(const char *fn, struct cms_signedData *sd) +{ + CBS digestAlgorithms, alg; + + CBS_dup(&sd->digestAlgorithms, &digestAlgorithms); - obj = CMS_get0_eContentType(cms); - if (obj == NULL) { - warnx("%s: RFC 6488 section 2.1.3.1: eContentType: " - "OID object is NULL", fn); + if (!CBS_get_asn1(&digestAlgorithms, &alg, CBS_ASN1_SEQUENCE)) return 0; - } - if (OBJ_cmp(obj, oid) != 0) { - OBJ_obj2txt(buf, sizeof(buf), obj, 1); - OBJ_obj2txt(obuf, sizeof(obuf), oid, 1); - warnx("%s: RFC 6488 section 2.1.3.1: eContentType: " - "unknown OID: %s, want %s", fn, buf, obuf); + if (!cbs_is_sha256_algorithmIdentifier_value(&alg)) + return 0; + + /* Only one algorithm is allowed. */ + if (CBS_len(&digestAlgorithms) != 0) + return 0; + + return 1; +} + +/* + * RFC 5652, section 5.2: + * + * EncapsulatedContentInfo ::= SEQUENCE { + * eContentType ContentType, + * eContent [0] EXPLICIT OCTET STRING OPTIONAL } + * + * ContentType ::= OBJECT IDENTIFIER + * + * Detached signatures are not supported, so this requires eContent to be + * present. For the callers' convenience, which never need tag and length, + * out_eContentValue contains the eContent's value octets. + */ +static int +cms_get_encapContentInfo(const char *fn, struct cms_signedData *sd, + CBS *out_eContentType, CBS *out_eContentValue) +{ + CBS cbs, eContent; + int eContent_present; + + CBS_dup(&sd->encapContentInfo, &cbs); + + if (!CBS_get_asn1(&cbs, out_eContentType, CBS_ASN1_OBJECT)) + return 0; + if (!CBS_get_optional_asn1(&cbs, &eContent, &eContent_present, + CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0x00)) + return 0; + /* No support for detached signatures for now. */ + if (!eContent_present) return 0; - } - /* Compare content-type with eContentType */ - octype = CMS_signed_get0_data_by_OBJ(si, cnt_type_oid, - -3, V_ASN1_OBJECT); /* - * Since lastpos == -3, octype can be NULL for 4 reasons: - * 1. requested attribute OID is missing - * 2. signedAttrs contains multiple attributes with requested OID - * 3. attribute with requested OID has multiple values (malformed) - * 4. X509_ATTRIBUTE_get0_data() returned NULL. This is also malformed, - * but libcrypto will create, sign, and verify such objects. - * Reasons 1 and 2 are excluded because has_ct == 1. We don't know which - * one of 3 or 4 we hit. Doesn't matter, drop the garbage on the floor. + * Strip tag and length octets off the eContent OCTET STRING because + * they are never used explicitly. Avoids repeated dances. */ - if (octype == NULL) { - warnx("%s: RFC 6488, section 2.1.6.4.1: malformed value " - "for content-type attribute", fn); + if (!CBS_get_asn1(&eContent, out_eContentValue, CBS_ASN1_OCTETSTRING)) return 0; - } - if (OBJ_cmp(obj, octype) != 0) { - OBJ_obj2txt(buf, sizeof(buf), obj, 1); - OBJ_obj2txt(obuf, sizeof(obuf), octype, 1); - warnx("%s: RFC 6488: eContentType does not match Content-Type " - "OID: %s, want %s", fn, buf, obuf); + if (CBS_len(&eContent) != 0) return 0; - } - if (CMS_SignerInfo_get0_signer_id(si, &kid, NULL, NULL) != 1 || - kid == NULL) { - warnx("%s: RFC 6488: could not extract SKI from SID", fn); + if (CBS_len(&cbs) != 0) return 0; - } - if (CMS_SignerInfo_cert_cmp(si, cert->x509) != 0) { - warnx("%s: RFC 6488: wrong cert referenced by SignerInfo", fn); + + return 1; +} + +static int +cms_get_rfc6488_ee_cert(const char *fn, struct cms_signedData *sd, int talid, + struct cert **out_cert) +{ + struct cert *cert; + + /* + * RFC 6488, 2.1.4: The certificates field MUST be included, and MUST + * contain exactly one certificate [...]. + * The last point is ensured by the trailing garbage check of + * cert_deserialize_and_parse(). + */ + if (!sd->certificates_present) return 0; - } + + if ((cert = cert_parse_ee_cert(fn, talid, CBS_data(&sd->certificates), + CBS_len(&sd->certificates))) == NULL) + return 0; + + *out_cert = cert; return 1; } +static int +cms_check_rfc6488_crls(const char *fn, struct cms_signedData *sd) +{ + /* RFC 6488, 2.1.5: the crls field MUST be omitted. */ + return !sd->crls_present; +} + static const struct signed_obj * cms_object_from_rtype(const char *fn, enum rtype rtype) { @@ -316,102 +781,114 @@ cms_object_from_rtype(const char *fn, en } static void * -cms_parse_validate(struct cert **out_cert, const char *fn, enum rtype rtype, - int talid, const unsigned char *der, size_t len) +cms_validate_rfc6488_object(struct cert **out_cert, const char *fn, + enum rtype rtype, int talid, size_t len, struct cms_signedData *sd) { - void *obj = NULL, *ret_obj = NULL; - const struct signed_obj *sobj; - struct cert *cert = NULL; - const unsigned char *oder; - CMS_ContentInfo *cms = NULL; - STACK_OF(X509) *certs = NULL; - STACK_OF(X509_CRL) *crls = NULL; - const uint8_t *econtent = NULL; - size_t econtent_len = 0; - time_t signtime = 0; + void *obj = NULL, *ret_obj = NULL; + const struct signed_obj *sobj; + CBB cbb, child; + uint8_t *sig = NULL; + size_t sig_len = 0; + CBS eContentType, eContentValue; + struct cert *cert = NULL; + uint8_t ski[SHA_DIGEST_LENGTH]; + struct cms_signerInfo signerInfo; + EVP_MD_CTX *md_ctx = NULL; + EVP_PKEY *pkey; + time_t signingtime = 0; assert(*out_cert == NULL); + memset(&cbb, 0, sizeof(cbb)); + sobj = cms_object_from_rtype(fn, rtype); - /* just fail for empty buffers, the warning was printed elsewhere */ - if (der == NULL) + if (!cms_check_rfc6488_signedData_version(fn, sd)) goto out; - - if (len < 2) { - warnx("%s: RFC 6488: CMS encoding too short", fn); + if (!cms_check_rfc6488_digestAlgorithms(fn, sd)) goto out; - } - if (der[0] == ASN1_TAG_SEQUENCE && der[1] == ASN1_LENGTH_INDEFINITE) { - warnx("%s: RFC 6488: indefinite length encoding disallowed " - "in DER", fn); - if (!filemode) - goto out; - } - - oder = der; - if ((cms = d2i_CMS_ContentInfo(NULL, &der, len)) == NULL) { - warnx("%s: RFC 6488: failed CMS parse", fn); + if (!cms_get_encapContentInfo(fn, sd, &eContentType, &eContentValue)) goto out; - } - if (der != oder + len) { - warnx("%s: %td bytes trailing garbage", fn, oder + len - der); + /* XXX - should be a _check_ function and directly validate cert. */ + if (!cms_get_rfc6488_ee_cert(fn, sd, talid, &cert)) + goto out; + if (!cms_check_rfc6488_crls(fn, sd)) + goto out; + if (!cms_get_rfc6488_signerInfo(fn, sd, &signerInfo, &signingtime)) goto out; - } + + if (signingtime > cert->notafter) + warnx("%s: CMS signing-time after X.509 notAfter", fn); /* - * The CMS is self-signed with a signing certificate. - * Verify that the self-signage is correct and set up internal - * structs so that the following CMS API calls work correctly. + * The eContentType must match the OID of the signed object we expect. */ - if (!CMS_verify(cms, NULL, NULL, NULL, NULL, - CMS_NO_SIGNER_CERT_VERIFY)) { - warnx("%s: CMS verification error", fn); + if (!cbs_oid_value_matches_obj(&eContentType, sobj->oid())) goto out; - } /* - * Check that there are no CRLs in this CMS message. - * XXX - can only error check for OpenSSL >= 3.4. + * RFC 6488, section 2.1.6.2: For RPKI signed objects the sid MUST be + * the SubjectKeyIdentifier that appears in the EE certificate [...] + * + * This is a bit ugly but seems simplest: cert_ski() fully validates + * the SKI against the SPKI and stores its hex encoding in cert->ski. */ - crls = CMS_get1_crls(cms); - if (crls != NULL && sk_X509_CRL_num(crls) != 0) { - warnx("%s: RFC 6488: CMS has CRLs", fn); + if (hex_decode(cert->ski, ski, sizeof(ski)) == -1) + goto out; + if (!CBS_mem_equal(&signerInfo.sid, ski, sizeof(ski))) goto out; - } /* - * The self-signing certificate is further signed by the input - * signing authority according to RFC 6488, 2.1.4. - * We extract that certificate now for later verification. + * RFC 5652, section 5.4: A separate encoding of the signedAttrs + * field is performed for message digest calculation. [...] an + * EXPLICIT SET OF tag is used. */ - - certs = CMS_get0_signers(cms); - if (certs == NULL || sk_X509_num(certs) != 1) { - warnx("%s: RFC 6488 section 2.1.4: eContent: " - "want 1 signer, have %d", fn, sk_X509_num(certs)); + if (!CBB_init(&cbb, 0)) goto out; - } - - cert = cert_parse_ee_cert(fn, talid, sk_X509_value(certs, 0)); - if (cert == NULL) + if (!CBB_add_asn1(&cbb, &child, CBS_ASN1_SET)) goto out; - - /* RFC 6488 section 3 verify the CMS */ - if (!cms_check_SignerInfo(fn, cms, sobj->oid(), cert, &signtime)) + if (!CBB_add_bytes(&child, CBS_data(&signerInfo.signedAttrs), + CBS_len(&signerInfo.signedAttrs))) + goto out; + if (!CBB_finish(&cbb, &sig, &sig_len)) goto out; - if (signtime > cert->notafter) - warnx("%s: dating issue: CMS signing-time after X.509 notAfter", - fn); - - if (!cms_extract_econtent(fn, cms, &econtent, &econtent_len)) + /* + * RFC 5652, section 5.6: Signature Verification Process + * + * Verify sig with the EE cert's pubkey using digestAlgorithm and + * signature in signerInfo. This covers section 5.6 because we matched + * the eContentType and the digested eContentValue in the signedAttrs + * as part of cms_check_rfc6488_signedAttrs(). + * + * Since RFC 7935 only allows SHA-256, the additional requirements + * for algorithm protection in RFC 8933, section 3, are covered. + * + * XXX - The recommended use of the RFC 6211 CMSAlgorithmProtection + * attribute (or its draft update) in RFC 8933, section 4, conflicts + * with the requirements on signedAttrs of RFC 6488 (and RFC 9589). + * This is arguably also covered by RFC 7935, but this may be worth + * a reconsideration when more algorithms will be added to the RPKI + * CMS profile. + */ + if ((md_ctx = EVP_MD_CTX_new()) == NULL) + goto out; + if ((pkey = X509_get0_pubkey(cert->x509)) == NULL) + goto out; + if (EVP_DigestVerifyInit(md_ctx, NULL, EVP_sha256(), NULL, pkey) <= 0) + goto out; + if (EVP_DigestVerify(md_ctx, CBS_data(&signerInfo.signature), + CBS_len(&signerInfo.signature), sig, sig_len) <= 0) goto out; - obj = sobj->new(len, signtime); + /* + * Finally parse and validate the eContent. + */ + obj = sobj->new(len, signingtime); if (!sobj->cert_info(fn, obj, cert)) goto out; - if (!sobj->parse_econtent(fn, obj, econtent, econtent_len)) + if (!sobj->parse_econtent(fn, obj, + CBS_data(&eContentValue), CBS_len(&eContentValue))) goto out; if (!sobj->validate(fn, obj, cert)) goto out; @@ -423,17 +900,142 @@ cms_parse_validate(struct cert **out_cer obj = NULL; out: + CBB_cleanup(&cbb); sobj->free(obj); cert_free(cert); - sk_X509_CRL_pop_free(crls, X509_CRL_free); - sk_X509_free(certs); - CMS_ContentInfo_free(cms); + free(sig); + EVP_MD_CTX_free(md_ctx); + return ret_obj; } +static void * +cms_parse_validate(struct cert **out_cert, const char *fn, enum rtype rtype, + int talid, const unsigned char *der, size_t len) +{ + CBS cbs, contentType, content; + struct cms_signedData signedData; + uint8_t *buf = NULL; + size_t buf_len; + void *ret = NULL; + + /* just fail for empty buffers, the warning was printed elsewhere */ + if (der == NULL) + goto out; + + CBS_init(&cbs, der, len); + + /* + * ARIN special. In filemode re-encode the "der" buffer into definite + * length encoding. The DER parser can then deserialize the signed + * object. This miraculously works because of the way CMS signatures + * are computed: signedAttrs MUST be DER encoded (per RFC 5652, 5.3) + * and the value octets of the eContent do not use the invalid encoding + * (but its length octets do!). The signature and the message-digest + * attribute therefore check out despite this modification. + */ + if (filemode) { + if (!CBS_asn1_indefinite_to_definite(&cbs, &buf, &buf_len)) + goto out; + if (buf != NULL) { + warnx("%s: RFC 6488: indefinite length encoding " + "disallowed in DER", fn); + CBS_init(&cbs, buf, buf_len); + } + } + + if (!cms_get_contentInfo(&cbs, &contentType, &content)) { + warnx("%s: RFC 6488: failed to parse outermost DER", fn); + goto out; + } + if (CBS_len(&cbs) != 0) { + warnx("%s: RFC 6488: trailing garbage", fn); + goto out; + } + + /* We only support RFC 6488, which yields a few simplifications. */ + if (!cbs_oid_value_matches_nid(&contentType, NID_pkcs7_signed)) { + warnx("%s: RFC 6488, 3.1.a: CMS content not signedData", fn); + goto out; + } + if (!cms_get_signedData(&content, &signedData)) { + warnx("%s: RFC 6488, failed to parse signedData DER", fn); + goto out; + } + + /* + * In filemode we pass the original len, not buf_len. This is only + * used for mft->mftsize, for which we want the actual file size. + */ + ret = cms_validate_rfc6488_object(out_cert, fn, rtype, talid, len, + &signedData); + + out: + free(buf); + + return ret; +} + void * signed_object_parse(struct cert **out_cert, const char *fn, enum rtype rtype, int talid, const unsigned char *der, size_t len) { return cms_parse_validate(out_cert, fn, rtype, talid, der, len); +} + +/* + * filemode CMS rtype prober. If this is the DER of a CMS object, try + * to determine its rtype from the contentType or the eContentType. + * Returns rtype if heuristics succeed and RTYPE_INVALID on error. + */ +enum rtype +cms_rtype_from_der(const char *fn, const unsigned char *der, size_t len) +{ + CBS cbs, contentType, content, eContentType; + struct cms_signedData signedData; + uint8_t *buf = NULL; + size_t buf_len; + enum rtype rtype = RTYPE_INVALID; + + CBS_init(&cbs, der, len); + + /* ARIN special. See cms_parse_validate(). */ + if (!CBS_asn1_indefinite_to_definite(&cbs, &buf, &buf_len)) + goto out; + if (buf != NULL) + CBS_init(&cbs, buf, buf_len); + + if (!cms_get_contentInfo(&cbs, &contentType, &content)) + goto out; + + if (cbs_oid_value_matches_obj(&contentType, ccr_oid)) { + rtype = RTYPE_CCR; + goto out; + } + + if (!cbs_oid_value_matches_nid(&contentType, NID_pkcs7_signed)) + goto out; + if (!cms_get_signedData(&content, &signedData)) + goto out; + if (!cms_get_encapContentInfo(fn, &signedData, &eContentType, + NULL)) + goto out; + + if (cbs_oid_value_matches_obj(&eContentType, aspa_oid)) + rtype = RTYPE_ASPA; + else if (cbs_oid_value_matches_obj(&eContentType, mft_oid)) + rtype = RTYPE_MFT; + else if (cbs_oid_value_matches_obj(&eContentType, roa_oid)) + rtype = RTYPE_ROA; + else if (cbs_oid_value_matches_obj(&eContentType, rsc_oid)) + rtype = RTYPE_RSC; + else if (cbs_oid_value_matches_obj(&eContentType, spl_oid)) + rtype = RTYPE_SPL; + else if (cbs_oid_value_matches_obj(&eContentType, tak_oid)) + rtype = RTYPE_TAK; + + out: + free(buf); + + return rtype; } Index: usr.sbin/rpki-client/extern.h =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/extern.h,v diff -u -p -r1.300 extern.h --- usr.sbin/rpki-client/extern.h 29 Sep 2026 21:59:14 -0000 1.300 +++ usr.sbin/rpki-client/extern.h 4 Oct 2026 11:27:01 -0000 @@ -763,7 +763,8 @@ void cert_free(struct cert *); void auth_tree_free(struct auth_tree *); struct cert *cert_parse_ca_or_brk(const char *, const unsigned char *, size_t); -struct cert *cert_parse_ee_cert(const char *, int, X509 *); +struct cert *cert_parse_ee_cert(const char *, int, const unsigned char *, + size_t); struct cert *cert_parse_ta(const char *, const unsigned char *, size_t, const unsigned char *, size_t); struct cert *cert_parse_filemode(const char *, const unsigned char *, @@ -843,6 +844,8 @@ int valid_spl(const char *, struct cer /* Working with CMS. */ void *signed_object_parse(struct cert **, const char *, enum rtype, int, const unsigned char *, size_t); +enum rtype cms_rtype_from_der(const char *, const unsigned char *, + size_t); /* Work with RFC 3779 IP addresses, prefixes, ranges. */ Index: usr.sbin/rpki-client/filemode.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/filemode.c,v diff -u -p -r1.91 filemode.c --- usr.sbin/rpki-client/filemode.c 24 Sep 2026 14:44:03 -0000 1.91 +++ usr.sbin/rpki-client/filemode.c 4 Oct 2026 11:27:01 -0000 @@ -32,7 +32,6 @@ #include #include -#include #include #include #include @@ -339,50 +338,14 @@ print_signature_path(const char *crl, co static enum rtype rtype_from_der(const char *fn, const unsigned char *der, size_t len) { - CMS_ContentInfo *cms = NULL; X509 *x509 = NULL; X509_CRL *crl = NULL; const unsigned char *p; enum rtype rtype = RTYPE_INVALID; /* Does der parse as a CMS ContentInfo? Is it a known signed object? */ - p = der; - if ((cms = d2i_CMS_ContentInfo(NULL, &p, len)) != NULL) { - const ASN1_OBJECT *obj; - - if ((obj = CMS_get0_type(cms)) != NULL) { - if (OBJ_cmp(obj, ccr_oid) == 0) { - rtype = RTYPE_CCR; - goto out; - } - } - - if (CMS_get0_SignerInfos(cms) == NULL) { - warnx("%s: CMS object not signedData", fn); - goto out; - } - - if ((obj = CMS_get0_eContentType(cms)) == NULL) { - warnx("%s: RFC 6488, section 2.1.3.1: eContentType: " - "OID object is NULL", fn); - goto out; - } - - if (OBJ_cmp(obj, aspa_oid) == 0) - rtype = RTYPE_ASPA; - else if (OBJ_cmp(obj, mft_oid) == 0) - rtype = RTYPE_MFT; - else if (OBJ_cmp(obj, roa_oid) == 0) - rtype = RTYPE_ROA; - else if (OBJ_cmp(obj, rsc_oid) == 0) - rtype = RTYPE_RSC; - else if (OBJ_cmp(obj, spl_oid) == 0) - rtype = RTYPE_SPL; - else if (OBJ_cmp(obj, tak_oid) == 0) - rtype = RTYPE_TAK; - + if ((rtype = cms_rtype_from_der(fn, der, len)) != RTYPE_INVALID) goto out; - } /* Does der parse as a certificate? */ p = der; @@ -405,7 +368,6 @@ rtype_from_der(const char *fn, const uns */ out: - CMS_ContentInfo_free(cms); X509_free(x509); X509_CRL_free(crl); Index: regress/usr.sbin/rpki-client/openssl/unistd.h =================================================================== RCS file: /cvs/src/regress/usr.sbin/rpki-client/openssl/unistd.h,v diff -u -p -r1.7 unistd.h --- regress/usr.sbin/rpki-client/openssl/unistd.h 3 Oct 2026 19:15:49 -0000 1.7 +++ regress/usr.sbin/rpki-client/openssl/unistd.h 4 Oct 2026 11:27:01 -0000 @@ -10,27 +10,12 @@ #include_next -#include #include #include #ifndef DECLARE_STACK_OF #define DECLARE_STACK_OF DEFINE_STACK_OF #endif - -static inline int -CMS_get_version(CMS_ContentInfo *cms, long *version) -{ - *version = 3; - return 1; -} - -static inline int -CMS_SignerInfo_get_version(CMS_SignerInfo *si, long *version) -{ - *version = 3; - return 1; -} #if !HAVE_X509_CRL_GET0_TBS_SIGALG static inline const X509_ALGOR *