diff --git a/pom.xml b/pom.xml index 09fc072cc..c283b341d 100644 --- a/pom.xml +++ b/pom.xml @@ -78,6 +78,8 @@ 4.1.0 1.17.1 4.2.18.Final + + 2.0.84.Final 4.36.0 @@ -141,6 +143,11 @@ pom import + + io.netty + netty-tcnative-boringssl-static + ${netty-tcnative.version} + com.google.protobuf protobuf-bom diff --git a/service/pom.xml b/service/pom.xml index a8361ed58..089e7e0f0 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -513,6 +513,11 @@ netty-resolver-dns + + io.netty + netty-tcnative-boringssl-static + + io.netty netty-transport @@ -795,6 +800,7 @@ -Djdk.nio.maxCachedBufferSize=262144 -Dlog4j2.formatMsgNoLookups=true -Djdk.tls.server.newSessionTicketCount=0 + -Djdk.tls.server.enableSessionTicketExtension=false -XX:MaxRAMPercentage=75 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.bin diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index a644623da..b2615ef00 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -666,7 +666,7 @@ public class WhisperServerService extends Application dnsResolutionEventLoopGroup = new ManagedEventLoopGroup<>(new NioEventLoopGroup()); final DnsNameResolver cloudflareDnsResolver = new DnsNameResolverBuilder(dnsResolutionEventLoopGroup.getEventLoopGroup().next()) - .resolvedAddressTypes(ResolvedAddressTypes.IPV6_PREFERRED) - .completeOncePreferredResolved(false) - .channelType(NioDatagramChannel.class) - .socketChannelType(NioSocketChannel.class) - .build(); + .resolvedAddressTypes(ResolvedAddressTypes.IPV6_PREFERRED) + .completeOncePreferredResolved(false) + .channelType(NioDatagramChannel.class) + .socketChannelType(NioSocketChannel.class) + .build(); ExternalServiceCredentialsGenerator directoryV2CredentialsGenerator = DirectoryV2Controller.credentialsGenerator( config.getDirectoryV2Configuration().getDirectoryV2ClientConfiguration()); @@ -1218,7 +1218,9 @@ public class WhisperServerService extends Application sniMapping = config.getGrpc().h2c() ? null - : SniMapper.buildSniMapping(config.getTlsKeyStoreConfiguration().path(), config.getTlsKeyStoreConfiguration().password().value()); + : SniMapper.buildSniMapping(config.getTlsKeyStoreConfiguration().path(), + config.getTlsKeyStoreConfiguration().password().value(), + config.getGrpc().sslProvider()); final OmnibusH2Server omnibusH2Server = new OmnibusH2Server( sniMapping, omnibusNioEventLoopGroup.getEventLoopGroup(), diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/GrpcConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/GrpcConfiguration.java index 295879b8f..5c72f25ba 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/GrpcConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/GrpcConfiguration.java @@ -4,6 +4,7 @@ */ package org.whispersystems.textsecuregcm.configuration; +import io.netty.handler.ssl.SslProvider; import jakarta.validation.constraints.NotNull; import java.time.Duration; @@ -15,13 +16,15 @@ import java.time.Duration; /// @param websocketPort The port of a listening websocket server for handling legacy requests /// @param idleTimeout The duration after which an idle connection may be disconnected /// @param h2c If true, listen for plaintext h2c with prior-knowledge +/// @param sslProvider The TLS implementation used to terminate TLS. Defaults to `OPENSSL`. public record GrpcConfiguration( @NotNull String bindAddress, @NotNull Integer port, @NotNull String websocketAddress, @NotNull Integer websocketPort, @NotNull Duration idleTimeout, - boolean h2c) { + boolean h2c, + @NotNull SslProvider sslProvider) { public GrpcConfiguration { if (bindAddress == null || bindAddress.isEmpty()) { @@ -33,5 +36,8 @@ public record GrpcConfiguration( if (idleTimeout == null) { idleTimeout = Duration.ofMinutes(5); } + if (sslProvider == null) { + sslProvider = SslProvider.OPENSSL; + } } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/net/BoringSslServerKeyManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/net/BoringSslServerKeyManager.java new file mode 100644 index 000000000..b2cd4f656 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/net/BoringSslServerKeyManager.java @@ -0,0 +1,123 @@ +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ +package org.whispersystems.textsecuregcm.grpc.net; + +import com.google.common.annotations.VisibleForTesting; +import io.netty.handler.ssl.OpenSsl; +import io.netty.handler.ssl.ReferenceCountedOpenSslEngine; +import io.netty.internal.tcnative.SSL; +import java.net.Socket; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Optional; +import javax.annotation.Nullable; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.X509ExtendedKeyManager; +import javax.net.ssl.X509KeyManager; + +/// A server key manager for netty's BoringSSL provider that uses a client's signature algorithm preferences, enabling +/// support for ed25519. +/// +/// This works around [netty#10916](https://github.com/netty/netty/issues/10916) by addressing two issues in key selection: +/// +/// 1. Netty’s engine only declares support for RSA- and EC-family key types, and +/// 2. the handshake session's peer signature algorithm converter (`io.netty.handler.ssl.SignatureAlgorithmConverter`) +/// doesn't match BoringSSL's `ed25519`, so ed25519 gets dropped, even if one is presented as a candidate +class BoringSslServerKeyManager extends X509ExtendedKeyManager { + + private final X509KeyManager delegate; + + @VisibleForTesting + BoringSslServerKeyManager(final X509KeyManager delegate) { + if (!"BoringSSL".equals(OpenSsl.versionString())) { + throw new IllegalStateException("BoringSSL required, found " + OpenSsl.versionString()); + } + this.delegate = delegate; + } + + /// @throws IllegalArgumentException if `keyManagers` contains no [X509KeyManager] + static BoringSslServerKeyManager wrap(final KeyManager[] keyManagers) { + return Arrays.stream(keyManagers) + .filter(X509KeyManager.class::isInstance) + .map(X509KeyManager.class::cast) + .findFirst() + .map(BoringSslServerKeyManager::new) + .orElseThrow(() -> new IllegalArgumentException("No X509KeyManager found")); + } + + @Override + public String chooseEngineServerAlias(final String keyType, final Principal[] issuers, final SSLEngine engine) { + if (!(engine instanceof ReferenceCountedOpenSslEngine openSslEngine)) { + throw new IllegalArgumentException("BoringSslServerKeyManager requires netty's OpenSSL provider"); + } + + @Nullable final String[] peerSignatureAlgorithms = SSL.getSigAlgs(openSslEngine.sslPointer()); + if (peerSignatureAlgorithms == null) { + return null; + } + + for (final String signatureAlgorithm : peerSignatureAlgorithms) { + final Optional maybeAlias = getJdkKeyType(signatureAlgorithm) + // The Netty OpenSSL engine doesn't advertise support for Ed25519, and so we can't include the engine in the + // delegate's selection process. Instead, we loop through the peer signature algorithms and find a matching key. + .map(jdkKeyType -> delegate.chooseServerAlias(jdkKeyType, issuers, null)); + + if (maybeAlias.isPresent()) { + return maybeAlias.get(); + } + } + + return null; + } + + /// Maps a [BoringSSL signature algorithm name](https://github.com/google/boringssl/blob/8525ff3/ssl/ssl_privkey.cc#L420-L433) + /// to the JDK key type that can produce it + @VisibleForTesting + static Optional getJdkKeyType(final String signatureAlgorithm) { + if (signatureAlgorithm.equals("ed25519")) { + return Optional.of("EdDSA"); + } else if (signatureAlgorithm.startsWith("ecdsa_")) { + return Optional.of("EC"); + } else if (signatureAlgorithm.startsWith("rsa_pss_rsae_")) { + return Optional.of("RSA"); + } + + // We don't need to be exhaustive, because we require TLS 1.3 and only use a limited number of key types. + return Optional.empty(); + } + + @Override + public String chooseServerAlias(final String keyType, final Principal[] issuers, final Socket socket) { + return delegate.chooseServerAlias(keyType, issuers, socket); + } + + @Override + public String[] getServerAliases(final String keyType, final Principal[] issuers) { + return delegate.getServerAliases(keyType, issuers); + } + + @Override + public String chooseClientAlias(final String[] keyTypes, final Principal[] issuers, final Socket socket) { + return delegate.chooseClientAlias(keyTypes, issuers, socket); + } + + @Override + public String[] getClientAliases(final String keyType, final Principal[] issuers) { + return delegate.getClientAliases(keyType, issuers); + } + + @Override + public X509Certificate[] getCertificateChain(final String alias) { + return delegate.getCertificateChain(alias); + } + + @Override + public PrivateKey getPrivateKey(final String alias) { + return delegate.getPrivateKey(alias); + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/net/SniMapper.java b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/net/SniMapper.java index cb5d9299d..8e54d9ad7 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/net/SniMapper.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/net/SniMapper.java @@ -7,8 +7,10 @@ package org.whispersystems.textsecuregcm.grpc.net; import com.google.common.annotations.VisibleForTesting; import io.netty.handler.ssl.ApplicationProtocolConfig; import io.netty.handler.ssl.ApplicationProtocolNames; +import io.netty.handler.ssl.OpenSsl; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.SslProvider; import io.netty.util.Mapping; import java.io.FileInputStream; import java.io.IOException; @@ -49,25 +51,35 @@ public class SniMapper { /// /// @param keyStorePath The path to the [KeyStore] /// @param keyStorePassword The password for the keyStore + /// @param sslProvider The TLS implementation to use /// @return A [Mapping] that maps domains to the corresponding [SslContext] containing the certificates for that /// domain - public static Mapping buildSniMapping(final String keyStorePath, final String keyStorePassword) - throws IOException { + public static Mapping buildSniMapping(final String keyStorePath, final String keyStorePassword, + final SslProvider sslProvider) throws IOException { try (final FileInputStream fis = new FileInputStream(keyStorePath)) { - return buildSniMapping(fis, keyStorePassword); + return buildSniMapping(fis, keyStorePassword, sslProvider); } } @VisibleForTesting - static Mapping buildSniMapping(final InputStream keyStore, final String keyStorePassword) - throws IOException { + static Mapping buildSniMapping(final InputStream keyStore, final String keyStorePassword, + final SslProvider sslProvider) throws IOException { + + if (sslProvider != SslProvider.JDK && sslProvider != SslProvider.OPENSSL) { + throw new IllegalArgumentException("Unsupported SSL provider: " + sslProvider); + } + + if (sslProvider == SslProvider.OPENSSL) { + OpenSsl.ensureAvailability(); + } + try { final Map domainKeyStores = partitionByDomain(keyStore, keyStorePassword.toCharArray()); final Map sslContextsByDomain = new HashMap<>(); for (final Map.Entry entry : domainKeyStores.entrySet()) { final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(entry.getValue(), keyStorePassword.toCharArray()); - sslContextsByDomain.put(entry.getKey(), buildSslContext(kmf)); + sslContextsByDomain.put(entry.getKey(), buildSslContext(kmf, sslProvider)); } // Netty expects the SNI mapping to always return an SslContext. Per RFC-6066 it's valid to continue the handshake @@ -78,15 +90,23 @@ public class SniMapper { .orElseThrow(() -> new IllegalArgumentException("Key store contained no certificates")) .getValue(); - logger.info("Loaded TLS contexts for domains: {}", sslContextsByDomain.keySet()); + logger.info("Loaded {} TLS contexts for domains: {}", sslProvider, sslContextsByDomain.keySet()); return hostname -> sslContextsByDomain.getOrDefault(hostname, defaultSslContext); } catch (NoSuchAlgorithmException | KeyStoreException | CertificateException | UnrecoverableKeyException e) { throw new IOException("Failed to load keystore", e); } } - private static SslContext buildSslContext(final KeyManagerFactory kmf) throws SSLException { - return SslContextBuilder.forServer(kmf) + private static SslContext buildSslContext(final KeyManagerFactory kmf, final SslProvider sslProvider) + throws SSLException { + + final SslContextBuilder sslContextBuilder = sslProvider == SslProvider.OPENSSL + // work around netty's OpenSSL provider not selecting Ed25519 certificates on its own + ? SslContextBuilder.forServer(BoringSslServerKeyManager.wrap(kmf.getKeyManagers())) + : SslContextBuilder.forServer(kmf); + + return sslContextBuilder + .sslProvider(sslProvider) .applicationProtocolConfig(new ApplicationProtocolConfig( ApplicationProtocolConfig.Protocol.ALPN, ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/net/BoringSslServerKeyManagerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/net/BoringSslServerKeyManagerTest.java new file mode 100644 index 000000000..8a4047b49 --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/net/BoringSslServerKeyManagerTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ +package org.whispersystems.textsecuregcm.grpc.net; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Optional; +import javax.annotation.Nullable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class BoringSslServerKeyManagerTest { + + @ParameterizedTest + @CsvSource(nullValues = "null", value = { + "ed25519, EdDSA", + "ecdsa_sha256, EC", + "ecdsa_secp256r1_sha256, EC", + "rsa_pss_rsae_sha256, RSA", + "rsa_pss_rsae_sha512, RSA", + "rsa_pkcs1_sha256, null", + "rsa_pss_pss_sha256, null", + "ed448, null", + }) + void getJdkKeyType(final String boringSslSignatureAlgorithm, @Nullable final String expectedKeyType) { + assertEquals(Optional.ofNullable(expectedKeyType), BoringSslServerKeyManager.getJdkKeyType(boringSslSignatureAlgorithm)); + } +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/net/OmnibusH2ServerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/net/OmnibusH2ServerTest.java index df0437d78..438fecd6f 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/net/OmnibusH2ServerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/net/OmnibusH2ServerTest.java @@ -51,6 +51,7 @@ import io.netty.handler.ssl.ApplicationProtocolConfig; import io.netty.handler.ssl.ApplicationProtocolNames; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.netty.pkitesting.CertificateBuilder; import io.netty.util.Mapping; @@ -142,7 +143,7 @@ class OmnibusH2ServerTest { .store(byteArrayOutputStream, keyStorePassword); sniMapping = SniMapper.buildSniMapping(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()), - new String(keyStorePassword)); + new String(keyStorePassword), SslProvider.OPENSSL); nioEventLoopGroup = new NioEventLoopGroup(); localEventLoopGroup = new DefaultEventLoopGroup(); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/net/SniMapperTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/net/SniMapperTest.java index 2fbf50fe3..a43689d67 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/net/SniMapperTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/net/SniMapperTest.java @@ -5,7 +5,9 @@ package org.whispersystems.textsecuregcm.grpc.net; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import io.netty.bootstrap.Bootstrap; @@ -23,6 +25,7 @@ import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslHandler; import io.netty.handler.ssl.SslHandshakeCompletionEvent; +import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.netty.pkitesting.CertificateBuilder; import io.netty.pkitesting.X509Bundle; @@ -33,11 +36,16 @@ import java.security.KeyStore; import java.security.cert.X509Certificate; import java.time.Duration; import java.time.Instant; +import java.util.EnumMap; import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; import javax.net.ssl.SNIHostName; import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; @@ -45,10 +53,9 @@ import org.apache.commons.lang3.RandomStringUtils; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.MethodSource; class SniMapperTest { @@ -63,7 +70,8 @@ class SniMapperTest { }; private static DefaultEventLoopGroup eventLoopGroup; - private static Mapping sniMapping; + private static final Map> sniMappingsByProvider = + new EnumMap<>(SslProvider.class); private Channel serverChannel; @@ -96,14 +104,16 @@ class SniMapperTest { final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); keyStore.store(byteArrayOutputStream, keyStorePassword); - sniMapping = SniMapper.buildSniMapping(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()), - new String(keyStorePassword)); + for (final SslProvider sslProvider : new SslProvider[] { SslProvider.JDK, SslProvider.OPENSSL }) { + sniMappingsByProvider.put(sslProvider, SniMapper.buildSniMapping( + new ByteArrayInputStream(byteArrayOutputStream.toByteArray()), new String(keyStorePassword), sslProvider)); + } eventLoopGroup = new DefaultEventLoopGroup(); } - @BeforeEach - void setUp() throws Exception { + private void startServer(final SslProvider sslProvider) throws InterruptedException { + final Mapping sniMapping = sniMappingsByProvider.get(sslProvider); final LocalAddress localAddress = new LocalAddress(SniMapper.class.getSimpleName()); serverChannel = new ServerBootstrap() .group(eventLoopGroup) @@ -131,9 +141,11 @@ class SniMapperTest { eventLoopGroup.shutdownGracefully(1, 1000, TimeUnit.MILLISECONDS).sync(); } - @Test - void unknownDomain() throws Exception { - assertNotNull(sniMapping.map("unknown.example.com")); + @ParameterizedTest + @EnumSource(value = SslProvider.class, names = {"JDK", "OPENSSL"}) + void unknownDomain(final SslProvider sslProvider) throws Exception { + assertNotNull(sniMappingsByProvider.get(sslProvider).map("unknown.example.com")); + startServer(sslProvider); final X509Certificate defaultCertificate = connectAndGetServerCertificate("unknown.example.com", null); // bar.example.com is the lexicographically first domain, so we should default to it. @@ -141,7 +153,7 @@ class SniMapperTest { } static List selectCertificate() { - return List.of( + final List cases = List.of( Arguments.of(FOO_DOMAIN, List.of(), "Ed25519"), Arguments.of(BAR_DOMAIN, List.of(), "Ed25519"), Arguments.of(BAR_DOMAIN, List.of("ed25519"), "Ed25519"), @@ -149,21 +161,44 @@ class SniMapperTest { Arguments.of(FOO_DOMAIN, List.of("rsa_pss_rsae_sha256", "rsa_pss_rsae_sha384", "rsa_pss_rsae_sha512", "rsa_pkcs1_sha256", "ed25519"), "SHA256withRSA"), Arguments.of(FOO_DOMAIN, List.of("ed25519", "rsa_pss_rsae_sha256", "rsa_pss_rsae_sha384", "rsa_pss_rsae_sha512"), "Ed25519") ); + + // Each provider should select the same certificate + return Stream.of(SslProvider.JDK, SslProvider.OPENSSL) + .flatMap(sslProvider -> cases.stream().map(arguments -> { + final Object[] args = arguments.get(); + return Arguments.of(sslProvider, args[0], args[1], args[2]); + })) + .toList(); } @ParameterizedTest @MethodSource - void selectCertificate(final String sni, final List signatureSchemes, final String expectedSigAlgorithm) - throws Exception { + void selectCertificate(final SslProvider sslProvider, final String sni, final List signatureSchemes, + final String expectedSigAlgorithm) throws Exception { + startServer(sslProvider); final X509Certificate serverCert = connectAndGetServerCertificate(sni, signatureSchemes.toArray(String[]::new)); assertNotNull(serverCert); assertCertificateIsForDomain(serverCert, sni); assertEquals(expectedSigAlgorithm, serverCert.getSigAlgName()); } + @ParameterizedTest + @EnumSource(value = SslProvider.class, names = {"JDK", "OPENSSL"}) + void noCommonSignatureAlgorithm(final SslProvider sslProvider) throws Exception { + startServer(sslProvider); + + final ExecutionException executionException = assertThrows(ExecutionException.class, + () -> connectAndGetServerCertificate(FOO_DOMAIN, new String[] { "ecdsa_secp256r1_sha256" }), + "server doesn’t have an ECDSA key"); + + assertInstanceOf(SSLException.class, executionException.getCause()); + } + private X509Certificate connectAndGetServerCertificate(final String sniHostname, final String[] signatureSchemes) throws Exception { final SslContext clientSsl = SslContextBuilder.forClient() + // the client can always use the JDK provider + .sslProvider(SslProvider.JDK) .trustManager(InsecureTrustManagerFactory.INSTANCE) .protocols("TLSv1.3") .build(); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/net/TlsHandshakeMetricsHandlerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/net/TlsHandshakeMetricsHandlerTest.java index ab85e8a7e..684a73102 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/net/TlsHandshakeMetricsHandlerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/net/TlsHandshakeMetricsHandlerTest.java @@ -51,7 +51,8 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; class TlsHandshakeMetricsHandlerTest { @@ -114,9 +115,10 @@ class TlsHandshakeMetricsHandlerTest { eventLoopGroup.shutdownGracefully(0, 1000, TimeUnit.MILLISECONDS).sync(); } - @Test - void handshake() throws Exception { - final AtomicReference lastServerChildChannel = startServer(buildSniMapping()); + @ParameterizedTest + @EnumSource(value = SslProvider.class, names = {"JDK", "OPENSSL"}) + void handshake(final SslProvider sslProvider) throws Exception { + final AtomicReference lastServerChildChannel = startServer(buildSniMapping(sslProvider)); connect(null); assertEquals(1, getCount(true)); @@ -125,9 +127,11 @@ class TlsHandshakeMetricsHandlerTest { assertNull(lastServerChildChannel.get().pipeline().get(TlsHandshakeMetricsHandler.class), "the metrics handler should remove itself"); } - @Test - void failedHandshake() throws Exception { - startServer(buildSniMapping()); + @ParameterizedTest + @EnumSource(value = SslProvider.class, names = {"JDK", "OPENSSL"}) + void failedHandshake(final SslProvider sslProvider) throws Exception { + startServer(buildSniMapping(sslProvider)); + // The server only holds an Ed25519 key connect(new String[] { "rsa_pss_rsae_sha256" }); @@ -136,8 +140,8 @@ class TlsHandshakeMetricsHandlerTest { assertEquals(1, getCount(false)); } - private static Mapping buildSniMapping() throws Exception { - return SniMapper.buildSniMapping(new ByteArrayInputStream(keyStoreBytes), keyStorePassword); + private static Mapping buildSniMapping(final SslProvider sslProvider) throws Exception { + return SniMapper.buildSniMapping(new ByteArrayInputStream(keyStoreBytes), keyStorePassword, sslProvider); } /// Starts the server and returns a reference to the child channel, for introspection by tests