Repository navigation
Reject partial-label wildcard names by default (RFC 9525). - #12119
kareem-wolfssl wants to merge 4 commits into
Conversation
This matches OpenSSL's behavior for these flags and allows an easy way to opt out of partial wildcard matching.
Previously, the host flags set by X509_VERIFY_PARAM_set_hostflags() were being ignored by the time they reached X509_verify_cert(), because ProcessPeerCert was passing in a literal 0 for these flags. Now, the host flags are passed in if set.
RFC 6125 sec. 6.4.3 rule 3 said a client MAY match a presented identifier whose wildcard is not the only character of the left-most label, so "foo*.example.com" matched "foobar.example.com" and "foo.example.com". RFC 9525, which obsoletes RFC 6125, requires the wildcard to be the complete content of the left-most label and says a presented identifier that is not MUST be ignored (sec. 6.3, and Appendix A lists this as a change). Apply WOLFSSL_NO_PARTIAL_WILDCARDS by default. Define WOLFSSL_ALLOW_PARTIAL_WILDCARDS to keep the RFC 6125 behavior. Callers may still set the flag explicitly in either build. Thanks to Han Qiu and Shuaigangran Yue (Southeast University) for reporting this issue.
|
@claude review |
There was a problem hiding this comment.
🟡 Changes recommended
The set1_host() verification paths still ignore configured host flags, allowing wildcard certificates that callers explicitly forbid.
1 open issue
What changed in this PR
This PR tightens wolfSSL certificate hostname matching to reject partial-label wildcards by default under RFC 9525, while retaining an opt-in compatibility mode.
Changes:
- Implements wildcard restriction flags and forwards host flags in the domain-name handshake check.
- Updates hostname-matching tests for strict defaults and compatibility behavior.
- Moves legacy partial-wildcard handshake cases into a conditional test suite.
| File | Description |
|---|---|
tests/test.conf |
Removes partial-wildcard cases from the default suite. |
tests/test-wildcard-partial.conf |
Adds compatibility-only partial-wildcard cases. |
tests/suites.c |
Runs the compatibility suite when partial wildcards are enabled. |
tests/include.am |
Includes the new test configuration in distributions. |
tests/api/test_ossl_x509.c |
Tests wildcard restrictions and adjusts default expectations. |
src/x509.c |
Enables supported wildcard flags and corrects an unsupported-flag diagnostic. |
src/internal.c |
Enforces wildcard restrictions and forwards handshake host flags. |
Review effort: Balanced
| unsigned int hostFlags = (ssl->param != NULL) ? | ||
| ssl->param->hostFlags : 0; |
|
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
Additional findings (outside the current diff — GitHub can't attach inline comments there):
-
🟡
src/internal.c— nit: The header comment on WildcardPlacementOK still says partial wildcards like "a*" and "ab" keep their old matching, which is now wrong in default builds. The function now rejects them whenever WOLFSSL_NO_PARTIAL_WILDCARDS is set, and MatchDomainName sets that flag unless WOLFSSL_ALLOW_PARTIAL_WILDCARDS is defined. Fix: rewrite the comment to say partial-label wildcards are refused under WOLFSSL_NO_PARTIAL_WILDCARDS, which is the default, and are only exempt from the two-label rule when WOLFSSL_ALLOW_PARTIAL_WILDCARDS is defined.Why this was flagged
The comment at src/internal.c:15447-15450 says "Partial left-most wildcards such as "a*" or "ab" retain their existing matching behavior". The new check at src/internal.c:15481-15483 returns 0 for those patterns when WOLFSSL_NO_PARTIAL_WILDCARDS is set. MatchDomainName ORs that flag in at src/internal.c:15565-15571 unless WOLFSSL_ALLOW_PARTIAL_WILDCARDS is defined, so the flag is set in every default build. Someone maintaining this security-sensitive hostname matcher, going by the comment, would assume partial wildcards still match by default, the opposite of what the code does. The code itself behaves correctly; only the documentation is wrong.
Verification: nit. The trigger is any default build, i.e. one without WOLFSSL_ALLOW_PARTIAL_WILDCARDS. The diff leaves the WildcardPlacementOK header comment unchanged at src/internal.c:15447-15449. The check added at src/internal.c:15481-15483 returns 0 for "a*" and "ab". MatchDomainName sets that flag unconditionally at src/internal.c:15565-15571. Runtime behavior is unaffected.
-
🟣
src/internal.c— Host flags are still ignored for names set with X509_VERIFY_PARAM_set1_host, so a WOLFSSL_NO_WILDCARDS app still accepts a "*.example.com" certificate. CheckPeerHostName at src/internal.c:19663 passes flags 0 to CheckForAltNames and to both MatchDomainName CN calls. ProcessPeerCerts at :21298 calls it with ssl->param->hostName, the usual OpenSSL path. Fix: pass ssl->param->hostFlags into CheckPeerHostName and use it at all three match calls, like the wolfSSL_check_domain_name path this PR updated.Why this was flagged
The trigger is an OPENSSL_EXTRA app that pairs X509_VERIFY_PARAM_set1_host (or SSL_set1_host) with X509_VERIFY_PARAM_set_hostflags(WOLFSSL_NO_WILDCARDS), which is the standard OpenSSL idiom. In the handshake, ProcessPeerCerts (src/internal.c:21295-21298) checks ssl->param->hostName through CheckPeerHostName. That function hard-codes flags 0 in CheckForAltNames (src/internal.c:19663) and in MatchDomainName (src/internal.c:19669, 19678). A peer certificate with the SAN "*.example.com" therefore still matches "foo.example.com", even though the app asked for no wildcards. This PR changed only the wolfSSL_check_domain_name block (src/internal.c:21227-21280) to read hostFlags, and its stated goal is to honor host flags in the handshake peer-name check. The behavior on the verify-param path is pre-existing; this path was left as it was on the base branch. The x509_str.c:1396 path that does honor hostFlags runs only in wolfSSL_X509_verify_cert, not in this handshake check.
Verification: Pre-existing: the base also ignores the flags on this path. ProcessPeerCerts checks the name at src/internal.c:21295-21299 with
CheckPeerHostName(args->dCert, ssl->param->hostName). The flags are never passed in. CheckPeerHostName hard-codes flags 0 in all four match calls (src/internal.c:19663, 19669-19670, 19678-19679, 19682). Result: a SAN "*.example.com" still passes for "www.example.com" despite NO_WILDCARDS.
Update comment.

Description
Implement X509_CHECK_FLAG_NO_WILDCARDS and NO_PARTIAL_WILDCARDS.
Honor the host flags in the handshake peer-name check.
Fixes zd#22737
Testing
Built in + added tests, provided reproducer
Checklist