Affects both stacks, and it is the certificate shape most operators actually hold.
SNI lookup is an exact, case-insensitive byte compare on both sides — HostTable.Find on TCP, the host array in the shim on QUIC. So registering *.example.com is accepted, reported by TlsService.ServerNames, costs a full SSL_CTX, and is served to nobody: no client ever sends *.example.com as its server name, and a.example.com misses the table and falls back to the default certificate.
The failure is entirely silent. The server starts, the operator sees their name in the configuration, and clients get the wrong certificate with a name mismatch they must diagnose from the client side.
TlsOptions documents the intent correctly — "Names are matched case-insensitively and exactly - a wildcard certificate covers its names through the certificate, not through this table" — but documentation is not what an operator hits at 2am. The startup path already refuses two entries for the same name, on exactly the reasoning that applies here: one of them would silently never be served.
Two possible resolutions, and this issue is mostly to force the choice:
- Refuse an entry whose key starts with
*. at startup, the way a duplicate name is refused, and say what to register instead. Cheap, and consistent with the existing check.
- Support wildcards in the lookup — one label, leftmost only, as RFC 6125 defines it. More useful, and more surface: the precedence rule (exact beats wildcard) has to be stated and tested, and it is the sort of matching that goes subtly wrong.
The same startup pass could also refuse the other entries that can never be served, all found while mining nginxs ssl_sni.t: an IP literal (not a legal SNI value), a name over 255 bytes (refused by OpenSSL before the callback runs), and a non-ASCII name — an international name has to be registered in its A-label form, and a Unicode key is dead weight no client can ask for.
Affects both stacks, and it is the certificate shape most operators actually hold.
SNI lookup is an exact, case-insensitive byte compare on both sides —
HostTable.Findon TCP, the host array in the shim on QUIC. So registering*.example.comis accepted, reported byTlsService.ServerNames, costs a fullSSL_CTX, and is served to nobody: no client ever sends*.example.comas its server name, anda.example.commisses the table and falls back to the default certificate.The failure is entirely silent. The server starts, the operator sees their name in the configuration, and clients get the wrong certificate with a name mismatch they must diagnose from the client side.
TlsOptionsdocuments the intent correctly — "Names are matched case-insensitively and exactly - a wildcard certificate covers its names through the certificate, not through this table" — but documentation is not what an operator hits at 2am. The startup path already refuses two entries for the same name, on exactly the reasoning that applies here: one of them would silently never be served.Two possible resolutions, and this issue is mostly to force the choice:
*.at startup, the way a duplicate name is refused, and say what to register instead. Cheap, and consistent with the existing check.The same startup pass could also refuse the other entries that can never be served, all found while mining nginxs
ssl_sni.t: an IP literal (not a legal SNI value), a name over 255 bytes (refused by OpenSSL before the callback runs), and a non-ASCII name — an international name has to be registered in its A-label form, and a Unicode key is dead weight no client can ask for.