@@ -24480,6 +24480,172 @@ static int test_NameConstraints_OtherName(void)
2448024480 return EXPECT_RESULT();
2448124481}
2448224482
24483+ #if defined(WOLFSSL_ASN_TEMPLATE) && \
24484+ defined(WOLFSSL_CERT_REQ) && !defined(NO_ASN_TIME) && \
24485+ defined(WOLFSSL_CERT_GEN) && defined(HAVE_ECC) && \
24486+ defined(WOLFSSL_CERT_EXT) && !defined(NO_CERTS) && \
24487+ defined(WOLFSSL_ALT_NAMES) && defined(WOLFSSL_CUSTOM_OID) && \
24488+ defined(HAVE_OID_ENCODING) && !defined(IGNORE_NAME_CONSTRAINTS)
24489+ /* Build a SubjectAltName extension value with a single GeneralName of the
24490+ * given context tag (0x82 dnsName, 0x86 URI) carrying `val`. */
24491+ static word32 build_simple_san(byte* out, word32 outSz, byte gnTag,
24492+ const char* val)
24493+ {
24494+ word32 vlen = (word32)XSTRLEN(val);
24495+ if (vlen > 0x7F || outSz < vlen + 4)
24496+ return 0;
24497+ out[0] = 0x30; /* SEQUENCE */
24498+ out[1] = (byte)(vlen + 2);
24499+ out[2] = gnTag; /* [tag] GeneralName */
24500+ out[3] = (byte)vlen;
24501+ XMEMCPY(out + 4, val, vlen);
24502+ return vlen + 4;
24503+ }
24504+
24505+ /* Build a NameConstraints extension value with a single subtree ([0]
24506+ * permitted or [1] excluded) carrying a GeneralName of context tag `gnTag`
24507+ * with value `val`. */
24508+ static word32 build_simple_nameConstraints(byte* out, word32 outSz,
24509+ int excluded, byte gnTag, const char* val)
24510+ {
24511+ word32 vlen = (word32)XSTRLEN(val);
24512+ word32 n3 = vlen + 2; /* GeneralSubtree content: the base GN */
24513+ word32 n2 = n3 + 2; /* subtrees list content: one GeneralSubtree */
24514+ word32 n1 = n2 + 2; /* SEQUENCE content: the subtrees list */
24515+ if (vlen > 0x7F || outSz < n1 + 2)
24516+ return 0;
24517+ out[0] = 0x30; /* SEQUENCE */
24518+ out[1] = (byte)n1;
24519+ out[2] = excluded ? 0xA1 : 0xA0; /* [1] excluded / [0] permitted */
24520+ out[3] = (byte)n2;
24521+ out[4] = 0x30; /* GeneralSubtree */
24522+ out[5] = (byte)n3;
24523+ out[6] = gnTag; /* base GeneralName */
24524+ out[7] = (byte)vlen;
24525+ XMEMCPY(out + 8, val, vlen);
24526+ return n1 + 2;
24527+ }
24528+ #endif
24529+
24530+ /* End-to-end enforcement of DNS and URI nameConstraints against wildcard and
24531+ * sub-host leaf SANs, exercised through the real chain-verification path.
24532+ *
24533+ * DNS subtree semantics (RFC 5280 4.2.1.10) plus wildcard reasoning:
24534+ * - excluded DNS:foo.example.com must reject a leaf SAN *.example.com,
24535+ * because the wildcard can expand to the excluded host (security gap).
24536+ * - permitted DNS:example.com must accept *.example.com (contained), while
24537+ * permitted DNS:foo.example.com must reject it (the wildcard can escape).
24538+ * URI semantics: a constraint without a leading dot is an EXACT host match,
24539+ * not a subtree, so permitted URI:host.com must reject https://www.host.com/.
24540+ */
24541+ static int test_NameConstraints_DnsUriWildcard(void)
24542+ {
24543+ EXPECT_DECLS;
24544+ #if defined(WOLFSSL_ASN_TEMPLATE) && \
24545+ defined(WOLFSSL_CERT_REQ) && !defined(NO_ASN_TIME) && \
24546+ defined(WOLFSSL_CERT_GEN) && defined(HAVE_ECC) && \
24547+ defined(WOLFSSL_CERT_EXT) && !defined(NO_CERTS) && \
24548+ defined(WOLFSSL_ALT_NAMES) && defined(WOLFSSL_CUSTOM_OID) && \
24549+ defined(HAVE_OID_ENCODING) && !defined(IGNORE_NAME_CONSTRAINTS)
24550+ byte nc[64];
24551+ byte san[64];
24552+ word32 ncSz, sanSz;
24553+ const byte DNS = 0x82; /* [2] dnsName */
24554+ const byte URI = 0x86; /* [6] uniformResourceIdentifier */
24555+
24556+ /* --- DNS, excluded subtree --- */
24557+
24558+ /* (1) Excluded foo.example.com vs wildcard SAN that can reach it: the
24559+ * pre-fix byte-length guard let this through. Must reject. */
24560+ ncSz = build_simple_nameConstraints(nc, sizeof(nc), 1, DNS,
24561+ "foo.example.com");
24562+ sanSz = build_simple_san(san, sizeof(san), DNS, "*.example.com");
24563+ ExpectIntGT((int)ncSz, 0);
24564+ ExpectIntGT((int)sanSz, 0);
24565+ ExpectIntEQ(verify_with_otherName_chain(nc, ncSz, 1, san, sanSz),
24566+ WC_NO_ERR_TRACE(ASN_NAME_INVALID_E));
24567+
24568+ /* (2) Positive control: same exclusion, wildcard in a different domain
24569+ * cannot reach foo.example.com -> accept. */
24570+ sanSz = build_simple_san(san, sizeof(san), DNS, "*.other.com");
24571+ ExpectIntGT((int)sanSz, 0);
24572+ ExpectIntEQ(verify_with_otherName_chain(nc, ncSz, 1, san, sanSz), 0);
24573+
24574+ /* (3) Regression: literal SAN inside the excluded subtree still rejects. */
24575+ sanSz = build_simple_san(san, sizeof(san), DNS, "foo.example.com");
24576+ ExpectIntGT((int)sanSz, 0);
24577+ ExpectIntEQ(verify_with_otherName_chain(nc, ncSz, 1, san, sanSz),
24578+ WC_NO_ERR_TRACE(ASN_NAME_INVALID_E));
24579+
24580+ /* (4) Regression: literal SAN outside the excluded subtree accepts. */
24581+ sanSz = build_simple_san(san, sizeof(san), DNS, "bar.other.com");
24582+ ExpectIntGT((int)sanSz, 0);
24583+ ExpectIntEQ(verify_with_otherName_chain(nc, ncSz, 1, san, sanSz), 0);
24584+
24585+ /* --- DNS, permitted subtree --- */
24586+
24587+ /* (5) Permitted example.com accepts *.example.com (fully contained). */
24588+ ncSz = build_simple_nameConstraints(nc, sizeof(nc), 0, DNS, "example.com");
24589+ sanSz = build_simple_san(san, sizeof(san), DNS, "*.example.com");
24590+ ExpectIntGT((int)ncSz, 0);
24591+ ExpectIntGT((int)sanSz, 0);
24592+ ExpectIntEQ(verify_with_otherName_chain(nc, ncSz, 1, san, sanSz), 0);
24593+
24594+ /* (6) Permitted foo.example.com rejects *.example.com (the wildcard can
24595+ * expand outside the permitted subtree). */
24596+ ncSz = build_simple_nameConstraints(nc, sizeof(nc), 0, DNS,
24597+ "foo.example.com");
24598+ sanSz = build_simple_san(san, sizeof(san), DNS, "*.example.com");
24599+ ExpectIntGT((int)ncSz, 0);
24600+ ExpectIntGT((int)sanSz, 0);
24601+ ExpectIntEQ(verify_with_otherName_chain(nc, ncSz, 1, san, sanSz),
24602+ WC_NO_ERR_TRACE(ASN_NAME_INVALID_E));
24603+
24604+ /* --- URI, exact-host vs subtree semantics --- */
24605+
24606+ /* (7) Permitted URI host.com (no leading dot) is an EXACT host match:
24607+ * a sub-host URI is NOT contained -> reject. Pre-fix this wrongly
24608+ * subtree-matched and accepted. */
24609+ ncSz = build_simple_nameConstraints(nc, sizeof(nc), 0, URI, "host.com");
24610+ sanSz = build_simple_san(san, sizeof(san), URI, "https://www.host.com/");
24611+ ExpectIntGT((int)ncSz, 0);
24612+ ExpectIntGT((int)sanSz, 0);
24613+ ExpectIntEQ(verify_with_otherName_chain(nc, ncSz, 1, san, sanSz),
24614+ WC_NO_ERR_TRACE(ASN_NAME_INVALID_E));
24615+
24616+ /* (8) Permitted URI host.com accepts the exact host. */
24617+ sanSz = build_simple_san(san, sizeof(san), URI, "https://host.com/path");
24618+ ExpectIntGT((int)sanSz, 0);
24619+ ExpectIntEQ(verify_with_otherName_chain(nc, ncSz, 1, san, sanSz), 0);
24620+
24621+ /* (9) Permitted URI .host.com (leading dot) is a subtree: the sub-host is
24622+ * accepted but the bare host is not. */
24623+ ncSz = build_simple_nameConstraints(nc, sizeof(nc), 0, URI, ".host.com");
24624+ sanSz = build_simple_san(san, sizeof(san), URI, "https://www.host.com/");
24625+ ExpectIntGT((int)ncSz, 0);
24626+ ExpectIntGT((int)sanSz, 0);
24627+ ExpectIntEQ(verify_with_otherName_chain(nc, ncSz, 1, san, sanSz), 0);
24628+
24629+ sanSz = build_simple_san(san, sizeof(san), URI, "https://host.com/");
24630+ ExpectIntGT((int)sanSz, 0);
24631+ ExpectIntEQ(verify_with_otherName_chain(nc, ncSz, 1, san, sanSz),
24632+ WC_NO_ERR_TRACE(ASN_NAME_INVALID_E));
24633+
24634+ /* (10) Excluded URI host.com (exact): bare host excluded, sub-host not. */
24635+ ncSz = build_simple_nameConstraints(nc, sizeof(nc), 1, URI, "host.com");
24636+ sanSz = build_simple_san(san, sizeof(san), URI, "https://host.com/");
24637+ ExpectIntGT((int)ncSz, 0);
24638+ ExpectIntGT((int)sanSz, 0);
24639+ ExpectIntEQ(verify_with_otherName_chain(nc, ncSz, 1, san, sanSz),
24640+ WC_NO_ERR_TRACE(ASN_NAME_INVALID_E));
24641+
24642+ sanSz = build_simple_san(san, sizeof(san), URI, "https://www.host.com/");
24643+ ExpectIntGT((int)sanSz, 0);
24644+ ExpectIntEQ(verify_with_otherName_chain(nc, ncSz, 1, san, sanSz), 0);
24645+ #endif
24646+ return EXPECT_RESULT();
24647+ }
24648+
2448324649static int test_MakeCertWithCaFalse(void)
2448424650{
2448524651 EXPECT_DECLS;
@@ -40240,6 +40406,7 @@ TEST_CASE testCases[] = {
4024040406 TEST_DECL(test_PathLenSelfIssuedAllowed),
4024140407 TEST_DECL(test_PathLenNoKeyUsage),
4024240408 TEST_DECL(test_NameConstraints_OtherName),
40409+ TEST_DECL(test_NameConstraints_DnsUriWildcard),
4024340410 TEST_DECL(test_ParseSerial0FixtureMatrix),
4024440411 TEST_DECL(test_MakeCertWithCaFalse),
4024540412#ifdef WOLFSSL_CERT_SIGN_CB
0 commit comments