From eef1a33687b70aca799eee912c2b98fde6a2d504 Mon Sep 17 00:00:00 2001 From: Kareem Date: Mon, 29 Jun 2026 16:25:17 -0700 Subject: [PATCH 1/3] Add support for id-on-bundleEID from RFC 9174. Fixes #10694. --- .wolfssl_known_macro_extras | 1 + certs/bundle-eid-cert.der | Bin 0 -> 690 bytes certs/renewcerts.sh | 19 +++++++++++ certs/renewcerts/wolfssl.cnf | 13 ++++++++ scripts/asn1_oid_sum.pl | 6 ++++ tests/api/test_asn.c | 60 +++++++++++++++++++++++++++++++++++ tests/api/test_asn.h | 2 ++ wolfcrypt/src/asn.c | 21 ++++++++++++ wolfssl/wolfcrypt/oid_sum.h | 10 ++++++ 9 files changed, 132 insertions(+) create mode 100644 certs/bundle-eid-cert.der diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 31b7d4be79b..2c0774daac0 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -763,6 +763,7 @@ WOLFSSL_DTLS_DISALLOW_FUTURE WOLFSSL_DTLS_NO_HVR_ON_RESUME WOLFSSL_DTLS_RECORDS_CAN_SPAN_DATAGRAMS WOLFSSL_DTLS_RESEND_ONLY_TIMEOUT +WOLFSSL_DTN WOLFSSL_DUMP_MEMIO_STREAM WOLFSSL_DUP_CERTPOL WOLFSSL_EARLY_DATA_NO_ANTI_REPLAY diff --git a/certs/bundle-eid-cert.der b/certs/bundle-eid-cert.der new file mode 100644 index 0000000000000000000000000000000000000000..ebc7cf98f135903b74f0bdcb3a582c64eae26112 GIT binary patch literal 690 zcmXqLVp?a=#1yiCnTe5!iBZ6Si;Y98&EuRc3p0~J<8(uA15P&PP!={}rqEzRVFN)B zhl7VJJh3<JD>0NX5Ce%a^YE9Km+OJl78mE}CFkcF${WbCaRQyu_MMlJlTl0{GcPUQ z0ZD;@oH(zMnSq&+nW3eTfvHgxkZWmRY-9xG8Z`Exx*Y0A4jy*j{JfIHyhNBIfzEcy zuS(5LgnIO~7 zm~`G)+&I&qahd@S(4DfvjEw(Tm<$+zfd}OAv52vV#Kx+ bundle-eid-req.pem + check_result $? "Step 1" + + openssl x509 -req -in bundle-eid-req.pem -extfile wolfssl.cnf -extensions bundle_eid_ext -days 1000 -CA ca-ecc-cert.pem -CAkey ca-ecc-key.pem -set_serial 16 -out bundle-eid-cert.der -outform DER + check_result $? "Step 2" + rm bundle-eid-req.pem + echo "End of section" + echo "---------------------------------------------------------------------" + ########################################################### ########## update and sign server-cert.pem ################ ########################################################### echo "Updating server-cert.pem" diff --git a/certs/renewcerts/wolfssl.cnf b/certs/renewcerts/wolfssl.cnf index 40de5d92e37..85bb3c64455 100644 --- a/certs/renewcerts/wolfssl.cnf +++ b/certs/renewcerts/wolfssl.cnf @@ -452,3 +452,16 @@ DNS.1 = www.example.org URI.1 = https://www.wolfssl.com/ otherName.2 = 2.16.840.1.101.3.6.6;FORMAT:HEX,OCT:D1:38:10:D8:28:AF:2C:10:84:35:15:A1:68:58:28:AF:02:10:86:A2:84:E7:39:C3:EB +# RFC 9174 (Bundle Protocol / DTN) id-on-bundleEID OtherName with an +# IA5String value, followed by a dNSName. Used by the WOLFSSL_DTN test. +[bundle_eid_ext] +basicConstraints = critical, CA:FALSE +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid +keyUsage = critical, digitalSignature +subjectAltName = @bundle_eid_altname + +[bundle_eid_altname] +otherName.1 = 1.3.6.1.5.5.7.8.11;IA5STRING:dtn://node000/ +DNS.1 = node000.local + diff --git a/scripts/asn1_oid_sum.pl b/scripts/asn1_oid_sum.pl index 8df6545f022..52414c45ce1 100755 --- a/scripts/asn1_oid_sum.pl +++ b/scripts/asn1_oid_sum.pl @@ -912,6 +912,12 @@ sub print_footer { { name => "HW_NAME", oid => \@sep_hw_name }, ); +# NOTE: SepHardwareName_Sum also carries BUNDLE_EID_OID (1.3.6.1.5.5.7.8.11, +# id-on-bundleEID, RFC 9174). It is maintained directly in oid_sum.h behind +# "#ifdef WOLFSSL_DTN" rather than emitted here, since this generator has no +# per-entry feature-guard support (same approach as ACME_IDENTIFIER_OID). +# old sum = 86, new sum = 0x0e09012e. + print_sum_enum("SepHardwareName", "_OID", \@seps); diff --git a/tests/api/test_asn.c b/tests/api/test_asn.c index 7e74c03cca0..69ca90dd958 100644 --- a/tests/api/test_asn.c +++ b/tests/api/test_asn.c @@ -1535,6 +1535,66 @@ int test_DecodeAltNames_length_underflow(void) return EXPECT_RESULT(); } +/* Regression test for id-on-bundleEID (RFC 9174) OtherName decoding. + * + * A SAN OtherName with OID 1.3.6.1.5.5.7.8.11 whose value is an IA5String must + * be decoded - not rejected with ASN_PARSE_E - when WOLFSSL_DTN is enabled. + * Reproduces the GitHub report "X509 decoder fails to handle OtherName with + * value of an IA5String". + * + * The certificate is generated by certs/renewcerts.sh (bundle-eid-cert.der): + * an ECDSA P-256 leaf whose SAN holds an id-on-bundleEID OtherName carrying + * the IA5String "dtn://node000/", followed by a dNSName "node000.local". */ +int test_DecodeOtherName_bundleEID(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTN) && \ + (defined(WOLFSSL_SEP) || defined(WOLFSSL_FPKI)) && \ + defined(HAVE_ECC) && !defined(NO_CERTS) && !defined(NO_ASN) && \ + !defined(NO_FILESYSTEM) + const char* bundleEidCert = "./certs/bundle-eid-cert.der"; + XFILE f = XBADFILE; + byte buf[2048]; + int bytes = 0; + DecodedCert cert; + DNS_entry* dns = NULL; + int foundBundleEid = 0; + int foundDns = 0; + + ExpectTrue((f = XFOPEN(bundleEidCert, "rb")) != XBADFILE); + ExpectIntGT(bytes = (int)XFREAD(buf, 1, sizeof(buf), f), 0); + if (f != XBADFILE) + XFCLOSE(f); + + wc_InitDecodedCert(&cert, buf, (word32)bytes, NULL); + /* Before the fix this failed with ASN_PARSE_E because the OtherName value + * (an IA5String) did not match any of the supported value types. */ + ExpectIntEQ(wc_ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL), 0); + + for (dns = cert.altNames; dns != NULL; dns = dns->next) { + if ((dns->type == ASN_OTHER_TYPE) && (dns->len == 14) && + (XMEMCMP(dns->name, "dtn://node000/", 14) == 0)) { + foundBundleEid = 1; + #ifdef WOLFSSL_FPKI + ExpectIntEQ(dns->oidSum, BUNDLE_EID_OID); + #endif + } + if ((dns->type == ASN_DNS_TYPE) && (dns->len == 13) && + (XMEMCMP(dns->name, "node000.local", 13) == 0)) { + foundDns = 1; + } + } + /* The bundleEID IA5String value must be decoded into an OtherName entry, + * and the trailing dNSName must still be parsed. */ + ExpectIntEQ(foundBundleEid, 1); + ExpectIntEQ(foundDns, 1); + + wc_FreeDecodedCert(&cert); +#endif /* WOLFSSL_DTN && (WOLFSSL_SEP || WOLFSSL_FPKI) && HAVE_ECC && + * !NO_CERTS && !NO_ASN && !NO_FILESYSTEM */ + return EXPECT_RESULT(); +} + /* A certificate must not carry two certificatePolicies extensions * (non-repeatable per RFC 5280 4.2). DecodeCertExtensions calls * DecodeExtensionType once per extension; with strict ASN.1 (the default) a diff --git a/tests/api/test_asn.h b/tests/api/test_asn.h index d48aac7a6f5..0885404b097 100644 --- a/tests/api/test_asn.h +++ b/tests/api/test_asn.h @@ -35,6 +35,7 @@ int test_wolfssl_local_MatchUriNameConstraint(void); int test_wc_DecodeRsaPssParams(void); int test_SerialNumber0_RootCA(void); int test_DecodeAltNames_length_underflow(void); +int test_DecodeOtherName_bundleEID(void); int test_DecodeCertExtensions_dup_certpol(void); int test_ParseCert_SM3wSM2_short_pubkey(void); int test_wc_DecodeObjectId(void); @@ -55,6 +56,7 @@ int test_ToTraditional_ex_mldsa_bad_params(void); TEST_DECL_GROUP("asn", test_wc_DecodeRsaPssParams), \ TEST_DECL_GROUP("asn", test_SerialNumber0_RootCA), \ TEST_DECL_GROUP("asn", test_DecodeAltNames_length_underflow), \ + TEST_DECL_GROUP("asn", test_DecodeOtherName_bundleEID), \ TEST_DECL_GROUP("asn", test_DecodeCertExtensions_dup_certpol), \ TEST_DECL_GROUP("asn", test_ParseCert_SM3wSM2_short_pubkey), \ TEST_DECL_GROUP("asn", test_wc_DecodeObjectId), \ diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 5d961fc9aaf..f2d5ac6f5af 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -18939,12 +18939,20 @@ static int ConfirmNameConstraints(Signer* signer, DecodedCert* cert) * (RFC3280 sec 4.2.1.7). Often used with FIPS 201 smartcard login. * FASC-N (Federal Agency Smart Credential Number), defined in the document * fpki-x509-cert-policy-common.pdf. Used for a smart card ID. + * + * id-on-bundleEID (RFC 9174, sec 4.4.1), an Other Name whose value is an + * IA5String holding a Bundle Protocol node/endpoint ID (e.g. "dtn://node/"). + * Only handled when WOLFSSL_DTN is defined as these OIDs are specific to + * Delay-Tolerant Networking (DTN) / the Bundle Protocol. */ static const ASNItem otherNameASN[] = { /* TYPEID */ { 0, ASN_OBJECT_ID, 0, 0, 0 }, /* VALUE */ { 0, ASN_CONTEXT_SPECIFIC | ASN_OTHERNAME_VALUE, 1, 1, 0 }, /* UPN */ { 1, ASN_UTF8STRING, 0, 0, 2 }, /* FASC-N */ { 1, ASN_OCTET_STRING, 0, 0, 2 }, +#ifdef WOLFSSL_DTN +/* BEID */ { 1, ASN_IA5_STRING, 0, 0, 2 }, +#endif /* HWN_SEQ */ { 1, ASN_SEQUENCE, 1, 0, 2 }, /* HWN_TYPE */ { 2, ASN_OBJECT_ID, 0, 0, 0 }, /* HWN_NUM */ { 2, ASN_OCTET_STRING, 0, 0, 0 } @@ -18954,6 +18962,9 @@ enum { OTHERNAMEASN_IDX_VALUE, OTHERNAMEASN_IDX_UPN, OTHERNAMEASN_IDX_FASCN, +#ifdef WOLFSSL_DTN + OTHERNAMEASN_IDX_BEID, +#endif OTHERNAMEASN_IDX_HWN_SEQ, OTHERNAMEASN_IDX_HWN_TYPE, OTHERNAMEASN_IDX_HWN_NUM @@ -19021,6 +19032,13 @@ static int DecodeOtherHelper(ASNGetData* dataASN, DecodedCert* cert, int oid) bufLen = dataASN[OTHERNAMEASN_IDX_UPN].data.ref.length; buf = (const char*)dataASN[OTHERNAMEASN_IDX_UPN].data.ref.data; break; +#ifdef WOLFSSL_DTN + case BUNDLE_EID_OID: + /* id-on-bundleEID (RFC 9174) carries an IA5String value. */ + bufLen = dataASN[OTHERNAMEASN_IDX_BEID].data.ref.length; + buf = (const char*)dataASN[OTHERNAMEASN_IDX_BEID].data.ref.data; + break; +#endif /* WOLFSSL_DTN */ default: WOLFSSL_ERROR_VERBOSE(ASN_UNKNOWN_OID_E); ret = ASN_UNKNOWN_OID_E; @@ -19084,6 +19102,9 @@ static int DecodeOtherName(DecodedCert* cert, const byte* input, #ifdef WOLFSSL_FPKI case FASCN_OID: #endif /* WOLFSSL_FPKI */ + #ifdef WOLFSSL_DTN + case BUNDLE_EID_OID: + #endif /* WOLFSSL_DTN */ case UPN_OID: ret = DecodeOtherHelper(dataASN, cert, (int)dataASN[OTHERNAMEASN_IDX_TYPEID].data.oid.sum); diff --git a/wolfssl/wolfcrypt/oid_sum.h b/wolfssl/wolfcrypt/oid_sum.h index d56405c8247..6f71310f19e 100644 --- a/wolfssl/wolfcrypt/oid_sum.h +++ b/wolfssl/wolfcrypt/oid_sum.h @@ -1250,9 +1250,19 @@ enum SepHardwareName_Sum { #ifdef WOLFSSL_OLD_OID_SUM /* 0x2b,0x06,0x01,0x05,0x05,0x07,0x08,0x04 */ HW_NAME_OID = 79 /* 1.3.6.1.5.5.7.8.4 */ +#ifdef WOLFSSL_DTN + , + /* 0x2b,0x06,0x01,0x05,0x05,0x07,0x08,0x0b */ + BUNDLE_EID_OID = 86 /* 1.3.6.1.5.5.7.8.11 */ +#endif #else /* 0x2b,0x06,0x01,0x05,0x05,0x07,0x08,0x04 */ HW_NAME_OID = 0x0109012e /* 1.3.6.1.5.5.7.8.4 */ +#ifdef WOLFSSL_DTN + , + /* 0x2b,0x06,0x01,0x05,0x05,0x07,0x08,0x0b */ + BUNDLE_EID_OID = 0x0e09012e /* 1.3.6.1.5.5.7.8.11 */ +#endif #endif }; From 34eb1021206ad711c1d47ac8dc776c2fabbedb85 Mon Sep 17 00:00:00 2001 From: Kareem Date: Tue, 30 Jun 2026 14:31:47 -0700 Subject: [PATCH 2/3] Code review feedback: Fix XBADFILE issue in unit test. --- tests/api/test_asn.c | 55 +++++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/tests/api/test_asn.c b/tests/api/test_asn.c index 69ca90dd958..cfa5aa5f68c 100644 --- a/tests/api/test_asn.c +++ b/tests/api/test_asn.c @@ -1561,35 +1561,42 @@ int test_DecodeOtherName_bundleEID(void) int foundBundleEid = 0; int foundDns = 0; + /* Only read once the file actually opened: ExpectTrue records a failure + * but does not stop execution, so reading from a XBADFILE handle here + * would otherwise crash the test runner. */ ExpectTrue((f = XFOPEN(bundleEidCert, "rb")) != XBADFILE); - ExpectIntGT(bytes = (int)XFREAD(buf, 1, sizeof(buf), f), 0); - if (f != XBADFILE) + if (f != XBADFILE) { + ExpectIntGT(bytes = (int)XFREAD(buf, 1, sizeof(buf), f), 0); XFCLOSE(f); + } - wc_InitDecodedCert(&cert, buf, (word32)bytes, NULL); - /* Before the fix this failed with ASN_PARSE_E because the OtherName value - * (an IA5String) did not match any of the supported value types. */ - ExpectIntEQ(wc_ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL), 0); - - for (dns = cert.altNames; dns != NULL; dns = dns->next) { - if ((dns->type == ASN_OTHER_TYPE) && (dns->len == 14) && - (XMEMCMP(dns->name, "dtn://node000/", 14) == 0)) { - foundBundleEid = 1; - #ifdef WOLFSSL_FPKI - ExpectIntEQ(dns->oidSum, BUNDLE_EID_OID); - #endif - } - if ((dns->type == ASN_DNS_TYPE) && (dns->len == 13) && - (XMEMCMP(dns->name, "node000.local", 13) == 0)) { - foundDns = 1; + /* Likewise, only parse when something was actually read. */ + if (bytes > 0) { + wc_InitDecodedCert(&cert, buf, (word32)bytes, NULL); + /* Before the fix this failed with ASN_PARSE_E because the OtherName + * value (an IA5String) did not match any supported value type. */ + ExpectIntEQ(wc_ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL), 0); + + for (dns = cert.altNames; dns != NULL; dns = dns->next) { + if ((dns->type == ASN_OTHER_TYPE) && (dns->len == 14) && + (XMEMCMP(dns->name, "dtn://node000/", 14) == 0)) { + foundBundleEid = 1; + #ifdef WOLFSSL_FPKI + ExpectIntEQ(dns->oidSum, BUNDLE_EID_OID); + #endif + } + if ((dns->type == ASN_DNS_TYPE) && (dns->len == 13) && + (XMEMCMP(dns->name, "node000.local", 13) == 0)) { + foundDns = 1; + } } - } - /* The bundleEID IA5String value must be decoded into an OtherName entry, - * and the trailing dNSName must still be parsed. */ - ExpectIntEQ(foundBundleEid, 1); - ExpectIntEQ(foundDns, 1); + /* The bundleEID IA5String value must be decoded into an OtherName + * entry, and the trailing dNSName must still be parsed. */ + ExpectIntEQ(foundBundleEid, 1); + ExpectIntEQ(foundDns, 1); - wc_FreeDecodedCert(&cert); + wc_FreeDecodedCert(&cert); + } #endif /* WOLFSSL_DTN && (WOLFSSL_SEP || WOLFSSL_FPKI) && HAVE_ECC && * !NO_CERTS && !NO_ASN && !NO_FILESYSTEM */ return EXPECT_RESULT(); From 7a433de2e148a088f39bc6a51ab18ab5c5a36b38 Mon Sep 17 00:00:00 2001 From: Kareem Date: Tue, 7 Jul 2026 17:01:42 -0700 Subject: [PATCH 3/3] Code review feedback --- wolfcrypt/src/asn.c | 4 +++- wolfssl/wolfcrypt/settings.h | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index f2d5ac6f5af..8d5903998b7 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -107,6 +107,8 @@ ASN Options: * WOLFSSL_ALLOW_AKID_SKID_MATCH: By default cert issuer is found using hash * of cert subject hash with signers subject hash. This option allows fallback * to using AKID and SKID matching. + * WOLFSSL_DTN: Enables Delay-Tolerant Networking (RFC 9174) support. + * Currently this enables id-on-bundleEID in otherName. * * Certificate Generation/Parsing: * WOLFSSL_CERT_REQ: Enable certificate request (CSR) support @@ -19310,7 +19312,7 @@ static int DecodeGeneralName(const byte* input, word32* inOutIdx, byte tag, if (ret != 0) { return ret; } - #if defined(WOLFSSL_SEP) || defined(WOLFSSL_FPKI) + #if defined(WOLFSSL_SEP) || defined(WOLFSSL_FPKI) || defined(WOLFSSL_DTN) /* FPKI/SEP also OID-decode the otherName into a separate altNames * entry that holds the parsed UPN/FASCN value (with oidSum != 0). * That parsed entry is consumed by wc_GetUUIDFromCert / diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index 853ae55f1c3..1766d29a31e 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -4069,6 +4069,10 @@ #if defined(WOLFSSL_ASN_TEMPLATE) && !defined(WOLFSSL_ACERT) #define WOLFSSL_ACERT #endif + + /* Delay-Tolerant Networking (RFC 9174) support */ + #undef WOLFSSL_DTN + #define WOLFSSL_DTN #endif #if defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA) || \