From 17c8a76efb92e4095939033aeaf1564d4fe12801 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Wed, 10 Jul 2024 23:17:14 +0200 Subject: [PATCH] Deny HTTP/3 connection creation for clients missing cert when needClientAuth is true (#12014) #11996 deny connection creation for clients missing needed cert Signed-off-by: Ludovic Orban --- .../jetty-http3/jetty-http3-tests/pom.xml | 21 +++++++++++++++++++ .../jetty/http3/tests/ClientServerTest.java | 21 +++++++++++++++++++ .../jetty-quic/jetty-quic-client/pom.xml | 2 +- .../jetty/quic/client/ClientQuicSession.java | 6 ++++++ .../jetty/quic/common/QuicSession.java | 8 +++++++ .../jetty/quic/quiche/PemExporter.java | 3 +++ .../quic/server/ServerQuicConnection.java | 5 +++++ .../jetty/quic/server/ServerQuicSession.java | 16 ++++++++++++-- 8 files changed, 79 insertions(+), 3 deletions(-) diff --git a/jetty-core/jetty-http3/jetty-http3-tests/pom.xml b/jetty-core/jetty-http3/jetty-http3-tests/pom.xml index abbdc3f0235..be19d51d3f3 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 7e0b11832a2..3490b1b67ba 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.frames.SettingsFrame; 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 class ClientServerTest extends AbstractClientServerTest 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 539da0b1cfa..f05e727eba7 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 28248924767..3d87cc0babe 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 @@ public class ClientQuicSession extends QuicSession 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 75ee111e659..b6a54be7346 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 abstract class QuicSession extends ContainerLifeCycle ProtocolSession protocol = protocolSession; if (protocol == null) { + if (!validateNewlyEstablishedConnection()) + return null; + protocolSession = protocol = createProtocolSession(); addManaged(protocol); } @@ -343,6 +346,11 @@ public abstract class QuicSession extends ContainerLifeCycle 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 cba6a9aeaa5..5faac9b12e7 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 class PemExporter 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 ce9d77f4484..f4535588243 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 class ServerQuicConnection extends QuicConnection 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 20e69736d2f..9803843bde4 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.Connection; 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 @@ public class ServerQuicSession extends QuicSession implements CyclicTimeouts.Exp 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) {