diff --git a/jetty-core/jetty-http3/jetty-http3-tests/pom.xml b/jetty-core/jetty-http3/jetty-http3-tests/pom.xml index abbdc3f0235d..be19d51d3f3e 100644 --- a/jetty-core/jetty-http3/jetty-http3-tests/pom.xml +++ b/jetty-core/jetty-http3/jetty-http3-tests/pom.xml @@ -66,4 +66,25 @@ + + + enable-foreign + + [22,) + + + + + maven-surefire-plugin + + @{argLine} + ${jetty.surefire.argLine} + --enable-native-access=ALL-UNNAMED + + + + + + + diff --git a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java index 7e0b11832a20..3490b1b67ba7 100644 --- a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java +++ b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java @@ -38,6 +38,7 @@ import org.eclipse.jetty.http3.server.AbstractHTTP3ServerConnectionFactory; import org.eclipse.jetty.http3.server.internal.HTTP3SessionServer; import org.eclipse.jetty.quic.client.ClientQuicSession; +import org.eclipse.jetty.quic.common.QuicErrorCode; import org.eclipse.jetty.quic.common.QuicSession; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -640,4 +641,24 @@ public void onResponse(Stream.Client stream, HeadersFrame frame) assertTrue(responseLatch.await(5, TimeUnit.SECONDS)); } + + @Test + public void testMissingNeededClientCertificateDeniesConnection() throws Exception + { + start(new Session.Server.Listener() {}); + connector.getQuicConfiguration().getSslContextFactory().setNeedClientAuth(true); + + CountDownLatch latch = new CountDownLatch(1); + newSession(new Session.Client.Listener() + { + @Override + public void onDisconnect(Session session, long error, String reason) + { + assertEquals(QuicErrorCode.CONNECTION_REFUSED.code(), error); + assertEquals("missing_client_certificate_chain", reason); + latch.countDown(); + } + }); + assertTrue(latch.await(5, TimeUnit.SECONDS)); + } } diff --git a/jetty-core/jetty-quic/jetty-quic-client/pom.xml b/jetty-core/jetty-quic/jetty-quic-client/pom.xml index 539da0b1cfa9..f05e727eba7c 100644 --- a/jetty-core/jetty-quic/jetty-quic-client/pom.xml +++ b/jetty-core/jetty-quic/jetty-quic-client/pom.xml @@ -73,7 +73,7 @@ @{argLine} ${jetty.surefire.argLine} - --enable-native-access org.eclipse.jetty.quic.quiche.foreign + --enable-native-access=ALL-UNNAMED diff --git a/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicSession.java b/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicSession.java index 28248924767c..3d87cc0babef 100644 --- a/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicSession.java +++ b/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicSession.java @@ -74,6 +74,12 @@ protected ProtocolSession createProtocolSession() return new ClientProtocolSession(this); } + @Override + protected boolean validateNewlyEstablishedConnection() + { + return true; + } + @Override public Connection newConnection(QuicStreamEndPoint endPoint) { 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 75ee111e659a..b6a54be7346c 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 @@ -319,6 +319,9 @@ public Runnable process(SocketAddress remoteAddress, ByteBuffer cipherBufferIn) ProtocolSession protocol = protocolSession; if (protocol == null) { + if (!validateNewlyEstablishedConnection()) + return null; + protocolSession = protocol = createProtocolSession(); addManaged(protocol); } @@ -343,6 +346,11 @@ protected Runnable pollTask() protected abstract ProtocolSession createProtocolSession(); + /** + * @return true if the connection is valid, false otherwise. + */ + protected abstract boolean validateNewlyEstablishedConnection(); + List getWritableStreamIds() { return quicheConnection.writableStreamIds(); diff --git a/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/PemExporter.java b/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/PemExporter.java index cba6a9aeaa56..5faac9b12e79 100644 --- a/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/PemExporter.java +++ b/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/PemExporter.java @@ -80,6 +80,9 @@ public static Path[] exportKeyPair(KeyStore keyStore, String alias, char[] keyPa try (OutputStream os = Files.newOutputStream(paths[1])) { Certificate[] certChain = keyStore.getCertificateChain(alias); + if (certChain == null) + throw new IllegalArgumentException("Alias does not exist in key store: " + alias); + for (Certificate cert : certChain) writeAsPEM(os, cert); Files.setPosixFilePermissions(paths[1], Set.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE)); diff --git a/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConnection.java b/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConnection.java index ce9d77f44844..f45355882439 100644 --- a/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConnection.java +++ b/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConnection.java @@ -71,6 +71,11 @@ public Connector getQuicServerConnector() return connector; } + ServerQuicConfiguration getQuicConfiguration() + { + return quicConfiguration; + } + @Override public void onOpen() { diff --git a/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicSession.java b/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicSession.java index 20e69736d2ff..9803843bde43 100644 --- a/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicSession.java +++ b/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicSession.java @@ -25,7 +25,7 @@ import org.eclipse.jetty.io.CyclicTimeouts; import org.eclipse.jetty.io.RuntimeIOException; import org.eclipse.jetty.quic.common.ProtocolSession; -import org.eclipse.jetty.quic.common.QuicConnection; +import org.eclipse.jetty.quic.common.QuicErrorCode; import org.eclipse.jetty.quic.common.QuicSession; import org.eclipse.jetty.quic.common.QuicStreamEndPoint; import org.eclipse.jetty.quic.quiche.QuicheConnection; @@ -46,7 +46,7 @@ public class ServerQuicSession extends QuicSession implements CyclicTimeouts.Exp private final Connector connector; private long expireNanoTime = Long.MAX_VALUE; - public ServerQuicSession(Executor executor, Scheduler scheduler, ByteBufferPool bufferPool, QuicheConnection quicheConnection, QuicConnection connection, SocketAddress remoteAddress, Connector connector) + public ServerQuicSession(Executor executor, Scheduler scheduler, ByteBufferPool bufferPool, QuicheConnection quicheConnection, ServerQuicConnection connection, SocketAddress remoteAddress, Connector connector) { super(executor, scheduler, bufferPool, quicheConnection, connection, remoteAddress); this.connector = connector; @@ -67,6 +67,18 @@ protected ProtocolSession createProtocolSession() return new ServerProtocolSession(this); } + @Override + protected boolean validateNewlyEstablishedConnection() + { + if (getQuicConnection().getQuicConfiguration().getSslContextFactory().getNeedClientAuth() && getPeerCertificates() == null) + { + outwardClose(QuicErrorCode.CONNECTION_REFUSED.code(), "missing_client_certificate_chain"); + flush(); + return false; + } + return true; + } + @Override public Connection newConnection(QuicStreamEndPoint endPoint) {