#9397 add trust store config to H3
Signed-off-by: Ludovic Orban <lorban@bitronix.be>
This commit is contained in:
parent
e30b23aca6
commit
871022f3b0
|
@ -13,8 +13,10 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.http3.tests;
|
package org.eclipse.jetty.http3.tests;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
import java.lang.management.ManagementFactory;
|
import java.lang.management.ManagementFactory;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.security.KeyStore;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import javax.management.MBeanServer;
|
import javax.management.MBeanServer;
|
||||||
|
|
||||||
|
@ -35,15 +37,21 @@ import org.eclipse.jetty.jmx.MBeanContainer;
|
||||||
import org.eclipse.jetty.server.ConnectionFactory;
|
import org.eclipse.jetty.server.ConnectionFactory;
|
||||||
import org.eclipse.jetty.server.Handler;
|
import org.eclipse.jetty.server.Handler;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
|
||||||
|
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
|
||||||
import org.eclipse.jetty.util.component.LifeCycle;
|
import org.eclipse.jetty.util.component.LifeCycle;
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
|
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
|
||||||
|
@ExtendWith(WorkDirExtension.class)
|
||||||
public class AbstractClientServerTest
|
public class AbstractClientServerTest
|
||||||
{
|
{
|
||||||
|
public WorkDir workDir;
|
||||||
|
|
||||||
@RegisterExtension
|
@RegisterExtension
|
||||||
final BeforeTestExecutionCallback printMethodName = context ->
|
final BeforeTestExecutionCallback printMethodName = context ->
|
||||||
System.err.printf("Running %s.%s() %s%n", context.getRequiredTestClass().getSimpleName(), context.getRequiredTestMethod().getName(), context.getDisplayName());
|
System.err.printf("Running %s.%s() %s%n", context.getRequiredTestClass().getSimpleName(), context.getRequiredTestMethod().getName(), context.getDisplayName());
|
||||||
|
@ -81,6 +89,7 @@ public class AbstractClientServerTest
|
||||||
serverThreads.setName("server");
|
serverThreads.setName("server");
|
||||||
server = new Server(serverThreads);
|
server = new Server(serverThreads);
|
||||||
connector = new HTTP3ServerConnector(server, sslContextFactory, serverConnectionFactory);
|
connector = new HTTP3ServerConnector(server, sslContextFactory, serverConnectionFactory);
|
||||||
|
connector.getQuicConfiguration().setPemWorkDirectory(workDir.getEmptyPathDir());
|
||||||
server.addConnector(connector);
|
server.addConnector(connector);
|
||||||
MBeanContainer mbeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
|
MBeanContainer mbeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
|
||||||
server.addBean(mbeanContainer);
|
server.addBean(mbeanContainer);
|
||||||
|
@ -88,8 +97,16 @@ public class AbstractClientServerTest
|
||||||
|
|
||||||
protected void startClient() throws Exception
|
protected void startClient() throws Exception
|
||||||
{
|
{
|
||||||
|
KeyStore trustStore = KeyStore.getInstance("PKCS12");
|
||||||
|
try (InputStream is = getClass().getResourceAsStream("/keystore.p12"))
|
||||||
|
{
|
||||||
|
trustStore.load(is, "storepwd".toCharArray());
|
||||||
|
}
|
||||||
|
|
||||||
http3Client = new HTTP3Client();
|
http3Client = new HTTP3Client();
|
||||||
http3Client.getQuicConfiguration().setVerifyPeerCertificates(false);
|
SslContextFactory.Client clientSslContextFactory = new SslContextFactory.Client();
|
||||||
|
clientSslContextFactory.setTrustStore(trustStore);
|
||||||
|
http3Client.getClientConnector().setSslContextFactory(clientSslContextFactory);
|
||||||
httpClient = new HttpClient(new HttpClientTransportDynamic(new ClientConnectionFactoryOverHTTP3.HTTP3(http3Client)));
|
httpClient = new HttpClient(new HttpClientTransportDynamic(new ClientConnectionFactoryOverHTTP3.HTTP3(http3Client)));
|
||||||
QueuedThreadPool clientThreads = new QueuedThreadPool();
|
QueuedThreadPool clientThreads = new QueuedThreadPool();
|
||||||
clientThreads.setName("client");
|
clientThreads.setName("client");
|
||||||
|
|
|
@ -373,6 +373,7 @@ public class ClientConnector extends ContainerLifeCycle
|
||||||
selectorManager = newSelectorManager();
|
selectorManager = newSelectorManager();
|
||||||
selectorManager.setConnectTimeout(getConnectTimeout().toMillis());
|
selectorManager.setConnectTimeout(getConnectTimeout().toMillis());
|
||||||
addBean(selectorManager);
|
addBean(selectorManager);
|
||||||
|
configurator.addBean(this);
|
||||||
super.doStart();
|
super.doStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -381,6 +382,7 @@ public class ClientConnector extends ContainerLifeCycle
|
||||||
{
|
{
|
||||||
super.doStop();
|
super.doStop();
|
||||||
removeBean(selectorManager);
|
removeBean(selectorManager);
|
||||||
|
configurator.removeBean(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SslContextFactory.Client newSslContextFactory()
|
protected SslContextFactory.Client newSslContextFactory()
|
||||||
|
@ -588,7 +590,7 @@ public class ClientConnector extends ContainerLifeCycle
|
||||||
/**
|
/**
|
||||||
* <p>Configures a {@link ClientConnector}.</p>
|
* <p>Configures a {@link ClientConnector}.</p>
|
||||||
*/
|
*/
|
||||||
public static class Configurator
|
public static class Configurator extends ContainerLifeCycle
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* <p>Returns whether the connection to a given {@link SocketAddress} is intrinsically secure.</p>
|
* <p>Returns whether the connection to a given {@link SocketAddress} is intrinsically secure.</p>
|
||||||
|
|
|
@ -18,6 +18,7 @@ import java.net.InetSocketAddress;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -80,7 +81,10 @@ public class ClientQuicConnection extends QuicConnection
|
||||||
QuicheConfig quicheConfig = new QuicheConfig();
|
QuicheConfig quicheConfig = new QuicheConfig();
|
||||||
quicheConfig.setApplicationProtos(protocols.toArray(String[]::new));
|
quicheConfig.setApplicationProtos(protocols.toArray(String[]::new));
|
||||||
quicheConfig.setDisableActiveMigration(quicConfiguration.isDisableActiveMigration());
|
quicheConfig.setDisableActiveMigration(quicConfiguration.isDisableActiveMigration());
|
||||||
quicheConfig.setVerifyPeer(quicConfiguration.isVerifyPeerCertificates());
|
quicheConfig.setVerifyPeer(!connector.getSslContextFactory().isTrustAll());
|
||||||
|
Path trustStorePath = (Path)quicConfiguration.getImplementationSpecifixContext().get(QuicClientConnectorConfigurator.TRUSTSTORE_PATH_KEY);
|
||||||
|
if (trustStorePath != null)
|
||||||
|
quicheConfig.setTrustedCertsPemPath(trustStorePath.toString());
|
||||||
// Idle timeouts must not be managed by Quiche.
|
// Idle timeouts must not be managed by Quiche.
|
||||||
quicheConfig.setMaxIdleTimeout(0L);
|
quicheConfig.setMaxIdleTimeout(0L);
|
||||||
quicheConfig.setInitialMaxData((long)quicConfiguration.getSessionRecvWindow());
|
quicheConfig.setInitialMaxData((long)quicConfiguration.getSessionRecvWindow());
|
||||||
|
@ -147,6 +151,13 @@ public class ClientQuicConnection extends QuicConnection
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onFailure(Throwable failure)
|
||||||
|
{
|
||||||
|
pendingSessions.values().forEach(session -> outwardClose(session, failure));
|
||||||
|
super.onFailure(failure);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onIdleExpired()
|
public boolean onIdleExpired()
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,6 +19,8 @@ import java.nio.channels.DatagramChannel;
|
||||||
import java.nio.channels.SelectableChannel;
|
import java.nio.channels.SelectableChannel;
|
||||||
import java.nio.channels.SelectionKey;
|
import java.nio.channels.SelectionKey;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.security.KeyStore;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.UnaryOperator;
|
import java.util.function.UnaryOperator;
|
||||||
|
@ -30,6 +32,8 @@ import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.io.ManagedSelector;
|
import org.eclipse.jetty.io.ManagedSelector;
|
||||||
import org.eclipse.jetty.io.SocketChannelEndPoint;
|
import org.eclipse.jetty.io.SocketChannelEndPoint;
|
||||||
import org.eclipse.jetty.quic.common.QuicConfiguration;
|
import org.eclipse.jetty.quic.common.QuicConfiguration;
|
||||||
|
import org.eclipse.jetty.quic.quiche.PemExporter;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>A QUIC specific {@link ClientConnector.Configurator}.</p>
|
* <p>A QUIC specific {@link ClientConnector.Configurator}.</p>
|
||||||
|
@ -41,6 +45,8 @@ import org.eclipse.jetty.quic.common.QuicConfiguration;
|
||||||
*/
|
*/
|
||||||
public class QuicClientConnectorConfigurator extends ClientConnector.Configurator
|
public class QuicClientConnectorConfigurator extends ClientConnector.Configurator
|
||||||
{
|
{
|
||||||
|
static final String TRUSTSTORE_PATH_KEY = QuicClientConnectorConfigurator.class.getName() + ".trustStorePath";
|
||||||
|
|
||||||
private final QuicConfiguration configuration = new QuicConfiguration();
|
private final QuicConfiguration configuration = new QuicConfiguration();
|
||||||
private final UnaryOperator<Connection> configurator;
|
private final UnaryOperator<Connection> configurator;
|
||||||
|
|
||||||
|
@ -56,7 +62,6 @@ public class QuicClientConnectorConfigurator extends ClientConnector.Configurato
|
||||||
configuration.setSessionRecvWindow(16 * 1024 * 1024);
|
configuration.setSessionRecvWindow(16 * 1024 * 1024);
|
||||||
configuration.setBidirectionalStreamRecvWindow(8 * 1024 * 1024);
|
configuration.setBidirectionalStreamRecvWindow(8 * 1024 * 1024);
|
||||||
configuration.setDisableActiveMigration(true);
|
configuration.setDisableActiveMigration(true);
|
||||||
configuration.setVerifyPeerCertificates(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public QuicConfiguration getQuicConfiguration()
|
public QuicConfiguration getQuicConfiguration()
|
||||||
|
@ -64,6 +69,19 @@ public class QuicClientConnectorConfigurator extends ClientConnector.Configurato
|
||||||
return configuration;
|
return configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doStart() throws Exception
|
||||||
|
{
|
||||||
|
ClientConnector clientConnector = getBean(ClientConnector.class);
|
||||||
|
SslContextFactory.Client sslContextFactory = clientConnector.getSslContextFactory();
|
||||||
|
KeyStore trustStore = sslContextFactory.getTrustStore();
|
||||||
|
if (trustStore != null)
|
||||||
|
{
|
||||||
|
Path trustStorePath = PemExporter.exportTrustStore(trustStore, Path.of(System.getProperty("java.io.tmpdir")));
|
||||||
|
configuration.getImplementationSpecifixContext().put(TRUSTSTORE_PATH_KEY, trustStorePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isIntrinsicallySecure(ClientConnector clientConnector, SocketAddress address)
|
public boolean isIntrinsicallySecure(ClientConnector clientConnector, SocketAddress address)
|
||||||
{
|
{
|
||||||
|
@ -74,6 +92,7 @@ public class QuicClientConnectorConfigurator extends ClientConnector.Configurato
|
||||||
public ChannelWithAddress newChannelWithAddress(ClientConnector clientConnector, SocketAddress address, Map<String, Object> context) throws IOException
|
public ChannelWithAddress newChannelWithAddress(ClientConnector clientConnector, SocketAddress address, Map<String, Object> context) throws IOException
|
||||||
{
|
{
|
||||||
context.put(QuicConfiguration.CONTEXT_KEY, configuration);
|
context.put(QuicConfiguration.CONTEXT_KEY, configuration);
|
||||||
|
|
||||||
DatagramChannel channel = DatagramChannel.open();
|
DatagramChannel channel = DatagramChannel.open();
|
||||||
if (clientConnector.getBindAddress() == null)
|
if (clientConnector.getBindAddress() == null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,7 +14,9 @@
|
||||||
package org.eclipse.jetty.quic.client;
|
package org.eclipse.jetty.quic.client;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
import java.security.KeyStore;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
@ -36,18 +38,24 @@ import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.eclipse.jetty.server.Request;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
|
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
|
||||||
|
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
|
||||||
import org.eclipse.jetty.util.component.LifeCycle;
|
import org.eclipse.jetty.util.component.LifeCycle;
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Tag;
|
import org.junit.jupiter.api.Tag;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.core.Is.is;
|
import static org.hamcrest.core.Is.is;
|
||||||
|
|
||||||
|
@ExtendWith(WorkDirExtension.class)
|
||||||
public class End2EndClientTest
|
public class End2EndClientTest
|
||||||
{
|
{
|
||||||
|
public WorkDir workDir;
|
||||||
|
|
||||||
private Server server;
|
private Server server;
|
||||||
private QuicServerConnector connector;
|
private QuicServerConnector connector;
|
||||||
private HttpClient client;
|
private HttpClient client;
|
||||||
|
@ -61,8 +69,14 @@ public class End2EndClientTest
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setUp() throws Exception
|
public void setUp() throws Exception
|
||||||
{
|
{
|
||||||
|
KeyStore keyStore = KeyStore.getInstance("PKCS12");
|
||||||
|
try (InputStream is = getClass().getResourceAsStream("/keystore.p12"))
|
||||||
|
{
|
||||||
|
keyStore.load(is, "storepwd".toCharArray());
|
||||||
|
}
|
||||||
|
|
||||||
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
|
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
|
||||||
sslContextFactory.setKeyStorePath("src/test/resources/keystore.p12");
|
sslContextFactory.setKeyStore(keyStore);
|
||||||
sslContextFactory.setKeyStorePassword("storepwd");
|
sslContextFactory.setKeyStorePassword("storepwd");
|
||||||
|
|
||||||
server = new Server();
|
server = new Server();
|
||||||
|
@ -71,6 +85,7 @@ public class End2EndClientTest
|
||||||
HttpConnectionFactory http1 = new HttpConnectionFactory(httpConfiguration);
|
HttpConnectionFactory http1 = new HttpConnectionFactory(httpConfiguration);
|
||||||
HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(httpConfiguration);
|
HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(httpConfiguration);
|
||||||
connector = new QuicServerConnector(server, sslContextFactory, http1, http2);
|
connector = new QuicServerConnector(server, sslContextFactory, http1, http2);
|
||||||
|
connector.getQuicConfiguration().setPemWorkDirectory(workDir.getEmptyPathDir());
|
||||||
server.addConnector(connector);
|
server.addConnector(connector);
|
||||||
|
|
||||||
server.setHandler(new AbstractHandler()
|
server.setHandler(new AbstractHandler()
|
||||||
|
@ -86,11 +101,13 @@ public class End2EndClientTest
|
||||||
|
|
||||||
server.start();
|
server.start();
|
||||||
|
|
||||||
|
ClientConnector clientConnector = new ClientConnector(new QuicClientConnectorConfigurator());
|
||||||
|
SslContextFactory.Client clientSslContextFactory = new SslContextFactory.Client();
|
||||||
|
clientSslContextFactory.setTrustStore(keyStore);
|
||||||
|
clientConnector.setSslContextFactory(clientSslContextFactory);
|
||||||
ClientConnectionFactory.Info http1Info = HttpClientConnectionFactory.HTTP11;
|
ClientConnectionFactory.Info http1Info = HttpClientConnectionFactory.HTTP11;
|
||||||
ClientConnectionFactoryOverHTTP2.HTTP2 http2Info = new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client());
|
ClientConnectionFactoryOverHTTP2.HTTP2 http2Info = new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector));
|
||||||
QuicClientConnectorConfigurator configurator = new QuicClientConnectorConfigurator();
|
HttpClientTransportDynamic transport = new HttpClientTransportDynamic(clientConnector, http1Info, http2Info);
|
||||||
configurator.getQuicConfiguration().setVerifyPeerCertificates(false);
|
|
||||||
HttpClientTransportDynamic transport = new HttpClientTransportDynamic(new ClientConnector(configurator), http1Info, http2Info);
|
|
||||||
client = new HttpClient(transport);
|
client = new HttpClient(transport);
|
||||||
client.start();
|
client.start();
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,10 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.quic.common;
|
package org.eclipse.jetty.quic.common;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>A record that captures QUIC configuration parameters.</p>
|
* <p>A record that captures QUIC configuration parameters.</p>
|
||||||
|
@ -24,12 +27,13 @@ public class QuicConfiguration
|
||||||
|
|
||||||
private List<String> protocols = List.of();
|
private List<String> protocols = List.of();
|
||||||
private boolean disableActiveMigration;
|
private boolean disableActiveMigration;
|
||||||
private boolean verifyPeerCertificates;
|
|
||||||
private int maxBidirectionalRemoteStreams;
|
private int maxBidirectionalRemoteStreams;
|
||||||
private int maxUnidirectionalRemoteStreams;
|
private int maxUnidirectionalRemoteStreams;
|
||||||
private int sessionRecvWindow;
|
private int sessionRecvWindow;
|
||||||
private int bidirectionalStreamRecvWindow;
|
private int bidirectionalStreamRecvWindow;
|
||||||
private int unidirectionalStreamRecvWindow;
|
private int unidirectionalStreamRecvWindow;
|
||||||
|
private Path pemWorkDirectory;
|
||||||
|
private final Map<String, Object> implementationSpecifixContext = new HashMap<>();
|
||||||
|
|
||||||
public List<String> getProtocols()
|
public List<String> getProtocols()
|
||||||
{
|
{
|
||||||
|
@ -51,16 +55,6 @@ public class QuicConfiguration
|
||||||
this.disableActiveMigration = disableActiveMigration;
|
this.disableActiveMigration = disableActiveMigration;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isVerifyPeerCertificates()
|
|
||||||
{
|
|
||||||
return verifyPeerCertificates;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVerifyPeerCertificates(boolean verifyPeerCertificates)
|
|
||||||
{
|
|
||||||
this.verifyPeerCertificates = verifyPeerCertificates;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMaxBidirectionalRemoteStreams()
|
public int getMaxBidirectionalRemoteStreams()
|
||||||
{
|
{
|
||||||
return maxBidirectionalRemoteStreams;
|
return maxBidirectionalRemoteStreams;
|
||||||
|
@ -110,4 +104,19 @@ public class QuicConfiguration
|
||||||
{
|
{
|
||||||
this.unidirectionalStreamRecvWindow = unidirectionalStreamRecvWindow;
|
this.unidirectionalStreamRecvWindow = unidirectionalStreamRecvWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Path getPemWorkDirectory()
|
||||||
|
{
|
||||||
|
return pemWorkDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPemWorkDirectory(Path pemWorkDirectory)
|
||||||
|
{
|
||||||
|
this.pemWorkDirectory = pemWorkDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getImplementationSpecifixContext()
|
||||||
|
{
|
||||||
|
return implementationSpecifixContext;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
package org.eclipse.jetty.quic.quiche;
|
package org.eclipse.jetty.quic.quiche;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -22,56 +21,62 @@ import java.nio.file.Path;
|
||||||
import java.nio.file.attribute.PosixFilePermission;
|
import java.nio.file.attribute.PosixFilePermission;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.security.KeyStoreException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.UnrecoverableKeyException;
|
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.security.cert.CertificateEncodingException;
|
import java.security.cert.CertificateEncodingException;
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
import java.util.Enumeration;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.eclipse.jetty.util.resource.Resource;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class SSLKeyPair
|
public class PemExporter
|
||||||
{
|
{
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(SSLKeyPair.class);
|
private static final Logger LOG = LoggerFactory.getLogger(PemExporter.class);
|
||||||
|
|
||||||
private static final byte[] BEGIN_KEY = "-----BEGIN PRIVATE KEY-----".getBytes(StandardCharsets.US_ASCII);
|
private static final byte[] BEGIN_KEY = "-----BEGIN PRIVATE KEY-----".getBytes(StandardCharsets.US_ASCII);
|
||||||
private static final byte[] END_KEY = "-----END PRIVATE KEY-----".getBytes(StandardCharsets.US_ASCII);
|
private static final byte[] END_KEY = "-----END PRIVATE KEY-----".getBytes(StandardCharsets.US_ASCII);
|
||||||
private static final byte[] BEGIN_CERT = "-----BEGIN CERTIFICATE-----".getBytes(StandardCharsets.US_ASCII);
|
private static final byte[] BEGIN_CERT = "-----BEGIN CERTIFICATE-----".getBytes(StandardCharsets.US_ASCII);
|
||||||
private static final byte[] END_CERT = "-----END CERTIFICATE-----".getBytes(StandardCharsets.US_ASCII);
|
private static final byte[] END_CERT = "-----END CERTIFICATE-----".getBytes(StandardCharsets.US_ASCII);
|
||||||
private static final byte[] LINE_SEPARATOR = System.getProperty("line.separator").getBytes(StandardCharsets.US_ASCII);
|
private static final byte[] LINE_SEPARATOR = System.getProperty("line.separator").getBytes(StandardCharsets.US_ASCII);
|
||||||
private static final int LINE_LENGTH = 64;
|
private static final Base64.Encoder ENCODER = Base64.getMimeEncoder(64, LINE_SEPARATOR);
|
||||||
|
|
||||||
private final Base64.Encoder encoder = Base64.getMimeEncoder(LINE_LENGTH, LINE_SEPARATOR);
|
private PemExporter()
|
||||||
private final Key key;
|
|
||||||
private final Certificate[] certChain;
|
|
||||||
private final String alias;
|
|
||||||
|
|
||||||
public SSLKeyPair(KeyStore keyStore, String alias, char[] keyPassword) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException
|
|
||||||
{
|
{
|
||||||
this.alias = alias;
|
}
|
||||||
this.key = keyStore.getKey(alias, keyPassword);
|
|
||||||
this.certChain = keyStore.getCertificateChain(alias);
|
public static Path exportTrustStore(KeyStore keyStore, Path targetFolder) throws Exception
|
||||||
|
{
|
||||||
|
if (!Files.isDirectory(targetFolder))
|
||||||
|
throw new IllegalArgumentException("Target folder is not a directory: " + targetFolder);
|
||||||
|
|
||||||
|
Path path = Files.createTempFile(targetFolder, "truststore-", ".crt");
|
||||||
|
try (OutputStream os = Files.newOutputStream(path))
|
||||||
|
{
|
||||||
|
Enumeration<String> aliases = keyStore.aliases();
|
||||||
|
while (aliases.hasMoreElements())
|
||||||
|
{
|
||||||
|
String alias = aliases.nextElement();
|
||||||
|
Certificate cert = keyStore.getCertificate(alias);
|
||||||
|
writeAsPEM(os, cert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return [0] is the key file, [1] is the cert file.
|
* @return [0] is the key file, [1] is the cert file.
|
||||||
*/
|
*/
|
||||||
public Path[] export(Path targetFolder) throws Exception
|
public static Path[] exportKeyPair(KeyStore keyStore, String alias, char[] keyPassword, Path targetFolder) throws Exception
|
||||||
{
|
{
|
||||||
if (!Files.isDirectory(targetFolder))
|
if (!Files.isDirectory(targetFolder))
|
||||||
throw new IllegalArgumentException("Target folder is not a directory: " + targetFolder);
|
throw new IllegalArgumentException("Target folder is not a directory: " + targetFolder);
|
||||||
|
|
||||||
Path[] paths = new Path[2];
|
Path[] paths = new Path[2];
|
||||||
paths[0] = targetFolder.resolve(alias + ".key");
|
|
||||||
paths[1] = targetFolder.resolve(alias + ".crt");
|
paths[1] = targetFolder.resolve(alias + ".crt");
|
||||||
|
|
||||||
try (OutputStream os = Files.newOutputStream(paths[1]))
|
try (OutputStream os = Files.newOutputStream(paths[1]))
|
||||||
{
|
{
|
||||||
|
Certificate[] certChain = keyStore.getCertificateChain(alias);
|
||||||
for (Certificate cert : certChain)
|
for (Certificate cert : certChain)
|
||||||
writeAsPEM(os, cert);
|
writeAsPEM(os, cert);
|
||||||
Files.setPosixFilePermissions(paths[1], Set.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE));
|
Files.setPosixFilePermissions(paths[1], Set.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE));
|
||||||
|
@ -82,8 +87,10 @@ public class SSLKeyPair
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Unable to set Posix file permissions", e);
|
LOG.debug("Unable to set Posix file permissions", e);
|
||||||
}
|
}
|
||||||
|
paths[0] = targetFolder.resolve(alias + ".key");
|
||||||
try (OutputStream os = Files.newOutputStream(paths[0]))
|
try (OutputStream os = Files.newOutputStream(paths[0]))
|
||||||
{
|
{
|
||||||
|
Key key = keyStore.getKey(alias, keyPassword);
|
||||||
writeAsPEM(os, key);
|
writeAsPEM(os, key);
|
||||||
Files.setPosixFilePermissions(paths[0], Set.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE));
|
Files.setPosixFilePermissions(paths[0], Set.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE));
|
||||||
}
|
}
|
||||||
|
@ -93,13 +100,12 @@ public class SSLKeyPair
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Unable to set Posix file permissions", e);
|
LOG.debug("Unable to set Posix file permissions", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return paths;
|
return paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeAsPEM(OutputStream outputStream, Key key) throws IOException
|
private static void writeAsPEM(OutputStream outputStream, Key key) throws IOException
|
||||||
{
|
{
|
||||||
byte[] encoded = encoder.encode(key.getEncoded());
|
byte[] encoded = ENCODER.encode(key.getEncoded());
|
||||||
outputStream.write(BEGIN_KEY);
|
outputStream.write(BEGIN_KEY);
|
||||||
outputStream.write(LINE_SEPARATOR);
|
outputStream.write(LINE_SEPARATOR);
|
||||||
outputStream.write(encoded);
|
outputStream.write(encoded);
|
||||||
|
@ -108,9 +114,9 @@ public class SSLKeyPair
|
||||||
outputStream.write(LINE_SEPARATOR);
|
outputStream.write(LINE_SEPARATOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeAsPEM(OutputStream outputStream, Certificate certificate) throws CertificateEncodingException, IOException
|
private static void writeAsPEM(OutputStream outputStream, Certificate certificate) throws CertificateEncodingException, IOException
|
||||||
{
|
{
|
||||||
byte[] encoded = encoder.encode(certificate.getEncoded());
|
byte[] encoded = ENCODER.encode(certificate.getEncoded());
|
||||||
outputStream.write(BEGIN_CERT);
|
outputStream.write(BEGIN_CERT);
|
||||||
outputStream.write(LINE_SEPARATOR);
|
outputStream.write(LINE_SEPARATOR);
|
||||||
outputStream.write(encoded);
|
outputStream.write(encoded);
|
|
@ -35,6 +35,7 @@ public class QuicheConfig
|
||||||
|
|
||||||
private int version = Quiche.QUICHE_PROTOCOL_VERSION;
|
private int version = Quiche.QUICHE_PROTOCOL_VERSION;
|
||||||
private Boolean verifyPeer;
|
private Boolean verifyPeer;
|
||||||
|
private String trustedCertsPemPath;
|
||||||
private String certChainPemPath;
|
private String certChainPemPath;
|
||||||
private String privKeyPemPath;
|
private String privKeyPemPath;
|
||||||
private String[] applicationProtos;
|
private String[] applicationProtos;
|
||||||
|
@ -65,6 +66,11 @@ public class QuicheConfig
|
||||||
return verifyPeer;
|
return verifyPeer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getTrustedCertsPemPath()
|
||||||
|
{
|
||||||
|
return trustedCertsPemPath;
|
||||||
|
}
|
||||||
|
|
||||||
public String getCertChainPemPath()
|
public String getCertChainPemPath()
|
||||||
{
|
{
|
||||||
return certChainPemPath;
|
return certChainPemPath;
|
||||||
|
@ -150,6 +156,11 @@ public class QuicheConfig
|
||||||
this.verifyPeer = verify;
|
this.verifyPeer = verify;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setTrustedCertsPemPath(String trustedCertsPemPath)
|
||||||
|
{
|
||||||
|
this.trustedCertsPemPath = trustedCertsPemPath;
|
||||||
|
}
|
||||||
|
|
||||||
public void setCertChainPemPath(String path)
|
public void setCertChainPemPath(String path)
|
||||||
{
|
{
|
||||||
this.certChainPemPath = path;
|
this.certChainPemPath = path;
|
||||||
|
|
|
@ -28,6 +28,7 @@ import jdk.incubator.foreign.CLinker;
|
||||||
import jdk.incubator.foreign.MemoryAddress;
|
import jdk.incubator.foreign.MemoryAddress;
|
||||||
import jdk.incubator.foreign.MemorySegment;
|
import jdk.incubator.foreign.MemorySegment;
|
||||||
import jdk.incubator.foreign.ResourceScope;
|
import jdk.incubator.foreign.ResourceScope;
|
||||||
|
import org.eclipse.jetty.quic.quiche.Quiche;
|
||||||
import org.eclipse.jetty.quic.quiche.Quiche.quiche_error;
|
import org.eclipse.jetty.quic.quiche.Quiche.quiche_error;
|
||||||
import org.eclipse.jetty.quic.quiche.QuicheConfig;
|
import org.eclipse.jetty.quic.quiche.QuicheConfig;
|
||||||
import org.eclipse.jetty.quic.quiche.QuicheConnection;
|
import org.eclipse.jetty.quic.quiche.QuicheConnection;
|
||||||
|
@ -148,7 +149,7 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
|
||||||
|
|
||||||
MemorySegment localSockaddr = sockaddr.convert(local, scope);
|
MemorySegment localSockaddr = sockaddr.convert(local, scope);
|
||||||
MemorySegment peerSockaddr = sockaddr.convert(peer, scope);
|
MemorySegment peerSockaddr = sockaddr.convert(peer, scope);
|
||||||
MemoryAddress quicheConn = quiche_h.quiche_connect(CLinker.toCString(peer.getHostName(), scope), scid, scid.byteSize(), localSockaddr, localSockaddr.byteSize(), peerSockaddr, peerSockaddr.byteSize(), libQuicheConfig);
|
MemoryAddress quicheConn = quiche_h.quiche_connect(CLinker.toCString(peer.getHostString(), scope), scid, scid.byteSize(), localSockaddr, localSockaddr.byteSize(), peerSockaddr, peerSockaddr.byteSize(), libQuicheConfig);
|
||||||
ForeignIncubatorQuicheConnection connection = new ForeignIncubatorQuicheConnection(quicheConn, libQuicheConfig, scope);
|
ForeignIncubatorQuicheConnection connection = new ForeignIncubatorQuicheConnection(quicheConn, libQuicheConfig, scope);
|
||||||
keepScope = true;
|
keepScope = true;
|
||||||
return connection;
|
return connection;
|
||||||
|
@ -170,13 +171,29 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
|
||||||
if (verifyPeer != null)
|
if (verifyPeer != null)
|
||||||
quiche_h.quiche_config_verify_peer(quicheConfig, verifyPeer ? C_TRUE : C_FALSE);
|
quiche_h.quiche_config_verify_peer(quicheConfig, verifyPeer ? C_TRUE : C_FALSE);
|
||||||
|
|
||||||
|
String trustedCertsPemPath = config.getTrustedCertsPemPath();
|
||||||
|
if (trustedCertsPemPath != null)
|
||||||
|
{
|
||||||
|
int rc = quiche_h.quiche_config_load_verify_locations_from_file(quicheConfig, CLinker.toCString(trustedCertsPemPath, scope).address());
|
||||||
|
if (rc < 0)
|
||||||
|
throw new IOException("Error loading trusted certificates file " + trustedCertsPemPath + " : " + Quiche.quiche_error.errToString(rc));
|
||||||
|
}
|
||||||
|
|
||||||
String certChainPemPath = config.getCertChainPemPath();
|
String certChainPemPath = config.getCertChainPemPath();
|
||||||
if (certChainPemPath != null)
|
if (certChainPemPath != null)
|
||||||
quiche_h.quiche_config_load_cert_chain_from_pem_file(quicheConfig, CLinker.toCString(certChainPemPath, scope).address());
|
{
|
||||||
|
int rc = quiche_h.quiche_config_load_cert_chain_from_pem_file(quicheConfig, CLinker.toCString(certChainPemPath, scope).address());
|
||||||
|
if (rc < 0)
|
||||||
|
throw new IOException("Error loading certificate chain file " + certChainPemPath + " : " + Quiche.quiche_error.errToString(rc));
|
||||||
|
}
|
||||||
|
|
||||||
String privKeyPemPath = config.getPrivKeyPemPath();
|
String privKeyPemPath = config.getPrivKeyPemPath();
|
||||||
if (privKeyPemPath != null)
|
if (privKeyPemPath != null)
|
||||||
quiche_h.quiche_config_load_priv_key_from_pem_file(quicheConfig, CLinker.toCString(privKeyPemPath, scope).address());
|
{
|
||||||
|
int rc = quiche_h.quiche_config_load_priv_key_from_pem_file(quicheConfig, CLinker.toCString(privKeyPemPath, scope).address());
|
||||||
|
if (rc < 0)
|
||||||
|
throw new IOException("Error loading private key file " + privKeyPemPath + " : " + Quiche.quiche_error.errToString(rc));
|
||||||
|
}
|
||||||
|
|
||||||
String[] applicationProtos = config.getApplicationProtos();
|
String[] applicationProtos = config.getApplicationProtos();
|
||||||
if (applicationProtos != null)
|
if (applicationProtos != null)
|
||||||
|
@ -566,6 +583,9 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
|
||||||
received = quiche_h.quiche_conn_recv(quicheConn, bufferSegment.address(), buffer.remaining(), recvInfo.address());
|
received = quiche_h.quiche_conn_recv(quicheConn, bufferSegment.address(), buffer.remaining(), recvInfo.address());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If quiche_conn_recv() fails, quiche_conn_local_error() can be called to get the SSL alert; the err_code would contain
|
||||||
|
// a value from which 100 must be substracted to get one of the codes specified in
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc5246#section-7.2 ; see https://github.com/curl/curl/pull/8275 for details.
|
||||||
if (received < 0)
|
if (received < 0)
|
||||||
throw new IOException("failed to receive packet; err=" + quiche_error.errToString(received));
|
throw new IOException("failed to receive packet; err=" + quiche_error.errToString(received));
|
||||||
buffer.position((int)(buffer.position() + received));
|
buffer.position((int)(buffer.position() + received));
|
||||||
|
|
|
@ -52,6 +52,12 @@ public class quiche_h
|
||||||
FunctionDescriptor.ofVoid(C_POINTER, C_INT)
|
FunctionDescriptor.ofVoid(C_POINTER, C_INT)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private static final MethodHandle quiche_config_load_verify_locations_from_file$MH = downcallHandle(
|
||||||
|
"quiche_config_load_verify_locations_from_file",
|
||||||
|
"(Ljdk/incubator/foreign/MemoryAddress;Ljdk/incubator/foreign/MemoryAddress;)I",
|
||||||
|
FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER)
|
||||||
|
);
|
||||||
|
|
||||||
private static final MethodHandle quiche_config_load_cert_chain_from_pem_file$MH = downcallHandle(
|
private static final MethodHandle quiche_config_load_cert_chain_from_pem_file$MH = downcallHandle(
|
||||||
"quiche_config_load_cert_chain_from_pem_file",
|
"quiche_config_load_cert_chain_from_pem_file",
|
||||||
"(Ljdk/incubator/foreign/MemoryAddress;Ljdk/incubator/foreign/MemoryAddress;)I",
|
"(Ljdk/incubator/foreign/MemoryAddress;Ljdk/incubator/foreign/MemoryAddress;)I",
|
||||||
|
@ -370,6 +376,18 @@ public class quiche_h
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int quiche_config_load_verify_locations_from_file(MemoryAddress config, MemoryAddress path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (int) quiche_config_load_verify_locations_from_file$MH.invokeExact(config, path);
|
||||||
|
}
|
||||||
|
catch (Throwable ex)
|
||||||
|
{
|
||||||
|
throw new AssertionError("should not reach here", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static int quiche_config_load_cert_chain_from_pem_file(MemoryAddress config, MemoryAddress path)
|
public static int quiche_config_load_cert_chain_from_pem_file(MemoryAddress config, MemoryAddress path)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
@ -30,7 +30,7 @@ import java.util.Map;
|
||||||
|
|
||||||
import org.eclipse.jetty.quic.quiche.QuicheConfig;
|
import org.eclipse.jetty.quic.quiche.QuicheConfig;
|
||||||
import org.eclipse.jetty.quic.quiche.QuicheConnection;
|
import org.eclipse.jetty.quic.quiche.QuicheConnection;
|
||||||
import org.eclipse.jetty.quic.quiche.SSLKeyPair;
|
import org.eclipse.jetty.quic.quiche.PemExporter;
|
||||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
|
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
|
||||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
|
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
@ -65,10 +65,18 @@ public class LowLevelQuicheTest
|
||||||
clientSocketAddress = new InetSocketAddress("localhost", 9999);
|
clientSocketAddress = new InetSocketAddress("localhost", 9999);
|
||||||
serverSocketAddress = new InetSocketAddress("localhost", 8888);
|
serverSocketAddress = new InetSocketAddress("localhost", 8888);
|
||||||
|
|
||||||
|
KeyStore keyStore = KeyStore.getInstance("PKCS12");
|
||||||
|
try (InputStream is = getClass().getResourceAsStream("/keystore.p12"))
|
||||||
|
{
|
||||||
|
keyStore.load(is, "storepwd".toCharArray());
|
||||||
|
}
|
||||||
|
Path targetFolder = workDir.getEmptyPathDir();
|
||||||
|
|
||||||
clientQuicheConfig = new QuicheConfig();
|
clientQuicheConfig = new QuicheConfig();
|
||||||
clientQuicheConfig.setApplicationProtos("http/0.9");
|
clientQuicheConfig.setApplicationProtos("http/0.9");
|
||||||
clientQuicheConfig.setDisableActiveMigration(true);
|
clientQuicheConfig.setDisableActiveMigration(true);
|
||||||
clientQuicheConfig.setVerifyPeer(false);
|
clientQuicheConfig.setVerifyPeer(true);
|
||||||
|
clientQuicheConfig.setTrustedCertsPemPath(PemExporter.exportTrustStore(keyStore, targetFolder).toString());
|
||||||
clientQuicheConfig.setMaxIdleTimeout(1_000L);
|
clientQuicheConfig.setMaxIdleTimeout(1_000L);
|
||||||
clientQuicheConfig.setInitialMaxData(10_000_000L);
|
clientQuicheConfig.setInitialMaxData(10_000_000L);
|
||||||
clientQuicheConfig.setInitialMaxStreamDataBidiLocal(10_000_000L);
|
clientQuicheConfig.setInitialMaxStreamDataBidiLocal(10_000_000L);
|
||||||
|
@ -78,17 +86,11 @@ public class LowLevelQuicheTest
|
||||||
clientQuicheConfig.setInitialMaxStreamsBidi(100L);
|
clientQuicheConfig.setInitialMaxStreamsBidi(100L);
|
||||||
clientQuicheConfig.setCongestionControl(QuicheConfig.CongestionControl.CUBIC);
|
clientQuicheConfig.setCongestionControl(QuicheConfig.CongestionControl.CUBIC);
|
||||||
|
|
||||||
KeyStore keyStore = KeyStore.getInstance("PKCS12");
|
|
||||||
try (InputStream is = getClass().getResourceAsStream("/keystore.p12"))
|
|
||||||
{
|
|
||||||
keyStore.load(is, "storepwd".toCharArray());
|
|
||||||
}
|
|
||||||
serverCertificateChain = keyStore.getCertificateChain("mykey");
|
serverCertificateChain = keyStore.getCertificateChain("mykey");
|
||||||
SSLKeyPair serverKeyPair = new SSLKeyPair(keyStore, "mykey", "storepwd".toCharArray());
|
|
||||||
Path[] pemFiles = serverKeyPair.export(workDir.getEmptyPathDir());
|
|
||||||
serverQuicheConfig = new QuicheConfig();
|
serverQuicheConfig = new QuicheConfig();
|
||||||
serverQuicheConfig.setPrivKeyPemPath(pemFiles[0].toString());
|
Path[] keyPair = PemExporter.exportKeyPair(keyStore, "mykey", "storepwd".toCharArray(), targetFolder);
|
||||||
serverQuicheConfig.setCertChainPemPath(pemFiles[1].toString());
|
serverQuicheConfig.setPrivKeyPemPath(keyPair[0].toString());
|
||||||
|
serverQuicheConfig.setCertChainPemPath(keyPair[1].toString());
|
||||||
serverQuicheConfig.setApplicationProtos("http/0.9");
|
serverQuicheConfig.setApplicationProtos("http/0.9");
|
||||||
serverQuicheConfig.setVerifyPeer(false);
|
serverQuicheConfig.setVerifyPeer(false);
|
||||||
serverQuicheConfig.setMaxIdleTimeout(1_000L);
|
serverQuicheConfig.setMaxIdleTimeout(1_000L);
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.security.SecureRandom;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.quic.quiche.Quiche;
|
||||||
import org.eclipse.jetty.quic.quiche.Quiche.quiche_error;
|
import org.eclipse.jetty.quic.quiche.Quiche.quiche_error;
|
||||||
import org.eclipse.jetty.quic.quiche.QuicheConfig;
|
import org.eclipse.jetty.quic.quiche.QuicheConfig;
|
||||||
import org.eclipse.jetty.quic.quiche.QuicheConnection;
|
import org.eclipse.jetty.quic.quiche.QuicheConnection;
|
||||||
|
@ -114,7 +115,7 @@ public class JnaQuicheConnection extends QuicheConnection
|
||||||
|
|
||||||
SizedStructure<sockaddr> localSockaddr = sockaddr.convert(local);
|
SizedStructure<sockaddr> localSockaddr = sockaddr.convert(local);
|
||||||
SizedStructure<sockaddr> peerSockaddr = sockaddr.convert(peer);
|
SizedStructure<sockaddr> peerSockaddr = sockaddr.convert(peer);
|
||||||
LibQuiche.quiche_conn quicheConn = LibQuiche.INSTANCE.quiche_connect(peer.getHostName(), scid, new size_t(scid.length), localSockaddr.getStructure(), localSockaddr.getSize(), peerSockaddr.getStructure(), peerSockaddr.getSize(), libQuicheConfig);
|
LibQuiche.quiche_conn quicheConn = LibQuiche.INSTANCE.quiche_connect(peer.getHostString(), scid, new size_t(scid.length), localSockaddr.getStructure(), localSockaddr.getSize(), peerSockaddr.getStructure(), peerSockaddr.getSize(), libQuicheConfig);
|
||||||
return new JnaQuicheConnection(quicheConn, libQuicheConfig);
|
return new JnaQuicheConnection(quicheConn, libQuicheConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,13 +129,29 @@ public class JnaQuicheConnection extends QuicheConnection
|
||||||
if (verifyPeer != null)
|
if (verifyPeer != null)
|
||||||
LibQuiche.INSTANCE.quiche_config_verify_peer(quicheConfig, verifyPeer);
|
LibQuiche.INSTANCE.quiche_config_verify_peer(quicheConfig, verifyPeer);
|
||||||
|
|
||||||
|
String trustedCertsPemPath = config.getTrustedCertsPemPath();
|
||||||
|
if (trustedCertsPemPath != null)
|
||||||
|
{
|
||||||
|
int rc = LibQuiche.INSTANCE.quiche_config_load_verify_locations_from_file(quicheConfig, trustedCertsPemPath);
|
||||||
|
if (rc != 0)
|
||||||
|
throw new IOException("Error loading trusted certificates file " + trustedCertsPemPath + " : " + Quiche.quiche_error.errToString(rc));
|
||||||
|
}
|
||||||
|
|
||||||
String certChainPemPath = config.getCertChainPemPath();
|
String certChainPemPath = config.getCertChainPemPath();
|
||||||
if (certChainPemPath != null)
|
if (certChainPemPath != null)
|
||||||
LibQuiche.INSTANCE.quiche_config_load_cert_chain_from_pem_file(quicheConfig, certChainPemPath);
|
{
|
||||||
|
int rc = LibQuiche.INSTANCE.quiche_config_load_cert_chain_from_pem_file(quicheConfig, certChainPemPath);
|
||||||
|
if (rc < 0)
|
||||||
|
throw new IOException("Error loading certificate chain file " + certChainPemPath + " : " + Quiche.quiche_error.errToString(rc));
|
||||||
|
}
|
||||||
|
|
||||||
String privKeyPemPath = config.getPrivKeyPemPath();
|
String privKeyPemPath = config.getPrivKeyPemPath();
|
||||||
if (privKeyPemPath != null)
|
if (privKeyPemPath != null)
|
||||||
LibQuiche.INSTANCE.quiche_config_load_priv_key_from_pem_file(quicheConfig, privKeyPemPath);
|
{
|
||||||
|
int rc = LibQuiche.INSTANCE.quiche_config_load_priv_key_from_pem_file(quicheConfig, privKeyPemPath);
|
||||||
|
if (rc < 0)
|
||||||
|
throw new IOException("Error loading private key file " + privKeyPemPath + " : " + Quiche.quiche_error.errToString(rc));
|
||||||
|
}
|
||||||
|
|
||||||
String[] applicationProtos = config.getApplicationProtos();
|
String[] applicationProtos = config.getApplicationProtos();
|
||||||
if (applicationProtos != null)
|
if (applicationProtos != null)
|
||||||
|
@ -450,6 +467,9 @@ public class JnaQuicheConnection extends QuicheConnection
|
||||||
SizedStructure<sockaddr> peerSockaddr = sockaddr.convert(peer);
|
SizedStructure<sockaddr> peerSockaddr = sockaddr.convert(peer);
|
||||||
info.from = peerSockaddr.getStructure().byReference();
|
info.from = peerSockaddr.getStructure().byReference();
|
||||||
info.from_len = peerSockaddr.getSize();
|
info.from_len = peerSockaddr.getSize();
|
||||||
|
// If quiche_conn_recv() fails, quiche_conn_local_error() can be called to get the SSL alert; the err_code would contain
|
||||||
|
// a value from which 100 must be substracted to get one of the codes specified in
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc5246#section-7.2 ; see https://github.com/curl/curl/pull/8275 for details.
|
||||||
int received = LibQuiche.INSTANCE.quiche_conn_recv(quicheConn, buffer, new size_t(buffer.remaining()), info).intValue();
|
int received = LibQuiche.INSTANCE.quiche_conn_recv(quicheConn, buffer, new size_t(buffer.remaining()), info).intValue();
|
||||||
if (received < 0)
|
if (received < 0)
|
||||||
throw new IOException("failed to receive packet; err=" + quiche_error.errToString(received));
|
throw new IOException("failed to receive packet; err=" + quiche_error.errToString(received));
|
||||||
|
|
|
@ -86,6 +86,9 @@ public interface LibQuiche extends Library
|
||||||
// Configures whether to verify the peer's certificate.
|
// Configures whether to verify the peer's certificate.
|
||||||
void quiche_config_verify_peer(quiche_config config, boolean v);
|
void quiche_config_verify_peer(quiche_config config, boolean v);
|
||||||
|
|
||||||
|
// Specifies a file where trusted CA certificates are stored for the purposes of certificate verification.
|
||||||
|
int quiche_config_load_verify_locations_from_file(quiche_config config, String path);
|
||||||
|
|
||||||
// Configures the list of supported application protocols.
|
// Configures the list of supported application protocols.
|
||||||
int quiche_config_set_application_protos(quiche_config config, byte[] protos, size_t protos_len);
|
int quiche_config_set_application_protos(quiche_config config, byte[] protos, size_t protos_len);
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ import java.util.Map;
|
||||||
|
|
||||||
import org.eclipse.jetty.quic.quiche.QuicheConfig;
|
import org.eclipse.jetty.quic.quiche.QuicheConfig;
|
||||||
import org.eclipse.jetty.quic.quiche.QuicheConnection;
|
import org.eclipse.jetty.quic.quiche.QuicheConnection;
|
||||||
import org.eclipse.jetty.quic.quiche.SSLKeyPair;
|
import org.eclipse.jetty.quic.quiche.PemExporter;
|
||||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
|
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
|
||||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
|
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
@ -64,10 +64,18 @@ public class LowLevelQuicheTest
|
||||||
clientSocketAddress = new InetSocketAddress("localhost", 9999);
|
clientSocketAddress = new InetSocketAddress("localhost", 9999);
|
||||||
serverSocketAddress = new InetSocketAddress("localhost", 8888);
|
serverSocketAddress = new InetSocketAddress("localhost", 8888);
|
||||||
|
|
||||||
|
KeyStore keyStore = KeyStore.getInstance("PKCS12");
|
||||||
|
try (InputStream is = getClass().getResourceAsStream("/keystore.p12"))
|
||||||
|
{
|
||||||
|
keyStore.load(is, "storepwd".toCharArray());
|
||||||
|
}
|
||||||
|
Path targetFolder = workDir.getEmptyPathDir();
|
||||||
|
|
||||||
clientQuicheConfig = new QuicheConfig();
|
clientQuicheConfig = new QuicheConfig();
|
||||||
clientQuicheConfig.setApplicationProtos("http/0.9");
|
clientQuicheConfig.setApplicationProtos("http/0.9");
|
||||||
clientQuicheConfig.setDisableActiveMigration(true);
|
clientQuicheConfig.setDisableActiveMigration(true);
|
||||||
clientQuicheConfig.setVerifyPeer(false);
|
clientQuicheConfig.setVerifyPeer(true);
|
||||||
|
clientQuicheConfig.setTrustedCertsPemPath(PemExporter.exportTrustStore(keyStore, targetFolder).toString());
|
||||||
clientQuicheConfig.setMaxIdleTimeout(1_000L);
|
clientQuicheConfig.setMaxIdleTimeout(1_000L);
|
||||||
clientQuicheConfig.setInitialMaxData(10_000_000L);
|
clientQuicheConfig.setInitialMaxData(10_000_000L);
|
||||||
clientQuicheConfig.setInitialMaxStreamDataBidiLocal(10_000_000L);
|
clientQuicheConfig.setInitialMaxStreamDataBidiLocal(10_000_000L);
|
||||||
|
@ -77,17 +85,11 @@ public class LowLevelQuicheTest
|
||||||
clientQuicheConfig.setInitialMaxStreamsBidi(100L);
|
clientQuicheConfig.setInitialMaxStreamsBidi(100L);
|
||||||
clientQuicheConfig.setCongestionControl(QuicheConfig.CongestionControl.CUBIC);
|
clientQuicheConfig.setCongestionControl(QuicheConfig.CongestionControl.CUBIC);
|
||||||
|
|
||||||
KeyStore keyStore = KeyStore.getInstance("PKCS12");
|
|
||||||
try (InputStream is = getClass().getResourceAsStream("/keystore.p12"))
|
|
||||||
{
|
|
||||||
keyStore.load(is, "storepwd".toCharArray());
|
|
||||||
}
|
|
||||||
serverCertificateChain = keyStore.getCertificateChain("mykey");
|
serverCertificateChain = keyStore.getCertificateChain("mykey");
|
||||||
SSLKeyPair serverKeyPair = new SSLKeyPair(keyStore, "mykey", "storepwd".toCharArray());
|
|
||||||
Path[] pemFiles = serverKeyPair.export(workDir.getEmptyPathDir());
|
|
||||||
serverQuicheConfig = new QuicheConfig();
|
serverQuicheConfig = new QuicheConfig();
|
||||||
serverQuicheConfig.setPrivKeyPemPath(pemFiles[0].toString());
|
Path[] keyPair = PemExporter.exportKeyPair(keyStore, "mykey", "storepwd".toCharArray(), targetFolder);
|
||||||
serverQuicheConfig.setCertChainPemPath(pemFiles[1].toString());
|
serverQuicheConfig.setPrivKeyPemPath(keyPair[0].toString());
|
||||||
|
serverQuicheConfig.setCertChainPemPath(keyPair[1].toString());
|
||||||
serverQuicheConfig.setApplicationProtos("http/0.9");
|
serverQuicheConfig.setApplicationProtos("http/0.9");
|
||||||
serverQuicheConfig.setVerifyPeer(false);
|
serverQuicheConfig.setVerifyPeer(false);
|
||||||
serverQuicheConfig.setMaxIdleTimeout(1_000L);
|
serverQuicheConfig.setMaxIdleTimeout(1_000L);
|
||||||
|
|
|
@ -37,8 +37,8 @@ import org.eclipse.jetty.quic.common.QuicConfiguration;
|
||||||
import org.eclipse.jetty.quic.common.QuicSession;
|
import org.eclipse.jetty.quic.common.QuicSession;
|
||||||
import org.eclipse.jetty.quic.common.QuicSessionContainer;
|
import org.eclipse.jetty.quic.common.QuicSessionContainer;
|
||||||
import org.eclipse.jetty.quic.common.QuicStreamEndPoint;
|
import org.eclipse.jetty.quic.common.QuicStreamEndPoint;
|
||||||
|
import org.eclipse.jetty.quic.quiche.PemExporter;
|
||||||
import org.eclipse.jetty.quic.quiche.QuicheConfig;
|
import org.eclipse.jetty.quic.quiche.QuicheConfig;
|
||||||
import org.eclipse.jetty.quic.quiche.SSLKeyPair;
|
|
||||||
import org.eclipse.jetty.server.AbstractNetworkConnector;
|
import org.eclipse.jetty.server.AbstractNetworkConnector;
|
||||||
import org.eclipse.jetty.server.ConnectionFactory;
|
import org.eclipse.jetty.server.ConnectionFactory;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
|
@ -90,7 +90,6 @@ public class QuicServerConnector extends AbstractNetworkConnector
|
||||||
// One bidirectional stream to simulate the TCP stream, and no unidirectional streams.
|
// One bidirectional stream to simulate the TCP stream, and no unidirectional streams.
|
||||||
quicConfiguration.setMaxBidirectionalRemoteStreams(1);
|
quicConfiguration.setMaxBidirectionalRemoteStreams(1);
|
||||||
quicConfiguration.setMaxUnidirectionalRemoteStreams(0);
|
quicConfiguration.setMaxUnidirectionalRemoteStreams(0);
|
||||||
quicConfiguration.setVerifyPeerCertificates(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public QuicConfiguration getQuicConfiguration()
|
public QuicConfiguration getQuicConfiguration()
|
||||||
|
@ -169,31 +168,24 @@ public class QuicServerConnector extends AbstractNetworkConnector
|
||||||
char[] password = keyManagerPassword == null ? sslContextFactory.getKeyStorePassword().toCharArray() : keyManagerPassword.toCharArray();
|
char[] password = keyManagerPassword == null ? sslContextFactory.getKeyStorePassword().toCharArray() : keyManagerPassword.toCharArray();
|
||||||
|
|
||||||
KeyStore keyStore = sslContextFactory.getKeyStore();
|
KeyStore keyStore = sslContextFactory.getKeyStore();
|
||||||
SSLKeyPair keyPair = new SSLKeyPair(
|
Path[] keyPair = PemExporter.exportKeyPair(keyStore, alias, password, findCertificateWorkPath());
|
||||||
keyStore,
|
privateKeyPath = keyPair[0];
|
||||||
alias,
|
certificateChainPath = keyPair[1];
|
||||||
password
|
|
||||||
);
|
|
||||||
Path[] pemFiles = keyPair.export(findTargetPath());
|
|
||||||
privateKeyPath = pemFiles[0];
|
|
||||||
certificateChainPath = pemFiles[1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path findTargetPath() throws IOException
|
private Path findCertificateWorkPath()
|
||||||
{
|
{
|
||||||
Path target;
|
Path pemWorkDirectory = getQuicConfiguration().getPemWorkDirectory();
|
||||||
|
if (pemWorkDirectory != null)
|
||||||
|
return pemWorkDirectory;
|
||||||
String jettyBase = System.getProperty("jetty.base");
|
String jettyBase = System.getProperty("jetty.base");
|
||||||
if (jettyBase != null)
|
if (jettyBase != null)
|
||||||
{
|
{
|
||||||
target = Path.of(jettyBase).resolve("work");
|
pemWorkDirectory = Path.of(jettyBase).resolve("work");
|
||||||
|
if (Files.exists(pemWorkDirectory))
|
||||||
|
return pemWorkDirectory;
|
||||||
}
|
}
|
||||||
else
|
throw new IllegalStateException("No PEM work directory configured");
|
||||||
{
|
|
||||||
target = sslContextFactory.getKeyStoreResource().getFile().getParentFile().toPath();
|
|
||||||
if (!Files.isDirectory(target))
|
|
||||||
target = Path.of(".");
|
|
||||||
}
|
|
||||||
return target;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -231,7 +223,7 @@ public class QuicServerConnector extends AbstractNetworkConnector
|
||||||
QuicheConfig quicheConfig = new QuicheConfig();
|
QuicheConfig quicheConfig = new QuicheConfig();
|
||||||
quicheConfig.setPrivKeyPemPath(privateKeyPath.toString());
|
quicheConfig.setPrivKeyPemPath(privateKeyPath.toString());
|
||||||
quicheConfig.setCertChainPemPath(certificateChainPath.toString());
|
quicheConfig.setCertChainPemPath(certificateChainPath.toString());
|
||||||
quicheConfig.setVerifyPeer(quicConfiguration.isVerifyPeerCertificates());
|
quicheConfig.setVerifyPeer(false);
|
||||||
// Idle timeouts must not be managed by Quiche.
|
// Idle timeouts must not be managed by Quiche.
|
||||||
quicheConfig.setMaxIdleTimeout(0L);
|
quicheConfig.setMaxIdleTimeout(0L);
|
||||||
quicheConfig.setInitialMaxData((long)quicConfiguration.getSessionRecvWindow());
|
quicheConfig.setInitialMaxData((long)quicConfiguration.getSessionRecvWindow());
|
||||||
|
|
|
@ -23,7 +23,6 @@ import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
@ -1141,7 +1140,7 @@ public class DistributionTests extends AbstractJettyHomeTest
|
||||||
assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS));
|
assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS));
|
||||||
|
|
||||||
HTTP3Client http3Client = new HTTP3Client();
|
HTTP3Client http3Client = new HTTP3Client();
|
||||||
http3Client.getQuicConfiguration().setVerifyPeerCertificates(false);
|
http3Client.getClientConnector().setSslContextFactory(new SslContextFactory.Client(true));
|
||||||
this.client = new HttpClient(new HttpClientTransportOverHTTP3(http3Client));
|
this.client = new HttpClient(new HttpClientTransportOverHTTP3(http3Client));
|
||||||
this.client.start();
|
this.client.start();
|
||||||
ContentResponse response = this.client.newRequest("localhost", h3Port)
|
ContentResponse response = this.client.newRequest("localhost", h3Port)
|
||||||
|
|
|
@ -183,7 +183,6 @@ public class HttpChannelAssociationTest extends AbstractTest<TransportScenario>
|
||||||
HTTP3Client http3Client = new HTTP3Client();
|
HTTP3Client http3Client = new HTTP3Client();
|
||||||
http3Client.getClientConnector().setSelectors(1);
|
http3Client.getClientConnector().setSelectors(1);
|
||||||
http3Client.getClientConnector().setSslContextFactory(scenario.newClientSslContextFactory());
|
http3Client.getClientConnector().setSslContextFactory(scenario.newClientSslContextFactory());
|
||||||
http3Client.getQuicConfiguration().setVerifyPeerCertificates(false);
|
|
||||||
return new HttpClientTransportOverHTTP3(http3Client)
|
return new HttpClientTransportOverHTTP3(http3Client)
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -16,6 +16,7 @@ package org.eclipse.jetty.http.client;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InterruptedIOException;
|
import java.io.InterruptedIOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -56,7 +57,6 @@ import org.eclipse.jetty.util.ProcessorUtils;
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
import org.eclipse.jetty.util.thread.Scheduler;
|
import org.eclipse.jetty.util.thread.Scheduler;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.jupiter.api.Assumptions;
|
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.ArgumentsSource;
|
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -389,7 +389,9 @@ public class HttpClientLoadTest extends AbstractTest<HttpClientLoadTest.LoadTran
|
||||||
case FCGI:
|
case FCGI:
|
||||||
return new ServerConnector(server, null, null, byteBufferPool, 1, selectors, provideServerConnectionFactory(transport));
|
return new ServerConnector(server, null, null, byteBufferPool, 1, selectors, provideServerConnectionFactory(transport));
|
||||||
case H3:
|
case H3:
|
||||||
return new HTTP3ServerConnector(server, null, null, byteBufferPool, sslContextFactory, provideServerConnectionFactory(transport));
|
HTTP3ServerConnector http3ServerConnector = new HTTP3ServerConnector(server, null, null, byteBufferPool, sslContextFactory, provideServerConnectionFactory(transport));
|
||||||
|
http3ServerConnector.getQuicConfiguration().setPemWorkDirectory(Path.of(System.getProperty("java.io.tmpdir")));
|
||||||
|
return http3ServerConnector;
|
||||||
case UNIX_DOMAIN:
|
case UNIX_DOMAIN:
|
||||||
UnixDomainServerConnector unixSocketConnector = new UnixDomainServerConnector(server, null, null, byteBufferPool, 1, selectors, provideServerConnectionFactory(transport));
|
UnixDomainServerConnector unixSocketConnector = new UnixDomainServerConnector(server, null, null, byteBufferPool, 1, selectors, provideServerConnectionFactory(transport));
|
||||||
unixSocketConnector.setUnixDomainPath(unixDomainPath);
|
unixSocketConnector.setUnixDomainPath(unixDomainPath);
|
||||||
|
|
|
@ -47,7 +47,6 @@ import org.eclipse.jetty.http.HttpScheme;
|
||||||
import org.eclipse.jetty.http.HttpStatus;
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.eclipse.jetty.http.HttpURI;
|
import org.eclipse.jetty.http.HttpURI;
|
||||||
import org.eclipse.jetty.http2.FlowControlStrategy;
|
import org.eclipse.jetty.http2.FlowControlStrategy;
|
||||||
import org.eclipse.jetty.http3.client.http.HttpClientTransportOverHTTP3;
|
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.eclipse.jetty.server.Request;
|
||||||
import org.eclipse.jetty.server.SecureRequestCustomizer;
|
import org.eclipse.jetty.server.SecureRequestCustomizer;
|
||||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
|
@ -364,15 +363,10 @@ public class HttpClientTest extends AbstractTest<TransportScenario>
|
||||||
clientThreads.setName("client");
|
clientThreads.setName("client");
|
||||||
scenario.client.setExecutor(clientThreads);
|
scenario.client.setExecutor(clientThreads);
|
||||||
scenario.client.start();
|
scenario.client.start();
|
||||||
if (transport == Transport.H3)
|
|
||||||
{
|
|
||||||
Assumptions.assumeTrue(false, "certificate verification not yet supported in quic");
|
|
||||||
// TODO: the lines below should be enough, but they don't work. To be investigated.
|
|
||||||
HttpClientTransportOverHTTP3 http3Transport = (HttpClientTransportOverHTTP3)scenario.client.getTransport();
|
|
||||||
http3Transport.getHTTP3Client().getQuicConfiguration().setVerifyPeerCertificates(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThrows(ExecutionException.class, () ->
|
// H3 times out b/c it is QUIC's way of figuring out a connection cannot be established.
|
||||||
|
Class<? extends Exception> expectedType = transport == Transport.H3 ? TimeoutException.class : ExecutionException.class;
|
||||||
|
assertThrows(expectedType, () ->
|
||||||
{
|
{
|
||||||
// Use an IP address not present in the certificate.
|
// Use an IP address not present in the certificate.
|
||||||
int serverPort = scenario.getServerPort().orElse(0);
|
int serverPort = scenario.getServerPort().orElse(0);
|
||||||
|
|
|
@ -14,9 +14,11 @@
|
||||||
package org.eclipse.jetty.http.client;
|
package org.eclipse.jetty.http.client;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.lang.management.ManagementFactory;
|
import java.lang.management.ManagementFactory;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.security.KeyStore;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.OptionalInt;
|
import java.util.OptionalInt;
|
||||||
|
@ -122,7 +124,9 @@ public class TransportScenario
|
||||||
case FCGI:
|
case FCGI:
|
||||||
return new ServerConnector(server, 1, 1, provideServerConnectionFactory(transport));
|
return new ServerConnector(server, 1, 1, provideServerConnectionFactory(transport));
|
||||||
case H3:
|
case H3:
|
||||||
return new HTTP3ServerConnector(server, sslContextFactory, provideServerConnectionFactory(transport));
|
HTTP3ServerConnector http3ServerConnector = new HTTP3ServerConnector(server, sslContextFactory, provideServerConnectionFactory(transport));
|
||||||
|
http3ServerConnector.getQuicConfiguration().setPemWorkDirectory(Path.of(System.getProperty("java.io.tmpdir")));
|
||||||
|
return http3ServerConnector;
|
||||||
case UNIX_DOMAIN:
|
case UNIX_DOMAIN:
|
||||||
UnixDomainServerConnector connector = new UnixDomainServerConnector(server, provideServerConnectionFactory(transport));
|
UnixDomainServerConnector connector = new UnixDomainServerConnector(server, provideServerConnectionFactory(transport));
|
||||||
connector.setUnixDomainPath(unixDomainPath);
|
connector.setUnixDomainPath(unixDomainPath);
|
||||||
|
@ -175,7 +179,6 @@ public class TransportScenario
|
||||||
ClientConnector clientConnector = http3Client.getClientConnector();
|
ClientConnector clientConnector = http3Client.getClientConnector();
|
||||||
clientConnector.setSelectors(1);
|
clientConnector.setSelectors(1);
|
||||||
clientConnector.setSslContextFactory(sslContextFactory);
|
clientConnector.setSslContextFactory(sslContextFactory);
|
||||||
http3Client.getQuicConfiguration().setVerifyPeerCertificates(false);
|
|
||||||
return new HttpClientTransportOverHTTP3(http3Client);
|
return new HttpClientTransportOverHTTP3(http3Client);
|
||||||
}
|
}
|
||||||
case FCGI:
|
case FCGI:
|
||||||
|
@ -384,10 +387,23 @@ public class TransportScenario
|
||||||
|
|
||||||
private void configureSslContextFactory(SslContextFactory sslContextFactory)
|
private void configureSslContextFactory(SslContextFactory sslContextFactory)
|
||||||
{
|
{
|
||||||
sslContextFactory.setKeyStorePath("src/test/resources/keystore.p12");
|
try
|
||||||
sslContextFactory.setKeyStorePassword("storepwd");
|
{
|
||||||
sslContextFactory.setUseCipherSuitesOrder(true);
|
KeyStore keystore = KeyStore.getInstance("PKCS12");
|
||||||
sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR);
|
try (InputStream is = Files.newInputStream(Path.of("src/test/resources/keystore.p12")))
|
||||||
|
{
|
||||||
|
keystore.load(is, "storepwd".toCharArray());
|
||||||
|
}
|
||||||
|
sslContextFactory.setTrustStore(keystore);
|
||||||
|
sslContextFactory.setKeyStore(keystore);
|
||||||
|
sslContextFactory.setKeyStorePassword("storepwd");
|
||||||
|
sslContextFactory.setUseCipherSuitesOrder(true);
|
||||||
|
sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stopClient() throws Exception
|
public void stopClient() throws Exception
|
||||||
|
|
Loading…
Reference in New Issue