From b24c1807499594dc7ee12e05c0f859fb1ea6a599 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Mon, 15 May 2023 11:22:26 +0200 Subject: [PATCH 1/8] #8694 use SslContextFactory.getKeyStore() API in QuicServerConnector instead of re-creating the keystore Signed-off-by: Ludovic Orban --- .../eclipse/jetty/quic/quiche/SSLKeyPair.java | 67 ++++++++++++------- .../foreign/incubator/LowLevelQuicheTest.java | 25 +++++-- .../quic/quiche/jna/LowLevelQuicheTest.java | 25 +++++-- .../quic/server/QuicServerConnector.java | 37 +++++----- 4 files changed, 101 insertions(+), 53 deletions(-) diff --git a/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/SSLKeyPair.java b/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/SSLKeyPair.java index 17a8ad36538..c5a6ad54b7c 100644 --- a/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/SSLKeyPair.java +++ b/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/SSLKeyPair.java @@ -13,12 +13,13 @@ package org.eclipse.jetty.quic.quiche; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.PosixFilePermission; import java.security.Key; import java.security.KeyStore; import java.security.KeyStoreException; @@ -28,9 +29,16 @@ import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.util.Base64; +import java.util.Set; + +import org.eclipse.jetty.util.resource.Resource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class SSLKeyPair { + private static final Logger LOG = LoggerFactory.getLogger(SSLKeyPair.class); + 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[] BEGIN_CERT = "-----BEGIN CERTIFICATE-----".getBytes(StandardCharsets.US_ASCII); @@ -43,37 +51,50 @@ public class SSLKeyPair private final Certificate[] certChain; private final String alias; - public SSLKeyPair(File storeFile, String storeType, char[] storePassword, String alias, char[] keyPassword) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, IOException, CertificateException + public SSLKeyPair(KeyStore keyStore, String alias, char[] keyPassword) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException { - KeyStore keyStore = KeyStore.getInstance(storeType); - try (FileInputStream fis = new FileInputStream(storeFile)) - { - keyStore.load(fis, storePassword); - this.alias = alias; - this.key = keyStore.getKey(alias, keyPassword); - this.certChain = keyStore.getCertificateChain(alias); - } + this.alias = alias; + this.key = keyStore.getKey(alias, keyPassword); + this.certChain = keyStore.getCertificateChain(alias); } /** * @return [0] is the key file, [1] is the cert file. */ - public File[] export(File targetFolder) throws Exception + public Path[] export(Path targetFolder) throws Exception { - File[] files = new File[2]; - files[0] = new File(targetFolder, alias + ".key"); - files[1] = new File(targetFolder, alias + ".crt"); + if (!Files.isDirectory(targetFolder)) + throw new IllegalArgumentException("Target folder is not a directory: " + targetFolder); - try (FileOutputStream fos = new FileOutputStream(files[0])) - { - writeAsPEM(fos, key); - } - try (FileOutputStream fos = new FileOutputStream(files[1])) + Path[] paths = new Path[2]; + paths[0] = targetFolder.resolve(alias + ".key"); + paths[1] = targetFolder.resolve(alias + ".crt"); + + try (OutputStream os = Files.newOutputStream(paths[1])) { for (Certificate cert : certChain) - writeAsPEM(fos, cert); + writeAsPEM(os, cert); + Files.setPosixFilePermissions(paths[1], Set.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE)); } - return files; + catch (UnsupportedOperationException e) + { + // Expected on Windows. + if (LOG.isDebugEnabled()) + LOG.debug("Unable to set Posix file permissions", e); + } + try (OutputStream os = Files.newOutputStream(paths[0])) + { + writeAsPEM(os, key); + Files.setPosixFilePermissions(paths[0], Set.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE)); + } + catch (UnsupportedOperationException e) + { + // Expected on Windows. + if (LOG.isDebugEnabled()) + LOG.debug("Unable to set Posix file permissions", e); + } + + return paths; } private void writeAsPEM(OutputStream outputStream, Key key) throws IOException diff --git a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java index 57c16423291..84cf9e88ffc 100644 --- a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java +++ b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java @@ -13,12 +13,13 @@ package org.eclipse.jetty.quic.quiche.foreign.incubator; -import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -import java.nio.file.Paths; +import java.nio.file.Path; +import java.security.KeyStore; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collection; @@ -29,9 +30,13 @@ import java.util.Objects; import org.eclipse.jetty.quic.quiche.QuicheConfig; import org.eclipse.jetty.quic.quiche.QuicheConnection; import org.eclipse.jetty.quic.quiche.SSLKeyPair; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; +import org.eclipse.jetty.util.resource.Resource; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import static org.eclipse.jetty.quic.quiche.Quiche.QUICHE_MIN_CLIENT_INITIAL_LEN; import static org.hamcrest.MatcherAssert.assertThat; @@ -39,8 +44,11 @@ import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.core.Is.is; +@ExtendWith(WorkDirExtension.class) public class LowLevelQuicheTest { + public WorkDir workDir; + private final Collection connectionsToDisposeOf = new ArrayList<>(); private InetSocketAddress clientSocketAddress; @@ -69,11 +77,16 @@ public class LowLevelQuicheTest clientQuicheConfig.setInitialMaxStreamsBidi(100L); clientQuicheConfig.setCongestionControl(QuicheConfig.CongestionControl.CUBIC); - SSLKeyPair serverKeyPair = new SSLKeyPair(Paths.get(Objects.requireNonNull(getClass().getResource("/keystore.p12")).toURI()).toFile(), "PKCS12", "storepwd".toCharArray(), "mykey", "storepwd".toCharArray()); - File[] pemFiles = serverKeyPair.export(new File(System.getProperty("java.io.tmpdir"))); + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + try (InputStream is = getClass().getResourceAsStream("/keystore.p12")) + { + keyStore.load(is, "storepwd".toCharArray()); + } + SSLKeyPair serverKeyPair = new SSLKeyPair(keyStore, "mykey", "storepwd".toCharArray()); + Path[] pemFiles = serverKeyPair.export(workDir.getEmptyPathDir()); serverQuicheConfig = new QuicheConfig(); - serverQuicheConfig.setPrivKeyPemPath(pemFiles[0].getPath()); - serverQuicheConfig.setCertChainPemPath(pemFiles[1].getPath()); + serverQuicheConfig.setPrivKeyPemPath(pemFiles[0].toString()); + serverQuicheConfig.setCertChainPemPath(pemFiles[1].toString()); serverQuicheConfig.setApplicationProtos("http/0.9"); serverQuicheConfig.setVerifyPeer(false); serverQuicheConfig.setMaxIdleTimeout(1_000L); diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java b/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java index 320cd77401e..988f44f45d1 100644 --- a/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java +++ b/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java @@ -13,11 +13,12 @@ package org.eclipse.jetty.quic.quiche.jna; -import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.net.InetSocketAddress; import java.nio.ByteBuffer; -import java.nio.file.Paths; +import java.nio.file.Path; +import java.security.KeyStore; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collection; @@ -28,9 +29,13 @@ import java.util.Objects; import org.eclipse.jetty.quic.quiche.QuicheConfig; import org.eclipse.jetty.quic.quiche.QuicheConnection; import org.eclipse.jetty.quic.quiche.SSLKeyPair; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; +import org.eclipse.jetty.util.resource.Resource; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import static org.eclipse.jetty.quic.quiche.Quiche.QUICHE_MIN_CLIENT_INITIAL_LEN; import static org.hamcrest.MatcherAssert.assertThat; @@ -38,8 +43,11 @@ import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.core.Is.is; +@ExtendWith(WorkDirExtension.class) public class LowLevelQuicheTest { + public WorkDir workDir; + private final Collection connectionsToDisposeOf = new ArrayList<>(); private InetSocketAddress clientSocketAddress; @@ -68,11 +76,16 @@ public class LowLevelQuicheTest clientQuicheConfig.setInitialMaxStreamsBidi(100L); clientQuicheConfig.setCongestionControl(QuicheConfig.CongestionControl.CUBIC); - SSLKeyPair serverKeyPair = new SSLKeyPair(Paths.get(Objects.requireNonNull(getClass().getResource("/keystore.p12")).toURI()).toFile(), "PKCS12", "storepwd".toCharArray(), "mykey", "storepwd".toCharArray()); - File[] pemFiles = serverKeyPair.export(new File(System.getProperty("java.io.tmpdir"))); + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + try (InputStream is = getClass().getResourceAsStream("/keystore.p12")) + { + keyStore.load(is, "storepwd".toCharArray()); + } + SSLKeyPair serverKeyPair = new SSLKeyPair(keyStore, "mykey", "storepwd".toCharArray()); + Path[] pemFiles = serverKeyPair.export(workDir.getEmptyPathDir()); serverQuicheConfig = new QuicheConfig(); - serverQuicheConfig.setPrivKeyPemPath(pemFiles[0].getPath()); - serverQuicheConfig.setCertChainPemPath(pemFiles[1].getPath()); + serverQuicheConfig.setPrivKeyPemPath(pemFiles[0].toString()); + serverQuicheConfig.setCertChainPemPath(pemFiles[1].toString()); serverQuicheConfig.setApplicationProtos("http/0.9"); serverQuicheConfig.setVerifyPeer(false); serverQuicheConfig.setMaxIdleTimeout(1_000L); diff --git a/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java b/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java index 55788eac966..284768d251d 100644 --- a/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java +++ b/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java @@ -13,13 +13,14 @@ package org.eclipse.jetty.quic.server; -import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.DatagramChannel; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyStore; import java.util.EventListener; import java.util.List; import java.util.Set; @@ -60,8 +61,8 @@ public class QuicServerConnector extends AbstractNetworkConnector private final QuicSessionContainer container = new QuicSessionContainer(); private final ServerDatagramSelectorManager selectorManager; private final SslContextFactory.Server sslContextFactory; - private File privateKeyFile; - private File certificateChainFile; + private Path privateKeyPath; + private Path certificateChainPath; private volatile DatagramChannel datagramChannel; private volatile int localPort = -1; private int inputBufferSize = 2048; @@ -163,19 +164,19 @@ public class QuicServerConnector extends AbstractNetworkConnector throw new IllegalStateException("Invalid KeyStore: no aliases"); String alias = sslContextFactory.getCertAlias(); if (alias == null) - alias = aliases.stream().findFirst().orElse("mykey"); - char[] keyStorePassword = sslContextFactory.getKeyStorePassword().toCharArray(); + alias = aliases.stream().findFirst().orElseThrow(); String keyManagerPassword = sslContextFactory.getKeyManagerPassword(); + char[] password = keyManagerPassword == null ? sslContextFactory.getKeyStorePassword().toCharArray() : keyManagerPassword.toCharArray(); + + KeyStore keyStore = sslContextFactory.getKeyStore(); SSLKeyPair keyPair = new SSLKeyPair( - sslContextFactory.getKeyStoreResource().getFile(), - sslContextFactory.getKeyStoreType(), - keyStorePassword, + keyStore, alias, - keyManagerPassword == null ? keyStorePassword : keyManagerPassword.toCharArray() + password ); - File[] pemFiles = keyPair.export(new File(System.getProperty("java.io.tmpdir"))); - privateKeyFile = pemFiles[0]; - certificateChainFile = pemFiles[1]; + Path[] pemFiles = keyPair.export(Path.of(System.getProperty("java.io.tmpdir"))); + privateKeyPath = pemFiles[0]; + certificateChainPath = pemFiles[1]; } @Override @@ -211,8 +212,8 @@ public class QuicServerConnector extends AbstractNetworkConnector QuicheConfig newQuicheConfig() { QuicheConfig quicheConfig = new QuicheConfig(); - quicheConfig.setPrivKeyPemPath(privateKeyFile.getPath()); - quicheConfig.setCertChainPemPath(certificateChainFile.getPath()); + quicheConfig.setPrivKeyPemPath(privateKeyPath.toString()); + quicheConfig.setCertChainPemPath(certificateChainPath.toString()); quicheConfig.setVerifyPeer(quicConfiguration.isVerifyPeerCertificates()); // Idle timeouts must not be managed by Quiche. quicheConfig.setMaxIdleTimeout(0L); @@ -240,8 +241,8 @@ public class QuicServerConnector extends AbstractNetworkConnector @Override protected void doStop() throws Exception { - deleteFile(privateKeyFile); - deleteFile(certificateChainFile); + deleteFile(privateKeyPath); + deleteFile(certificateChainPath); // We want the DatagramChannel to be stopped by the SelectorManager. super.doStop(); @@ -254,12 +255,12 @@ public class QuicServerConnector extends AbstractNetworkConnector selectorManager.removeEventListener(l); } - private void deleteFile(File file) + private void deleteFile(Path file) { try { if (file != null) - Files.delete(file.toPath()); + Files.delete(file); } catch (IOException x) { From 07a1c124f222037f24ef383e7157a9a8ae48d5bc Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Mon, 15 May 2023 11:36:04 +0200 Subject: [PATCH 2/8] #9772 improve QuicServerConnector key+crt export folder Signed-off-by: Ludovic Orban --- .../src/main/config/modules/http3.mod | 1 + .../quic/server/QuicServerConnector.java | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/jetty-http3/http3-server/src/main/config/modules/http3.mod b/jetty-http3/http3-server/src/main/config/modules/http3.mod index 74d3ebb09ff..eff3d069164 100644 --- a/jetty-http3/http3-server/src/main/config/modules/http3.mod +++ b/jetty-http3/http3-server/src/main/config/modules/http3.mod @@ -12,6 +12,7 @@ experimental http2 jna quiche +work [lib] lib/http3/*.jar diff --git a/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java b/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java index 284768d251d..2cbf0cc843f 100644 --- a/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java +++ b/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java @@ -174,11 +174,28 @@ public class QuicServerConnector extends AbstractNetworkConnector alias, password ); - Path[] pemFiles = keyPair.export(Path.of(System.getProperty("java.io.tmpdir"))); + Path[] pemFiles = keyPair.export(findTargetPath()); privateKeyPath = pemFiles[0]; certificateChainPath = pemFiles[1]; } + private Path findTargetPath() throws IOException + { + Path target; + String jettyBase = System.getProperty("jetty.base"); + if (jettyBase != null) + { + target = Path.of(jettyBase).resolve("work"); + } + else + { + target = sslContextFactory.getKeyStoreResource().getFile().getParentFile().toPath(); + if (!Files.isDirectory(target)) + target = Path.of("."); + } + return target; + } + @Override public void open() throws IOException { From b3b1d93152e5857565dccd4ddc0f49f06ef0afe8 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Tue, 16 May 2023 14:38:44 +0200 Subject: [PATCH 3/8] #9397 add quiche_conn_peer_cert() API mapping Signed-off-by: Ludovic Orban --- .../ForeignIncubatorQuicheConnection.java | 25 +++++++++++++++++++ .../quiche/foreign/incubator/quiche_h.java | 18 +++++++++++++ .../foreign/incubator/LowLevelQuicheTest.java | 21 ++++++++++++++-- .../quic/quiche/jna/JnaQuicheConnection.java | 25 +++++++++++++++++-- .../jetty/quic/quiche/jna/LibQuiche.java | 3 +++ .../jetty/quic/quiche/jna/char_pointer.java | 5 ++++ .../quic/quiche/jna/LowLevelQuicheTest.java | 21 ++++++++++++++-- 7 files changed, 112 insertions(+), 6 deletions(-) diff --git a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/ForeignIncubatorQuicheConnection.java b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/ForeignIncubatorQuicheConnection.java index d8b4f87dc22..105270640aa 100644 --- a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/ForeignIncubatorQuicheConnection.java +++ b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/ForeignIncubatorQuicheConnection.java @@ -483,6 +483,31 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection } } + public byte[] getPeerCertificate() + { + try (AutoLock ignore = lock.lock()) + { + if (quicheConn == null) + throw new IllegalStateException("connection was released"); + + try (ResourceScope scope = ResourceScope.newConfinedScope()) + { + MemorySegment outSegment = MemorySegment.allocateNative(CLinker.C_POINTER, scope); + MemorySegment outLenSegment = MemorySegment.allocateNative(CLinker.C_LONG, scope); + quiche_h.quiche_conn_peer_cert(quicheConn, outSegment.address(), outLenSegment.address()); + + long outLen = getLong(outLenSegment); + if (outLen == 0L) + return null; + byte[] out = new byte[(int)outLen]; + // dereference outSegment pointer + MemoryAddress memoryAddress = MemoryAddress.ofLong(getLong(outSegment)); + memoryAddress.asSegment(outLen, ResourceScope.globalScope()).asByteBuffer().get(out); + return out; + } + } + } + @Override protected List iterableStreamIds(boolean write) { diff --git a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/quiche_h.java b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/quiche_h.java index fb9803c066c..59329251e5b 100644 --- a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/quiche_h.java +++ b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/quiche_h.java @@ -250,6 +250,12 @@ public class quiche_h FunctionDescriptor.of(C_CHAR, C_POINTER) ); + private static final MethodHandle quiche_conn_peer_cert$MH = downcallHandle( + "quiche_conn_peer_cert", + "(Ljdk/incubator/foreign/MemoryAddress;Ljdk/incubator/foreign/MemoryAddress;Ljdk/incubator/foreign/MemoryAddress;)V", + FunctionDescriptor.ofVoid(C_POINTER, C_POINTER, C_POINTER) + ); + private static final MethodHandle quiche_conn_peer_error$MH = downcallHandle( "quiche_conn_peer_error", "(Ljdk/incubator/foreign/MemoryAddress;Ljdk/incubator/foreign/MemoryAddress;Ljdk/incubator/foreign/MemoryAddress;Ljdk/incubator/foreign/MemoryAddress;Ljdk/incubator/foreign/MemoryAddress;)B", @@ -688,6 +694,18 @@ public class quiche_h } } + public static void quiche_conn_peer_cert(MemoryAddress conn, MemoryAddress out, MemoryAddress out_len) + { + try + { + quiche_conn_peer_cert$MH.invokeExact(conn, out, out_len); + } + catch (Throwable ex) + { + throw new AssertionError("should not reach here", ex); + } + } + public static byte quiche_conn_peer_error(MemoryAddress conn, MemoryAddress is_app, MemoryAddress error_code, MemoryAddress reason, MemoryAddress reason_len) { try diff --git a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java index 84cf9e88ffc..dce5ad6df16 100644 --- a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java +++ b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java @@ -20,19 +20,19 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.security.KeyStore; +import java.security.cert.Certificate; import java.util.AbstractMap; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.Objects; import org.eclipse.jetty.quic.quiche.QuicheConfig; import org.eclipse.jetty.quic.quiche.QuicheConnection; import org.eclipse.jetty.quic.quiche.SSLKeyPair; import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; -import org.eclipse.jetty.util.resource.Resource; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -57,6 +57,7 @@ public class LowLevelQuicheTest private QuicheConfig serverQuicheConfig; private ForeignIncubatorQuicheConnection.TokenMinter tokenMinter; private ForeignIncubatorQuicheConnection.TokenValidator tokenValidator; + private Certificate[] serverCertificateChain; @BeforeEach protected void setUp() throws Exception @@ -82,6 +83,7 @@ public class LowLevelQuicheTest { keyStore.load(is, "storepwd".toCharArray()); } + serverCertificateChain = keyStore.getCertificateChain("mykey"); SSLKeyPair serverKeyPair = new SSLKeyPair(keyStore, "mykey", "storepwd".toCharArray()); Path[] pemFiles = serverKeyPair.export(workDir.getEmptyPathDir()); serverQuicheConfig = new QuicheConfig(); @@ -147,6 +149,11 @@ public class LowLevelQuicheTest // assert that stream 0 is finished on server assertThat(serverQuicheConnection.isStreamFinished(0), is(true)); + + // assert that the server certificate was correctly received by the client + byte[] peerCertificate = clientQuicheConnection.getPeerCertificate(); + byte[] serverCert = serverCertificateChain[0].getEncoded(); + assertThat(Arrays.equals(serverCert, peerCertificate), is(true)); } @Test @@ -180,6 +187,11 @@ public class LowLevelQuicheTest // assert that stream 0 is finished on server assertThat(serverQuicheConnection.isStreamFinished(0), is(true)); + + // assert that the server certificate was correctly received by the client + byte[] peerCertificate = clientQuicheConnection.getPeerCertificate(); + byte[] serverCert = serverCertificateChain[0].getEncoded(); + assertThat(Arrays.equals(serverCert, peerCertificate), is(true)); } @Test @@ -195,6 +207,11 @@ public class LowLevelQuicheTest assertThat(clientQuicheConnection.getNegotiatedProtocol(), is("€")); assertThat(serverQuicheConnection.getNegotiatedProtocol(), is("€")); + + // assert that the server certificate was correctly received by the client + byte[] peerCertificate = clientQuicheConnection.getPeerCertificate(); + byte[] serverCert = serverCertificateChain[0].getEncoded(); + assertThat(Arrays.equals(serverCert, peerCertificate), is(true)); } private void drainServerToFeedClient(Map.Entry entry, int expectedSize) throws IOException diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java b/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java index 730c55414f6..c9228230a5b 100644 --- a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java +++ b/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java @@ -385,8 +385,29 @@ public class JnaQuicheConnection extends QuicheConnection public void enableQlog(String filename, String title, String desc) throws IOException { - if (!LibQuiche.INSTANCE.quiche_conn_set_qlog_path(quicheConn, filename, title, desc)) - throw new IOException("unable to set qlog path to " + filename); + try (AutoLock ignore = lock.lock()) + { + if (quicheConn == null) + throw new IllegalStateException("connection was released"); + + if (!LibQuiche.INSTANCE.quiche_conn_set_qlog_path(quicheConn, filename, title, desc)) + throw new IOException("unable to set qlog path to " + filename); + } + } + + public byte[] getPeerCertificate() + { + try (AutoLock ignore = lock.lock()) + { + if (quicheConn == null) + throw new IllegalStateException("connection was released"); + + char_pointer out = new char_pointer(); + size_t_pointer out_len = new size_t_pointer(); + LibQuiche.INSTANCE.quiche_conn_peer_cert(quicheConn, out, out_len); + int len = out_len.getPointee().intValue(); + return out.getValueAsBytes(len); + } } @Override diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/LibQuiche.java b/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/LibQuiche.java index 813d0d2f8ac..f61fe82e0c1 100644 --- a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/LibQuiche.java +++ b/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/LibQuiche.java @@ -425,6 +425,9 @@ public interface LibQuiche extends Library // Returns true if the connection was closed due to the idle timeout. boolean quiche_conn_is_timed_out(quiche_conn conn); + // Returns the peer's leaf certificate (if any) as a DER-encoded buffer. + void quiche_conn_peer_cert(quiche_conn conn, char_pointer out, size_t_pointer out_len); + // Returns true if a connection error was received, and updates the provided // parameters accordingly. boolean quiche_conn_peer_error(quiche_conn conn, diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/char_pointer.java b/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/char_pointer.java index 19f310c5cdc..a55a778bafb 100644 --- a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/char_pointer.java +++ b/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/char_pointer.java @@ -23,4 +23,9 @@ public class char_pointer extends PointerByReference { return new String(getValue().getByteArray(0, len), charset); } + + public byte[] getValueAsBytes(int len) + { + return getValue().getByteArray(0, len); + } } diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java b/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java index 988f44f45d1..3946cfe98a7 100644 --- a/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java +++ b/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java @@ -19,19 +19,19 @@ import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.file.Path; import java.security.KeyStore; +import java.security.cert.Certificate; import java.util.AbstractMap; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.Objects; import org.eclipse.jetty.quic.quiche.QuicheConfig; import org.eclipse.jetty.quic.quiche.QuicheConnection; import org.eclipse.jetty.quic.quiche.SSLKeyPair; import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; -import org.eclipse.jetty.util.resource.Resource; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -56,6 +56,7 @@ public class LowLevelQuicheTest private QuicheConfig serverQuicheConfig; private JnaQuicheConnection.TokenMinter tokenMinter; private JnaQuicheConnection.TokenValidator tokenValidator; + private Certificate[] serverCertificateChain; @BeforeEach protected void setUp() throws Exception @@ -81,6 +82,7 @@ public class LowLevelQuicheTest { keyStore.load(is, "storepwd".toCharArray()); } + serverCertificateChain = keyStore.getCertificateChain("mykey"); SSLKeyPair serverKeyPair = new SSLKeyPair(keyStore, "mykey", "storepwd".toCharArray()); Path[] pemFiles = serverKeyPair.export(workDir.getEmptyPathDir()); serverQuicheConfig = new QuicheConfig(); @@ -146,6 +148,11 @@ public class LowLevelQuicheTest // assert that stream 0 is finished on server assertThat(serverQuicheConnection.isStreamFinished(0), is(true)); + + // assert that the server certificate was correctly received by the client + byte[] peerCertificate = clientQuicheConnection.getPeerCertificate(); + byte[] serverCert = serverCertificateChain[0].getEncoded(); + assertThat(Arrays.equals(serverCert, peerCertificate), is(true)); } @Test @@ -179,6 +186,11 @@ public class LowLevelQuicheTest // assert that stream 0 is finished on server assertThat(serverQuicheConnection.isStreamFinished(0), is(true)); + + // assert that the server certificate was correctly received by the client + byte[] peerCertificate = clientQuicheConnection.getPeerCertificate(); + byte[] serverCert = serverCertificateChain[0].getEncoded(); + assertThat(Arrays.equals(serverCert, peerCertificate), is(true)); } @Test @@ -194,6 +206,11 @@ public class LowLevelQuicheTest assertThat(clientQuicheConnection.getNegotiatedProtocol(), is("€")); assertThat(serverQuicheConnection.getNegotiatedProtocol(), is("€")); + + // assert that the server certificate was correctly received by the client + byte[] peerCertificate = clientQuicheConnection.getPeerCertificate(); + byte[] serverCert = serverCertificateChain[0].getEncoded(); + assertThat(Arrays.equals(serverCert, peerCertificate), is(true)); } private void drainServerToFeedClient(Map.Entry entry, int expectedSize) throws IOException From e30b23aca602cb9fa86be33bcb32894d3a5d9eeb Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Tue, 16 May 2023 14:47:03 +0200 Subject: [PATCH 4/8] #9397 fix keystores with invalid Subject Alternate Names Signed-off-by: Ludovic Orban --- .../src/test/resources/client_keystore.p12 | Bin 4629 -> 4806 bytes jetty-client/src/test/resources/keystore.p12 | Bin 2613 -> 2774 bytes .../src/test/resources/readme_keystores.txt | 2 +- .../src/test/resources/keystore.p12 | Bin 2597 -> 2774 bytes .../src/test/resources/keystore.p12 | Bin 2445 -> 2565 bytes .../src/test/resources/keystore.p12 | Bin 2445 -> 2565 bytes .../foreign/incubator/LowLevelQuicheTest.java | 2 +- .../src/test/resources/keystore.p12 | Bin 2445 -> 2565 bytes .../quic/quiche/jna/LowLevelQuicheTest.java | 2 +- .../src/test/resources/keystore.p12 | Bin 2445 -> 2565 bytes .../src/test/resources/keystore.p12 | Bin 2445 -> 2565 bytes jetty-server/src/test/resources/keystore.p12 | Bin 2613 -> 2774 bytes .../src/test/resources/keystore.p12 | Bin 2597 -> 2774 bytes 13 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jetty-client/src/test/resources/client_keystore.p12 b/jetty-client/src/test/resources/client_keystore.p12 index 90f717671652d2c7cf3a45ef50132f1f5f3b1536..52a94c5591b4dcc0fc4805e92b3cee5e553d17f1 100644 GIT binary patch literal 4806 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 C)Bn@} delta 2589 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 C)Bn@} delta 2573 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 diff --git a/jetty-http3/http3-tests/src/test/resources/keystore.p12 b/jetty-http3/http3-tests/src/test/resources/keystore.p12 index 0b56dd34ee9df09c61bd635095485252c86e6df7..8ab40f72afd41321f506c8398f0d6c6ba6301d07 100644 GIT binary patch delta 2467 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 Qar+W14`8xA0s{etps?Ir{Qv*} diff --git a/jetty-quic/quic-client/src/test/resources/keystore.p12 b/jetty-quic/quic-client/src/test/resources/keystore.p12 index 0b56dd34ee9df09c61bd635095485252c86e6df7..8ab40f72afd41321f506c8398f0d6c6ba6301d07 100644 GIT binary patch delta 2467 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 Qar+W14`8xA0s{etps?Ir{Qv*} diff --git a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java index dce5ad6df16..34f22140459 100644 --- a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java +++ b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java @@ -287,7 +287,7 @@ public class LowLevelQuicheTest for (String proto : clientQuicheConfig.getApplicationProtos()) protosLen += 1 + proto.getBytes(StandardCharsets.UTF_8).length; - drainServerToFeedClient(entry, 300 + protosLen); + drainServerToFeedClient(entry, 420 + protosLen); assertThat(serverQuicheConnection.isConnectionEstablished(), is(false)); assertThat(clientQuicheConnection.isConnectionEstablished(), is(true)); diff --git a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/resources/keystore.p12 b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/resources/keystore.p12 index 0b56dd34ee9df09c61bd635095485252c86e6df7..8ab40f72afd41321f506c8398f0d6c6ba6301d07 100644 GIT binary patch delta 2467 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 Qar+W14`8xA0s{etps?Ir{Qv*} diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java b/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java index 3946cfe98a7..92a58926f17 100644 --- a/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java +++ b/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java @@ -286,7 +286,7 @@ public class LowLevelQuicheTest for (String proto : clientQuicheConfig.getApplicationProtos()) protosLen += 1 + proto.getBytes(LibQuiche.CHARSET).length; - drainServerToFeedClient(entry, 300 + protosLen); + drainServerToFeedClient(entry, 420 + protosLen); assertThat(serverQuicheConnection.isConnectionEstablished(), is(false)); assertThat(clientQuicheConnection.isConnectionEstablished(), is(true)); diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/test/resources/keystore.p12 b/jetty-quic/quic-quiche/quic-quiche-jna/src/test/resources/keystore.p12 index 0b56dd34ee9df09c61bd635095485252c86e6df7..8ab40f72afd41321f506c8398f0d6c6ba6301d07 100644 GIT binary patch delta 2467 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 Qar+W14`8xA0s{etps?Ir{Qv*} diff --git a/jetty-quic/quic-server/src/test/resources/keystore.p12 b/jetty-quic/quic-server/src/test/resources/keystore.p12 index 0b56dd34ee9df09c61bd635095485252c86e6df7..8ab40f72afd41321f506c8398f0d6c6ba6301d07 100644 GIT binary patch delta 2467 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 Qar+W14`8xA0s{etps?Ir{Qv*} diff --git a/jetty-server/src/test/resources/keystore.p12 b/jetty-server/src/test/resources/keystore.p12 index 01a1aea534e2e511e52396db50a52632434e2d81..d96d0667bd6619914779294596679f38f320be7e 100644 GIT binary patch literal 2774 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 C)Bn@} delta 2589 zcmV+&3gY$F6}1$9FoFs(0s#Xsf(hyd2`Yw2hW8Bt2LYgh3EKpM3D+=!3DYow1!o2c zDuzgg_YDCD0ic2fSOkIvR4{@CP%wf8Oa==ohDe6@4FL=a0Ro_c1p6?81otp01_~;M zNQU@!E#;lJ!Q^Ho1BYfGjnz)M zq(QpdDbe45ylL!>Kg}Wh)<=P9oi%MjkfAGq^|bX(l`=*?p$uqKX|!*tYN)Ud&Zf}M`jC~|5dHY&p^!-{j9j8OuM<=c$h1T> zf$Hl3ZItZ4qOIl`<<&8&=tDoyfu=TVJn`6wc#@!h{+=7v<6AFvKEOQ?6jdQO;KBL1 zJ?O8JmIYB#xi+V?n^sfDi$1RKM5|!?Xg*^{-#`3trTZ=ASSw6-o`E zx>kB{U;atGVBpdu>9+~0x3a8c^BO-Ac1SO8ES zvpNSF@m|`(NWWP`0;-@_w4@^*IaM1$pFWJ({(-sAX`;2Jv;U~|@<-)w_J`ADAh85A zb?tq9B1n_lFPg@+V9n``P0dDhWQ^~B4VKev={kiq52#84?Y;VpP(qZvY>oAMHCqb( zYS>67c5@FweJ5ZEidy1b9@mhHVyMbr{iv|I>A#hklK$;twJ)#Vuncc{eCbokj;N{)=z~g1pe7QmX1(uVOHq-360gUO zjVQcfR>7~1=`1+3FPVkgk&5D&A5+#z*i^4$;9sDpk}tuAw8k=DqG*m5__{OTiMr|> zI)ny|VZhuk*o_vd3e1B*&#T!BBmdy1mv+c;peX8ydt(cxg0tJG{4dJq4$=6h-Auyxh4_kJ2-dR(bm@p zcw((cjng<$QLZqUv>^Xnpt@g&6Jb>-*8^O$W4=XEu((?4?ra-{(4%ly>Jj(xdCZ*7 zntd4vtfSXfqY#bjAWtyMR?G@P@kv$r!23^Hf&MtuJ5FZ4oS5^MeWd2F4anEdfc!dF_y7Tfj zo-87N4aI*b=VzHy6sOS#^0O%pbkb*^?P3qZj zHphLkE#FCdz!x(^Ue%td(Qn3y4`g+QO08($9F;0&{C#f-Msw7@CGd2Yi)hM+DCu{KzqAqj6 z3V!Eoi3P@@j(Cx3XRmO3mPd~#{{RFY6Ej2tfaLvo-u@#dJre=<1CfE-KVbs}f*Pdv zWbH+qOfa?Jg!6cq2St}qiXW^jqcCK&pg$t3&WC6_Pm4~j;b5TnM@Bh&@FRqyPAqGG z$W49@zynwH{zeT7iZS0;DY(;#@EN1B3hkr{ zYDpaU>wfcC;}kL@4cz>f7M|@i!*}~Yp>5+cNLsN*NIoa6wo_wG2eqe zso@E!tPB&G+5}BCc81U?gvjY+g znn4_Oq8n`W%VwMUv!*b9>?^naICYZ;99tvDiJXOWQcE>Z&Lhi@)4(*Ziz_jI0rY1P zOr|uaj%&Y9hh7Bl^N4)5gd*akgh{-V);P+xP~rtx6JpksxFv989~P`{axBee8rUb( zgKA@C*T;*n0d}B&Yd)D^!8mzXw7CkB`J!^)F%L{%T&v9n9b8U;_{qW4;Et>vKd`)J zZ=Fd@XtpqRZfn<%NK(JYBbLN}dyE4AU615QBC89WR6idw??Np-Qw_F&%=<_E0)ADZ zm(Z?pZbC;-mXul5*B~I3IOczSay`TgN=k?eu%+haZ1MDgj4r z4SWwfeoj%Q4v)o+H%^OtxR|8Kr1N@WF~-w6RxuNg$HuCheKZk^k+J1}tmWR$+%WXM zWzR*Mmo|_seV5J()R`em*AR0ZI$B$UG2*$pU+39+WODQAYDCbMmW_nt*Qi7h?=)p5 zH=$!gBucFEqVu8ceXG57C_1{KJ}@CXFbM_)D-Ht!8U+9Z z6h?b+v13kusCLr#+o?bCbZkwRR|FJ!xV_9qU2A?QTKK$68aAOx&2{Di0|ADhbs*D8 diff --git a/tests/test-http-client-transport/src/test/resources/keystore.p12 b/tests/test-http-client-transport/src/test/resources/keystore.p12 index 8934437fa14490265dfdb5d9a55bfaa88d039dcd..d96d0667bd6619914779294596679f38f320be7e 100644 GIT binary patch literal 2774 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 C)Bn@} delta 2573 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 Date: Tue, 16 May 2023 15:49:21 +0200 Subject: [PATCH 5/8] #9397 add trust store config to H3 Signed-off-by: Ludovic Orban --- .../http3/tests/AbstractClientServerTest.java | 19 +++++- .../org/eclipse/jetty/io/ClientConnector.java | 4 +- .../quic/client/ClientQuicConnection.java | 13 ++++- .../QuicClientConnectorConfigurator.java | 21 ++++++- .../jetty/quic/client/End2EndClientTest.java | 27 +++++++-- .../jetty/quic/common/QuicConfiguration.java | 31 ++++++---- .../{SSLKeyPair.java => PemExporter.java} | 58 ++++++++++--------- .../jetty/quic/quiche/QuicheConfig.java | 11 ++++ .../ForeignIncubatorQuicheConnection.java | 26 ++++++++- .../quiche/foreign/incubator/quiche_h.java | 18 ++++++ .../foreign/incubator/LowLevelQuicheTest.java | 24 ++++---- .../quic/quiche/jna/JnaQuicheConnection.java | 26 ++++++++- .../jetty/quic/quiche/jna/LibQuiche.java | 3 + .../quic/quiche/jna/LowLevelQuicheTest.java | 24 ++++---- .../quic/server/QuicServerConnector.java | 34 +++++------ .../tests/distribution/DistributionTests.java | 3 +- .../client/HttpChannelAssociationTest.java | 1 - .../jetty/http/client/HttpClientLoadTest.java | 6 +- .../jetty/http/client/HttpClientTest.java | 12 +--- .../jetty/http/client/TransportScenario.java | 28 +++++++-- 20 files changed, 274 insertions(+), 115 deletions(-) rename jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/{SSLKeyPair.java => PemExporter.java} (68%) diff --git a/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/AbstractClientServerTest.java b/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/AbstractClientServerTest.java index 9c9b9189687..585276ec67b 100644 --- a/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/AbstractClientServerTest.java +++ b/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/AbstractClientServerTest.java @@ -13,8 +13,10 @@ package org.eclipse.jetty.http3.tests; +import java.io.InputStream; import java.lang.management.ManagementFactory; import java.net.InetSocketAddress; +import java.security.KeyStore; import java.util.concurrent.TimeUnit; 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.Handler; 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.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.RegisterExtension; +@ExtendWith(WorkDirExtension.class) public class AbstractClientServerTest { + public WorkDir workDir; + @RegisterExtension final BeforeTestExecutionCallback printMethodName = context -> 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"); server = new Server(serverThreads); connector = new HTTP3ServerConnector(server, sslContextFactory, serverConnectionFactory); + connector.getQuicConfiguration().setPemWorkDirectory(workDir.getEmptyPathDir()); server.addConnector(connector); MBeanContainer mbeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer()); server.addBean(mbeanContainer); @@ -88,8 +97,16 @@ public class AbstractClientServerTest 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.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))); QueuedThreadPool clientThreads = new QueuedThreadPool(); clientThreads.setName("client"); diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java index 4fcd115dd23..7e167132ec0 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java @@ -373,6 +373,7 @@ public class ClientConnector extends ContainerLifeCycle selectorManager = newSelectorManager(); selectorManager.setConnectTimeout(getConnectTimeout().toMillis()); addBean(selectorManager); + configurator.addBean(this); super.doStart(); } @@ -381,6 +382,7 @@ public class ClientConnector extends ContainerLifeCycle { super.doStop(); removeBean(selectorManager); + configurator.removeBean(this); } protected SslContextFactory.Client newSslContextFactory() @@ -588,7 +590,7 @@ public class ClientConnector extends ContainerLifeCycle /** *

Configures a {@link ClientConnector}.

*/ - public static class Configurator + public static class Configurator extends ContainerLifeCycle { /** *

Returns whether the connection to a given {@link SocketAddress} is intrinsically secure.

diff --git a/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java b/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java index 697cc3aa7fb..66e48cba119 100644 --- a/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java +++ b/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java @@ -18,6 +18,7 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.SocketTimeoutException; import java.nio.ByteBuffer; +import java.nio.file.Path; import java.util.Collection; import java.util.List; import java.util.Map; @@ -80,7 +81,10 @@ public class ClientQuicConnection extends QuicConnection QuicheConfig quicheConfig = new QuicheConfig(); quicheConfig.setApplicationProtos(protocols.toArray(String[]::new)); 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. quicheConfig.setMaxIdleTimeout(0L); quicheConfig.setInitialMaxData((long)quicConfiguration.getSessionRecvWindow()); @@ -147,6 +151,13 @@ public class ClientQuicConnection extends QuicConnection return null; } + @Override + protected void onFailure(Throwable failure) + { + pendingSessions.values().forEach(session -> outwardClose(session, failure)); + super.onFailure(failure); + } + @Override public boolean onIdleExpired() { diff --git a/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/QuicClientConnectorConfigurator.java b/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/QuicClientConnectorConfigurator.java index ee8040af351..cf1b618980e 100644 --- a/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/QuicClientConnectorConfigurator.java +++ b/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/QuicClientConnectorConfigurator.java @@ -19,6 +19,8 @@ import java.nio.channels.DatagramChannel; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; +import java.nio.file.Path; +import java.security.KeyStore; import java.util.Map; import java.util.Objects; 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.SocketChannelEndPoint; import org.eclipse.jetty.quic.common.QuicConfiguration; +import org.eclipse.jetty.quic.quiche.PemExporter; +import org.eclipse.jetty.util.ssl.SslContextFactory; /** *

A QUIC specific {@link ClientConnector.Configurator}.

@@ -41,6 +45,8 @@ import org.eclipse.jetty.quic.common.QuicConfiguration; */ public class QuicClientConnectorConfigurator extends ClientConnector.Configurator { + static final String TRUSTSTORE_PATH_KEY = QuicClientConnectorConfigurator.class.getName() + ".trustStorePath"; + private final QuicConfiguration configuration = new QuicConfiguration(); private final UnaryOperator configurator; @@ -56,7 +62,6 @@ public class QuicClientConnectorConfigurator extends ClientConnector.Configurato configuration.setSessionRecvWindow(16 * 1024 * 1024); configuration.setBidirectionalStreamRecvWindow(8 * 1024 * 1024); configuration.setDisableActiveMigration(true); - configuration.setVerifyPeerCertificates(true); } public QuicConfiguration getQuicConfiguration() @@ -64,6 +69,19 @@ public class QuicClientConnectorConfigurator extends ClientConnector.Configurato 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 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 context) throws IOException { context.put(QuicConfiguration.CONTEXT_KEY, configuration); + DatagramChannel channel = DatagramChannel.open(); if (clientConnector.getBindAddress() == null) { diff --git a/jetty-quic/quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientTest.java b/jetty-quic/quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientTest.java index 156f96a3723..3aa97b2232b 100644 --- a/jetty-quic/quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientTest.java +++ b/jetty-quic/quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientTest.java @@ -14,7 +14,9 @@ package org.eclipse.jetty.quic.client; import java.io.IOException; +import java.io.InputStream; import java.io.PrintWriter; +import java.security.KeyStore; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; 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.Server; 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.ssl.SslContextFactory; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; +@ExtendWith(WorkDirExtension.class) public class End2EndClientTest { + public WorkDir workDir; + private Server server; private QuicServerConnector connector; private HttpClient client; @@ -61,8 +69,14 @@ public class End2EndClientTest @BeforeEach 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.setKeyStorePath("src/test/resources/keystore.p12"); + sslContextFactory.setKeyStore(keyStore); sslContextFactory.setKeyStorePassword("storepwd"); server = new Server(); @@ -71,6 +85,7 @@ public class End2EndClientTest HttpConnectionFactory http1 = new HttpConnectionFactory(httpConfiguration); HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(httpConfiguration); connector = new QuicServerConnector(server, sslContextFactory, http1, http2); + connector.getQuicConfiguration().setPemWorkDirectory(workDir.getEmptyPathDir()); server.addConnector(connector); server.setHandler(new AbstractHandler() @@ -86,11 +101,13 @@ public class End2EndClientTest 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; - ClientConnectionFactoryOverHTTP2.HTTP2 http2Info = new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client()); - QuicClientConnectorConfigurator configurator = new QuicClientConnectorConfigurator(); - configurator.getQuicConfiguration().setVerifyPeerCertificates(false); - HttpClientTransportDynamic transport = new HttpClientTransportDynamic(new ClientConnector(configurator), http1Info, http2Info); + ClientConnectionFactoryOverHTTP2.HTTP2 http2Info = new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector)); + HttpClientTransportDynamic transport = new HttpClientTransportDynamic(clientConnector, http1Info, http2Info); client = new HttpClient(transport); client.start(); } diff --git a/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConfiguration.java b/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConfiguration.java index 8005bb98e6d..aacbb64ba9e 100644 --- a/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConfiguration.java +++ b/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConfiguration.java @@ -13,7 +13,10 @@ package org.eclipse.jetty.quic.common; +import java.nio.file.Path; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** *

A record that captures QUIC configuration parameters.

@@ -24,12 +27,13 @@ public class QuicConfiguration private List protocols = List.of(); private boolean disableActiveMigration; - private boolean verifyPeerCertificates; private int maxBidirectionalRemoteStreams; private int maxUnidirectionalRemoteStreams; private int sessionRecvWindow; private int bidirectionalStreamRecvWindow; private int unidirectionalStreamRecvWindow; + private Path pemWorkDirectory; + private final Map implementationSpecifixContext = new HashMap<>(); public List getProtocols() { @@ -51,16 +55,6 @@ public class QuicConfiguration this.disableActiveMigration = disableActiveMigration; } - public boolean isVerifyPeerCertificates() - { - return verifyPeerCertificates; - } - - public void setVerifyPeerCertificates(boolean verifyPeerCertificates) - { - this.verifyPeerCertificates = verifyPeerCertificates; - } - public int getMaxBidirectionalRemoteStreams() { return maxBidirectionalRemoteStreams; @@ -110,4 +104,19 @@ public class QuicConfiguration { this.unidirectionalStreamRecvWindow = unidirectionalStreamRecvWindow; } + + public Path getPemWorkDirectory() + { + return pemWorkDirectory; + } + + public void setPemWorkDirectory(Path pemWorkDirectory) + { + this.pemWorkDirectory = pemWorkDirectory; + } + + public Map getImplementationSpecifixContext() + { + return implementationSpecifixContext; + } } diff --git a/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/SSLKeyPair.java b/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/PemExporter.java similarity index 68% rename from jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/SSLKeyPair.java rename to jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/PemExporter.java index c5a6ad54b7c..e02e6817e23 100644 --- a/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/SSLKeyPair.java +++ b/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/PemExporter.java @@ -14,7 +14,6 @@ package org.eclipse.jetty.quic.quiche; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -22,56 +21,62 @@ import java.nio.file.Path; import java.nio.file.attribute.PosixFilePermission; import java.security.Key; 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.CertificateEncodingException; -import java.security.cert.CertificateException; import java.util.Base64; +import java.util.Enumeration; import java.util.Set; -import org.eclipse.jetty.util.resource.Resource; import org.slf4j.Logger; 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[] 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[] 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 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 final Key key; - private final Certificate[] certChain; - private final String alias; - - public SSLKeyPair(KeyStore keyStore, String alias, char[] keyPassword) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException + private PemExporter() { - 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 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. */ - 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)) throw new IllegalArgumentException("Target folder is not a directory: " + targetFolder); Path[] paths = new Path[2]; - paths[0] = targetFolder.resolve(alias + ".key"); paths[1] = targetFolder.resolve(alias + ".crt"); - try (OutputStream os = Files.newOutputStream(paths[1])) { + Certificate[] certChain = keyStore.getCertificateChain(alias); for (Certificate cert : certChain) writeAsPEM(os, cert); Files.setPosixFilePermissions(paths[1], Set.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE)); @@ -82,8 +87,10 @@ public class SSLKeyPair if (LOG.isDebugEnabled()) LOG.debug("Unable to set Posix file permissions", e); } + paths[0] = targetFolder.resolve(alias + ".key"); try (OutputStream os = Files.newOutputStream(paths[0])) { + Key key = keyStore.getKey(alias, keyPassword); writeAsPEM(os, key); Files.setPosixFilePermissions(paths[0], Set.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE)); } @@ -93,13 +100,12 @@ public class SSLKeyPair if (LOG.isDebugEnabled()) LOG.debug("Unable to set Posix file permissions", e); } - 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(LINE_SEPARATOR); outputStream.write(encoded); @@ -108,9 +114,9 @@ public class SSLKeyPair 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(LINE_SEPARATOR); outputStream.write(encoded); diff --git a/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/QuicheConfig.java b/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/QuicheConfig.java index 33b36a41ea7..08fcff2d4d3 100644 --- a/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/QuicheConfig.java +++ b/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/QuicheConfig.java @@ -35,6 +35,7 @@ public class QuicheConfig private int version = Quiche.QUICHE_PROTOCOL_VERSION; private Boolean verifyPeer; + private String trustedCertsPemPath; private String certChainPemPath; private String privKeyPemPath; private String[] applicationProtos; @@ -65,6 +66,11 @@ public class QuicheConfig return verifyPeer; } + public String getTrustedCertsPemPath() + { + return trustedCertsPemPath; + } + public String getCertChainPemPath() { return certChainPemPath; @@ -150,6 +156,11 @@ public class QuicheConfig this.verifyPeer = verify; } + public void setTrustedCertsPemPath(String trustedCertsPemPath) + { + this.trustedCertsPemPath = trustedCertsPemPath; + } + public void setCertChainPemPath(String path) { this.certChainPemPath = path; diff --git a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/ForeignIncubatorQuicheConnection.java b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/ForeignIncubatorQuicheConnection.java index 105270640aa..1df4681f8d9 100644 --- a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/ForeignIncubatorQuicheConnection.java +++ b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/ForeignIncubatorQuicheConnection.java @@ -28,6 +28,7 @@ import jdk.incubator.foreign.CLinker; import jdk.incubator.foreign.MemoryAddress; import jdk.incubator.foreign.MemorySegment; 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.QuicheConfig; import org.eclipse.jetty.quic.quiche.QuicheConnection; @@ -148,7 +149,7 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection MemorySegment localSockaddr = sockaddr.convert(local, 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); keepScope = true; return connection; @@ -170,13 +171,29 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection if (verifyPeer != null) 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(); 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(); 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(); 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()); } } + // 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) throw new IOException("failed to receive packet; err=" + quiche_error.errToString(received)); buffer.position((int)(buffer.position() + received)); diff --git a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/quiche_h.java b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/quiche_h.java index 59329251e5b..71c30ec34e8 100644 --- a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/quiche_h.java +++ b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/quiche_h.java @@ -52,6 +52,12 @@ public class quiche_h 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( "quiche_config_load_cert_chain_from_pem_file", "(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) { try diff --git a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java index 34f22140459..6bc6f129625 100644 --- a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java +++ b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java @@ -30,7 +30,7 @@ import java.util.Map; import org.eclipse.jetty.quic.quiche.QuicheConfig; 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.WorkDirExtension; import org.junit.jupiter.api.AfterEach; @@ -65,10 +65,18 @@ public class LowLevelQuicheTest clientSocketAddress = new InetSocketAddress("localhost", 9999); 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.setApplicationProtos("http/0.9"); clientQuicheConfig.setDisableActiveMigration(true); - clientQuicheConfig.setVerifyPeer(false); + clientQuicheConfig.setVerifyPeer(true); + clientQuicheConfig.setTrustedCertsPemPath(PemExporter.exportTrustStore(keyStore, targetFolder).toString()); clientQuicheConfig.setMaxIdleTimeout(1_000L); clientQuicheConfig.setInitialMaxData(10_000_000L); clientQuicheConfig.setInitialMaxStreamDataBidiLocal(10_000_000L); @@ -78,17 +86,11 @@ public class LowLevelQuicheTest clientQuicheConfig.setInitialMaxStreamsBidi(100L); 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"); - SSLKeyPair serverKeyPair = new SSLKeyPair(keyStore, "mykey", "storepwd".toCharArray()); - Path[] pemFiles = serverKeyPair.export(workDir.getEmptyPathDir()); serverQuicheConfig = new QuicheConfig(); - serverQuicheConfig.setPrivKeyPemPath(pemFiles[0].toString()); - serverQuicheConfig.setCertChainPemPath(pemFiles[1].toString()); + Path[] keyPair = PemExporter.exportKeyPair(keyStore, "mykey", "storepwd".toCharArray(), targetFolder); + serverQuicheConfig.setPrivKeyPemPath(keyPair[0].toString()); + serverQuicheConfig.setCertChainPemPath(keyPair[1].toString()); serverQuicheConfig.setApplicationProtos("http/0.9"); serverQuicheConfig.setVerifyPeer(false); serverQuicheConfig.setMaxIdleTimeout(1_000L); diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java b/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java index c9228230a5b..f29a67face4 100644 --- a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java +++ b/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java @@ -22,6 +22,7 @@ import java.security.SecureRandom; import java.util.ArrayList; 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.QuicheConfig; import org.eclipse.jetty.quic.quiche.QuicheConnection; @@ -114,7 +115,7 @@ public class JnaQuicheConnection extends QuicheConnection SizedStructure localSockaddr = sockaddr.convert(local); SizedStructure 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); } @@ -128,13 +129,29 @@ public class JnaQuicheConnection extends QuicheConnection if (verifyPeer != null) 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(); 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(); 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(); if (applicationProtos != null) @@ -450,6 +467,9 @@ public class JnaQuicheConnection extends QuicheConnection SizedStructure peerSockaddr = sockaddr.convert(peer); info.from = peerSockaddr.getStructure().byReference(); 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(); if (received < 0) throw new IOException("failed to receive packet; err=" + quiche_error.errToString(received)); diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/LibQuiche.java b/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/LibQuiche.java index f61fe82e0c1..1cb5e5530b4 100644 --- a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/LibQuiche.java +++ b/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/LibQuiche.java @@ -86,6 +86,9 @@ public interface LibQuiche extends Library // Configures whether to verify the peer's certificate. 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. int quiche_config_set_application_protos(quiche_config config, byte[] protos, size_t protos_len); diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java b/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java index 92a58926f17..25a62696848 100644 --- a/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java +++ b/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java @@ -29,7 +29,7 @@ import java.util.Map; import org.eclipse.jetty.quic.quiche.QuicheConfig; 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.WorkDirExtension; import org.junit.jupiter.api.AfterEach; @@ -64,10 +64,18 @@ public class LowLevelQuicheTest clientSocketAddress = new InetSocketAddress("localhost", 9999); 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.setApplicationProtos("http/0.9"); clientQuicheConfig.setDisableActiveMigration(true); - clientQuicheConfig.setVerifyPeer(false); + clientQuicheConfig.setVerifyPeer(true); + clientQuicheConfig.setTrustedCertsPemPath(PemExporter.exportTrustStore(keyStore, targetFolder).toString()); clientQuicheConfig.setMaxIdleTimeout(1_000L); clientQuicheConfig.setInitialMaxData(10_000_000L); clientQuicheConfig.setInitialMaxStreamDataBidiLocal(10_000_000L); @@ -77,17 +85,11 @@ public class LowLevelQuicheTest clientQuicheConfig.setInitialMaxStreamsBidi(100L); 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"); - SSLKeyPair serverKeyPair = new SSLKeyPair(keyStore, "mykey", "storepwd".toCharArray()); - Path[] pemFiles = serverKeyPair.export(workDir.getEmptyPathDir()); serverQuicheConfig = new QuicheConfig(); - serverQuicheConfig.setPrivKeyPemPath(pemFiles[0].toString()); - serverQuicheConfig.setCertChainPemPath(pemFiles[1].toString()); + Path[] keyPair = PemExporter.exportKeyPair(keyStore, "mykey", "storepwd".toCharArray(), targetFolder); + serverQuicheConfig.setPrivKeyPemPath(keyPair[0].toString()); + serverQuicheConfig.setCertChainPemPath(keyPair[1].toString()); serverQuicheConfig.setApplicationProtos("http/0.9"); serverQuicheConfig.setVerifyPeer(false); serverQuicheConfig.setMaxIdleTimeout(1_000L); diff --git a/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java b/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java index 2cbf0cc843f..145065577ba 100644 --- a/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java +++ b/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java @@ -37,8 +37,8 @@ import org.eclipse.jetty.quic.common.QuicConfiguration; import org.eclipse.jetty.quic.common.QuicSession; import org.eclipse.jetty.quic.common.QuicSessionContainer; 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.SSLKeyPair; import org.eclipse.jetty.server.AbstractNetworkConnector; import org.eclipse.jetty.server.ConnectionFactory; 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. quicConfiguration.setMaxBidirectionalRemoteStreams(1); quicConfiguration.setMaxUnidirectionalRemoteStreams(0); - quicConfiguration.setVerifyPeerCertificates(false); } public QuicConfiguration getQuicConfiguration() @@ -169,31 +168,24 @@ public class QuicServerConnector extends AbstractNetworkConnector char[] password = keyManagerPassword == null ? sslContextFactory.getKeyStorePassword().toCharArray() : keyManagerPassword.toCharArray(); KeyStore keyStore = sslContextFactory.getKeyStore(); - SSLKeyPair keyPair = new SSLKeyPair( - keyStore, - alias, - password - ); - Path[] pemFiles = keyPair.export(findTargetPath()); - privateKeyPath = pemFiles[0]; - certificateChainPath = pemFiles[1]; + Path[] keyPair = PemExporter.exportKeyPair(keyStore, alias, password, findCertificateWorkPath()); + privateKeyPath = keyPair[0]; + certificateChainPath = keyPair[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"); if (jettyBase != null) { - target = Path.of(jettyBase).resolve("work"); + pemWorkDirectory = Path.of(jettyBase).resolve("work"); + if (Files.exists(pemWorkDirectory)) + return pemWorkDirectory; } - else - { - target = sslContextFactory.getKeyStoreResource().getFile().getParentFile().toPath(); - if (!Files.isDirectory(target)) - target = Path.of("."); - } - return target; + throw new IllegalStateException("No PEM work directory configured"); } @Override @@ -231,7 +223,7 @@ public class QuicServerConnector extends AbstractNetworkConnector QuicheConfig quicheConfig = new QuicheConfig(); quicheConfig.setPrivKeyPemPath(privateKeyPath.toString()); quicheConfig.setCertChainPemPath(certificateChainPath.toString()); - quicheConfig.setVerifyPeer(quicConfiguration.isVerifyPeerCertificates()); + quicheConfig.setVerifyPeer(false); // Idle timeouts must not be managed by Quiche. quicheConfig.setMaxIdleTimeout(0L); quicheConfig.setInitialMaxData((long)quicConfiguration.getSessionRecvWindow()); diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java index 7a1d9f7f985..00d5e8c5cbe 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java @@ -23,7 +23,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Queue; @@ -1141,7 +1140,7 @@ public class DistributionTests extends AbstractJettyHomeTest assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS)); HTTP3Client http3Client = new HTTP3Client(); - http3Client.getQuicConfiguration().setVerifyPeerCertificates(false); + http3Client.getClientConnector().setSslContextFactory(new SslContextFactory.Client(true)); this.client = new HttpClient(new HttpClientTransportOverHTTP3(http3Client)); this.client.start(); ContentResponse response = this.client.newRequest("localhost", h3Port) diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpChannelAssociationTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpChannelAssociationTest.java index 159f945ab68..bd76d1b9ea3 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpChannelAssociationTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpChannelAssociationTest.java @@ -183,7 +183,6 @@ public class HttpChannelAssociationTest extends AbstractTest HTTP3Client http3Client = new HTTP3Client(); http3Client.getClientConnector().setSelectors(1); http3Client.getClientConnector().setSslContextFactory(scenario.newClientSslContextFactory()); - http3Client.getQuicConfiguration().setVerifyPeerCertificates(false); return new HttpClientTransportOverHTTP3(http3Client) { @Override diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientLoadTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientLoadTest.java index 46533bfd3f3..eaaeda2d464 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientLoadTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientLoadTest.java @@ -16,6 +16,7 @@ package org.eclipse.jetty.http.client; import java.io.IOException; import java.io.InterruptedIOException; import java.nio.ByteBuffer; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; 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.thread.Scheduler; import org.hamcrest.Matchers; -import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; import org.slf4j.Logger; @@ -389,7 +389,9 @@ public class HttpClientLoadTest extends AbstractTest clientThreads.setName("client"); scenario.client.setExecutor(clientThreads); 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 expectedType = transport == Transport.H3 ? TimeoutException.class : ExecutionException.class; + assertThrows(expectedType, () -> { // Use an IP address not present in the certificate. int serverPort = scenario.getServerPort().orElse(0); diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/TransportScenario.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/TransportScenario.java index 2a050095148..51a9eaf4d54 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/TransportScenario.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/TransportScenario.java @@ -14,9 +14,11 @@ package org.eclipse.jetty.http.client; import java.io.IOException; +import java.io.InputStream; import java.lang.management.ManagementFactory; import java.nio.file.Files; import java.nio.file.Path; +import java.security.KeyStore; import java.util.ArrayList; import java.util.List; import java.util.OptionalInt; @@ -122,7 +124,9 @@ public class TransportScenario case FCGI: return new ServerConnector(server, 1, 1, provideServerConnectionFactory(transport)); 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: UnixDomainServerConnector connector = new UnixDomainServerConnector(server, provideServerConnectionFactory(transport)); connector.setUnixDomainPath(unixDomainPath); @@ -175,7 +179,6 @@ public class TransportScenario ClientConnector clientConnector = http3Client.getClientConnector(); clientConnector.setSelectors(1); clientConnector.setSslContextFactory(sslContextFactory); - http3Client.getQuicConfiguration().setVerifyPeerCertificates(false); return new HttpClientTransportOverHTTP3(http3Client); } case FCGI: @@ -384,10 +387,23 @@ public class TransportScenario private void configureSslContextFactory(SslContextFactory sslContextFactory) { - sslContextFactory.setKeyStorePath("src/test/resources/keystore.p12"); - sslContextFactory.setKeyStorePassword("storepwd"); - sslContextFactory.setUseCipherSuitesOrder(true); - sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR); + try + { + KeyStore keystore = KeyStore.getInstance("PKCS12"); + 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 From 9982381e18eed715a9cc8f2d8b9c14380ca8eee7 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Tue, 23 May 2023 20:10:40 +0200 Subject: [PATCH 6/8] #9397 add support for sending a client cert over quic Signed-off-by: Ludovic Orban --- .../quic/client/ClientQuicConnection.java | 8 +- .../QuicClientConnectorConfigurator.java | 59 ++++- .../End2EndClientWithClientCertAuthTest.java | 165 ++++++++++++ .../jetty/quic/quiche/PemExporter.java | 3 + .../LowLevelQuicheClientCertTest.java | 249 ++++++++++++++++++ .../foreign/incubator/LowLevelQuicheTest.java | 9 + .../jetty/quic/quiche/jna/char_pointer.java | 11 +- .../jna/LowLevelQuicheClientCertTest.java | 248 +++++++++++++++++ .../quic/quiche/jna/LowLevelQuicheTest.java | 9 + .../quic/server/QuicServerConnector.java | 15 +- 10 files changed, 765 insertions(+), 11 deletions(-) create mode 100644 jetty-quic/quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientWithClientCertAuthTest.java create mode 100644 jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheClientCertTest.java create mode 100644 jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheClientCertTest.java diff --git a/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java b/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java index 66e48cba119..0a8784e4c07 100644 --- a/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java +++ b/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java @@ -18,7 +18,6 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.SocketTimeoutException; import java.nio.ByteBuffer; -import java.nio.file.Path; import java.util.Collection; import java.util.List; import java.util.Map; @@ -82,9 +81,10 @@ public class ClientQuicConnection extends QuicConnection quicheConfig.setApplicationProtos(protocols.toArray(String[]::new)); quicheConfig.setDisableActiveMigration(quicConfiguration.isDisableActiveMigration()); quicheConfig.setVerifyPeer(!connector.getSslContextFactory().isTrustAll()); - Path trustStorePath = (Path)quicConfiguration.getImplementationSpecifixContext().get(QuicClientConnectorConfigurator.TRUSTSTORE_PATH_KEY); - if (trustStorePath != null) - quicheConfig.setTrustedCertsPemPath(trustStorePath.toString()); + Map implCtx = quicConfiguration.getImplementationSpecifixContext(); + quicheConfig.setTrustedCertsPemPath((String)implCtx.get(QuicClientConnectorConfigurator.TRUSTSTORE_PATH_KEY)); + quicheConfig.setPrivKeyPemPath((String)implCtx.get(QuicClientConnectorConfigurator.PRIVATE_KEY_PATH_KEY)); + quicheConfig.setCertChainPemPath((String)implCtx.get(QuicClientConnectorConfigurator.CERTIFICATE_CHAIN_PATH_KEY)); // Idle timeouts must not be managed by Quiche. quicheConfig.setMaxIdleTimeout(0L); quicheConfig.setInitialMaxData((long)quicConfiguration.getSessionRecvWindow()); diff --git a/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/QuicClientConnectorConfigurator.java b/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/QuicClientConnectorConfigurator.java index cf1b618980e..87a0daea966 100644 --- a/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/QuicClientConnectorConfigurator.java +++ b/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/QuicClientConnectorConfigurator.java @@ -19,6 +19,7 @@ import java.nio.channels.DatagramChannel; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; +import java.nio.file.Files; import java.nio.file.Path; import java.security.KeyStore; import java.util.Map; @@ -34,6 +35,8 @@ import org.eclipse.jetty.io.SocketChannelEndPoint; import org.eclipse.jetty.quic.common.QuicConfiguration; import org.eclipse.jetty.quic.quiche.PemExporter; import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** *

A QUIC specific {@link ClientConnector.Configurator}.

@@ -45,10 +48,17 @@ import org.eclipse.jetty.util.ssl.SslContextFactory; */ public class QuicClientConnectorConfigurator extends ClientConnector.Configurator { + private static final Logger LOG = LoggerFactory.getLogger(QuicClientConnectorConfigurator.class); + + static final String PRIVATE_KEY_PATH_KEY = QuicClientConnectorConfigurator.class.getName() + ".privateKeyPath"; + static final String CERTIFICATE_CHAIN_PATH_KEY = QuicClientConnectorConfigurator.class.getName() + ".certificateChainPath"; static final String TRUSTSTORE_PATH_KEY = QuicClientConnectorConfigurator.class.getName() + ".trustStorePath"; private final QuicConfiguration configuration = new QuicConfiguration(); private final UnaryOperator configurator; + private Path privateKeyPath; + private Path certificateChainPath; + private Path trustStorePath; public QuicClientConnectorConfigurator() { @@ -72,13 +82,58 @@ public class QuicClientConnectorConfigurator extends ClientConnector.Configurato @Override protected void doStart() throws Exception { + Path pemWorkDirectory = configuration.getPemWorkDirectory(); 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); + trustStorePath = PemExporter.exportTrustStore(trustStore, pemWorkDirectory != null ? pemWorkDirectory : Path.of(System.getProperty("java.io.tmpdir"))); + configuration.getImplementationSpecifixContext().put(TRUSTSTORE_PATH_KEY, trustStorePath.toString()); + } + String certAlias = sslContextFactory.getCertAlias(); + if (certAlias != null) + { + if (pemWorkDirectory == null) + throw new IllegalStateException("No PEM work directory configured"); + KeyStore keyStore = sslContextFactory.getKeyStore(); + String keyManagerPassword = sslContextFactory.getKeyManagerPassword(); + char[] password = keyManagerPassword == null ? sslContextFactory.getKeyStorePassword().toCharArray() : keyManagerPassword.toCharArray(); + Path[] keyPair = PemExporter.exportKeyPair(keyStore, certAlias, password, pemWorkDirectory); + privateKeyPath = keyPair[0]; + certificateChainPath = keyPair[1]; + configuration.getImplementationSpecifixContext().put(PRIVATE_KEY_PATH_KEY, privateKeyPath.toString()); + configuration.getImplementationSpecifixContext().put(CERTIFICATE_CHAIN_PATH_KEY, certificateChainPath.toString()); + } + super.doStart(); + } + + @Override + protected void doStop() throws Exception + { + super.doStop(); + deleteFile(privateKeyPath); + privateKeyPath = null; + configuration.getImplementationSpecifixContext().remove(PRIVATE_KEY_PATH_KEY); + deleteFile(certificateChainPath); + certificateChainPath = null; + configuration.getImplementationSpecifixContext().remove(CERTIFICATE_CHAIN_PATH_KEY); + deleteFile(trustStorePath); + trustStorePath = null; + configuration.getImplementationSpecifixContext().remove(TRUSTSTORE_PATH_KEY); + } + + private void deleteFile(Path file) + { + try + { + if (file != null) + Files.delete(file); + } + catch (IOException x) + { + if (LOG.isDebugEnabled()) + LOG.debug("could not delete {}", file, x); } } diff --git a/jetty-quic/quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientWithClientCertAuthTest.java b/jetty-quic/quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientWithClientCertAuthTest.java new file mode 100644 index 00000000000..db30a56085b --- /dev/null +++ b/jetty-quic/quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientWithClientCertAuthTest.java @@ -0,0 +1,165 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.quic.client; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyStore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic; +import org.eclipse.jetty.client.http.HttpClientConnectionFactory; +import org.eclipse.jetty.http2.client.HTTP2Client; +import org.eclipse.jetty.http2.client.http.ClientConnectionFactoryOverHTTP2; +import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; +import org.eclipse.jetty.io.ClientConnectionFactory; +import org.eclipse.jetty.io.ClientConnector; +import org.eclipse.jetty.quic.server.QuicServerConnector; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.SecureRequestCustomizer; +import org.eclipse.jetty.server.Server; +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.ssl.SslContextFactory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(WorkDirExtension.class) +public class End2EndClientWithClientCertAuthTest +{ + public WorkDir workDir; + + private Server server; + private QuicServerConnector connector; + private HttpClient client; + private final String responseContent = "" + + "\n" + + "\t\n" + + "\t\tRequest served\n" + + "\t\n" + + ""; + private SslContextFactory.Server serverSslContextFactory; + + @BeforeEach + public void setUp() throws Exception + { + Path workPath = workDir.getEmptyPathDir(); + Path serverWorkPath = workPath.resolve("server"); + Files.createDirectories(serverWorkPath); + Path clientWorkPath = workPath.resolve("client"); + Files.createDirectories(clientWorkPath); + + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + try (InputStream is = getClass().getResourceAsStream("/keystore.p12")) + { + keyStore.load(is, "storepwd".toCharArray()); + } + + serverSslContextFactory = new SslContextFactory.Server(); + serverSslContextFactory.setKeyStore(keyStore); + serverSslContextFactory.setKeyStorePassword("storepwd"); + serverSslContextFactory.setTrustStore(keyStore); + serverSslContextFactory.setNeedClientAuth(true); + + server = new Server(); + + HttpConfiguration httpConfiguration = new HttpConfiguration(); + httpConfiguration.addCustomizer(new SecureRequestCustomizer()); + HttpConnectionFactory http1 = new HttpConnectionFactory(httpConfiguration); + HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(httpConfiguration); + connector = new QuicServerConnector(server, serverSslContextFactory, http1, http2); + connector.getQuicConfiguration().setPemWorkDirectory(serverWorkPath); + server.addConnector(connector); + + server.setHandler(new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + { + baseRequest.setHandled(true); + PrintWriter writer = response.getWriter(); + writer.print(responseContent); + } + }); + + server.start(); + + QuicClientConnectorConfigurator configurator = new QuicClientConnectorConfigurator(); + configurator.getQuicConfiguration().setPemWorkDirectory(clientWorkPath); + ClientConnector clientConnector = new ClientConnector(configurator); + SslContextFactory.Client clientSslContextFactory = new SslContextFactory.Client(); + clientSslContextFactory.setCertAlias("mykey"); + clientSslContextFactory.setKeyStore(keyStore); + clientSslContextFactory.setKeyStorePassword("storepwd"); + clientSslContextFactory.setTrustStore(keyStore); + clientConnector.setSslContextFactory(clientSslContextFactory); + ClientConnectionFactory.Info http1Info = HttpClientConnectionFactory.HTTP11; + ClientConnectionFactoryOverHTTP2.HTTP2 http2Info = new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector)); + HttpClientTransportDynamic transport = new HttpClientTransportDynamic(clientConnector, http1Info, http2Info); + client = new HttpClient(transport); + client.start(); + } + + @AfterEach + public void tearDown() + { + LifeCycle.stop(client); + LifeCycle.stop(server); + } + + @Test + public void testWorkingClientAuth() throws Exception + { + ContentResponse response = client.newRequest("https://localhost:" + connector.getLocalPort()) + .timeout(5, TimeUnit.SECONDS) + .send(); + assertThat(response.getStatus(), is(200)); + String contentAsString = response.getContentAsString(); + assertThat(contentAsString, is(responseContent)); + } + + @Test + public void testServerRejectsClientInvalidCert() throws Exception + { + // remove the trust store config from the server + server.stop(); + serverSslContextFactory.setTrustStore(null); + server.start(); + + assertThrows(TimeoutException.class, () -> + { + ContentResponse response = client.newRequest("https://localhost:" + connector.getLocalPort()) + .timeout(5, TimeUnit.SECONDS) + .send(); + }); + } +} diff --git a/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/PemExporter.java b/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/PemExporter.java index e02e6817e23..cba6a9aeaa5 100644 --- a/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/PemExporter.java +++ b/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/PemExporter.java @@ -45,6 +45,9 @@ public class PemExporter { } + /** + * @return a temp file that gets deleted on exit + */ public static Path exportTrustStore(KeyStore keyStore, Path targetFolder) throws Exception { if (!Files.isDirectory(targetFolder)) diff --git a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheClientCertTest.java b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheClientCertTest.java new file mode 100644 index 00000000000..379b85dd3ea --- /dev/null +++ b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheClientCertTest.java @@ -0,0 +1,249 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.quic.quiche.foreign.incubator; + +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; + +import org.eclipse.jetty.quic.quiche.PemExporter; +import org.eclipse.jetty.quic.quiche.QuicheConfig; +import org.eclipse.jetty.quic.quiche.QuicheConnection; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.eclipse.jetty.quic.quiche.Quiche.QUICHE_MIN_CLIENT_INITIAL_LEN; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.core.Is.is; + +@ExtendWith(WorkDirExtension.class) +public class LowLevelQuicheClientCertTest +{ + public WorkDir workDir; + + private final Collection connectionsToDisposeOf = new ArrayList<>(); + + private InetSocketAddress clientSocketAddress; + private InetSocketAddress serverSocketAddress; + private QuicheConfig clientQuicheConfig; + private QuicheConfig serverQuicheConfig; + private ForeignIncubatorQuicheConnection.TokenMinter tokenMinter; + private ForeignIncubatorQuicheConnection.TokenValidator tokenValidator; + private Certificate[] serverCertificateChain; + + @BeforeEach + protected void setUp() throws Exception + { + clientSocketAddress = new InetSocketAddress("localhost", 9999); + 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(); + Path[] keyPair = PemExporter.exportKeyPair(keyStore, "mykey", "storepwd".toCharArray(), targetFolder); + Path trustStorePath = PemExporter.exportTrustStore(keyStore, targetFolder); + + clientQuicheConfig = new QuicheConfig(); + clientQuicheConfig.setApplicationProtos("http/0.9"); + clientQuicheConfig.setDisableActiveMigration(true); + clientQuicheConfig.setPrivKeyPemPath(keyPair[0].toString()); + clientQuicheConfig.setCertChainPemPath(keyPair[1].toString()); + clientQuicheConfig.setVerifyPeer(true); + clientQuicheConfig.setTrustedCertsPemPath(trustStorePath.toString()); + clientQuicheConfig.setMaxIdleTimeout(1_000L); + clientQuicheConfig.setInitialMaxData(10_000_000L); + clientQuicheConfig.setInitialMaxStreamDataBidiLocal(10_000_000L); + clientQuicheConfig.setInitialMaxStreamDataBidiRemote(10_000_000L); + clientQuicheConfig.setInitialMaxStreamDataUni(10_000_000L); + clientQuicheConfig.setInitialMaxStreamsUni(100L); + clientQuicheConfig.setInitialMaxStreamsBidi(100L); + clientQuicheConfig.setCongestionControl(QuicheConfig.CongestionControl.CUBIC); + + serverCertificateChain = keyStore.getCertificateChain("mykey"); + serverQuicheConfig = new QuicheConfig(); + serverQuicheConfig.setPrivKeyPemPath(keyPair[0].toString()); + serverQuicheConfig.setCertChainPemPath(keyPair[1].toString()); + serverQuicheConfig.setApplicationProtos("http/0.9"); + serverQuicheConfig.setVerifyPeer(true); + serverQuicheConfig.setTrustedCertsPemPath(trustStorePath.toString()); + serverQuicheConfig.setMaxIdleTimeout(1_000L); + serverQuicheConfig.setInitialMaxData(10_000_000L); + serverQuicheConfig.setInitialMaxStreamDataBidiLocal(10_000_000L); + serverQuicheConfig.setInitialMaxStreamDataBidiRemote(10_000_000L); + serverQuicheConfig.setInitialMaxStreamDataUni(10_000_000L); + serverQuicheConfig.setInitialMaxStreamsUni(100L); + serverQuicheConfig.setInitialMaxStreamsBidi(100L); + serverQuicheConfig.setCongestionControl(QuicheConfig.CongestionControl.CUBIC); + + tokenMinter = new TestTokenMinter(); + tokenValidator = new TestTokenValidator(); + } + + @AfterEach + protected void tearDown() + { + connectionsToDisposeOf.forEach(ForeignIncubatorQuicheConnection::dispose); + connectionsToDisposeOf.clear(); + } + + @Test + public void testClientCert() throws Exception + { + // establish connection + Map.Entry entry = connectClientToServer(); + ForeignIncubatorQuicheConnection clientQuicheConnection = entry.getKey(); + ForeignIncubatorQuicheConnection serverQuicheConnection = entry.getValue(); + + // assert that the client certificate was correctly received by the server + byte[] receivedClientCertificate = serverQuicheConnection.getPeerCertificate(); + byte[] configuredClientCertificate = serverCertificateChain[0].getEncoded(); + assertThat(Arrays.equals(configuredClientCertificate, receivedClientCertificate), is(true)); + + // assert that the server certificate was correctly received by the client + byte[] receivedServerCertificate = clientQuicheConnection.getPeerCertificate(); + byte[] configuredServerCertificate = serverCertificateChain[0].getEncoded(); + assertThat(Arrays.equals(configuredServerCertificate, receivedServerCertificate), is(true)); + } + + private void drainServerToFeedClient(Map.Entry entry, int expectedSize) throws IOException + { + ForeignIncubatorQuicheConnection clientQuicheConnection = entry.getKey(); + ForeignIncubatorQuicheConnection serverQuicheConnection = entry.getValue(); + ByteBuffer buffer = ByteBuffer.allocate(QUICHE_MIN_CLIENT_INITIAL_LEN); + + int drained = serverQuicheConnection.drainCipherBytes(buffer); + assertThat(drained, is(expectedSize)); + buffer.flip(); + int fed = clientQuicheConnection.feedCipherBytes(buffer, clientSocketAddress, serverSocketAddress); + assertThat(fed, is(expectedSize)); + } + + private void drainClientToFeedServer(Map.Entry entry, int expectedSize) throws IOException + { + ForeignIncubatorQuicheConnection clientQuicheConnection = entry.getKey(); + ForeignIncubatorQuicheConnection serverQuicheConnection = entry.getValue(); + ByteBuffer buffer = ByteBuffer.allocate(QUICHE_MIN_CLIENT_INITIAL_LEN); + + int drained = clientQuicheConnection.drainCipherBytes(buffer); + assertThat(drained, is(expectedSize)); + buffer.flip(); + int fed = serverQuicheConnection.feedCipherBytes(buffer, serverSocketAddress, clientSocketAddress); + assertThat(fed, is(expectedSize)); + } + + private Map.Entry connectClientToServer() throws IOException + { + ByteBuffer buffer = ByteBuffer.allocate(QUICHE_MIN_CLIENT_INITIAL_LEN); + ByteBuffer buffer2 = ByteBuffer.allocate(QUICHE_MIN_CLIENT_INITIAL_LEN); + + ForeignIncubatorQuicheConnection clientQuicheConnection = ForeignIncubatorQuicheConnection.connect(clientQuicheConfig, clientSocketAddress, serverSocketAddress); + connectionsToDisposeOf.add(clientQuicheConnection); + + int drained = clientQuicheConnection.drainCipherBytes(buffer); + assertThat(drained, is(1200)); + buffer.flip(); + + ForeignIncubatorQuicheConnection serverQuicheConnection = ForeignIncubatorQuicheConnection.tryAccept(serverQuicheConfig, tokenValidator, buffer, serverSocketAddress, clientSocketAddress); + assertThat(serverQuicheConnection, is(nullValue())); + boolean negotiated = ForeignIncubatorQuicheConnection.negotiate(tokenMinter, buffer, buffer2); + assertThat(negotiated, is(true)); + buffer2.flip(); + + int fed = clientQuicheConnection.feedCipherBytes(buffer2, clientSocketAddress, serverSocketAddress); + assertThat(fed, is(79)); + + buffer.clear(); + drained = clientQuicheConnection.drainCipherBytes(buffer); + assertThat(drained, is(1200)); + buffer.flip(); + + serverQuicheConnection = ForeignIncubatorQuicheConnection.tryAccept(serverQuicheConfig, tokenValidator, buffer, serverSocketAddress, clientSocketAddress); + assertThat(serverQuicheConnection, is(not(nullValue()))); + connectionsToDisposeOf.add(serverQuicheConnection); + + buffer.clear(); + drained = serverQuicheConnection.drainCipherBytes(buffer); + assertThat(drained, is(1200)); + buffer.flip(); + + fed = clientQuicheConnection.feedCipherBytes(buffer, clientSocketAddress, serverSocketAddress); + assertThat(fed, is(1200)); + + assertThat(serverQuicheConnection.isConnectionEstablished(), is(false)); + assertThat(clientQuicheConnection.isConnectionEstablished(), is(false)); + + AbstractMap.SimpleImmutableEntry entry = new AbstractMap.SimpleImmutableEntry<>(clientQuicheConnection, serverQuicheConnection); + + int protosLen = 0; + for (String proto : clientQuicheConfig.getApplicationProtos()) + protosLen += 1 + proto.getBytes(StandardCharsets.UTF_8).length; + + // 1st round + drainServerToFeedClient(entry, 451 + protosLen); + assertThat(serverQuicheConnection.isConnectionEstablished(), is(false)); + assertThat(clientQuicheConnection.isConnectionEstablished(), is(true)); + + drainClientToFeedServer(entry, 1200); + assertThat(serverQuicheConnection.isConnectionEstablished(), is(false)); + assertThat(clientQuicheConnection.isConnectionEstablished(), is(true)); + + // 2nd round (needed b/c of client cert) + drainServerToFeedClient(entry, 71); + assertThat(serverQuicheConnection.isConnectionEstablished(), is(false)); + assertThat(clientQuicheConnection.isConnectionEstablished(), is(true)); + + drainClientToFeedServer(entry, 222); + assertThat(serverQuicheConnection.isConnectionEstablished(), is(true)); + assertThat(clientQuicheConnection.isConnectionEstablished(), is(true)); + + return entry; + } + + private static class TestTokenMinter implements QuicheConnection.TokenMinter + { + @Override + public byte[] mint(byte[] dcid, int len) + { + return ByteBuffer.allocate(len).put(dcid, 0, len).array(); + } + } + + private static class TestTokenValidator implements QuicheConnection.TokenValidator + { + @Override + public byte[] validate(byte[] token, int len) + { + return ByteBuffer.allocate(len).put(token, 0, len).array(); + } + } +} diff --git a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java index 6bc6f129625..ba535e48db2 100644 --- a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java +++ b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java @@ -152,6 +152,9 @@ public class LowLevelQuicheTest // assert that stream 0 is finished on server assertThat(serverQuicheConnection.isStreamFinished(0), is(true)); + // assert that there is not client certificate + assertThat(serverQuicheConnection.getPeerCertificate(), nullValue()); + // assert that the server certificate was correctly received by the client byte[] peerCertificate = clientQuicheConnection.getPeerCertificate(); byte[] serverCert = serverCertificateChain[0].getEncoded(); @@ -190,6 +193,9 @@ public class LowLevelQuicheTest // assert that stream 0 is finished on server assertThat(serverQuicheConnection.isStreamFinished(0), is(true)); + // assert that there is not client certificate + assertThat(serverQuicheConnection.getPeerCertificate(), nullValue()); + // assert that the server certificate was correctly received by the client byte[] peerCertificate = clientQuicheConnection.getPeerCertificate(); byte[] serverCert = serverCertificateChain[0].getEncoded(); @@ -210,6 +216,9 @@ public class LowLevelQuicheTest assertThat(clientQuicheConnection.getNegotiatedProtocol(), is("€")); assertThat(serverQuicheConnection.getNegotiatedProtocol(), is("€")); + // assert that there is not client certificate + assertThat(serverQuicheConnection.getPeerCertificate(), nullValue()); + // assert that the server certificate was correctly received by the client byte[] peerCertificate = clientQuicheConnection.getPeerCertificate(); byte[] serverCert = serverCertificateChain[0].getEncoded(); diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/char_pointer.java b/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/char_pointer.java index a55a778bafb..97871528104 100644 --- a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/char_pointer.java +++ b/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/char_pointer.java @@ -15,17 +15,24 @@ package org.eclipse.jetty.quic.quiche.jna; import java.nio.charset.Charset; +import com.sun.jna.Pointer; import com.sun.jna.ptr.PointerByReference; public class char_pointer extends PointerByReference { public String getValueAsString(int len, Charset charset) { - return new String(getValue().getByteArray(0, len), charset); + Pointer value = getValue(); + if (value == null) + return null; + return new String(value.getByteArray(0, len), charset); } public byte[] getValueAsBytes(int len) { - return getValue().getByteArray(0, len); + Pointer value = getValue(); + if (value == null) + return null; + return value.getByteArray(0, len); } } diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheClientCertTest.java b/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheClientCertTest.java new file mode 100644 index 00000000000..d42e784a093 --- /dev/null +++ b/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheClientCertTest.java @@ -0,0 +1,248 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.quic.quiche.jna; + +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.file.Path; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; + +import org.eclipse.jetty.quic.quiche.PemExporter; +import org.eclipse.jetty.quic.quiche.QuicheConfig; +import org.eclipse.jetty.quic.quiche.QuicheConnection; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.eclipse.jetty.quic.quiche.Quiche.QUICHE_MIN_CLIENT_INITIAL_LEN; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.core.Is.is; + +@ExtendWith(WorkDirExtension.class) +public class LowLevelQuicheClientCertTest +{ + public WorkDir workDir; + + private final Collection connectionsToDisposeOf = new ArrayList<>(); + + private InetSocketAddress clientSocketAddress; + private InetSocketAddress serverSocketAddress; + private QuicheConfig clientQuicheConfig; + private QuicheConfig serverQuicheConfig; + private JnaQuicheConnection.TokenMinter tokenMinter; + private JnaQuicheConnection.TokenValidator tokenValidator; + private Certificate[] serverCertificateChain; + + @BeforeEach + protected void setUp() throws Exception + { + clientSocketAddress = new InetSocketAddress("localhost", 9999); + 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(); + Path[] keyPair = PemExporter.exportKeyPair(keyStore, "mykey", "storepwd".toCharArray(), targetFolder); + Path trustStorePath = PemExporter.exportTrustStore(keyStore, targetFolder); + + clientQuicheConfig = new QuicheConfig(); + clientQuicheConfig.setApplicationProtos("http/0.9"); + clientQuicheConfig.setDisableActiveMigration(true); + clientQuicheConfig.setPrivKeyPemPath(keyPair[0].toString()); + clientQuicheConfig.setCertChainPemPath(keyPair[1].toString()); + clientQuicheConfig.setVerifyPeer(true); + clientQuicheConfig.setTrustedCertsPemPath(trustStorePath.toString()); + clientQuicheConfig.setMaxIdleTimeout(1_000L); + clientQuicheConfig.setInitialMaxData(10_000_000L); + clientQuicheConfig.setInitialMaxStreamDataBidiLocal(10_000_000L); + clientQuicheConfig.setInitialMaxStreamDataBidiRemote(10_000_000L); + clientQuicheConfig.setInitialMaxStreamDataUni(10_000_000L); + clientQuicheConfig.setInitialMaxStreamsUni(100L); + clientQuicheConfig.setInitialMaxStreamsBidi(100L); + clientQuicheConfig.setCongestionControl(QuicheConfig.CongestionControl.CUBIC); + + serverCertificateChain = keyStore.getCertificateChain("mykey"); + serverQuicheConfig = new QuicheConfig(); + serverQuicheConfig.setPrivKeyPemPath(keyPair[0].toString()); + serverQuicheConfig.setCertChainPemPath(keyPair[1].toString()); + serverQuicheConfig.setApplicationProtos("http/0.9"); + serverQuicheConfig.setVerifyPeer(true); + serverQuicheConfig.setTrustedCertsPemPath(trustStorePath.toString()); + serverQuicheConfig.setMaxIdleTimeout(1_000L); + serverQuicheConfig.setInitialMaxData(10_000_000L); + serverQuicheConfig.setInitialMaxStreamDataBidiLocal(10_000_000L); + serverQuicheConfig.setInitialMaxStreamDataBidiRemote(10_000_000L); + serverQuicheConfig.setInitialMaxStreamDataUni(10_000_000L); + serverQuicheConfig.setInitialMaxStreamsUni(100L); + serverQuicheConfig.setInitialMaxStreamsBidi(100L); + serverQuicheConfig.setCongestionControl(QuicheConfig.CongestionControl.CUBIC); + + tokenMinter = new TestTokenMinter(); + tokenValidator = new TestTokenValidator(); + } + + @AfterEach + protected void tearDown() + { + connectionsToDisposeOf.forEach(JnaQuicheConnection::dispose); + connectionsToDisposeOf.clear(); + } + + @Test + public void testClientCert() throws Exception + { + // establish connection + Map.Entry entry = connectClientToServer(); + JnaQuicheConnection clientQuicheConnection = entry.getKey(); + JnaQuicheConnection serverQuicheConnection = entry.getValue(); + + // assert that the client certificate was correctly received by the server + byte[] receivedClientCertificate = serverQuicheConnection.getPeerCertificate(); + byte[] configuredClientCertificate = serverCertificateChain[0].getEncoded(); + assertThat(Arrays.equals(configuredClientCertificate, receivedClientCertificate), is(true)); + + // assert that the server certificate was correctly received by the client + byte[] receivedServerCertificate = clientQuicheConnection.getPeerCertificate(); + byte[] configuredServerCertificate = serverCertificateChain[0].getEncoded(); + assertThat(Arrays.equals(configuredServerCertificate, receivedServerCertificate), is(true)); + } + + private void drainServerToFeedClient(Map.Entry entry, int expectedSize) throws IOException + { + JnaQuicheConnection clientQuicheConnection = entry.getKey(); + JnaQuicheConnection serverQuicheConnection = entry.getValue(); + ByteBuffer buffer = ByteBuffer.allocate(QUICHE_MIN_CLIENT_INITIAL_LEN); + + int drained = serverQuicheConnection.drainCipherBytes(buffer); + assertThat(drained, is(expectedSize)); + buffer.flip(); + int fed = clientQuicheConnection.feedCipherBytes(buffer, clientSocketAddress, serverSocketAddress); + assertThat(fed, is(expectedSize)); + } + + private void drainClientToFeedServer(Map.Entry entry, int expectedSize) throws IOException + { + JnaQuicheConnection clientQuicheConnection = entry.getKey(); + JnaQuicheConnection serverQuicheConnection = entry.getValue(); + ByteBuffer buffer = ByteBuffer.allocate(QUICHE_MIN_CLIENT_INITIAL_LEN); + + int drained = clientQuicheConnection.drainCipherBytes(buffer); + assertThat(drained, is(expectedSize)); + buffer.flip(); + int fed = serverQuicheConnection.feedCipherBytes(buffer, serverSocketAddress, clientSocketAddress); + assertThat(fed, is(expectedSize)); + } + + private Map.Entry connectClientToServer() throws IOException + { + ByteBuffer buffer = ByteBuffer.allocate(QUICHE_MIN_CLIENT_INITIAL_LEN); + ByteBuffer buffer2 = ByteBuffer.allocate(QUICHE_MIN_CLIENT_INITIAL_LEN); + + JnaQuicheConnection clientQuicheConnection = JnaQuicheConnection.connect(clientQuicheConfig, clientSocketAddress, serverSocketAddress); + connectionsToDisposeOf.add(clientQuicheConnection); + + int drained = clientQuicheConnection.drainCipherBytes(buffer); + assertThat(drained, is(1200)); + buffer.flip(); + + JnaQuicheConnection serverQuicheConnection = JnaQuicheConnection.tryAccept(serverQuicheConfig, tokenValidator, buffer, serverSocketAddress, clientSocketAddress); + assertThat(serverQuicheConnection, is(nullValue())); + boolean negotiated = JnaQuicheConnection.negotiate(tokenMinter, buffer, buffer2); + assertThat(negotiated, is(true)); + buffer2.flip(); + + int fed = clientQuicheConnection.feedCipherBytes(buffer2, clientSocketAddress, serverSocketAddress); + assertThat(fed, is(79)); + + buffer.clear(); + drained = clientQuicheConnection.drainCipherBytes(buffer); + assertThat(drained, is(1200)); + buffer.flip(); + + serverQuicheConnection = JnaQuicheConnection.tryAccept(serverQuicheConfig, tokenValidator, buffer, serverSocketAddress, clientSocketAddress); + assertThat(serverQuicheConnection, is(not(nullValue()))); + connectionsToDisposeOf.add(serverQuicheConnection); + + buffer.clear(); + drained = serverQuicheConnection.drainCipherBytes(buffer); + assertThat(drained, is(1200)); + buffer.flip(); + + fed = clientQuicheConnection.feedCipherBytes(buffer, clientSocketAddress, serverSocketAddress); + assertThat(fed, is(1200)); + + assertThat(serverQuicheConnection.isConnectionEstablished(), is(false)); + assertThat(clientQuicheConnection.isConnectionEstablished(), is(false)); + + AbstractMap.SimpleImmutableEntry entry = new AbstractMap.SimpleImmutableEntry<>(clientQuicheConnection, serverQuicheConnection); + + int protosLen = 0; + for (String proto : clientQuicheConfig.getApplicationProtos()) + protosLen += 1 + proto.getBytes(LibQuiche.CHARSET).length; + + // 1st round + drainServerToFeedClient(entry, 451 + protosLen); + assertThat(serverQuicheConnection.isConnectionEstablished(), is(false)); + assertThat(clientQuicheConnection.isConnectionEstablished(), is(true)); + + drainClientToFeedServer(entry, 1200); + assertThat(serverQuicheConnection.isConnectionEstablished(), is(false)); + assertThat(clientQuicheConnection.isConnectionEstablished(), is(true)); + + // 2nd round (needed b/c of client cert) + drainServerToFeedClient(entry, 72); + assertThat(serverQuicheConnection.isConnectionEstablished(), is(false)); + assertThat(clientQuicheConnection.isConnectionEstablished(), is(true)); + + drainClientToFeedServer(entry, 222); + assertThat(serverQuicheConnection.isConnectionEstablished(), is(true)); + assertThat(clientQuicheConnection.isConnectionEstablished(), is(true)); + + return entry; + } + + private static class TestTokenMinter implements QuicheConnection.TokenMinter + { + @Override + public byte[] mint(byte[] dcid, int len) + { + return ByteBuffer.allocate(len).put(dcid, 0, len).array(); + } + } + + private static class TestTokenValidator implements QuicheConnection.TokenValidator + { + @Override + public byte[] validate(byte[] token, int len) + { + return ByteBuffer.allocate(len).put(token, 0, len).array(); + } + } +} diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java b/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java index 25a62696848..f5b2ffc0b0e 100644 --- a/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java +++ b/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java @@ -151,6 +151,9 @@ public class LowLevelQuicheTest // assert that stream 0 is finished on server assertThat(serverQuicheConnection.isStreamFinished(0), is(true)); + // assert that there is not client certificate + assertThat(serverQuicheConnection.getPeerCertificate(), nullValue()); + // assert that the server certificate was correctly received by the client byte[] peerCertificate = clientQuicheConnection.getPeerCertificate(); byte[] serverCert = serverCertificateChain[0].getEncoded(); @@ -189,6 +192,9 @@ public class LowLevelQuicheTest // assert that stream 0 is finished on server assertThat(serverQuicheConnection.isStreamFinished(0), is(true)); + // assert that there is not client certificate + assertThat(serverQuicheConnection.getPeerCertificate(), nullValue()); + // assert that the server certificate was correctly received by the client byte[] peerCertificate = clientQuicheConnection.getPeerCertificate(); byte[] serverCert = serverCertificateChain[0].getEncoded(); @@ -209,6 +215,9 @@ public class LowLevelQuicheTest assertThat(clientQuicheConnection.getNegotiatedProtocol(), is("€")); assertThat(serverQuicheConnection.getNegotiatedProtocol(), is("€")); + // assert that there is not client certificate + assertThat(serverQuicheConnection.getPeerCertificate(), nullValue()); + // assert that the server certificate was correctly received by the client byte[] peerCertificate = clientQuicheConnection.getPeerCertificate(); byte[] serverCert = serverCertificateChain[0].getEncoded(); diff --git a/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java b/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java index 145065577ba..d65269f0aa0 100644 --- a/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java +++ b/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java @@ -63,6 +63,7 @@ public class QuicServerConnector extends AbstractNetworkConnector private final SslContextFactory.Server sslContextFactory; private Path privateKeyPath; private Path certificateChainPath; + private Path trustStorePath; private volatile DatagramChannel datagramChannel; private volatile int localPort = -1; private int inputBufferSize = 2048; @@ -166,11 +167,14 @@ public class QuicServerConnector extends AbstractNetworkConnector alias = aliases.stream().findFirst().orElseThrow(); String keyManagerPassword = sslContextFactory.getKeyManagerPassword(); char[] password = keyManagerPassword == null ? sslContextFactory.getKeyStorePassword().toCharArray() : keyManagerPassword.toCharArray(); - KeyStore keyStore = sslContextFactory.getKeyStore(); - Path[] keyPair = PemExporter.exportKeyPair(keyStore, alias, password, findCertificateWorkPath()); + Path certificateWorkPath = findCertificateWorkPath(); + Path[] keyPair = PemExporter.exportKeyPair(keyStore, alias, password, certificateWorkPath); privateKeyPath = keyPair[0]; certificateChainPath = keyPair[1]; + KeyStore trustStore = sslContextFactory.getTrustStore(); + if (trustStore != null) + trustStorePath = PemExporter.exportTrustStore(trustStore, certificateWorkPath); } private Path findCertificateWorkPath() @@ -223,7 +227,8 @@ public class QuicServerConnector extends AbstractNetworkConnector QuicheConfig quicheConfig = new QuicheConfig(); quicheConfig.setPrivKeyPemPath(privateKeyPath.toString()); quicheConfig.setCertChainPemPath(certificateChainPath.toString()); - quicheConfig.setVerifyPeer(false); + quicheConfig.setTrustedCertsPemPath(trustStorePath == null ? null : trustStorePath.toString()); + quicheConfig.setVerifyPeer(sslContextFactory.getNeedClientAuth() || sslContextFactory.getWantClientAuth()); // Idle timeouts must not be managed by Quiche. quicheConfig.setMaxIdleTimeout(0L); quicheConfig.setInitialMaxData((long)quicConfiguration.getSessionRecvWindow()); @@ -251,7 +256,11 @@ public class QuicServerConnector extends AbstractNetworkConnector protected void doStop() throws Exception { deleteFile(privateKeyPath); + privateKeyPath = null; deleteFile(certificateChainPath); + certificateChainPath = null; + deleteFile(trustStorePath); + trustStorePath = null; // We want the DatagramChannel to be stopped by the SelectorManager. super.doStop(); From bf5527ba43b7d246d75cdb8d92c2feb059c8304c Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Wed, 24 May 2023 14:30:40 +0200 Subject: [PATCH 7/8] #9397 add tls alert to error message Signed-off-by: Ludovic Orban --- .../org/eclipse/jetty/quic/quiche/Quiche.java | 94 +++++++++++++++++++ .../jetty/quic/quiche/QuicheConnection.java | 2 + .../ForeignIncubatorQuicheConnection.java | 57 +++++++++-- .../quiche/foreign/incubator/quiche_h.java | 18 ++++ .../quic/quiche/jna/JnaQuicheConnection.java | 35 +++++-- 5 files changed, 192 insertions(+), 14 deletions(-) diff --git a/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/Quiche.java b/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/Quiche.java index f4d79d6f2c6..5ebc4918505 100644 --- a/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/Quiche.java +++ b/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/Quiche.java @@ -119,4 +119,98 @@ public interface Quiche return "?? " + err; } } + + // TLS Alerts: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-6 + interface tls_alert + { + long CLOSE_NOTIFY = 0, + UNEXPECTED_MESSAGE = 10, + BAD_RECORD_MAC = 20, + RECORD_OVERFLOW = 22, + HANDSHAKE_FAILURE = 40, + BAD_CERTIFICATE = 42, + UNSUPPORTED_CERTIFICATE = 43, + CERTIFICATE_REVOKED = 44, + CERTIFICATE_EXPIRED = 45, + CERTIFICATE_UNKNOWN = 46, + ILLEGAL_PARAMETER = 47, + UNKNOWN_CA = 48, + ACCESS_DENIED = 49, + DECODE_ERROR = 50, + DECRYPT_ERROR = 51, + TOO_MANY_CIDS_REQUESTED = 52, + PROTOCOL_VERSION = 70, + INSUFFICIENT_SECURITY = 71, + INTERNAL_ERROR = 80, + INAPPROPRIATE_FALLBACK = 86, + USER_CANCELED = 90, + MISSING_EXTENSION = 109, + UNSUPPORTED_EXTENSION = 110, + UNRECOGNIZED_NAME = 112, + BAD_CERTIFICATE_STATUS_RESPONSE = 113, + UNKNOWN_PSK_IDENTITY = 115, + CERTIFICATE_REQUIRED = 116, + NO_APPLICATION_PROTOCOL = 120; + + static String errToString(long err) + { + if (err == CLOSE_NOTIFY) + return "CLOSE_NOTIFY"; + if (err == UNEXPECTED_MESSAGE) + return "UNEXPECTED_MESSAGE"; + if (err == BAD_RECORD_MAC) + return "BAD_RECORD_MAC"; + if (err == RECORD_OVERFLOW) + return "RECORD_OVERFLOW"; + if (err == HANDSHAKE_FAILURE) + return "HANDSHAKE_FAILURE"; + if (err == BAD_CERTIFICATE) + return "BAD_CERTIFICATE"; + if (err == UNSUPPORTED_CERTIFICATE) + return "UNSUPPORTED_CERTIFICATE"; + if (err == CERTIFICATE_REVOKED) + return "CERTIFICATE_REVOKED"; + if (err == CERTIFICATE_EXPIRED) + return "CERTIFICATE_EXPIRED"; + if (err == CERTIFICATE_UNKNOWN) + return "CERTIFICATE_UNKNOWN"; + if (err == ILLEGAL_PARAMETER) + return "ILLEGAL_PARAMETER"; + if (err == UNKNOWN_CA) + return "UNKNOWN_CA"; + if (err == ACCESS_DENIED) + return "ACCESS_DENIED"; + if (err == DECODE_ERROR) + return "DECODE_ERROR"; + if (err == DECRYPT_ERROR) + return "DECRYPT_ERROR"; + if (err == TOO_MANY_CIDS_REQUESTED) + return "TOO_MANY_CIDS_REQUESTED"; + if (err == PROTOCOL_VERSION) + return "PROTOCOL_VERSION"; + if (err == INSUFFICIENT_SECURITY) + return "INSUFFICIENT_SECURITY"; + if (err == INTERNAL_ERROR) + return "INTERNAL_ERROR"; + if (err == INAPPROPRIATE_FALLBACK) + return "INAPPROPRIATE_FALLBACK"; + if (err == USER_CANCELED) + return "USER_CANCELED"; + if (err == MISSING_EXTENSION) + return "MISSING_EXTENSION"; + if (err == UNSUPPORTED_EXTENSION) + return "UNSUPPORTED_EXTENSION"; + if (err == UNRECOGNIZED_NAME) + return "UNRECOGNIZED_NAME"; + if (err == BAD_CERTIFICATE_STATUS_RESPONSE) + return "BAD_CERTIFICATE_STATUS_RESPONSE"; + if (err == UNKNOWN_PSK_IDENTITY) + return "UNKNOWN_PSK_IDENTITY"; + if (err == CERTIFICATE_REQUIRED) + return "CERTIFICATE_REQUIRED"; + if (err == NO_APPLICATION_PROTOCOL) + return "NO_APPLICATION_PROTOCOL"; + return "?? " + err; + } + } } diff --git a/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/QuicheConnection.java b/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/QuicheConnection.java index 2a32da8738f..a9cd0ccf532 100644 --- a/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/QuicheConnection.java +++ b/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/QuicheConnection.java @@ -150,6 +150,8 @@ public abstract class QuicheConnection public abstract CloseInfo getRemoteCloseInfo(); + public abstract CloseInfo getLocalCloseInfo(); + public static class CloseInfo { private final long error; diff --git a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/ForeignIncubatorQuicheConnection.java b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/ForeignIncubatorQuicheConnection.java index 1df4681f8d9..ab691b23d80 100644 --- a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/ForeignIncubatorQuicheConnection.java +++ b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/ForeignIncubatorQuicheConnection.java @@ -30,6 +30,7 @@ import jdk.incubator.foreign.MemorySegment; 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.tls_alert; import org.eclipse.jetty.quic.quiche.QuicheConfig; import org.eclipse.jetty.quic.quiche.QuicheConnection; import org.eclipse.jetty.util.BufferUtil; @@ -584,10 +585,13 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection } } // 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. + // a value from which 0x100 must be substracted to get one of the codes specified at + // https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-6 + // see https://github.com/curl/curl/pull/8275 for details. if (received < 0) - throw new IOException("failed to receive packet; err=" + quiche_error.errToString(received)); + throw new IOException("failed to receive packet;" + + " quiche_err=" + quiche_error.errToString(received) + + " tls_alert=" + tls_alert.errToString(getLocalCloseInfo().error() - 0x100)); buffer.position((int)(buffer.position() + received)); return (int)received; } @@ -624,7 +628,7 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection if (written == quiche_error.QUICHE_ERR_DONE) return 0; if (written < 0L) - throw new IOException("failed to send packet; err=" + quiche_error.errToString(written)); + throw new IOException("failed to send packet; quiche_err=" + quiche_error.errToString(written)); buffer.position((int)(prevPosition + written)); return (int)written; } @@ -807,7 +811,7 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection if (value < 0) { if (LOG.isDebugEnabled()) - LOG.debug("could not read window capacity for stream {} err={}", streamId, quiche_error.errToString(value)); + LOG.debug("could not read window capacity for stream {} quiche_err={}", streamId, quiche_error.errToString(value)); } return value; } @@ -866,7 +870,7 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection if (written == quiche_error.QUICHE_ERR_DONE) return 0; if (written < 0L) - throw new IOException("failed to write to stream " + streamId + "; err=" + quiche_error.errToString(written)); + throw new IOException("failed to write to stream " + streamId + "; quiche_err=" + quiche_error.errToString(written)); buffer.position((int)(buffer.position() + written)); return (int)written; } @@ -907,7 +911,7 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection if (read == quiche_error.QUICHE_ERR_DONE) return isStreamFinished(streamId) ? -1 : 0; if (read < 0L) - throw new IOException("failed to read from stream " + streamId + "; err=" + quiche_error.errToString(read)); + throw new IOException("failed to read from stream " + streamId + "; quiche_err=" + quiche_error.errToString(read)); buffer.position((int)(buffer.position() + read)); return (int)read; } @@ -963,6 +967,45 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection } } + @Override + public CloseInfo getLocalCloseInfo() + { + try (AutoLock ignore = lock.lock()) + { + if (quicheConn == null) + throw new IllegalStateException("connection was released"); + try (ResourceScope scope = ResourceScope.newConfinedScope()) + { + MemorySegment app = MemorySegment.allocateNative(CLinker.C_CHAR, scope); + MemorySegment error = MemorySegment.allocateNative(CLinker.C_LONG, scope); + MemorySegment reason = MemorySegment.allocateNative(CLinker.C_POINTER, scope); + MemorySegment reasonLength = MemorySegment.allocateNative(CLinker.C_LONG, scope); + if (quiche_h.quiche_conn_local_error(quicheConn, app.address(), error.address(), reason.address(), reasonLength.address()) != C_FALSE) + { + long errorValue = getLong(error); + long reasonLengthValue = getLong(reasonLength); + + String reasonValue; + if (reasonLengthValue == 0L) + { + reasonValue = null; + } + else + { + byte[] reasonBytes = new byte[(int)reasonLengthValue]; + // dereference reason pointer + MemoryAddress memoryAddress = MemoryAddress.ofLong(getLong(reason)); + memoryAddress.asSegment(reasonLengthValue, ResourceScope.globalScope()).asByteBuffer().get(reasonBytes); + reasonValue = new String(reasonBytes, StandardCharsets.UTF_8); + } + + return new CloseInfo(errorValue, reasonValue); + } + return null; + } + } + } + private static void putLong(MemorySegment memorySegment, long value) { memorySegment.asByteBuffer().order(ByteOrder.nativeOrder()).putLong(value); diff --git a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/quiche_h.java b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/quiche_h.java index 71c30ec34e8..4e89ae1a303 100644 --- a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/quiche_h.java +++ b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/quiche_h.java @@ -268,6 +268,12 @@ public class quiche_h FunctionDescriptor.of(C_CHAR, C_POINTER, C_POINTER, C_POINTER, C_POINTER, C_POINTER) ); + private static final MethodHandle quiche_conn_local_error$MH = downcallHandle( + "quiche_conn_local_error", + "(Ljdk/incubator/foreign/MemoryAddress;Ljdk/incubator/foreign/MemoryAddress;Ljdk/incubator/foreign/MemoryAddress;Ljdk/incubator/foreign/MemoryAddress;Ljdk/incubator/foreign/MemoryAddress;)B", + FunctionDescriptor.of(C_CHAR, C_POINTER, C_POINTER, C_POINTER, C_POINTER, C_POINTER) + ); + private static final MethodHandle quiche_conn_stats$MH = downcallHandle( "quiche_conn_stats", "(Ljdk/incubator/foreign/MemoryAddress;Ljdk/incubator/foreign/MemoryAddress;)V", @@ -736,6 +742,18 @@ public class quiche_h } } + public static byte quiche_conn_local_error(MemoryAddress conn, MemoryAddress is_app, MemoryAddress error_code, MemoryAddress reason, MemoryAddress reason_len) + { + try + { + return (byte) quiche_conn_local_error$MH.invokeExact(conn, is_app, error_code, reason, reason_len); + } + catch (Throwable ex) + { + throw new AssertionError("should not reach here", ex); + } + } + public static long quiche_conn_stream_capacity(MemoryAddress conn, long stream_id) { try diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java b/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java index f29a67face4..0f99201a89c 100644 --- a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java +++ b/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java @@ -24,6 +24,7 @@ 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.tls_alert; import org.eclipse.jetty.quic.quiche.QuicheConfig; import org.eclipse.jetty.quic.quiche.QuicheConnection; import org.eclipse.jetty.util.BufferUtil; @@ -468,11 +469,14 @@ public class JnaQuicheConnection extends QuicheConnection info.from = peerSockaddr.getStructure().byReference(); 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. + // a value from which 0x100 must be substracted to get one of the codes specified at + // https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-6 + // 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(); if (received < 0) - throw new IOException("failed to receive packet; err=" + quiche_error.errToString(received)); + throw new IOException("failed to receive packet;" + + " quiche_err=" + quiche_error.errToString(received) + + " tls_alert=" + tls_alert.errToString(getLocalCloseInfo().error() - 0x100)); buffer.position(buffer.position() + received); return received; } @@ -501,7 +505,7 @@ public class JnaQuicheConnection extends QuicheConnection if (written == quiche_error.QUICHE_ERR_DONE) return 0; if (written < 0L) - throw new IOException("failed to send packet; err=" + quiche_error.errToString(written)); + throw new IOException("failed to send packet; quiche_err=" + quiche_error.errToString(written)); int prevPosition = buffer.position(); buffer.position(prevPosition + written); return written; @@ -661,7 +665,7 @@ public class JnaQuicheConnection extends QuicheConnection if (value < 0) { if (LOG.isDebugEnabled()) - LOG.debug("could not read window capacity for stream {} err={}", streamId, quiche_error.errToString(value)); + LOG.debug("could not read window capacity for stream {} quiche_err={}", streamId, quiche_error.errToString(value)); } return value; } @@ -693,7 +697,7 @@ public class JnaQuicheConnection extends QuicheConnection if (written == quiche_error.QUICHE_ERR_DONE) return 0; if (written < 0L) - throw new IOException("failed to write to stream " + streamId + "; err=" + quiche_error.errToString(written)); + throw new IOException("failed to write to stream " + streamId + "; quiche_err=" + quiche_error.errToString(written)); buffer.position(buffer.position() + written); return written; } @@ -711,7 +715,7 @@ public class JnaQuicheConnection extends QuicheConnection if (read == quiche_error.QUICHE_ERR_DONE) return isStreamFinished(streamId) ? -1 : 0; if (read < 0L) - throw new IOException("failed to read from stream " + streamId + "; err=" + quiche_error.errToString(read)); + throw new IOException("failed to read from stream " + streamId + "; quiche_err=" + quiche_error.errToString(read)); buffer.position(buffer.position() + read); return read; } @@ -744,4 +748,21 @@ public class JnaQuicheConnection extends QuicheConnection return null; } } + + @Override + public CloseInfo getLocalCloseInfo() + { + try (AutoLock ignore = lock.lock()) + { + if (quicheConn == null) + throw new IllegalStateException("connection was released"); + bool_pointer app = new bool_pointer(); + uint64_t_pointer error = new uint64_t_pointer(); + char_pointer reason = new char_pointer(); + size_t_pointer reasonLength = new size_t_pointer(); + if (LibQuiche.INSTANCE.quiche_conn_local_error(quicheConn, app, error, reason, reasonLength)) + return new CloseInfo(error.getValue(), reason.getValueAsString((int)reasonLength.getValue(), LibQuiche.CHARSET)); + return null; + } + } } From debb124dc940c30758d2e94ea136970285fc7085 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Fri, 26 May 2023 10:57:27 +0200 Subject: [PATCH 8/8] handle review comments Signed-off-by: Ludovic Orban --- .../org/eclipse/jetty/io/ClientConnector.java | 3 +- .../quic/client/ClientQuicConnection.java | 8 +-- .../QuicClientConnectorConfigurator.java | 42 ++++++------ .../jetty/quic/common/QuicConfiguration.java | 6 +- .../org/eclipse/jetty/quic/quiche/Quiche.java | 66 +++++++++++++++++++ .../ForeignIncubatorQuicheConnection.java | 9 +-- .../quic/quiche/jna/JnaQuicheConnection.java | 9 +-- .../quic/server/QuicServerConnector.java | 34 +++++----- 8 files changed, 118 insertions(+), 59 deletions(-) diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java index 7e167132ec0..0f809ce409b 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java @@ -117,6 +117,7 @@ public class ClientConnector extends ContainerLifeCycle { this.configurator = Objects.requireNonNull(configurator); addBean(configurator); + configurator.addBean(this, false); } /** @@ -373,7 +374,6 @@ public class ClientConnector extends ContainerLifeCycle selectorManager = newSelectorManager(); selectorManager.setConnectTimeout(getConnectTimeout().toMillis()); addBean(selectorManager); - configurator.addBean(this); super.doStart(); } @@ -382,7 +382,6 @@ public class ClientConnector extends ContainerLifeCycle { super.doStop(); removeBean(selectorManager); - configurator.removeBean(this); } protected SslContextFactory.Client newSslContextFactory() diff --git a/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java b/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java index 0a8784e4c07..3edb7e493d6 100644 --- a/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java +++ b/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java @@ -81,10 +81,10 @@ public class ClientQuicConnection extends QuicConnection quicheConfig.setApplicationProtos(protocols.toArray(String[]::new)); quicheConfig.setDisableActiveMigration(quicConfiguration.isDisableActiveMigration()); quicheConfig.setVerifyPeer(!connector.getSslContextFactory().isTrustAll()); - Map implCtx = quicConfiguration.getImplementationSpecifixContext(); - quicheConfig.setTrustedCertsPemPath((String)implCtx.get(QuicClientConnectorConfigurator.TRUSTSTORE_PATH_KEY)); - quicheConfig.setPrivKeyPemPath((String)implCtx.get(QuicClientConnectorConfigurator.PRIVATE_KEY_PATH_KEY)); - quicheConfig.setCertChainPemPath((String)implCtx.get(QuicClientConnectorConfigurator.CERTIFICATE_CHAIN_PATH_KEY)); + Map implCtx = quicConfiguration.getImplementationConfiguration(); + quicheConfig.setTrustedCertsPemPath((String)implCtx.get(QuicClientConnectorConfigurator.TRUSTED_CERTIFICATES_PEM_PATH_KEY)); + quicheConfig.setPrivKeyPemPath((String)implCtx.get(QuicClientConnectorConfigurator.PRIVATE_KEY_PEM_PATH_KEY)); + quicheConfig.setCertChainPemPath((String)implCtx.get(QuicClientConnectorConfigurator.CERTIFICATE_CHAIN_PEM_PATH_KEY)); // Idle timeouts must not be managed by Quiche. quicheConfig.setMaxIdleTimeout(0L); quicheConfig.setInitialMaxData((long)quicConfiguration.getSessionRecvWindow()); diff --git a/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/QuicClientConnectorConfigurator.java b/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/QuicClientConnectorConfigurator.java index 87a0daea966..2d471a85be0 100644 --- a/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/QuicClientConnectorConfigurator.java +++ b/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/QuicClientConnectorConfigurator.java @@ -50,15 +50,15 @@ public class QuicClientConnectorConfigurator extends ClientConnector.Configurato { private static final Logger LOG = LoggerFactory.getLogger(QuicClientConnectorConfigurator.class); - static final String PRIVATE_KEY_PATH_KEY = QuicClientConnectorConfigurator.class.getName() + ".privateKeyPath"; - static final String CERTIFICATE_CHAIN_PATH_KEY = QuicClientConnectorConfigurator.class.getName() + ".certificateChainPath"; - static final String TRUSTSTORE_PATH_KEY = QuicClientConnectorConfigurator.class.getName() + ".trustStorePath"; + static final String PRIVATE_KEY_PEM_PATH_KEY = QuicClientConnectorConfigurator.class.getName() + ".privateKeyPemPath"; + static final String CERTIFICATE_CHAIN_PEM_PATH_KEY = QuicClientConnectorConfigurator.class.getName() + ".certificateChainPemPath"; + static final String TRUSTED_CERTIFICATES_PEM_PATH_KEY = QuicClientConnectorConfigurator.class.getName() + ".trustedCertificatesPemPath"; private final QuicConfiguration configuration = new QuicConfiguration(); private final UnaryOperator configurator; - private Path privateKeyPath; - private Path certificateChainPath; - private Path trustStorePath; + private Path privateKeyPemPath; + private Path certificateChainPemPath; + private Path trustedCertificatesPemPath; public QuicClientConnectorConfigurator() { @@ -88,8 +88,8 @@ public class QuicClientConnectorConfigurator extends ClientConnector.Configurato KeyStore trustStore = sslContextFactory.getTrustStore(); if (trustStore != null) { - trustStorePath = PemExporter.exportTrustStore(trustStore, pemWorkDirectory != null ? pemWorkDirectory : Path.of(System.getProperty("java.io.tmpdir"))); - configuration.getImplementationSpecifixContext().put(TRUSTSTORE_PATH_KEY, trustStorePath.toString()); + trustedCertificatesPemPath = PemExporter.exportTrustStore(trustStore, pemWorkDirectory != null ? pemWorkDirectory : Path.of(System.getProperty("java.io.tmpdir"))); + configuration.getImplementationConfiguration().put(TRUSTED_CERTIFICATES_PEM_PATH_KEY, trustedCertificatesPemPath.toString()); } String certAlias = sslContextFactory.getCertAlias(); if (certAlias != null) @@ -100,10 +100,10 @@ public class QuicClientConnectorConfigurator extends ClientConnector.Configurato String keyManagerPassword = sslContextFactory.getKeyManagerPassword(); char[] password = keyManagerPassword == null ? sslContextFactory.getKeyStorePassword().toCharArray() : keyManagerPassword.toCharArray(); Path[] keyPair = PemExporter.exportKeyPair(keyStore, certAlias, password, pemWorkDirectory); - privateKeyPath = keyPair[0]; - certificateChainPath = keyPair[1]; - configuration.getImplementationSpecifixContext().put(PRIVATE_KEY_PATH_KEY, privateKeyPath.toString()); - configuration.getImplementationSpecifixContext().put(CERTIFICATE_CHAIN_PATH_KEY, certificateChainPath.toString()); + privateKeyPemPath = keyPair[0]; + certificateChainPemPath = keyPair[1]; + configuration.getImplementationConfiguration().put(PRIVATE_KEY_PEM_PATH_KEY, privateKeyPemPath.toString()); + configuration.getImplementationConfiguration().put(CERTIFICATE_CHAIN_PEM_PATH_KEY, certificateChainPemPath.toString()); } super.doStart(); } @@ -112,15 +112,15 @@ public class QuicClientConnectorConfigurator extends ClientConnector.Configurato protected void doStop() throws Exception { super.doStop(); - deleteFile(privateKeyPath); - privateKeyPath = null; - configuration.getImplementationSpecifixContext().remove(PRIVATE_KEY_PATH_KEY); - deleteFile(certificateChainPath); - certificateChainPath = null; - configuration.getImplementationSpecifixContext().remove(CERTIFICATE_CHAIN_PATH_KEY); - deleteFile(trustStorePath); - trustStorePath = null; - configuration.getImplementationSpecifixContext().remove(TRUSTSTORE_PATH_KEY); + deleteFile(privateKeyPemPath); + privateKeyPemPath = null; + configuration.getImplementationConfiguration().remove(PRIVATE_KEY_PEM_PATH_KEY); + deleteFile(certificateChainPemPath); + certificateChainPemPath = null; + configuration.getImplementationConfiguration().remove(CERTIFICATE_CHAIN_PEM_PATH_KEY); + deleteFile(trustedCertificatesPemPath); + trustedCertificatesPemPath = null; + configuration.getImplementationConfiguration().remove(TRUSTED_CERTIFICATES_PEM_PATH_KEY); } private void deleteFile(Path file) diff --git a/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConfiguration.java b/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConfiguration.java index aacbb64ba9e..ba74b7648d4 100644 --- a/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConfiguration.java +++ b/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConfiguration.java @@ -33,7 +33,7 @@ public class QuicConfiguration private int bidirectionalStreamRecvWindow; private int unidirectionalStreamRecvWindow; private Path pemWorkDirectory; - private final Map implementationSpecifixContext = new HashMap<>(); + private final Map implementationConfiguration = new HashMap<>(); public List getProtocols() { @@ -115,8 +115,8 @@ public class QuicConfiguration this.pemWorkDirectory = pemWorkDirectory; } - public Map getImplementationSpecifixContext() + public Map getImplementationConfiguration() { - return implementationSpecifixContext; + return implementationConfiguration; } } diff --git a/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/Quiche.java b/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/Quiche.java index 5ebc4918505..c7cf2c4c64d 100644 --- a/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/Quiche.java +++ b/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/Quiche.java @@ -120,6 +120,72 @@ public interface Quiche } } + // QUIC Transport Error Codes: https://www.iana.org/assignments/quic/quic.xhtml#quic-transport-error-codes + interface quic_error + { + long NO_ERROR = 0, + INTERNAL_ERROR = 1, + CONNECTION_REFUSED = 2, + FLOW_CONTROL_ERROR = 3, + STREAM_LIMIT_ERROR = 4, + STREAM_STATE_ERROR = 5, + FINAL_SIZE_ERROR = 6, + FRAME_ENCODING_ERROR = 7, + TRANSPORT_PARAMETER_ERROR = 8, + CONNECTION_ID_LIMIT_ERROR = 9, + PROTOCOL_VIOLATION = 10, + INVALID_TOKEN = 11, + APPLICATION_ERROR = 12, + CRYPTO_BUFFER_EXCEEDED = 13, + KEY_UPDATE_ERROR = 14, + AEAD_LIMIT_REACHED = 15, + NO_VIABLE_PATH = 16, + VERSION_NEGOTIATION_ERROR = 17; + + static String errToString(long err) + { + if (err == NO_ERROR) + return "NO_ERROR"; + if (err == INTERNAL_ERROR) + return "INTERNAL_ERROR"; + if (err == CONNECTION_REFUSED) + return "CONNECTION_REFUSED"; + if (err == FLOW_CONTROL_ERROR) + return "FLOW_CONTROL_ERROR"; + if (err == STREAM_LIMIT_ERROR) + return "STREAM_LIMIT_ERROR"; + if (err == STREAM_STATE_ERROR) + return "STREAM_STATE_ERROR"; + if (err == FINAL_SIZE_ERROR) + return "FINAL_SIZE_ERROR"; + if (err == FRAME_ENCODING_ERROR) + return "FRAME_ENCODING_ERROR"; + if (err == TRANSPORT_PARAMETER_ERROR) + return "TRANSPORT_PARAMETER_ERROR"; + if (err == CONNECTION_ID_LIMIT_ERROR) + return "CONNECTION_ID_LIMIT_ERROR"; + if (err == PROTOCOL_VIOLATION) + return "PROTOCOL_VIOLATION"; + if (err == INVALID_TOKEN) + return "INVALID_TOKEN"; + if (err == APPLICATION_ERROR) + return "APPLICATION_ERROR"; + if (err == CRYPTO_BUFFER_EXCEEDED) + return "CRYPTO_BUFFER_EXCEEDED"; + if (err == KEY_UPDATE_ERROR) + return "KEY_UPDATE_ERROR"; + if (err == AEAD_LIMIT_REACHED) + return "AEAD_LIMIT_REACHED"; + if (err == NO_VIABLE_PATH) + return "NO_VIABLE_PATH"; + if (err == VERSION_NEGOTIATION_ERROR) + return "VERSION_NEGOTIATION_ERROR"; + if (err >= 0x100 && err <= 0x01FF) + return "CRYPTO_ERROR " + tls_alert.errToString(err - 0x100); + return "?? " + err; + } + } + // TLS Alerts: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-6 interface tls_alert { diff --git a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/ForeignIncubatorQuicheConnection.java b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/ForeignIncubatorQuicheConnection.java index ab691b23d80..e3f8057f5b7 100644 --- a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/ForeignIncubatorQuicheConnection.java +++ b/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/ForeignIncubatorQuicheConnection.java @@ -30,7 +30,7 @@ import jdk.incubator.foreign.MemorySegment; 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.tls_alert; +import org.eclipse.jetty.quic.quiche.Quiche.quic_error; import org.eclipse.jetty.quic.quiche.QuicheConfig; import org.eclipse.jetty.quic.quiche.QuicheConnection; import org.eclipse.jetty.util.BufferUtil; @@ -584,14 +584,11 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection 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 0x100 must be substracted to get one of the codes specified at - // https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-6 - // see https://github.com/curl/curl/pull/8275 for details. + // If quiche_conn_recv() fails, quiche_conn_local_error() can be called to get the standard error. if (received < 0) throw new IOException("failed to receive packet;" + " quiche_err=" + quiche_error.errToString(received) + - " tls_alert=" + tls_alert.errToString(getLocalCloseInfo().error() - 0x100)); + " quic_err=" + quic_error.errToString(getLocalCloseInfo().error())); buffer.position((int)(buffer.position() + received)); return (int)received; } diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java b/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java index 0f99201a89c..ca150fe7695 100644 --- a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java +++ b/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java @@ -24,7 +24,7 @@ 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.tls_alert; +import org.eclipse.jetty.quic.quiche.Quiche.quic_error; import org.eclipse.jetty.quic.quiche.QuicheConfig; import org.eclipse.jetty.quic.quiche.QuicheConnection; import org.eclipse.jetty.util.BufferUtil; @@ -468,15 +468,12 @@ public class JnaQuicheConnection extends QuicheConnection SizedStructure peerSockaddr = sockaddr.convert(peer); info.from = peerSockaddr.getStructure().byReference(); 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 0x100 must be substracted to get one of the codes specified at - // https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-6 - // see https://github.com/curl/curl/pull/8275 for details. + // If quiche_conn_recv() fails, quiche_conn_local_error() can be called to get the standard error. int received = LibQuiche.INSTANCE.quiche_conn_recv(quicheConn, buffer, new size_t(buffer.remaining()), info).intValue(); if (received < 0) throw new IOException("failed to receive packet;" + " quiche_err=" + quiche_error.errToString(received) + - " tls_alert=" + tls_alert.errToString(getLocalCloseInfo().error() - 0x100)); + " quic_err=" + quic_error.errToString(getLocalCloseInfo().error())); buffer.position(buffer.position() + received); return received; } diff --git a/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java b/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java index d65269f0aa0..a23c680a4e9 100644 --- a/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java +++ b/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java @@ -61,9 +61,9 @@ public class QuicServerConnector extends AbstractNetworkConnector private final QuicSessionContainer container = new QuicSessionContainer(); private final ServerDatagramSelectorManager selectorManager; private final SslContextFactory.Server sslContextFactory; - private Path privateKeyPath; - private Path certificateChainPath; - private Path trustStorePath; + private Path privateKeyPemPath; + private Path certificateChainPemPath; + private Path trustedCertificatesPemPath; private volatile DatagramChannel datagramChannel; private volatile int localPort = -1; private int inputBufferSize = 2048; @@ -168,16 +168,16 @@ public class QuicServerConnector extends AbstractNetworkConnector String keyManagerPassword = sslContextFactory.getKeyManagerPassword(); char[] password = keyManagerPassword == null ? sslContextFactory.getKeyStorePassword().toCharArray() : keyManagerPassword.toCharArray(); KeyStore keyStore = sslContextFactory.getKeyStore(); - Path certificateWorkPath = findCertificateWorkPath(); + Path certificateWorkPath = findPemWorkDirectory(); Path[] keyPair = PemExporter.exportKeyPair(keyStore, alias, password, certificateWorkPath); - privateKeyPath = keyPair[0]; - certificateChainPath = keyPair[1]; + privateKeyPemPath = keyPair[0]; + certificateChainPemPath = keyPair[1]; KeyStore trustStore = sslContextFactory.getTrustStore(); if (trustStore != null) - trustStorePath = PemExporter.exportTrustStore(trustStore, certificateWorkPath); + trustedCertificatesPemPath = PemExporter.exportTrustStore(trustStore, certificateWorkPath); } - private Path findCertificateWorkPath() + private Path findPemWorkDirectory() { Path pemWorkDirectory = getQuicConfiguration().getPemWorkDirectory(); if (pemWorkDirectory != null) @@ -225,9 +225,9 @@ public class QuicServerConnector extends AbstractNetworkConnector QuicheConfig newQuicheConfig() { QuicheConfig quicheConfig = new QuicheConfig(); - quicheConfig.setPrivKeyPemPath(privateKeyPath.toString()); - quicheConfig.setCertChainPemPath(certificateChainPath.toString()); - quicheConfig.setTrustedCertsPemPath(trustStorePath == null ? null : trustStorePath.toString()); + quicheConfig.setPrivKeyPemPath(privateKeyPemPath.toString()); + quicheConfig.setCertChainPemPath(certificateChainPemPath.toString()); + quicheConfig.setTrustedCertsPemPath(trustedCertificatesPemPath == null ? null : trustedCertificatesPemPath.toString()); quicheConfig.setVerifyPeer(sslContextFactory.getNeedClientAuth() || sslContextFactory.getWantClientAuth()); // Idle timeouts must not be managed by Quiche. quicheConfig.setMaxIdleTimeout(0L); @@ -255,12 +255,12 @@ public class QuicServerConnector extends AbstractNetworkConnector @Override protected void doStop() throws Exception { - deleteFile(privateKeyPath); - privateKeyPath = null; - deleteFile(certificateChainPath); - certificateChainPath = null; - deleteFile(trustStorePath); - trustStorePath = null; + deleteFile(privateKeyPemPath); + privateKeyPemPath = null; + deleteFile(certificateChainPemPath); + certificateChainPemPath = null; + deleteFile(trustedCertificatesPemPath); + trustedCertificatesPemPath = null; // We want the DatagramChannel to be stopped by the SelectorManager. super.doStop();