DEV Community

Devanshu Biswas
Devanshu Biswas

Posted on

What Stops *.com Is Not a Public Suffix List but a Dot Count, so *.co.uk Is Live and Matches bbc.co.uk in Both

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;
Enter fullscreen mode Exit fullscreen mode

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)