@@ -23208,6 +23208,47 @@ static word32 build_simple_nameConstraints(byte* out, word32 outSz,
2320823208 XMEMCPY(out + 8, val, vlen);
2320923209 return n1 + 2;
2321023210}
23211+
23212+ /* Build a NameConstraints extension value with a single subtree ([0]
23213+ * permitted or [1] excluded) whose GeneralSubtree carries a base GeneralName
23214+ * of context tag `gnTag` plus optional minimum ([0]) and maximum ([1])
23215+ * BaseDistance fields. Used to confirm that a non-zero minimum or any
23216+ * maximum is rejected (RFC 5280 4.2.1.10 requires minimum 0, maximum absent
23217+ * within this profile). */
23218+ static word32 build_minmax_nameConstraints(byte* out, word32 outSz,
23219+ int excluded, byte gnTag, const char* val, int minPresent, byte minVal,
23220+ int maxPresent, byte maxVal)
23221+ {
23222+ word32 vlen = (word32)XSTRLEN(val);
23223+ word32 extra = (word32)((minPresent ? 3 : 0) + (maxPresent ? 3 : 0));
23224+ word32 n3 = vlen + 2 + extra; /* GeneralSubtree content: base GN + min/max */
23225+ word32 n2 = n3 + 2; /* subtrees list content: one GeneralSubtree */
23226+ word32 n1 = n2 + 2; /* SEQUENCE content: the subtrees list */
23227+ word32 idx;
23228+ if (vlen > 0x7F || n3 > 0x7F || outSz < n1 + 2)
23229+ return 0;
23230+ out[0] = 0x30; /* SEQUENCE */
23231+ out[1] = (byte)n1;
23232+ out[2] = excluded ? 0xA1 : 0xA0; /* [1] excluded / [0] permitted */
23233+ out[3] = (byte)n2;
23234+ out[4] = 0x30; /* GeneralSubtree */
23235+ out[5] = (byte)n3;
23236+ out[6] = gnTag; /* base GeneralName */
23237+ out[7] = (byte)vlen;
23238+ XMEMCPY(out + 8, val, vlen);
23239+ idx = 8 + vlen;
23240+ if (minPresent) {
23241+ out[idx++] = 0x80; /* [0] minimum BaseDistance */
23242+ out[idx++] = 0x01;
23243+ out[idx++] = minVal;
23244+ }
23245+ if (maxPresent) {
23246+ out[idx++] = 0x81; /* [1] maximum BaseDistance */
23247+ out[idx++] = 0x01;
23248+ out[idx++] = maxVal;
23249+ }
23250+ return n1 + 2;
23251+ }
2321123252#endif
2321223253
2321323254/* End-to-end enforcement of DNS and URI nameConstraints against wildcard and
@@ -23329,6 +23370,60 @@ static int test_NameConstraints_DnsUriWildcard(void)
2332923370 return EXPECT_RESULT();
2333023371}
2333123372
23373+ /* A GeneralSubtree's minimum/maximum BaseDistance fields were parsed but
23374+ * never stored or checked, so a subtree carrying a non-zero minimum or any
23375+ * maximum was silently accepted and then enforced as if it were minimum 0 /
23376+ * maximum absent. RFC 5280 4.2.1.10 requires minimum 0 and maximum absent
23377+ * within this profile, so such a constraint must be rejected. The bad
23378+ * constraint rides on the issuing CA, so a rejection surfaces as a failure
23379+ * to build the chain rather than a specific leaf error code. */
23380+ static int test_NameConstraints_SubtreeMinMax(void)
23381+ {
23382+ EXPECT_DECLS;
23383+ #if defined(WOLFSSL_ASN_TEMPLATE) && \
23384+ defined(WOLFSSL_CERT_REQ) && !defined(NO_ASN_TIME) && \
23385+ defined(WOLFSSL_CERT_GEN) && defined(HAVE_ECC) && \
23386+ defined(WOLFSSL_CERT_EXT) && !defined(NO_CERTS) && \
23387+ defined(WOLFSSL_ALT_NAMES) && defined(WOLFSSL_CUSTOM_OID) && \
23388+ defined(HAVE_OID_ENCODING) && !defined(IGNORE_NAME_CONSTRAINTS)
23389+ byte nc[64];
23390+ word32 ncSz;
23391+ const byte DNS = 0x82; /* [2] dnsName */
23392+
23393+ /* (1) Control: minimum and maximum both absent -> conformant, accepts. */
23394+ ncSz = build_minmax_nameConstraints(nc, sizeof(nc), 1, DNS, "example.com",
23395+ 0, 0, 0, 0);
23396+ ExpectIntGT((int)ncSz, 0);
23397+ ExpectIntEQ(verify_with_otherName_chain(nc, ncSz, 1, NULL, 0), 0);
23398+
23399+ /* (2) Control: explicit minimum == 0 is conformant -> accepts. Pins the
23400+ * rejection below to a non-zero minimum, not to any minimum field. */
23401+ ncSz = build_minmax_nameConstraints(nc, sizeof(nc), 1, DNS, "example.com",
23402+ 1, 0, 0, 0);
23403+ ExpectIntGT((int)ncSz, 0);
23404+ ExpectIntEQ(verify_with_otherName_chain(nc, ncSz, 1, NULL, 0), 0);
23405+
23406+ /* (3) minimum == 1 must be rejected. */
23407+ ncSz = build_minmax_nameConstraints(nc, sizeof(nc), 1, DNS, "example.com",
23408+ 1, 1, 0, 0);
23409+ ExpectIntGT((int)ncSz, 0);
23410+ ExpectIntNE(verify_with_otherName_chain(nc, ncSz, 1, NULL, 0), 0);
23411+
23412+ /* (4) maximum present (== 0) must be rejected. */
23413+ ncSz = build_minmax_nameConstraints(nc, sizeof(nc), 1, DNS, "example.com",
23414+ 0, 0, 1, 0);
23415+ ExpectIntGT((int)ncSz, 0);
23416+ ExpectIntNE(verify_with_otherName_chain(nc, ncSz, 1, NULL, 0), 0);
23417+
23418+ /* (5) maximum present (== 5) must be rejected. */
23419+ ncSz = build_minmax_nameConstraints(nc, sizeof(nc), 0, DNS, "example.com",
23420+ 0, 0, 1, 5);
23421+ ExpectIntGT((int)ncSz, 0);
23422+ ExpectIntNE(verify_with_otherName_chain(nc, ncSz, 1, NULL, 0), 0);
23423+ #endif
23424+ return EXPECT_RESULT();
23425+ }
23426+
2333223427static int test_MakeCertWithCaFalse(void)
2333323428{
2333423429 EXPECT_DECLS;
@@ -34948,6 +35043,7 @@ TEST_CASE testCases[] = {
3494835043 TEST_DECL(test_PathLenNoKeyUsage),
3494935044 TEST_DECL(test_NameConstraints_OtherName),
3495035045 TEST_DECL(test_NameConstraints_DnsUriWildcard),
35046+ TEST_DECL(test_NameConstraints_SubtreeMinMax),
3495135047 TEST_DECL(test_ParseSerial0FixtureMatrix),
3495235048 TEST_DECL(test_MakeCertWithCaFalse),
3495335049#ifdef WOLFSSL_CERT_SIGN_CB
0 commit comments