Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mTLS: client cert verification for QUIC/HTTP3 #11996

Closed
sandhu opened this issue Jul 2, 2024 · 8 comments · Fixed by #12014
Closed

mTLS: client cert verification for QUIC/HTTP3 #11996

sandhu opened this issue Jul 2, 2024 · 8 comments · Fixed by #12014
Labels
Bug For general bugs on Jetty side

Comments

@sandhu
Copy link

sandhu commented Jul 2, 2024

Jetty version(s)
12.0.10
Jetty Environment
core

Java version/vendor (use: java -version)
openjdk version "21.0.3" 2024-04-16
OpenJDK Runtime Environment Homebrew (build 21.0.3)
OpenJDK 64-Bit Server VM Homebrew (build 21.0.3, mixed mode, sharing)

OS type/version
Darwin ... 23.5.0 Darwin Kernel Version 23.5.0 arm64
and
Linux ... 6.5.0-41-generic 22.04.2-Ubuntu x86_64

Description
I'm using SslContextFactory.Server to configure the context which is used for HTTP 1.1, HTTP 2 and HTTP3. I have set setNeedClientAuth to true.

In the case of HTTP 1.1 and HTTP2, the server demands the client certificate and verifies it against the configured trust store before the connection can proceed

HTTP3 does not demand the certificate and allows connection without the client certificate.

It appears that Quiche provides a verify_peer option and there is a low level test for it in the code base https://github.com/jetty/jetty.project/blob/jetty-12.0.x/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-foreign/src/test/java/org/eclipse/jetty/quic/quiche/foreign/LowLevelQuicheClientCertTest.java#L100

I believe the underlying verifyPeer value should be set based on the value of getNeedClientAuth on the SslContextFactory.Server.

Additionally there should be a way to add the SecureRequestCustomizer or equivalent so that the handlers can access the client cert for the QUIC/HTTP3 connection.

How to reproduce?
Use an SslContextFactory.Server with setNeedClientAuth(true) with the example at https://jetty.org/docs/jetty/12/programming-guide/server/http3.html

@sandhu sandhu added the Bug For general bugs on Jetty side label Jul 2, 2024
@lorban
Copy link
Contributor

lorban commented Jul 2, 2024

Regarding SecureRequestCustomizer, this was done in #11900 that will be released in the coming days as part of Jetty 12.0.11.

As for quiche_config_verify_peer, it is already called based on SslContextFactory.getNeedClientAuth() and SslContextFactory.getWantClientAuth(), see: https://github.com/jetty/jetty.project/blob/jetty-12.0.x/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConnection.java#L154

Do you have a reproducer which shows needClientAuth being ignored?

@sandhu
Copy link
Author

sandhu commented Jul 2, 2024

Thank you for the fast response.

I'll re-verify my code and create a minimal example to reproduce the issue with peer verification.

@sandhu
Copy link
Author

sandhu commented Jul 2, 2024

I added some debug logging to ServerQuicConnection.java and can confirm that quicheConfig.getVerifyPeer() is true after line 154.

Testing with curl I get the following trace

$ curl -L --http3-only -s --trace-config "http/3" -vvv -k https://localhost:5443
* Host localhost:5443 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:5443...
* [HTTP/3] vquic_send(len=1200, gso=1200) -> 0, sent=1200
* [HTTP/3] Sent QUIC client Initial, ALPN: h3
* [HTTP/3] populate_x509_store, path=/ca.crt, blob=0
*  CAfile: /ca.crt
*  CApath: none
* QUIC: connection to ::1 port 5443 refused
* [HTTP/3] recvd 0 packets with 0 bytes -> 7
* connect to ::1 port 5443 failed: Couldn't connect to server
*   Trying 127.0.0.1:5443...
* [HTTP/3] vquic_send(len=1200, gso=1200) -> 0, sent=1200
* [HTTP/3] Sent QUIC client Initial, ALPN: h3
* [HTTP/3] recvd 1 packets with 90 bytes -> 0
* [HTTP/3] vquic_send(len=1200, gso=1200) -> 0, sent=1200
* [HTTP/3] recvd 1 packets with 1200 bytes -> 0
* [HTTP/3] vquic_send(len=1200, gso=1200) -> 0, sent=1200
* [HTTP/3] handshake complete after 48ms
* Server certificate:
*  subject: CN=localhost-dev-server
*  start date: Jun 19 19:46:27 2024 GMT
*  expire date: Jun 19 19:46:27 2029 GMT
*  subjectAltName: host "localhost" matched cert's "localhost"
*  issuer: CN=localhost-dev-ca
*  SSL certificate verify ok.
* [HTTP/3] peer verified
* Connected to localhost (127.0.0.1) port 5443
* using HTTP/3
* [HTTP/3] [0] OPENED stream for https://localhost:5443/
* [HTTP/3] [0] [:method: GET]
* [HTTP/3] [0] [:scheme: https]
* [HTTP/3] [0] [:authority: localhost:5443]
* [HTTP/3] [0] [:path: /]
* [HTTP/3] [0] [user-agent: curl/8.9.0-DEV]
* [HTTP/3] [0] [accept: */*]
* [HTTP/3] vquic_send(len=62, gso=1200) -> 0, sent=62
* [HTTP/3] vquic_send(len=44, gso=1200) -> 0, sent=44
* [HTTP/3] vquic_send(len=44, gso=1200) -> 0, sent=44
* [HTTP/3] vquic_send(len=113, gso=1200) -> 0, sent=113
* [HTTP/3] vquic_send(len=69, gso=1200) -> 0, sent=69
* [HTTP/3] [0] cf_send(len=79) -> 79, 0
> GET / HTTP/3
> Host: localhost:5443
> User-Agent: curl/8.9.0-DEV
> Accept: */*
>
* Request completely sent off
* [HTTP/3] recvd 1 packets with 445 bytes -> 0
* [HTTP/3] vquic_send(len=43, gso=1200) -> 0, sent=43
* [HTTP/3] [0] cf_recv(total=0) -> -1, 81
* [HTTP/3] recvd 1 packets with 56 bytes -> 0
* [HTTP/3] vquic_send(len=43, gso=1200) -> 0, sent=43
* [HTTP/3] [0] cf_recv(total=0) -> -1, 81
* [HTTP/3] recvd 1 packets with 44 bytes -> 0
* [HTTP/3] [0] cf_recv(total=0) -> -1, 81
* [HTTP/3] recvd 1 packets with 44 bytes -> 0
* [HTTP/3] [0] cf_recv(total=0) -> -1, 81
* [HTTP/3] recvd 1 packets with 44 bytes -> 0
* [HTTP/3] [0] cf_recv(total=0) -> -1, 81
* [HTTP/3] recvd 1 packets with 44 bytes -> 0
* [HTTP/3] [0] cf_recv(total=0) -> -1, 81
* [HTTP/3] recvd 1 packets with 44 bytes -> 0
* [HTTP/3] [0] cf_recv(total=0) -> -1, 81
* [HTTP/3] recvd 1 packets with 48 bytes -> 0
* [HTTP/3] [0] status: 200
* [HTTP/3] [0] <- [HEADERS]
* [HTTP/3] [0] read recvbuf(len=102400) -> 13, 0
* [HTTP/3] vquic_send(len=43, gso=1200) -> 0, sent=43
* [HTTP/3] [0] cf_recv(total=13) -> 13, 0
< HTTP/3 200
* [HTTP/3] [0] cf_recv(total=13) -> -1, 81
* [HTTP/3] recvd 1 packets with 49 bytes -> 0
* [HTTP/3] [0] read recvbuf(len=102400) -> 6, 0
* [HTTP/3] vquic_send(len=43, gso=1200) -> 0, sent=43
* [HTTP/3] [0] cf_recv(total=19) -> 6, 0
<
* [HTTP/3] [0] cf_recv(total=19) -> -1, 81
* [HTTP/3] recvd 1 packets with 45 bytes -> 0
* [HTTP/3] vquic_send(len=43, gso=1200) -> 0, sent=43
* [HTTP/3] [0] cf_recv(total=19) -> -1, 81
* [HTTP/3] [0] cf_recv(total=19) -> -1, 81
* [HTTP/3] recvd 1 packets with 43 bytes -> 0
* [HTTP/3] [0] CLOSED
* [HTTP/3] vquic_send(len=43, gso=1200) -> 0, sent=43
* [HTTP/3] [0] cf_recv(total=19) -> 0, 0
* [HTTP/3] [0] easy handle is done
* Connection #0 to host localhost left intact
root%

root is the response body from my handler.

My code base is in clojure and I'm having trouble creating a standalone java example to reproduce.

Hopefully I can get some guidance on debugging this further.

Thank you.

@lorban
Copy link
Contributor

lorban commented Jul 3, 2024

Looking at the Quiche doc of verify_peer, it looks like there is no way to force it to mandate a client certificate; quoting:

Note that on the server-side, enabling verification of the peer will trigger a certificate request and make authentication errors fatal, but will still allow anonymous clients (i.e. clients that don’t present a certificate at all). Servers can check whether a client presented a certificate by calling peer_cert() if they need to.

So the recommended action from Quiche's doc translated to Jetty would be to use a SecureRequestCustomizer to check that a certificate was presented. That's something you could do, once Jetty 12.0.11 is out.

I'm wondering if there is something Jetty could do internally to close connections that do not present a certificate after Quiche successfully created them.
Let me investigate that and come back to you.

@sandhu
Copy link
Author

sandhu commented Jul 3, 2024

Thank you.

I've tested with 2.0.11-SNAPSHOT and the SecureRequestCustomizer is providing the peer certificates as expected.

As a general question, is there a way for a handler to force a close on a client connection ?

@lorban
Copy link
Contributor

lorban commented Jul 3, 2024

No, you cannot force the server to close a connection.

You'd have to respond with some HTTP error status in the 400-500 ranges in this case.

lorban added a commit that referenced this issue Jul 8, 2024
Signed-off-by: Ludovic Orban <lorban@bitronix.be>
lorban added a commit that referenced this issue Jul 10, 2024
…entAuth is true (#12014)

#11996 deny connection creation for clients missing needed cert

Signed-off-by: Ludovic Orban <lorban@bitronix.be>
@sandhu
Copy link
Author

sandhu commented Jul 10, 2024

Thank you. Tested the jetty-12.0.x branch and it's working as expected.

@lorban
Copy link
Contributor

lorban commented Jul 10, 2024

Thanks for the feedback!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug For general bugs on Jetty side
Projects
None yet
2 participants