@@ -23187,6 +23187,47 @@ static word32 build_simple_nameConstraints(byte* out, word32 outSz,
2318723187 XMEMCPY(out + 8, val, vlen);
2318823188 return n1 + 2;
2318923189}
23190+
23191+ /* Build a NameConstraints extension value with a single subtree ([0]
23192+ * permitted or [1] excluded) whose GeneralSubtree carries a base GeneralName
23193+ * of context tag `gnTag` plus optional minimum ([0]) and maximum ([1])
23194+ * BaseDistance fields. Used to confirm that a non-zero minimum or any
23195+ * maximum is rejected (RFC 5280 4.2.1.10 requires minimum 0, maximum absent
23196+ * within this profile). */
23197+ static word32 build_minmax_nameConstraints(byte* out, word32 outSz,
23198+ int excluded, byte gnTag, const char* val, int minPresent, byte minVal,
23199+ int maxPresent, byte maxVal)
23200+ {
23201+ word32 vlen = (word32)XSTRLEN(val);
23202+ word32 extra = (word32)((minPresent ? 3 : 0) + (maxPresent ? 3 : 0));
23203+ word32 n3 = vlen + 2 + extra; /* GeneralSubtree content: base GN + min/max */
23204+ word32 n2 = n3 + 2; /* subtrees list content: one GeneralSubtree */
23205+ word32 n1 = n2 + 2; /* SEQUENCE content: the subtrees list */
23206+ word32 idx;
23207+ if (vlen > 0x7F || n3 > 0x7F || outSz < n1 + 2)
23208+ return 0;
23209+ out[0] = 0x30; /* SEQUENCE */
23210+ out[1] = (byte)n1;
23211+ out[2] = excluded ? 0xA1 : 0xA0; /* [1] excluded / [0] permitted */
23212+ out[3] = (byte)n2;
23213+ out[4] = 0x30; /* GeneralSubtree */
23214+ out[5] = (byte)n3;
23215+ out[6] = gnTag; /* base GeneralName */
23216+ out[7] = (byte)vlen;
23217+ XMEMCPY(out + 8, val, vlen);
23218+ idx = 8 + vlen;
23219+ if (minPresent) {
23220+ out[idx++] = 0x80; /* [0] minimum BaseDistance */
23221+ out[idx++] = 0x01;
23222+ out[idx++] = minVal;
23223+ }
23224+ if (maxPresent) {
23225+ out[idx++] = 0x81; /* [1] maximum BaseDistance */
23226+ out[idx++] = 0x01;
23227+ out[idx++] = maxVal;
23228+ }
23229+ return n1 + 2;
23230+ }
2319023231#endif
2319123232
2319223233/* End-to-end enforcement of DNS and URI nameConstraints against wildcard and
@@ -23348,6 +23389,60 @@ static int test_NameConstraints_DnsUriWildcard(void)
2334823389 return EXPECT_RESULT();
2334923390}
2335023391
23392+ /* A GeneralSubtree's minimum/maximum BaseDistance fields were parsed but
23393+ * never stored or checked, so a subtree carrying a non-zero minimum or any
23394+ * maximum was silently accepted and then enforced as if it were minimum 0 /
23395+ * maximum absent. RFC 5280 4.2.1.10 requires minimum 0 and maximum absent
23396+ * within this profile, so such a constraint must be rejected. The bad
23397+ * constraint rides on the issuing CA, so a rejection surfaces as a failure
23398+ * to build the chain rather than a specific leaf error code. */
23399+ static int test_NameConstraints_SubtreeMinMax(void)
23400+ {
23401+ EXPECT_DECLS;
23402+ #if defined(WOLFSSL_ASN_TEMPLATE) && \
23403+ defined(WOLFSSL_CERT_REQ) && !defined(NO_ASN_TIME) && \
23404+ defined(WOLFSSL_CERT_GEN) && defined(HAVE_ECC) && \
23405+ defined(WOLFSSL_CERT_EXT) && !defined(NO_CERTS) && \
23406+ defined(WOLFSSL_ALT_NAMES) && defined(WOLFSSL_CUSTOM_OID) && \
23407+ defined(HAVE_OID_ENCODING) && !defined(IGNORE_NAME_CONSTRAINTS)
23408+ byte nc[64];
23409+ word32 ncSz;
23410+ const byte DNS = 0x82; /* [2] dnsName */
23411+
23412+ /* (1) Control: minimum and maximum both absent -> conformant, accepts. */
23413+ ncSz = build_minmax_nameConstraints(nc, sizeof(nc), 1, DNS, "example.com",
23414+ 0, 0, 0, 0);
23415+ ExpectIntGT((int)ncSz, 0);
23416+ ExpectIntEQ(verify_with_otherName_chain(nc, ncSz, 1, NULL, 0), 0);
23417+
23418+ /* (2) Control: explicit minimum == 0 is conformant -> accepts. Pins the
23419+ * rejection below to a non-zero minimum, not to any minimum field. */
23420+ ncSz = build_minmax_nameConstraints(nc, sizeof(nc), 1, DNS, "example.com",
23421+ 1, 0, 0, 0);
23422+ ExpectIntGT((int)ncSz, 0);
23423+ ExpectIntEQ(verify_with_otherName_chain(nc, ncSz, 1, NULL, 0), 0);
23424+
23425+ /* (3) minimum == 1 must be rejected. */
23426+ ncSz = build_minmax_nameConstraints(nc, sizeof(nc), 1, DNS, "example.com",
23427+ 1, 1, 0, 0);
23428+ ExpectIntGT((int)ncSz, 0);
23429+ ExpectIntNE(verify_with_otherName_chain(nc, ncSz, 1, NULL, 0), 0);
23430+
23431+ /* (4) maximum present (== 0) must be rejected. */
23432+ ncSz = build_minmax_nameConstraints(nc, sizeof(nc), 1, DNS, "example.com",
23433+ 0, 0, 1, 0);
23434+ ExpectIntGT((int)ncSz, 0);
23435+ ExpectIntNE(verify_with_otherName_chain(nc, ncSz, 1, NULL, 0), 0);
23436+
23437+ /* (5) maximum present (== 5) must be rejected. */
23438+ ncSz = build_minmax_nameConstraints(nc, sizeof(nc), 0, DNS, "example.com",
23439+ 0, 0, 1, 5);
23440+ ExpectIntGT((int)ncSz, 0);
23441+ ExpectIntNE(verify_with_otherName_chain(nc, ncSz, 1, NULL, 0), 0);
23442+ #endif
23443+ return EXPECT_RESULT();
23444+ }
23445+
2335123446static int test_MakeCertWithCaFalse(void)
2335223447{
2335323448 EXPECT_DECLS;
@@ -34967,6 +35062,7 @@ TEST_CASE testCases[] = {
3496735062 TEST_DECL(test_PathLenNoKeyUsage),
3496835063 TEST_DECL(test_NameConstraints_OtherName),
3496935064 TEST_DECL(test_NameConstraints_DnsUriWildcard),
35065+ TEST_DECL(test_NameConstraints_SubtreeMinMax),
3497035066 TEST_DECL(test_ParseSerial0FixtureMatrix),
3497135067 TEST_DECL(test_MakeCertWithCaFalse),
3497235068#ifdef WOLFSSL_CERT_SIGN_CB
0 commit comments