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

Deny HTTP/3 connection creation for clients missing cert when needClientAuth is true #12014

Merged
merged 11 commits into from
Jul 10, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ public long newStreamId(StreamType streamType)
return getQuicSession().newStreamId(streamType);
}

@Override
public void disconnect(long code, String reason)
{
super.disconnect(code, reason);
session.getListener().onDisconnect(session, code, reason);
}

@Override
protected void onStart()
{
Expand Down
21 changes: 21 additions & 0 deletions jetty-core/jetty-http3/jetty-http3-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,25 @@
</dependency>
</dependencies>

<profiles>
<profile>
<id>enable-foreign</id>
<activation>
<jdk>[22,)</jdk>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>@{argLine}
${jetty.surefire.argLine}
--enable-native-access=ALL-UNNAMED</argLine>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -63,34 +63,41 @@ public class AbstractClientServerTest

protected void start(Handler handler) throws Exception
{
ServerQuicConfiguration quicConfiguration = newServerQuicConfiguration();
ServerQuicConfiguration quicConfiguration = newServerQuicConfiguration(false);
prepareServer(quicConfiguration, new HTTP3ServerConnectionFactory(quicConfiguration));
server.setHandler(handler);
server.start();
startClient();
}

private ServerQuicConfiguration newServerQuicConfiguration()
protected void start(Session.Server.Listener listener) throws Exception
{
SslContextFactory.Server sslServer = new SslContextFactory.Server();
sslServer.setKeyStorePath("src/test/resources/keystore.p12");
sslServer.setKeyStorePassword("storepwd");
return new ServerQuicConfiguration(sslServer, workDir.getEmptyPathDir());
startServer(false, listener);
startClient();
}

protected void start(Session.Server.Listener listener) throws Exception
protected void start(boolean needClientAuth, Session.Server.Listener listener) throws Exception
lorban marked this conversation as resolved.
Show resolved Hide resolved
{
startServer(listener);
startServer(needClientAuth, listener);
startClient();
}

protected void startServer(Session.Server.Listener listener) throws Exception
private void startServer(boolean needClientAuth, Session.Server.Listener listener) throws Exception
{
ServerQuicConfiguration quicConfiguration = newServerQuicConfiguration();
ServerQuicConfiguration quicConfiguration = newServerQuicConfiguration(needClientAuth);
prepareServer(quicConfiguration, new RawHTTP3ServerConnectionFactory(quicConfiguration, listener));
server.start();
}

private ServerQuicConfiguration newServerQuicConfiguration(boolean needClientAuth)
{
SslContextFactory.Server sslServer = new SslContextFactory.Server();
sslServer.setNeedClientAuth(needClientAuth);
sslServer.setKeyStorePath("src/test/resources/keystore.p12");
sslServer.setKeyStorePassword("storepwd");
return new ServerQuicConfiguration(sslServer, workDir.getEmptyPathDir());
}

private void prepareServer(ServerQuicConfiguration quicConfiguration, ConnectionFactory serverConnectionFactory)
{
QueuedThreadPool serverThreads = new QueuedThreadPool();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@

package org.eclipse.jetty.http3.tests;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.AbstractMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

Expand All @@ -38,7 +40,9 @@
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.eclipse.jetty.quic.server.ServerQuicSession;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
Expand All @@ -47,9 +51,11 @@
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

public class ClientServerTest extends AbstractClientServerTest
{
Expand All @@ -58,7 +64,7 @@ public void testConnectTriggersSettingsFrame() throws Exception
{
CountDownLatch serverPrefaceLatch = new CountDownLatch(1);
CountDownLatch serverSettingsLatch = new CountDownLatch(1);
start(new Session.Server.Listener()
start(true, new Session.Server.Listener()
lorban marked this conversation as resolved.
Show resolved Hide resolved
{
@Override
public Map<Long, Long> onPreface(Session session)
Expand Down Expand Up @@ -640,4 +646,46 @@ public void onResponse(Stream.Client stream, HeadersFrame frame)

assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
}

@Test
public void testMissingNeededClientCertDeniesConnection() throws Exception
lorban marked this conversation as resolved.
Show resolved Hide resolved
{
AtomicReference<HTTP3SessionServer> serverSessionRef = new AtomicReference<>();
AtomicReference<Long> serverErrorRef = new AtomicReference<>();
AtomicReference<String> serverReasonRef = new AtomicReference<>();
CountDownLatch serverDisconnectLatch = new CountDownLatch(1);
start(true, new Session.Server.Listener()
{
@Override
public void onDisconnect(Session session, long error, String reason)
{
serverSessionRef.set((HTTP3SessionServer)session);
serverErrorRef.set(error);
serverReasonRef.set(reason);
serverDisconnectLatch.countDown();
}
});

try
{
newSession(new Session.Client.Listener() {});
fail("expected ExecutionException");
}
catch (ExecutionException ex)
{
assertInstanceOf(IOException.class, ex.getCause());
}

assertTrue(serverDisconnectLatch.await(5, TimeUnit.SECONDS));

assertEquals(QuicErrorCode.CONNECTION_REFUSED.code(), serverErrorRef.get());
assertEquals("missing_client_cert", serverReasonRef.get());
HTTP3SessionServer serverSession = serverSessionRef.get();
assertTrue(serverSession.isClosed());
assertTrue(serverSession.getStreams().isEmpty());
ServerQuicSession serverQuicSession = serverSession.getProtocolSession().getQuicSession();
// While HTTP/3 is completely closed, QUIC may still be exchanging packets, so we need to await().
await().atMost(3, TimeUnit.SECONDS).until(() -> serverQuicSession.getQuicStreamEndPoints().isEmpty());
await().atMost(3, TimeUnit.SECONDS).until(() -> serverQuicSession.getQuicConnection().getQuicSessions().isEmpty());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ public QuicStreamEndPoint getOrCreateStreamEndPoint(long streamId, Consumer<Quic
return session.getOrCreateStreamEndPoint(streamId, consumer);
}

public void disconnect(long code, String reason)
{
session.outwardClose(code, reason);
}

protected void processWritableStreams()
{
List<Long> writableStreamIds = session.getWritableStreamIds();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@
import org.eclipse.jetty.io.CyclicTimeouts;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.RetainableByteBuffer;
import org.eclipse.jetty.quic.common.ProtocolSession;
import org.eclipse.jetty.quic.common.QuicConfiguration;
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.quiche.QuicheConfig;
import org.eclipse.jetty.quic.quiche.QuicheConnection;
Expand Down Expand Up @@ -114,6 +116,20 @@ protected QuicSession createSession(SocketAddress remoteAddress, ByteBuffer ciph
}
else
{
if (quicConfiguration.getSslContextFactory().getNeedClientAuth())
{
byte[] peerCertificate = quicheConnection.getPeerCertificate();
if (peerCertificate == null)
{
ServerQuicSession session = newQuicSession(remoteAddress, quicheConnection);
ProtocolSession protocolSession = session.createProtocolSession();
protocolSession.disconnect(QuicErrorCode.CONNECTION_REFUSED.code(), "missing_client_cert");
// Send the response packet(s) that disconnect() generated.
session.flush();
return null;
}
}

ServerQuicSession session = newQuicSession(remoteAddress, quicheConnection);
// Send the response packet(s) that tryAccept() generated.
session.flush();
lorban marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Loading