From 409916f70776ad2d6dae58b28b2d6a151b9e067e Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 10 Jun 2024 17:23:14 +0200 Subject: [PATCH 1/4] Fixes #11892 - mtls not working with http/3. The client certificate is now exposed in QuicheConnection, so that it can be returned by QuicStreamEndPoint.getSslSessionData(). Not much else is exposed by Quiche, so not much else that we can provide to applications, for example no TLS session id, no cipher suite, etc. Signed-off-by: Simone Bordet --- .../jetty/quic/common/QuicSession.java | 21 +++++++ .../jetty/quic/common/QuicStreamEndPoint.java | 10 ++++ .../jetty/quic/quiche/QuicheConnection.java | 2 + .../quic/quiche/jna/JnaQuicheConnection.java | 3 + .../test/client/transport/AbstractTest.java | 8 +++ .../client/transport/NeedClientAuthTest.java | 60 +++++++++++++++++++ 6 files changed, 104 insertions(+) create mode 100644 jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/NeedClientAuthTest.java diff --git a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java index c853fd1aec86..a99afddcaca0 100644 --- a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java +++ b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java @@ -13,10 +13,14 @@ package org.eclipse.jetty.quic.common; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Collection; import java.util.EventListener; @@ -423,6 +427,23 @@ private void finishOutwardClose(Throwable failure) } } + public X509Certificate[] getPeerCertificates() + { + try + { + byte[] encoded = quicheConnection.getPeerCertificate(); + if (encoded == null) + return null; + CertificateFactory factory = CertificateFactory.getInstance("X509"); + X509Certificate certificate = (X509Certificate)factory.generateCertificate(new ByteArrayInputStream(encoded)); + return new X509Certificate[]{certificate}; + } + catch (CertificateException x) + { + return null; + } + } + @Override public void dump(Appendable out, String indent) throws IOException { diff --git a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicStreamEndPoint.java b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicStreamEndPoint.java index e7e6f8da59a4..b8a2a731a63e 100644 --- a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicStreamEndPoint.java +++ b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicStreamEndPoint.java @@ -16,6 +16,7 @@ import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; +import java.security.cert.X509Certificate; import java.util.List; import java.util.stream.IntStream; @@ -221,6 +222,15 @@ public Object getTransport() return session; } + @Override + public SslSessionData getSslSessionData() + { + X509Certificate[] peerCertificates = getQuicSession().getPeerCertificates(); + if (peerCertificates == null) + return null; + return SslSessionData.from(null, null, null, peerCertificates); + } + public void onWritable() { if (LOG.isDebugEnabled()) diff --git a/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/QuicheConnection.java b/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/QuicheConnection.java index b426242cbcb7..439c25707d0b 100644 --- a/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/QuicheConnection.java +++ b/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/QuicheConnection.java @@ -152,6 +152,8 @@ public final int feedClearBytesForStream(long streamId, ByteBuffer buffer) throw public abstract CloseInfo getLocalCloseInfo(); + public abstract byte[] getPeerCertificate(); + public static class CloseInfo { private final long error; diff --git a/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java b/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java index db05de71b71e..d633de77e21d 100644 --- a/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java +++ b/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java @@ -413,6 +413,7 @@ public void enableQlog(String filename, String title, String desc) throws IOExce } } + @Override public byte[] getPeerCertificate() { try (AutoLock ignore = lock.lock()) @@ -424,6 +425,8 @@ public byte[] getPeerCertificate() size_t_pointer out_len = new size_t_pointer(); LibQuiche.INSTANCE.quiche_conn_peer_cert(quicheConn, out, out_len); int len = out_len.getPointee().intValue(); + if (len <= 0) + return null; return out.getValueAsBytes(len); } } diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java index 806ada98f318..a04f32398b8e 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java @@ -102,6 +102,14 @@ public static Collection transportsWithPushSupport() return transports; } + public static Collection transportsSecure() + { + EnumSet transports = EnumSet.of(Transport.HTTPS, Transport.H2, Transport.H3); + if ("ci".equals(System.getProperty("env"))) + transports.remove(Transport.H3); + return transports; + } + @BeforeEach public void prepare() { diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/NeedClientAuthTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/NeedClientAuthTest.java new file mode 100644 index 000000000000..1191480d51a4 --- /dev/null +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/NeedClientAuthTest.java @@ -0,0 +1,60 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.ee10.test.client.transport; + +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.client.ContentResponse; +import org.eclipse.jetty.ee10.servlet.ServletContextRequest; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class NeedClientAuthTest extends AbstractTest +{ + @ParameterizedTest + @MethodSource("transportsSecure") + public void testNeedClientAuth(Transport transport) throws Exception + { + prepareServer(transport, new HttpServlet() + { + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) + { + // Verify that the request attribute is present. + assertNotNull(request.getAttribute(ServletContextRequest.PEER_CERTIFICATES)); + } + }); + sslContextFactoryServer.setNeedClientAuth(true); + server.start(); + + startClient(transport, httpClient -> + { + // Configure the SslContextFactory to send a certificate to the server. + SslContextFactory.Client clientSSL = httpClient.getSslContextFactory(); + clientSSL.setKeyStorePath("src/test/resources/keystore.p12"); + clientSSL.setKeyStorePassword("storepwd"); + clientSSL.setCertAlias("mykey"); + }); + + ContentResponse response = client.newRequest(newURI(transport)).send(); + + assertEquals(HttpStatus.OK_200, response.getStatus()); + } +} From 9abb881f8e55316a9008aeb49d3ed3f1596a3b08 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 11 Jun 2024 17:11:31 +0200 Subject: [PATCH 2/4] Updates after review. Signed-off-by: Simone Bordet --- .../jetty/quic/quiche/foreign/ForeignQuicheConnection.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-foreign/src/main/java/org/eclipse/jetty/quic/quiche/foreign/ForeignQuicheConnection.java b/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-foreign/src/main/java/org/eclipse/jetty/quic/quiche/foreign/ForeignQuicheConnection.java index 9e67935e59c1..2327aa664948 100644 --- a/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-foreign/src/main/java/org/eclipse/jetty/quic/quiche/foreign/ForeignQuicheConnection.java +++ b/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-foreign/src/main/java/org/eclipse/jetty/quic/quiche/foreign/ForeignQuicheConnection.java @@ -518,6 +518,7 @@ public static ForeignQuicheConnection tryAccept(QuicheConfig quicheConfig, Token } } + @Override public byte[] getPeerCertificate() { try (AutoLock ignore = lock.lock()) @@ -532,7 +533,7 @@ public byte[] getPeerCertificate() quiche_h.quiche_conn_peer_cert(quicheConn, outSegment, outLenSegment); long outLen = outLenSegment.get(NativeHelper.C_LONG, 0L); - if (outLen == 0L) + if (outLen <= 0L) return null; byte[] out = new byte[(int)outLen]; // dereference outSegment pointer From af353259c549f80c46d98c50d892b9e772cf3a99 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 11 Jun 2024 18:27:38 +0200 Subject: [PATCH 3/4] Fixed --enable-native-access command line option to run tests, as the foreign dependency is in the class-path. Signed-off-by: Simone Bordet --- .../jetty-ee10-tests/jetty-ee10-test-client-transports/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/pom.xml b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/pom.xml index b78feb1279f3..682078b859df 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/pom.xml +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/pom.xml @@ -112,7 +112,7 @@ @{argLine} ${jetty.surefire.argLine} - --enable-native-access org.eclipse.jetty.quic.quiche.foreign + --enable-native-access=ALL-UNNAMED From a1513c72ce86823e901d2653828d6f5b33b76cfc Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 12 Jun 2024 17:28:31 +0200 Subject: [PATCH 4/4] Updates after review. Signed-off-by: Simone Bordet --- .../java/org/eclipse/jetty/quic/common/QuicSession.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java index a99afddcaca0..8f998fa8743b 100644 --- a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java +++ b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java @@ -427,6 +427,14 @@ private void finishOutwardClose(Throwable failure) } } + /** + *

Returns the peer certificates chain.

+ *

Due to current Quiche C API limitations (that the Rust version does not have), + * only the last certificate in the chain is returned. + * This may change in the future when the C APIs are aligned to the Rust APIs.

+ * + * @return the peer certificates chain (currently only the last certificate in the chain) + */ public X509Certificate[] getPeerCertificates() { try