Certificate hostname verification is the one security check every TLS client makes and almost nobody has read. This page is a real X509_check_host() under three profiles at once — OpenSSL 3.2.1, Node 22's tls.checkServerIdentity, RFC 9525 as written — and every verdict carries the name of the clause that decided it.
Everyone knows what *.example.com means. Almost nobody knows what stops *.com, and it is not a public suffix list, because neither library contains one. It is a dot count:
/* the final label must not end in '-' or '.', and there must be at
least two dots after the star: the line that kills *.com */
if (state & (LABEL_START | LABEL_HYPHEN)) return -1;
if (dots < 2 && !(flags & F.SINGLE_LABEL_SUBDOMAINS)) return -1;
So *.com is dead and *.co.uk is live, matching bbc.co.uk in both real implementations — as are *.org.uk, *.github.io, *.appspot.com and *.s3.amazonaws.com: https://dev48.infy.uk/solve/day72-cert-hostname-verifier.html
Rejection would have been the safe outcome
OpenSSL never refuses a malformed wildcard. It demotes the star to an ordinary character and carries on comparing strings, so the pattern silently becomes a literal that matches nothing and no error is raised at any layer.
| reading of the same 103 starred identities | wildcard | silently a literal | refused |
|---|---|---|---|
| OpenSSL 3.2.1 | 26 | 77 | 0 |
| Node 22 | 52 | 24 | 27 |
| RFC 9525 (2023) | 20 | 0 | 83 |
Same certificates, three partitions, no error in either direction. A pattern your CA issued and your test suite passed can be a wildcard on one runtime, an inert literal on the next, and a hard rejection on the third.
The common misreading of the CN is wrong in the same direction. It is not deprecated and it is not disabled by the presence of a SAN — it is disabled by type. An email, an iPAddress or a URI SAN leaves the CN authoritative, and only a dNSName silences it, so 7 of the 31 SAN-bearing certificates in the exhaustive 64-certificate slot corpus are accepted purely on their CN.
Of the 148 cells in the 8,160-cell differential grid where OpenSSL and Node disagree, 142 are decided by a dot: a trailing one on the host, a trailing one in the certificate, or an empty label between.
What the measurement contradicted
The interesting failure was not in the engine. Every verdict is raced against the real thing — a throwaway CA issues 282 leaves, and each is checked across 8,480 cells against openssl verify -verify_hostname and Node's own checkServerIdentity — and one whole class of cells came back disagreeing.
The reference was wrong. The MinGW OpenSSL build globs its own argv, so a hostname of * or *.example.com was expanded into a list of filenames by the C runtime before X509_check_host() ever saw it. The oracle was answering a different question and answering it confidently. Quote the argument and the disagreements go to 0, against both binaries.
312,415 assertions in the page and 25,703 in the verifier. Self-contained: one file, inline CSS, no external asset of any kind.
Part of a from-scratch series — one tool a day, all client-side, dependency-free engine: https://dev48.infy.uk/solvefromzero.php
Top comments (0)