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)
{