Skip to content

tlshd: Verify the server certificate on every client handshake - #61

Merged
chucklever merged 3 commits into
mainfrom
quic-anon-verify
Oct 20, 2026
Merged

chucklever merged 3 commits into
mainfrom
quic-anon-verify

Conversation

@chucklever

Copy link
Copy Markdown
Contributor

An anonymous client handshake (HANDSHAKE_AUTH_UNAUTH) is one where
the client presents no certificate. Documentation/networking/
tls-handshake.rst states that tlshd still authenticates the server
on that path. As the code stands, the QUIC anonymous client path
does not verify the server certificate at all, and the TCP anonymous
path verifies it under a weaker policy than the mutual-auth paths:
MD2 and MD5 chain signatures are allowed and a stapled OCSP response
is ignored.

After this series every client handshake, TCP or QUIC, anonymous or
mutual-auth, verifies the server chain under the GnuTLS default
policy. One verify callback serves both QUIC client modes and
selects the anonymous or mutual-auth check from the auth mode the
kernel requested (patch 1). A resumed QUIC session carries no
Certificate message, so the callback does not run on it. The chain
GnuTLS stored in the resumption data is verified instead before the
handshake is reported to the kernel.

This is a behavioral change for anonymous clients. A server chain
signed with MD2 or MD5, or a stapled OCSP response that is invalid
or reports revocation, now fails the anonymous handshake as it
already fails the mutual-auth handshake.

fitandre and others added 3 commits October 20, 2026 00:26
When the kernel requests an anonymous client handshake
(HANDSHAKE_AUTH_UNAUTH) on a QUIC socket,
tlshd_quic_client_set_x509_session() loads the client trust store
but installs no verify callback. GnuTLS does not verify a peer
certificate on its own, so the handshake completes with any server
certificate: one from an unknown CA, a self-signed one, or one
issued for another name. The peer name is used only for SNI, and the
kernel consumer is told the handshake succeeded.

The TCP anonymous path authenticates the server with
tlshd_client_verify_peer(), and Documentation/networking/
tls-handshake.rst states that an anonymous client handshake still
performs server authentication. Install the same check on the QUIC
path, through one verify callback that selects the anonymous or
mutual-auth check by the kernel's auth mode.

The QUIC client has marked the anonymous path by storing a sentinel
in conn->cert_req, a two-bit field that otherwise carries the
kernel's quic_config.certificate_request verbatim. The auth mode is
the request the kernel made, and parms is reachable from the verify
callback, so dispatch on it directly and retire the sentinel.

A resumed session carries no Certificate message, so the verify
callback does not run on it, and a ticket does not record whether
the handshake that issued it authenticated the server. GnuTLS stores
the server's chain in the resumption data. After a resumed anonymous
handshake, verify that chain before reporting success to the kernel.

With verification in place, the verify callback records the PKIX or
DANE result, so the anonymous QUIC path no longer marks the
handshake unauthenticated for the DANE audit. That mark has no
other user; retire it. The anonymous credential carries no client
key, so GNUTLS_CERTIFICATE_SKIP_KEY_CERT_MATCH has nothing to skip
there; drop it too.

Fixes: 43a15fe ("tlshd: add support for quic handshake")
Signed-off-by: Andre Sieber Arens <andre@sieberarens.com>
Reviewed-by: Xin Long <lucien.xin@gmail.com>
Signed-off-by: Chuck Lever <cel@kernel.org>
The TCP and QUIC anonymous client handshakes set
GNUTLS_VERIFY_ALLOW_SIGN_RSA_MD2 and GNUTLS_VERIFY_ALLOW_SIGN_RSA_MD5
on their credentials and set GNUTLS_CERTIFICATE_SKIP_OCSP_RESPONSE_CHECK
as well, while the mutual-auth client handshakes set none of the
three. An anonymous client therefore accepts a server chain that a
mutual-auth client to the same server rejects, although the server
is authenticated the same way on both paths.

The verify flags date to the initial commit of ktls-utils, under a
comment describing them as allowing self-signed server certificates.
They never did that. GnuTLS has refused MD2 and MD5 chain signatures
since release 1.2.9 in 2005 and offers these flags only for
applications that must interoperate with such chains. A self-signed
certificate is accepted when it is in the trust store, irrespective
of its signature algorithm, and rejected otherwise, with or without
the flags. The only chain the flags admit is one in which a trusted
CA signed the server certificate or an intermediate with MD2 or MD5.
No issue, pull request, or list thread has ever asked for that.

The OCSP skip dates to the same commit and carries no rationale.
With it set, gnutls_certificate_verify_peers3() ignores the OCSP
response a server staples, so an anonymous client completes the
handshake with a server whose stapled response marks its own
certificate revoked. On the mutual-auth path the same response
fails verification with GNUTLS_CERT_REVOKED.

Drop the verify flags and the OCSP skip from both anonymous paths so
every client handshake verifies the server chain under the GnuTLS
default policy. After the change, a server chain carrying an MD2 or
MD5 signature, or a stapled OCSP response that is invalid or reports
revocation, fails the anonymous handshake as it already fails the
mutual-auth handshake.

Signed-off-by: Chuck Lever <cel@kernel.org>
GNUTLS_CERTIFICATE_SKIP_KEY_CERT_MATCH tells GnuTLS not to check
that a private key loaded into a credential matches its certificate.
The anonymous TCP client handshake loads no key or certificate into
xcred, so the flag has nothing to act on there. It has sat in the
credential flags since the initial commit and suggests a client
identity that this path never carries.

Drop the flag. The anonymous TCP path then sets no credential flags,
as the anonymous QUIC client path already does.

Signed-off-by: Chuck Lever <cel@kernel.org>
@chucklever
chucklever force-pushed the quic-anon-verify branch 2 times, most recently from d1fa053 to f74eb75 Compare October 20, 2026 18:19
@chucklever
chucklever merged commit af24a32 into main Oct 20, 2026
12 checks passed
@chucklever
chucklever deleted the quic-anon-verify branch October 20, 2026 18:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants