/* * Copyright 2015 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.handler.ssl; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.CompositeByteBuf; import io.netty.buffer.Unpooled; import io.netty.buffer.UnpooledByteBufAllocator; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.netty.handler.ssl.util.SelfSignedCertificate; import io.netty.handler.ssl.util.SimpleTrustManagerFactory; import io.netty.util.CharsetUtil; import io.netty.util.NetUtil; import io.netty.util.ReferenceCountUtil; import io.netty.util.concurrent.ImmediateEventExecutor; import io.netty.util.concurrent.PromiseNotifier; import io.netty.util.internal.ResourcesUtil; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Promise; import io.netty.util.internal.EmptyArrays; import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.StringUtil; import io.netty.util.internal.SystemPropertyUtil; import org.conscrypt.OpenSSLProvider; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.opentest4j.AssertionFailedError; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.file.Files; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.PrivateKey; import java.security.Provider; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import javax.crypto.SecretKey; import javax.net.ssl.ExtendedSSLSession; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.KeyManagerFactorySpi; import javax.net.ssl.ManagerFactoryParameters; import javax.net.ssl.SNIHostName; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult.Status; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSessionBindingEvent; import javax.net.ssl.SSLSessionBindingListener; import javax.net.ssl.SSLSessionContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactorySpi; import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509ExtendedTrustManager; import javax.net.ssl.X509TrustManager; import javax.security.cert.X509Certificate; import static io.netty.handler.ssl.SslUtils.*; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeFalse; import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.mockito.Mockito.verify; @TestInstance(TestInstance.Lifecycle.PER_CLASS) public abstract class SSLEngineTest { private static final String PRINCIPAL_NAME = "CN=e8ac02fa0d65a84219016045db8b05c485b4ecdf.netty.test"; private final boolean tlsv13Supported; @Mock protected MessageReceiver serverReceiver; @Mock protected MessageReceiver clientReceiver; protected Throwable serverException; protected Throwable clientException; protected SslContext serverSslCtx; protected SslContext clientSslCtx; protected ServerBootstrap sb; protected Bootstrap cb; protected Channel serverChannel; protected Channel serverConnectedChannel; protected Channel clientChannel; protected CountDownLatch serverLatch; protected CountDownLatch clientLatch; interface MessageReceiver { void messageReceived(ByteBuf msg); } protected static final class MessageDelegatorChannelHandler extends SimpleChannelInboundHandler { private final MessageReceiver receiver; private final CountDownLatch latch; public MessageDelegatorChannelHandler(MessageReceiver receiver, CountDownLatch latch) { super(false); this.receiver = receiver; this.latch = latch; } @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { receiver.messageReceived(msg); latch.countDown(); } } enum BufferType { Direct, Heap, Mixed } static final class ProtocolCipherCombo { private static final ProtocolCipherCombo TLSV12 = new ProtocolCipherCombo( SslProtocols.TLS_v1_2, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"); private static final ProtocolCipherCombo TLSV13 = new ProtocolCipherCombo( SslProtocols.TLS_v1_3, "TLS_AES_128_GCM_SHA256"); final String protocol; final String cipher; private ProtocolCipherCombo(String protocol, String cipher) { this.protocol = protocol; this.cipher = cipher; } static ProtocolCipherCombo tlsv12() { return TLSV12; } static ProtocolCipherCombo tlsv13() { return TLSV13; } @Override public String toString() { return "ProtocolCipherCombo{" + "protocol='" + protocol + '\'' + ", cipher='" + cipher + '\'' + '}'; } } protected SSLEngineTest(boolean tlsv13Supported) { this.tlsv13Supported = tlsv13Supported; } protected static class SSLEngineTestParam { private final BufferType type; private final ProtocolCipherCombo protocolCipherCombo; private final boolean delegate; SSLEngineTestParam(BufferType type, ProtocolCipherCombo protocolCipherCombo, boolean delegate) { this.type = type; this.protocolCipherCombo = protocolCipherCombo; this.delegate = delegate; } final BufferType type() { return type; } final ProtocolCipherCombo combo() { return protocolCipherCombo; } final boolean delegate() { return delegate; } final List protocols() { return Collections.singletonList(protocolCipherCombo.protocol); } final List ciphers() { return Collections.singletonList(protocolCipherCombo.cipher); } } protected List newTestParams() { List params = new ArrayList(); for (BufferType type: BufferType.values()) { params.add(new SSLEngineTestParam(type, ProtocolCipherCombo.tlsv12(), false)); params.add(new SSLEngineTestParam(type, ProtocolCipherCombo.tlsv12(), true)); if (tlsv13Supported) { params.add(new SSLEngineTestParam(type, ProtocolCipherCombo.tlsv13(), false)); params.add(new SSLEngineTestParam(type, ProtocolCipherCombo.tlsv13(), true)); } } return params; } private ExecutorService delegatingExecutor; protected ByteBuffer allocateBuffer(BufferType type, int len) { switch (type) { case Direct: return ByteBuffer.allocateDirect(len); case Heap: return ByteBuffer.allocate(len); case Mixed: return PlatformDependent.threadLocalRandom().nextBoolean() ? ByteBuffer.allocateDirect(len) : ByteBuffer.allocate(len); default: throw new Error(); } } private static final class TestByteBufAllocator implements ByteBufAllocator { private final ByteBufAllocator allocator; private final BufferType type; TestByteBufAllocator(ByteBufAllocator allocator, BufferType type) { this.allocator = allocator; this.type = type; } @Override public ByteBuf buffer() { switch (type) { case Direct: return allocator.directBuffer(); case Heap: return allocator.heapBuffer(); case Mixed: return PlatformDependent.threadLocalRandom().nextBoolean() ? allocator.directBuffer() : allocator.heapBuffer(); default: throw new Error(); } } @Override public ByteBuf buffer(int initialCapacity) { switch (type) { case Direct: return allocator.directBuffer(initialCapacity); case Heap: return allocator.heapBuffer(initialCapacity); case Mixed: return PlatformDependent.threadLocalRandom().nextBoolean() ? allocator.directBuffer(initialCapacity) : allocator.heapBuffer(initialCapacity); default: throw new Error(); } } @Override public ByteBuf buffer(int initialCapacity, int maxCapacity) { switch (type) { case Direct: return allocator.directBuffer(initialCapacity, maxCapacity); case Heap: return allocator.heapBuffer(initialCapacity, maxCapacity); case Mixed: return PlatformDependent.threadLocalRandom().nextBoolean() ? allocator.directBuffer(initialCapacity, maxCapacity) : allocator.heapBuffer(initialCapacity, maxCapacity); default: throw new Error(); } } @Override public ByteBuf ioBuffer() { return allocator.ioBuffer(); } @Override public ByteBuf ioBuffer(int initialCapacity) { return allocator.ioBuffer(initialCapacity); } @Override public ByteBuf ioBuffer(int initialCapacity, int maxCapacity) { return allocator.ioBuffer(initialCapacity, maxCapacity); } @Override public ByteBuf heapBuffer() { return allocator.heapBuffer(); } @Override public ByteBuf heapBuffer(int initialCapacity) { return allocator.heapBuffer(initialCapacity); } @Override public ByteBuf heapBuffer(int initialCapacity, int maxCapacity) { return allocator.heapBuffer(initialCapacity, maxCapacity); } @Override public ByteBuf directBuffer() { return allocator.directBuffer(); } @Override public ByteBuf directBuffer(int initialCapacity) { return allocator.directBuffer(initialCapacity); } @Override public ByteBuf directBuffer(int initialCapacity, int maxCapacity) { return allocator.directBuffer(initialCapacity, maxCapacity); } @Override public CompositeByteBuf compositeBuffer() { switch (type) { case Direct: return allocator.compositeDirectBuffer(); case Heap: return allocator.compositeHeapBuffer(); case Mixed: return PlatformDependent.threadLocalRandom().nextBoolean() ? allocator.compositeDirectBuffer() : allocator.compositeHeapBuffer(); default: throw new Error(); } } @Override public CompositeByteBuf compositeBuffer(int maxNumComponents) { switch (type) { case Direct: return allocator.compositeDirectBuffer(maxNumComponents); case Heap: return allocator.compositeHeapBuffer(maxNumComponents); case Mixed: return PlatformDependent.threadLocalRandom().nextBoolean() ? allocator.compositeDirectBuffer(maxNumComponents) : allocator.compositeHeapBuffer(maxNumComponents); default: throw new Error(); } } @Override public CompositeByteBuf compositeHeapBuffer() { return allocator.compositeHeapBuffer(); } @Override public CompositeByteBuf compositeHeapBuffer(int maxNumComponents) { return allocator.compositeHeapBuffer(maxNumComponents); } @Override public CompositeByteBuf compositeDirectBuffer() { return allocator.compositeDirectBuffer(); } @Override public CompositeByteBuf compositeDirectBuffer(int maxNumComponents) { return allocator.compositeDirectBuffer(maxNumComponents); } @Override public boolean isDirectBufferPooled() { return allocator.isDirectBufferPooled(); } @Override public int calculateNewCapacity(int minNewCapacity, int maxCapacity) { return allocator.calculateNewCapacity(minNewCapacity, maxCapacity); } } @BeforeEach public void setup() { MockitoAnnotations.initMocks(this); serverLatch = new CountDownLatch(1); clientLatch = new CountDownLatch(1); delegatingExecutor = Executors.newCachedThreadPool(); } @AfterEach public void tearDown() throws InterruptedException { ChannelFuture clientCloseFuture = null; ChannelFuture serverConnectedCloseFuture = null; ChannelFuture serverCloseFuture = null; if (clientChannel != null) { clientCloseFuture = clientChannel.close(); clientChannel = null; } if (serverConnectedChannel != null) { serverConnectedCloseFuture = serverConnectedChannel.close(); serverConnectedChannel = null; } if (serverChannel != null) { serverCloseFuture = serverChannel.close(); serverChannel = null; } // We must wait for the Channel cleanup to finish. In the case if the ReferenceCountedOpenSslEngineTest // the ReferenceCountedOpenSslEngine depends upon the SslContext and so we must wait the cleanup the // SslContext to avoid JVM core dumps! // // See https://github.com/netty/netty/issues/5692 if (clientCloseFuture != null) { clientCloseFuture.sync(); } if (serverConnectedCloseFuture != null) { serverConnectedCloseFuture.sync(); } if (serverCloseFuture != null) { serverCloseFuture.sync(); } if (serverSslCtx != null) { cleanupServerSslContext(serverSslCtx); serverSslCtx = null; } if (clientSslCtx != null) { cleanupClientSslContext(clientSslCtx); clientSslCtx = null; } Future serverGroupShutdownFuture = null; Future serverChildGroupShutdownFuture = null; Future clientGroupShutdownFuture = null; if (sb != null) { serverGroupShutdownFuture = sb.config().group().shutdownGracefully(0, 0, TimeUnit.MILLISECONDS); serverChildGroupShutdownFuture = sb.config().childGroup().shutdownGracefully(0, 0, TimeUnit.MILLISECONDS); } if (cb != null) { clientGroupShutdownFuture = cb.config().group().shutdownGracefully(0, 0, TimeUnit.MILLISECONDS); } if (serverGroupShutdownFuture != null) { serverGroupShutdownFuture.sync(); serverChildGroupShutdownFuture.sync(); } if (clientGroupShutdownFuture != null) { clientGroupShutdownFuture.sync(); } delegatingExecutor.shutdown(); serverException = null; clientException = null; } @MethodSource("newTestParams") @ParameterizedTest public void testMutualAuthSameCerts(SSLEngineTestParam param) throws Throwable { mySetupMutualAuth(param, ResourcesUtil.getFile(getClass(), "test_unencrypted.pem"), ResourcesUtil.getFile(getClass(), "test.crt"), null); runTest(null); assertTrue(serverLatch.await(2, TimeUnit.SECONDS)); Throwable cause = serverException; if (cause != null) { throw cause; } } @MethodSource("newTestParams") @ParameterizedTest public void testSetSupportedCiphers(SSLEngineTestParam param) throws Exception { if (param.protocolCipherCombo != ProtocolCipherCombo.tlsv12()) { return; } SelfSignedCertificate cert = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(cert.key(), cert.cert()) .protocols(param.protocols()) .ciphers(param.ciphers()) .sslProvider(sslServerProvider()).build()); final SSLEngine serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(cert.certificate()) .protocols(param.protocols()) .ciphers(param.ciphers()) .sslProvider(sslClientProvider()).build()); final SSLEngine clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); final String[] enabledCiphers = new String[]{ param.ciphers().get(0) }; try { clientEngine.setEnabledCipherSuites(enabledCiphers); serverEngine.setEnabledCipherSuites(enabledCiphers); assertArrayEquals(enabledCiphers, clientEngine.getEnabledCipherSuites()); assertArrayEquals(enabledCiphers, serverEngine.getEnabledCipherSuites()); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); cert.delete(); } } @MethodSource("newTestParams") @ParameterizedTest public void testIncompatibleCiphers(final SSLEngineTestParam param) throws Exception { assumeTrue(SslProvider.isTlsv13Supported(sslClientProvider())); assumeTrue(SslProvider.isTlsv13Supported(sslServerProvider())); SelfSignedCertificate ssc = new SelfSignedCertificate(); // Select a mandatory cipher from the TLSv1.2 RFC https://www.ietf.org/rfc/rfc5246.txt so handshakes won't fail // due to no shared/supported cipher. clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .protocols(SslProtocols.TLS_v1_3, SslProtocols.TLS_v1_2, SslProtocols.TLS_v1) .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .build()); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .protocols(SslProtocols.TLS_v1_3, SslProtocols.TLS_v1_2, SslProtocols.TLS_v1) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); // Set the server to only support a single TLSv1.2 cipher final String serverCipher = "TLS_RSA_WITH_AES_128_CBC_SHA"; serverEngine.setEnabledCipherSuites(new String[] { serverCipher }); // Set the client to only support a single TLSv1.3 cipher final String clientCipher = "TLS_AES_256_GCM_SHA384"; clientEngine.setEnabledCipherSuites(new String[] { clientCipher }); final SSLEngine client = clientEngine; final SSLEngine server = serverEngine; assertThrows(SSLHandshakeException.class, new Executable() { @Override public void execute() throws Throwable { handshake(param.type(), param.delegate(), client, server); } }); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } @MethodSource("newTestParams") @ParameterizedTest public void testMutualAuthDiffCerts(SSLEngineTestParam param) throws Exception { File serverKeyFile = ResourcesUtil.getFile(getClass(), "test_encrypted.pem"); File serverCrtFile = ResourcesUtil.getFile(getClass(), "test.crt"); String serverKeyPassword = "12345"; File clientKeyFile = ResourcesUtil.getFile(getClass(), "test2_encrypted.pem"); File clientCrtFile = ResourcesUtil.getFile(getClass(), "test2.crt"); String clientKeyPassword = "12345"; mySetupMutualAuth(param, clientCrtFile, serverKeyFile, serverCrtFile, serverKeyPassword, serverCrtFile, clientKeyFile, clientCrtFile, clientKeyPassword); runTest(null); assertTrue(serverLatch.await(2, TimeUnit.SECONDS)); } @MethodSource("newTestParams") @ParameterizedTest public void testMutualAuthDiffCertsServerFailure(SSLEngineTestParam param) throws Exception { File serverKeyFile = ResourcesUtil.getFile(getClass(), "test_encrypted.pem"); File serverCrtFile = ResourcesUtil.getFile(getClass(), "test.crt"); String serverKeyPassword = "12345"; File clientKeyFile = ResourcesUtil.getFile(getClass(), "test2_encrypted.pem"); File clientCrtFile = ResourcesUtil.getFile(getClass(), "test2.crt"); String clientKeyPassword = "12345"; // Client trusts server but server only trusts itself mySetupMutualAuth(param, serverCrtFile, serverKeyFile, serverCrtFile, serverKeyPassword, serverCrtFile, clientKeyFile, clientCrtFile, clientKeyPassword); assertTrue(serverLatch.await(10, TimeUnit.SECONDS)); assertTrue(serverException instanceof SSLHandshakeException); } @MethodSource("newTestParams") @ParameterizedTest public void testMutualAuthDiffCertsClientFailure(SSLEngineTestParam param) throws Exception { File serverKeyFile = ResourcesUtil.getFile(getClass(), "test_unencrypted.pem"); File serverCrtFile = ResourcesUtil.getFile(getClass(), "test.crt"); String serverKeyPassword = null; File clientKeyFile = ResourcesUtil.getFile(getClass(), "test2_unencrypted.pem"); File clientCrtFile = ResourcesUtil.getFile(getClass(), "test2.crt"); String clientKeyPassword = null; // Server trusts client but client only trusts itself mySetupMutualAuth(param, clientCrtFile, serverKeyFile, serverCrtFile, serverKeyPassword, clientCrtFile, clientKeyFile, clientCrtFile, clientKeyPassword); assertTrue(clientLatch.await(10, TimeUnit.SECONDS)); assertTrue(clientException instanceof SSLHandshakeException); } @MethodSource("newTestParams") @ParameterizedTest public void testMutualAuthInvalidIntermediateCASucceedWithOptionalClientAuth(SSLEngineTestParam param) throws Exception { testMutualAuthInvalidClientCertSucceed(param, ClientAuth.NONE); } @MethodSource("newTestParams") @ParameterizedTest public void testMutualAuthInvalidIntermediateCAFailWithOptionalClientAuth(SSLEngineTestParam param) throws Exception { testMutualAuthClientCertFail(param, ClientAuth.OPTIONAL); } @MethodSource("newTestParams") @ParameterizedTest public void testMutualAuthInvalidIntermediateCAFailWithRequiredClientAuth(SSLEngineTestParam param) throws Exception { testMutualAuthClientCertFail(param, ClientAuth.REQUIRE); } @MethodSource("newTestParams") @ParameterizedTest public void testMutualAuthValidClientCertChainTooLongFailOptionalClientAuth(SSLEngineTestParam param) throws Exception { testMutualAuthClientCertFail(param, ClientAuth.OPTIONAL, "mutual_auth_client.p12", true); } @MethodSource("newTestParams") @ParameterizedTest public void testMutualAuthValidClientCertChainTooLongFailRequireClientAuth(SSLEngineTestParam param) throws Exception { testMutualAuthClientCertFail(param, ClientAuth.REQUIRE, "mutual_auth_client.p12", true); } private void testMutualAuthInvalidClientCertSucceed(SSLEngineTestParam param, ClientAuth auth) throws Exception { char[] password = "example".toCharArray(); final KeyStore serverKeyStore = KeyStore.getInstance("PKCS12"); serverKeyStore.load(getClass().getResourceAsStream("mutual_auth_server.p12"), password); final KeyStore clientKeyStore = KeyStore.getInstance("PKCS12"); clientKeyStore.load(getClass().getResourceAsStream("mutual_auth_invalid_client.p12"), password); final KeyManagerFactory serverKeyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); serverKeyManagerFactory.init(serverKeyStore, password); final KeyManagerFactory clientKeyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); clientKeyManagerFactory.init(clientKeyStore, password); File commonCertChain = ResourcesUtil.getFile(getClass(), "mutual_auth_ca.pem"); mySetupMutualAuth(param, serverKeyManagerFactory, commonCertChain, clientKeyManagerFactory, commonCertChain, auth, false, false); assertTrue(clientLatch.await(10, TimeUnit.SECONDS)); rethrowIfNotNull(clientException); assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); rethrowIfNotNull(serverException); } private void testMutualAuthClientCertFail(SSLEngineTestParam param, ClientAuth auth) throws Exception { testMutualAuthClientCertFail(param, auth, "mutual_auth_invalid_client.p12", false); } private void testMutualAuthClientCertFail(SSLEngineTestParam param, ClientAuth auth, String clientCert, boolean serverInitEngine) throws Exception { char[] password = "example".toCharArray(); final KeyStore serverKeyStore = KeyStore.getInstance("PKCS12"); serverKeyStore.load(getClass().getResourceAsStream("mutual_auth_server.p12"), password); final KeyStore clientKeyStore = KeyStore.getInstance("PKCS12"); clientKeyStore.load(getClass().getResourceAsStream(clientCert), password); final KeyManagerFactory serverKeyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); serverKeyManagerFactory.init(serverKeyStore, password); final KeyManagerFactory clientKeyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); clientKeyManagerFactory.init(clientKeyStore, password); File commonCertChain = ResourcesUtil.getFile(getClass(), "mutual_auth_ca.pem"); mySetupMutualAuth(param, serverKeyManagerFactory, commonCertChain, clientKeyManagerFactory, commonCertChain, auth, true, serverInitEngine); assertTrue(clientLatch.await(10, TimeUnit.SECONDS)); assertTrue(mySetupMutualAuthServerIsValidClientException(clientException), "unexpected exception: " + clientException); assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); assertTrue(mySetupMutualAuthServerIsValidServerException(serverException), "unexpected exception: " + serverException); } protected static boolean causedBySSLException(Throwable cause) { Throwable next = cause; do { if (next instanceof SSLException) { return true; } next = next.getCause(); } while (next != null); return false; } protected boolean mySetupMutualAuthServerIsValidServerException(Throwable cause) { return mySetupMutualAuthServerIsValidException(cause); } protected boolean mySetupMutualAuthServerIsValidClientException(Throwable cause) { return mySetupMutualAuthServerIsValidException(cause); } protected boolean mySetupMutualAuthServerIsValidException(Throwable cause) { // As in TLSv1.3 the handshake is sent without an extra roundtrip an SSLException is valid as well. return cause instanceof SSLException || cause instanceof ClosedChannelException; } protected void mySetupMutualAuthServerInitSslHandler(SslHandler handler) { } protected void mySetupMutualAuth(final SSLEngineTestParam param, KeyManagerFactory serverKMF, final File serverTrustManager, KeyManagerFactory clientKMF, File clientTrustManager, ClientAuth clientAuth, final boolean failureExpected, final boolean serverInitEngine) throws SSLException, InterruptedException { serverSslCtx = wrapContext(param, SslContextBuilder.forServer(serverKMF) .protocols(param.protocols()) .ciphers(param.ciphers()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .trustManager(serverTrustManager) .clientAuth(clientAuth) .ciphers(null, IdentityCipherSuiteFilter.INSTANCE) .sessionCacheSize(0) .sessionTimeout(0).build()); clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .protocols(param.protocols()) .ciphers(param.ciphers()) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .trustManager(clientTrustManager) .keyManager(clientKMF) .ciphers(null, IdentityCipherSuiteFilter.INSTANCE) .sessionCacheSize(0) .sessionTimeout(0).build()); serverConnectedChannel = null; sb = new ServerBootstrap(); cb = new Bootstrap(); sb.group(new NioEventLoopGroup(), new NioEventLoopGroup()); sb.channel(NioServerSocketChannel.class); sb.childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type())); ChannelPipeline p = ch.pipeline(); SslHandler handler = !param.delegate ? serverSslCtx.newHandler(ch.alloc()) : serverSslCtx.newHandler(ch.alloc(), delegatingExecutor); if (serverInitEngine) { mySetupMutualAuthServerInitSslHandler(handler); } p.addLast(handler); p.addLast(new MessageDelegatorChannelHandler(serverReceiver, serverLatch)); p.addLast(new ChannelInboundHandlerAdapter() { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt == SslHandshakeCompletionEvent.SUCCESS) { if (failureExpected) { serverException = new IllegalStateException("handshake complete. expected failure"); } serverLatch.countDown(); } else if (evt instanceof SslHandshakeCompletionEvent) { serverException = ((SslHandshakeCompletionEvent) evt).cause(); serverLatch.countDown(); } ctx.fireUserEventTriggered(evt); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause.getCause() instanceof SSLHandshakeException) { serverException = cause.getCause(); serverLatch.countDown(); } else { serverException = cause; ctx.fireExceptionCaught(cause); } } }); serverConnectedChannel = ch; } }); cb.group(new NioEventLoopGroup()); cb.channel(NioSocketChannel.class); cb.handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type)); ChannelPipeline p = ch.pipeline(); SslHandler handler = !param.delegate ? clientSslCtx.newHandler(ch.alloc()) : clientSslCtx.newHandler(ch.alloc(), delegatingExecutor); p.addLast(handler); p.addLast(new MessageDelegatorChannelHandler(clientReceiver, clientLatch)); p.addLast(new ChannelInboundHandlerAdapter() { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt == SslHandshakeCompletionEvent.SUCCESS) { // With TLS1.3 a mutal auth error will not be propagated as a handshake error most of the // time as the handshake needs NO extra roundtrip. if (!failureExpected) { clientLatch.countDown(); } } else if (evt instanceof SslHandshakeCompletionEvent) { clientException = ((SslHandshakeCompletionEvent) evt).cause(); clientLatch.countDown(); } ctx.fireUserEventTriggered(evt); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause.getCause() instanceof SSLException) { clientException = cause.getCause(); clientLatch.countDown(); } else { ctx.fireExceptionCaught(cause); } } }); } }); serverChannel = sb.bind(new InetSocketAddress(0)).sync().channel(); int port = ((InetSocketAddress) serverChannel.localAddress()).getPort(); ChannelFuture ccf = cb.connect(new InetSocketAddress(NetUtil.LOCALHOST, port)); assertTrue(ccf.awaitUninterruptibly().isSuccess()); clientChannel = ccf.channel(); } protected static void rethrowIfNotNull(Throwable error) { if (error != null) { throw new AssertionFailedError("Expected no error", error); } } @MethodSource("newTestParams") @ParameterizedTest public void testClientHostnameValidationSuccess(SSLEngineTestParam param) throws Exception { mySetupClientHostnameValidation(param, ResourcesUtil.getFile(getClass(), "localhost_server.pem"), ResourcesUtil.getFile(getClass(), "localhost_server.key"), ResourcesUtil.getFile(getClass(), "mutual_auth_ca.pem"), false); assertTrue(clientLatch.await(10, TimeUnit.SECONDS)); rethrowIfNotNull(clientException); assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); rethrowIfNotNull(serverException); } @MethodSource("newTestParams") @ParameterizedTest public void testClientHostnameValidationFail(SSLEngineTestParam param) throws Exception { Future clientWriteFuture = mySetupClientHostnameValidation(param, ResourcesUtil.getFile(getClass(), "notlocalhost_server.pem"), ResourcesUtil.getFile(getClass(), "notlocalhost_server.key"), ResourcesUtil.getFile(getClass(), "mutual_auth_ca.pem"), true); assertTrue(clientLatch.await(10, TimeUnit.SECONDS)); assertTrue(mySetupMutualAuthServerIsValidClientException(clientException), "unexpected exception: " + clientException); assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); assertTrue(mySetupMutualAuthServerIsValidServerException(serverException), "unexpected exception: " + serverException); // Verify that any pending writes are failed with the cached handshake exception and not a general SSLException. clientWriteFuture.awaitUninterruptibly(); Throwable actualCause = clientWriteFuture.cause(); assertSame(clientException, actualCause); } private Future mySetupClientHostnameValidation(final SSLEngineTestParam param, File serverCrtFile, File serverKeyFile, File clientTrustCrtFile, final boolean failureExpected) throws SSLException, InterruptedException { final String expectedHost = "localhost"; serverSslCtx = wrapContext(param, SslContextBuilder.forServer(serverCrtFile, serverKeyFile, null) .sslProvider(sslServerProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .sslContextProvider(serverSslContextProvider()) .trustManager(InsecureTrustManagerFactory.INSTANCE) .ciphers(null, IdentityCipherSuiteFilter.INSTANCE) .sessionCacheSize(0) .sessionTimeout(0) .build()); clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .sslProvider(sslClientProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .sslContextProvider(clientSslContextProvider()) .trustManager(clientTrustCrtFile) .ciphers(null, IdentityCipherSuiteFilter.INSTANCE) .sessionCacheSize(0) .sessionTimeout(0) .build()); serverConnectedChannel = null; sb = new ServerBootstrap(); cb = new Bootstrap(); sb.group(new NioEventLoopGroup(), new NioEventLoopGroup()); sb.channel(NioServerSocketChannel.class); sb.childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type)); ChannelPipeline p = ch.pipeline(); SslHandler handler = !param.delegate ? serverSslCtx.newHandler(ch.alloc()) : serverSslCtx.newHandler(ch.alloc(), delegatingExecutor); p.addLast(handler); p.addLast(new MessageDelegatorChannelHandler(serverReceiver, serverLatch)); p.addLast(new ChannelInboundHandlerAdapter() { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt == SslHandshakeCompletionEvent.SUCCESS) { if (failureExpected) { serverException = new IllegalStateException("handshake complete. expected failure"); } serverLatch.countDown(); } else if (evt instanceof SslHandshakeCompletionEvent) { serverException = ((SslHandshakeCompletionEvent) evt).cause(); serverLatch.countDown(); } ctx.fireUserEventTriggered(evt); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause.getCause() instanceof SSLHandshakeException) { serverException = cause.getCause(); serverLatch.countDown(); } else { serverException = cause; ctx.fireExceptionCaught(cause); } } }); serverConnectedChannel = ch; } }); final Promise clientWritePromise = ImmediateEventExecutor.INSTANCE.newPromise(); cb.group(new NioEventLoopGroup()); cb.channel(NioSocketChannel.class); cb.handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type)); ChannelPipeline p = ch.pipeline(); InetSocketAddress remoteAddress = (InetSocketAddress) serverChannel.localAddress(); SslHandler sslHandler = !param.delegate ? clientSslCtx.newHandler(ch.alloc(), expectedHost, 0) : clientSslCtx.newHandler(ch.alloc(), expectedHost, 0, delegatingExecutor); SSLParameters parameters = sslHandler.engine().getSSLParameters(); if (SslUtils.isValidHostNameForSNI(expectedHost)) { assertEquals(1, parameters.getServerNames().size()); assertEquals(new SNIHostName(expectedHost), parameters.getServerNames().get(0)); } parameters.setEndpointIdentificationAlgorithm("HTTPS"); sslHandler.engine().setSSLParameters(parameters); p.addLast(sslHandler); p.addLast(new MessageDelegatorChannelHandler(clientReceiver, clientLatch)); p.addLast(new ChannelInboundHandlerAdapter() { @Override public void handlerAdded(ChannelHandlerContext ctx) { // Only write if there is a failure expected. We don't actually care about the write going // through we just want to verify the local failure condition. This way we don't have to worry // about verifying the payload and releasing the content on the server side. if (failureExpected) { ChannelFuture f = ctx.write(ctx.alloc().buffer(1).writeByte(1)); PromiseNotifier.cascade(f, clientWritePromise); } } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt == SslHandshakeCompletionEvent.SUCCESS) { if (failureExpected) { clientException = new IllegalStateException("handshake complete. expected failure"); } clientLatch.countDown(); } else if (evt instanceof SslHandshakeCompletionEvent) { clientException = ((SslHandshakeCompletionEvent) evt).cause(); clientLatch.countDown(); } ctx.fireUserEventTriggered(evt); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause.getCause() instanceof SSLHandshakeException) { clientException = cause.getCause(); clientLatch.countDown(); } else { ctx.fireExceptionCaught(cause); } } }); } }); serverChannel = sb.bind(new InetSocketAddress(expectedHost, 0)).sync().channel(); final int port = ((InetSocketAddress) serverChannel.localAddress()).getPort(); ChannelFuture ccf = cb.connect(new InetSocketAddress(expectedHost, port)); assertTrue(ccf.awaitUninterruptibly().isSuccess()); clientChannel = ccf.channel(); return clientWritePromise; } private void mySetupMutualAuth(SSLEngineTestParam param, File keyFile, File crtFile, String keyPassword) throws SSLException, InterruptedException { mySetupMutualAuth(param, crtFile, keyFile, crtFile, keyPassword, crtFile, keyFile, crtFile, keyPassword); } private void verifySSLSessionForMutualAuth( SSLEngineTestParam param, SSLSession session, File certFile, String principalName) throws Exception { InputStream in = null; try { assertEquals(principalName, session.getLocalPrincipal().getName()); assertEquals(principalName, session.getPeerPrincipal().getName()); assertNotNull(session.getId()); assertEquals(param.combo().cipher, session.getCipherSuite()); assertEquals(param.combo().protocol, session.getProtocol()); assertTrue(session.getApplicationBufferSize() > 0); assertTrue(session.getCreationTime() > 0); assertTrue(session.isValid()); assertTrue(session.getLastAccessedTime() > 0); in = new FileInputStream(certFile); final byte[] certBytes = SslContext.X509_CERT_FACTORY .generateCertificate(in).getEncoded(); // Verify session assertEquals(1, session.getPeerCertificates().length); assertArrayEquals(certBytes, session.getPeerCertificates()[0].getEncoded()); try { assertEquals(1, session.getPeerCertificateChain().length); assertArrayEquals(certBytes, session.getPeerCertificateChain()[0].getEncoded()); } catch (UnsupportedOperationException e) { // See https://bugs.openjdk.java.net/browse/JDK-8241039 assertTrue(PlatformDependent.javaVersion() >= 15); } assertEquals(1, session.getLocalCertificates().length); assertArrayEquals(certBytes, session.getLocalCertificates()[0].getEncoded()); } finally { if (in != null) { in.close(); } } } private void mySetupMutualAuth(final SSLEngineTestParam param, File servertTrustCrtFile, File serverKeyFile, final File serverCrtFile, String serverKeyPassword, File clientTrustCrtFile, File clientKeyFile, final File clientCrtFile, String clientKeyPassword) throws InterruptedException, SSLException { serverSslCtx = wrapContext(param, SslContextBuilder.forServer(serverCrtFile, serverKeyFile, serverKeyPassword) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .trustManager(servertTrustCrtFile) .ciphers(null, IdentityCipherSuiteFilter.INSTANCE) .sessionCacheSize(0) .sessionTimeout(0).build()); clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .trustManager(clientTrustCrtFile) .keyManager(clientCrtFile, clientKeyFile, clientKeyPassword) .ciphers(null, IdentityCipherSuiteFilter.INSTANCE) .sessionCacheSize(0) .sessionTimeout(0).build()); serverConnectedChannel = null; sb = new ServerBootstrap(); cb = new Bootstrap(); sb.group(new NioEventLoopGroup(), new NioEventLoopGroup()); sb.channel(NioServerSocketChannel.class); sb.childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type)); ChannelPipeline p = ch.pipeline(); final SSLEngine engine = wrapEngine(serverSslCtx.newEngine(ch.alloc())); engine.setUseClientMode(false); engine.setNeedClientAuth(true); p.addLast(new SslHandler(engine)); p.addLast(new MessageDelegatorChannelHandler(serverReceiver, serverLatch)); p.addLast(new ChannelInboundHandlerAdapter() { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause.getCause() instanceof SSLHandshakeException) { serverException = cause.getCause(); serverLatch.countDown(); } else { serverException = cause; ctx.fireExceptionCaught(cause); } } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt == SslHandshakeCompletionEvent.SUCCESS) { try { verifySSLSessionForMutualAuth( param, engine.getSession(), serverCrtFile, PRINCIPAL_NAME); } catch (Throwable cause) { serverException = cause; } } } }); serverConnectedChannel = ch; } }); cb.group(new NioEventLoopGroup()); cb.channel(NioSocketChannel.class); cb.handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type)); final SslHandler handler = !param.delegate ? clientSslCtx.newHandler(ch.alloc()) : clientSslCtx.newHandler(ch.alloc(), delegatingExecutor); handler.engine().setNeedClientAuth(true); ChannelPipeline p = ch.pipeline(); p.addLast(handler); p.addLast(new MessageDelegatorChannelHandler(clientReceiver, clientLatch)); p.addLast(new ChannelInboundHandlerAdapter() { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt == SslHandshakeCompletionEvent.SUCCESS) { try { verifySSLSessionForMutualAuth( param, handler.engine().getSession(), clientCrtFile, PRINCIPAL_NAME); } catch (Throwable cause) { clientException = cause; } } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause.getCause() instanceof SSLHandshakeException) { clientException = cause.getCause(); clientLatch.countDown(); } else { ctx.fireExceptionCaught(cause); } } }); } }); serverChannel = sb.bind(new InetSocketAddress(0)).sync().channel(); int port = ((InetSocketAddress) serverChannel.localAddress()).getPort(); ChannelFuture ccf = cb.connect(new InetSocketAddress(NetUtil.LOCALHOST, port)); assertTrue(ccf.awaitUninterruptibly().isSuccess()); clientChannel = ccf.channel(); } protected void runTest(String expectedApplicationProtocol) throws Exception { final ByteBuf clientMessage = Unpooled.copiedBuffer("I am a client".getBytes()); final ByteBuf serverMessage = Unpooled.copiedBuffer("I am a server".getBytes()); try { writeAndVerifyReceived(clientMessage.retain(), clientChannel, serverLatch, serverReceiver); writeAndVerifyReceived(serverMessage.retain(), serverConnectedChannel, clientLatch, clientReceiver); verifyApplicationLevelProtocol(clientChannel, expectedApplicationProtocol); verifyApplicationLevelProtocol(serverConnectedChannel, expectedApplicationProtocol); } finally { clientMessage.release(); serverMessage.release(); } } private static void verifyApplicationLevelProtocol(Channel channel, String expectedApplicationProtocol) { SslHandler handler = channel.pipeline().get(SslHandler.class); assertNotNull(handler); String appProto = handler.applicationProtocol(); assertEquals(expectedApplicationProtocol, appProto); SSLEngine engine = handler.engine(); if (engine instanceof JdkAlpnSslEngine) { // Also verify the Java9 exposed method. JdkAlpnSslEngine java9SslEngine = (JdkAlpnSslEngine) engine; assertEquals(expectedApplicationProtocol == null ? StringUtil.EMPTY_STRING : expectedApplicationProtocol, java9SslEngine.getApplicationProtocol()); } } private static void writeAndVerifyReceived(ByteBuf message, Channel sendChannel, CountDownLatch receiverLatch, MessageReceiver receiver) throws Exception { List dataCapture = null; try { assertTrue(sendChannel.writeAndFlush(message).await(10, TimeUnit.SECONDS)); receiverLatch.await(5, TimeUnit.SECONDS); message.resetReaderIndex(); ArgumentCaptor captor = ArgumentCaptor.forClass(ByteBuf.class); verify(receiver).messageReceived(captor.capture()); dataCapture = captor.getAllValues(); assertEquals(message, dataCapture.get(0)); } finally { if (dataCapture != null) { for (ByteBuf data : dataCapture) { data.release(); } } } } @Test public void testGetCreationTime() throws Exception { clientSslCtx = wrapContext(null, SslContextBuilder.forClient() .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()).build()); SSLEngine engine = null; try { engine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); assertTrue(engine.getSession().getCreationTime() <= System.currentTimeMillis()); } finally { cleanupClientSslEngine(engine); } } @MethodSource("newTestParams") @ParameterizedTest public void testSessionInvalidate(SSLEngineTestParam param) throws Exception { clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); SSLSession session = serverEngine.getSession(); assertTrue(session.isValid()); session.invalidate(); assertFalse(session.isValid()); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } @MethodSource("newTestParams") @ParameterizedTest public void testSSLSessionId(SSLEngineTestParam param) throws Exception { clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) // This test only works for non TLSv1.3 for now .protocols(param.protocols()) .sslContextProvider(clientSslContextProvider()) .build()); SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) // This test only works for non TLSv1.3 for now .protocols(param.protocols()) .sslContextProvider(serverSslContextProvider()) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); // Before the handshake the id should have length == 0 assertEquals(0, clientEngine.getSession().getId().length); assertEquals(0, serverEngine.getSession().getId().length); handshake(param.type(), param.delegate(), clientEngine, serverEngine); if (param.protocolCipherCombo == ProtocolCipherCombo.TLSV13) { // Allocate something which is big enough for sure ByteBuffer packetBuffer = allocateBuffer(param.type(), 32 * 1024); ByteBuffer appBuffer = allocateBuffer(param.type(), 32 * 1024); appBuffer.clear().position(4).flip(); packetBuffer.clear(); do { SSLEngineResult result; do { result = serverEngine.wrap(appBuffer, packetBuffer); } while (appBuffer.hasRemaining() || result.bytesProduced() > 0); appBuffer.clear(); packetBuffer.flip(); do { result = clientEngine.unwrap(packetBuffer, appBuffer); } while (packetBuffer.hasRemaining() || result.bytesProduced() > 0); packetBuffer.clear(); appBuffer.clear().position(4).flip(); do { result = clientEngine.wrap(appBuffer, packetBuffer); } while (appBuffer.hasRemaining() || result.bytesProduced() > 0); appBuffer.clear(); packetBuffer.flip(); do { result = serverEngine.unwrap(packetBuffer, appBuffer); } while (packetBuffer.hasRemaining() || result.bytesProduced() > 0); packetBuffer.clear(); appBuffer.clear().position(4).flip(); } while (clientEngine.getSession().getId().length == 0); // With TLS1.3 we should see pseudo IDs and so these should never match. assertFalse(Arrays.equals(clientEngine.getSession().getId(), serverEngine.getSession().getId())); } else { // After the handshake the id should have length > 0 assertNotEquals(0, clientEngine.getSession().getId().length); assertNotEquals(0, serverEngine.getSession().getId().length); assertArrayEquals(clientEngine.getSession().getId(), serverEngine.getSession().getId()); } } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } @MethodSource("newTestParams") @ParameterizedTest @Timeout(30) public void clientInitiatedRenegotiationWithFatalAlertDoesNotInfiniteLoopServer(final SSLEngineTestParam param) throws Exception { assumeTrue(PlatformDependent.javaVersion() >= 11); final SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); sb = new ServerBootstrap() .group(new NioEventLoopGroup(1)) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type)); ChannelPipeline p = ch.pipeline(); SslHandler handler = !param.delegate ? serverSslCtx.newHandler(ch.alloc()) : serverSslCtx.newHandler(ch.alloc(), delegatingExecutor); p.addLast(handler); p.addLast(new ChannelInboundHandlerAdapter() { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { if (evt instanceof SslHandshakeCompletionEvent && ((SslHandshakeCompletionEvent) evt).isSuccess()) { // This data will be sent to the client before any of the re-negotiation data can be // sent. The client will read this, detect that it is not the response to // renegotiation which was expected, and respond with a fatal alert. ctx.writeAndFlush(ctx.alloc().buffer(1).writeByte(100)); } ctx.fireUserEventTriggered(evt); } @Override public void channelRead(final ChannelHandlerContext ctx, Object msg) { ReferenceCountUtil.release(msg); // The server then attempts to trigger a flush operation once the application data is // received from the client. The flush will encrypt all data and should not result in // deadlock. ctx.channel().eventLoop().schedule(new Runnable() { @Override public void run() { ctx.writeAndFlush(ctx.alloc().buffer(1).writeByte(101)); } }, 500, TimeUnit.MILLISECONDS); } @Override public void channelInactive(ChannelHandlerContext ctx) { serverLatch.countDown(); } }); serverConnectedChannel = ch; } }); serverChannel = sb.bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); clientSslCtx = wrapContext(param, SslContextBuilder.forClient() // OpenSslEngine doesn't support renegotiation on client side .sslProvider(SslProvider.JDK) .trustManager(InsecureTrustManagerFactory.INSTANCE) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); cb = new Bootstrap(); cb.group(new NioEventLoopGroup(1)) .channel(NioSocketChannel.class) .handler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type())); ChannelPipeline p = ch.pipeline(); SslHandler sslHandler = !param.delegate ? clientSslCtx.newHandler(ch.alloc()) : clientSslCtx.newHandler(ch.alloc(), delegatingExecutor); // The renegotiate is not expected to succeed, so we should stop trying in a timely manner so // the unit test can terminate relativley quicly. sslHandler.setHandshakeTimeout(1, TimeUnit.SECONDS); p.addLast(sslHandler); p.addLast(new ChannelInboundHandlerAdapter() { private int handshakeCount; @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { // OpenSSL SSLEngine sends a fatal alert for the renegotiation handshake because the // user data read as part of the handshake. The client receives this fatal alert and is // expected to shutdown the connection. The "invalid data" during the renegotiation // handshake is also delivered to channelRead(..) on the server. // JDK SSLEngine completes the renegotiation handshake and delivers the "invalid data" // is also delivered to channelRead(..) on the server. JDK SSLEngine does not send a // fatal error and so for testing purposes we close the connection after we have // completed the first renegotiation handshake (which is the second handshake). if (evt instanceof SslHandshakeCompletionEvent && ++handshakeCount == 2) { ctx.close(); return; } ctx.fireUserEventTriggered(evt); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { ReferenceCountUtil.release(msg); // Simulate a request that the server's application logic will think is invalid. ctx.writeAndFlush(ctx.alloc().buffer(1).writeByte(102)); ctx.pipeline().get(SslHandler.class).renegotiate(); } }); } }); ChannelFuture ccf = cb.connect(serverChannel.localAddress()); assertTrue(ccf.syncUninterruptibly().isSuccess()); clientChannel = ccf.channel(); serverLatch.await(); ssc.delete(); } protected void testEnablingAnAlreadyDisabledSslProtocol(SSLEngineTestParam param, String[] protocols1, String[] protocols2) throws Exception { SSLEngine sslEngine = null; try { File serverKeyFile = ResourcesUtil.getFile(getClass(), "test_unencrypted.pem"); File serverCrtFile = ResourcesUtil.getFile(getClass(), "test.crt"); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(serverCrtFile, serverKeyFile) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); sslEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); // Disable all protocols sslEngine.setEnabledProtocols(EmptyArrays.EMPTY_STRINGS); // The only protocol that should be enabled is SSLv2Hello String[] enabledProtocols = sslEngine.getEnabledProtocols(); assertArrayEquals(protocols1, enabledProtocols); // Enable a protocol that is currently disabled sslEngine.setEnabledProtocols(new String[]{ SslProtocols.TLS_v1_2 }); // The protocol that was just enabled should be returned enabledProtocols = sslEngine.getEnabledProtocols(); assertEquals(protocols2.length, enabledProtocols.length); assertArrayEquals(protocols2, enabledProtocols); } finally { if (sslEngine != null) { sslEngine.closeInbound(); sslEngine.closeOutbound(); cleanupServerSslEngine(sslEngine); } } } protected void handshake(BufferType type, boolean delegate, SSLEngine clientEngine, SSLEngine serverEngine) throws Exception { ByteBuffer cTOs = allocateBuffer(type, clientEngine.getSession().getPacketBufferSize()); ByteBuffer sTOc = allocateBuffer(type, serverEngine.getSession().getPacketBufferSize()); ByteBuffer serverAppReadBuffer = allocateBuffer(type, serverEngine.getSession().getApplicationBufferSize()); ByteBuffer clientAppReadBuffer = allocateBuffer(type, clientEngine.getSession().getApplicationBufferSize()); clientEngine.beginHandshake(); serverEngine.beginHandshake(); ByteBuffer empty = allocateBuffer(type, 0); SSLEngineResult clientResult; SSLEngineResult serverResult; boolean clientHandshakeFinished = false; boolean serverHandshakeFinished = false; boolean cTOsHasRemaining; boolean sTOcHasRemaining; do { int cTOsPos = cTOs.position(); int sTOcPos = sTOc.position(); if (!clientHandshakeFinished) { clientResult = clientEngine.wrap(empty, cTOs); runDelegatedTasks(delegate, clientResult, clientEngine); assertEquals(empty.remaining(), clientResult.bytesConsumed()); assertEquals(cTOs.position() - cTOsPos, clientResult.bytesProduced()); if (isHandshakeFinished(clientResult)) { clientHandshakeFinished = true; } } if (!serverHandshakeFinished) { serverResult = serverEngine.wrap(empty, sTOc); runDelegatedTasks(delegate, serverResult, serverEngine); assertEquals(empty.remaining(), serverResult.bytesConsumed()); assertEquals(sTOc.position() - sTOcPos, serverResult.bytesProduced()); if (isHandshakeFinished(serverResult)) { serverHandshakeFinished = true; } } cTOs.flip(); sTOc.flip(); cTOsPos = cTOs.position(); sTOcPos = sTOc.position(); if (!clientHandshakeFinished || // After the handshake completes it is possible we have more data that was send by the server as // the server will send session updates after the handshake. In this case continue to unwrap. SslProtocols.TLS_v1_3.equals(clientEngine.getSession().getProtocol())) { int clientAppReadBufferPos = clientAppReadBuffer.position(); clientResult = clientEngine.unwrap(sTOc, clientAppReadBuffer); runDelegatedTasks(delegate, clientResult, clientEngine); assertEquals(sTOc.position() - sTOcPos, clientResult.bytesConsumed()); assertEquals(clientAppReadBuffer.position() - clientAppReadBufferPos, clientResult.bytesProduced()); if (isHandshakeFinished(clientResult)) { clientHandshakeFinished = true; } } else { assertEquals(0, sTOc.remaining()); } if (!serverHandshakeFinished) { int serverAppReadBufferPos = serverAppReadBuffer.position(); serverResult = serverEngine.unwrap(cTOs, serverAppReadBuffer); runDelegatedTasks(delegate, serverResult, serverEngine); assertEquals(cTOs.position() - cTOsPos, serverResult.bytesConsumed()); assertEquals(serverAppReadBuffer.position() - serverAppReadBufferPos, serverResult.bytesProduced()); if (isHandshakeFinished(serverResult)) { serverHandshakeFinished = true; } } else { assertFalse(cTOs.hasRemaining()); } cTOsHasRemaining = cTOs.hasRemaining(); sTOcHasRemaining = sTOc.hasRemaining(); sTOc.compact(); cTOs.compact(); } while (!clientHandshakeFinished || !serverHandshakeFinished || // We need to ensure we feed all the data to the engine to not end up with a corrupted state. // This is especially important with TLS1.3 which may produce sessions after the "main handshake" is // done cTOsHasRemaining || sTOcHasRemaining); } private static boolean isHandshakeFinished(SSLEngineResult result) { return result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.FINISHED; } private void runDelegatedTasks(boolean delegate, SSLEngineResult result, SSLEngine engine) throws Exception { if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK) { for (;;) { Runnable task = engine.getDelegatedTask(); if (task == null) { break; } if (!delegate) { task.run(); } else { delegatingExecutor.submit(task).get(); } } } } protected abstract SslProvider sslClientProvider(); protected abstract SslProvider sslServerProvider(); protected Provider clientSslContextProvider() { return null; } protected Provider serverSslContextProvider() { return null; } /** * Called from the test cleanup code and can be used to release the {@code ctx} if it must be done manually. */ protected void cleanupClientSslContext(SslContext ctx) { } /** * Called from the test cleanup code and can be used to release the {@code ctx} if it must be done manually. */ protected void cleanupServerSslContext(SslContext ctx) { } /** * Called when ever an SSLEngine is not wrapped by a {@link SslHandler} and inserted into a pipeline. */ protected void cleanupClientSslEngine(SSLEngine engine) { } /** * Called when ever an SSLEngine is not wrapped by a {@link SslHandler} and inserted into a pipeline. */ protected void cleanupServerSslEngine(SSLEngine engine) { } protected void setupHandlers(SSLEngineTestParam param, ApplicationProtocolConfig apn) throws InterruptedException, SSLException, CertificateException { setupHandlers(param, apn, apn); } protected void setupHandlers(SSLEngineTestParam param, ApplicationProtocolConfig serverApn, ApplicationProtocolConfig clientApn) throws InterruptedException, SSLException, CertificateException { SelfSignedCertificate ssc = new SelfSignedCertificate(); try { SslContextBuilder serverCtxBuilder = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey(), null) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .ciphers(null, IdentityCipherSuiteFilter.INSTANCE) .applicationProtocolConfig(serverApn) .sessionCacheSize(0) .sessionTimeout(0); if (serverApn.protocol() == Protocol.NPN || serverApn.protocol() == Protocol.NPN_AND_ALPN) { // NPN is not really well supported with TLSv1.3 so force to use TLSv1.2 // See https://github.com/openssl/openssl/issues/3665 serverCtxBuilder.protocols(SslProtocols.TLS_v1_2); } SslContextBuilder clientCtxBuilder = SslContextBuilder.forClient() .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .applicationProtocolConfig(clientApn) .trustManager(InsecureTrustManagerFactory.INSTANCE) .ciphers(null, IdentityCipherSuiteFilter.INSTANCE) .sessionCacheSize(0) .sessionTimeout(0); if (clientApn.protocol() == Protocol.NPN || clientApn.protocol() == Protocol.NPN_AND_ALPN) { // NPN is not really well supported with TLSv1.3 so force to use TLSv1.2 // See https://github.com/openssl/openssl/issues/3665 clientCtxBuilder.protocols(SslProtocols.TLS_v1_2); } setupHandlers(param.type(), param.delegate(), wrapContext(param, serverCtxBuilder.build()), wrapContext(param, clientCtxBuilder.build())); } finally { ssc.delete(); } } protected void setupHandlers(final BufferType type, final boolean delegate, SslContext serverCtx, SslContext clientCtx) throws InterruptedException, SSLException, CertificateException { serverSslCtx = serverCtx; clientSslCtx = clientCtx; serverConnectedChannel = null; sb = new ServerBootstrap(); cb = new Bootstrap(); sb.group(new NioEventLoopGroup(), new NioEventLoopGroup()); sb.channel(NioServerSocketChannel.class); sb.childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), type)); ChannelPipeline p = ch.pipeline(); SslHandler sslHandler = !delegate ? serverSslCtx.newHandler(ch.alloc()) : serverSslCtx.newHandler(ch.alloc(), delegatingExecutor); p.addLast(sslHandler); p.addLast(new MessageDelegatorChannelHandler(serverReceiver, serverLatch)); p.addLast(new ChannelInboundHandlerAdapter() { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause.getCause() instanceof SSLHandshakeException) { serverException = cause.getCause(); serverLatch.countDown(); } else { ctx.fireExceptionCaught(cause); } } }); serverConnectedChannel = ch; } }); cb.group(new NioEventLoopGroup()); cb.channel(NioSocketChannel.class); cb.handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), type)); ChannelPipeline p = ch.pipeline(); SslHandler sslHandler = !delegate ? clientSslCtx.newHandler(ch.alloc()) : clientSslCtx.newHandler(ch.alloc(), delegatingExecutor); p.addLast(sslHandler); p.addLast(new MessageDelegatorChannelHandler(clientReceiver, clientLatch)); p.addLast(new ChannelInboundHandlerAdapter() { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause.getCause() instanceof SSLHandshakeException) { clientException = cause.getCause(); clientLatch.countDown(); } else { ctx.fireExceptionCaught(cause); } } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { clientLatch.countDown(); } }); } }); serverChannel = sb.bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); ChannelFuture ccf = cb.connect(serverChannel.localAddress()); assertTrue(ccf.syncUninterruptibly().isSuccess()); clientChannel = ccf.channel(); } @MethodSource("newTestParams") @ParameterizedTest @Timeout(30) public void testMutualAuthSameCertChain(final SSLEngineTestParam param) throws Exception { SelfSignedCertificate serverCert = new SelfSignedCertificate(); SelfSignedCertificate clientCert = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(serverCert.certificate(), serverCert.privateKey()) .trustManager(clientCert.cert()) .clientAuth(ClientAuth.REQUIRE).sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()).build()); sb = new ServerBootstrap(); sb.group(new NioEventLoopGroup(), new NioEventLoopGroup()); sb.channel(NioServerSocketChannel.class); final Promise promise = sb.config().group().next().newPromise(); serverChannel = sb.childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type())); SslHandler sslHandler = !param.delegate ? serverSslCtx.newHandler(ch.alloc()) : serverSslCtx.newHandler(ch.alloc(), delegatingExecutor); ch.pipeline().addFirst(sslHandler); ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof SslHandshakeCompletionEvent) { Throwable cause = ((SslHandshakeCompletionEvent) evt).cause(); if (cause == null) { SSLSession session = ((SslHandler) ctx.pipeline().first()).engine().getSession(); Certificate[] peerCertificates = session.getPeerCertificates(); if (peerCertificates == null) { promise.setFailure(new NullPointerException("peerCertificates")); return; } try { X509Certificate[] peerCertificateChain = session.getPeerCertificateChain(); if (peerCertificateChain == null) { promise.setFailure(new NullPointerException("peerCertificateChain")); } else if (peerCertificateChain.length + peerCertificates.length != 4) { String excTxtFmt = "peerCertificateChain.length:%s, peerCertificates.length:%s"; promise.setFailure(new IllegalStateException(String.format(excTxtFmt, peerCertificateChain.length, peerCertificates.length))); } else { for (int i = 0; i < peerCertificateChain.length; i++) { if (peerCertificateChain[i] == null || peerCertificates[i] == null) { promise.setFailure( new IllegalStateException("Certificate in chain is null")); return; } } promise.setSuccess(null); } } catch (UnsupportedOperationException e) { // See https://bugs.openjdk.java.net/browse/JDK-8241039 assertTrue(PlatformDependent.javaVersion() >= 15); assertEquals(2, peerCertificates.length); for (int i = 0; i < peerCertificates.length; i++) { if (peerCertificates[i] == null) { promise.setFailure( new IllegalStateException("Certificate in chain is null")); return; } } promise.setSuccess(null); } } else { promise.setFailure(cause); } } } }); serverConnectedChannel = ch; } }).bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); // We create a new chain for certificates which contains 2 certificates ByteArrayOutputStream chainStream = new ByteArrayOutputStream(); chainStream.write(Files.readAllBytes(clientCert.certificate().toPath())); chainStream.write(Files.readAllBytes(serverCert.certificate().toPath())); clientSslCtx = wrapContext(param, SslContextBuilder.forClient().keyManager( new ByteArrayInputStream(chainStream.toByteArray()), new FileInputStream(clientCert.privateKey())) .trustManager(new FileInputStream(serverCert.certificate())) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()).ciphers(param.ciphers()).build()); cb = new Bootstrap(); cb.group(new NioEventLoopGroup()); cb.channel(NioSocketChannel.class); clientChannel = cb.handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type())); ch.pipeline().addLast(new SslHandler(wrapEngine(clientSslCtx.newEngine(ch.alloc())))); } }).connect(serverChannel.localAddress()).syncUninterruptibly().channel(); promise.syncUninterruptibly(); serverCert.delete(); clientCert.delete(); } @MethodSource("newTestParams") @ParameterizedTest public void testUnwrapBehavior(SSLEngineTestParam param) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .trustManager(cert.cert()) .sslProvider(sslClientProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslProvider(sslServerProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); byte[] bytes = "Hello World".getBytes(CharsetUtil.US_ASCII); try { ByteBuffer plainClientOut = allocateBuffer(param.type, client.getSession().getApplicationBufferSize()); ByteBuffer encryptedClientToServer = allocateBuffer( param.type, server.getSession().getPacketBufferSize() * 2); ByteBuffer plainServerIn = allocateBuffer(param.type, server.getSession().getApplicationBufferSize()); handshake(param.type(), param.delegate(), client, server); // create two TLS frames // first frame plainClientOut.put(bytes, 0, 5); plainClientOut.flip(); SSLEngineResult result = client.wrap(plainClientOut, encryptedClientToServer); assertEquals(SSLEngineResult.Status.OK, result.getStatus()); assertEquals(5, result.bytesConsumed()); assertTrue(result.bytesProduced() > 0); assertFalse(plainClientOut.hasRemaining()); // second frame plainClientOut.clear(); plainClientOut.put(bytes, 5, 6); plainClientOut.flip(); result = client.wrap(plainClientOut, encryptedClientToServer); assertEquals(SSLEngineResult.Status.OK, result.getStatus()); assertEquals(6, result.bytesConsumed()); assertTrue(result.bytesProduced() > 0); // send over to server encryptedClientToServer.flip(); // try with too small output buffer first (to check BUFFER_OVERFLOW case) int remaining = encryptedClientToServer.remaining(); ByteBuffer small = allocateBuffer(param.type, 3); result = server.unwrap(encryptedClientToServer, small); assertEquals(SSLEngineResult.Status.BUFFER_OVERFLOW, result.getStatus()); assertEquals(remaining, encryptedClientToServer.remaining()); // now with big enough buffer result = server.unwrap(encryptedClientToServer, plainServerIn); assertEquals(SSLEngineResult.Status.OK, result.getStatus()); assertEquals(5, result.bytesProduced()); assertTrue(encryptedClientToServer.hasRemaining()); result = server.unwrap(encryptedClientToServer, plainServerIn); assertEquals(SSLEngineResult.Status.OK, result.getStatus()); assertEquals(6, result.bytesProduced()); assertFalse(encryptedClientToServer.hasRemaining()); plainServerIn.flip(); assertEquals(ByteBuffer.wrap(bytes), plainServerIn); } finally { cleanupClientSslEngine(client); cleanupServerSslEngine(server); cert.delete(); } } @MethodSource("newTestParams") @ParameterizedTest public void testProtocolMatch(SSLEngineTestParam param) throws Exception { testProtocol(param, false, new String[] {"TLSv1.2"}, new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"}); } @MethodSource("newTestParams") @ParameterizedTest public void testProtocolNoMatch(SSLEngineTestParam param) throws Exception { testProtocol(param, true, new String[] {"TLSv1.2"}, new String[] {"TLSv1", "TLSv1.1"}); } private void testProtocol(final SSLEngineTestParam param, boolean handshakeFails, String[] clientProtocols, String[] serverProtocols) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .trustManager(cert.cert()) .sslProvider(sslClientProvider()) .protocols(clientProtocols) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslProvider(sslServerProvider()) .protocols(serverProtocols) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { if (handshakeFails) { final SSLEngine clientEngine = client; final SSLEngine serverEngine = server; assertThrows(SSLHandshakeException.class, new Executable() { @Override public void execute() throws Throwable { handshake(param.type(), param.delegate(), clientEngine, serverEngine); } }); } else { handshake(param.type(), param.delegate(), client, server); } } finally { cleanupClientSslEngine(client); cleanupServerSslEngine(server); cert.delete(); } } private String[] nonContiguousProtocols(SslProvider provider) { if (provider != null) { // conscrypt not correctly filters out TLSv1 and TLSv1.1 which is required now by the JDK. // https://github.com/google/conscrypt/issues/1013 return new String[] { SslProtocols.TLS_v1_2 }; } return new String[] {SslProtocols.TLS_v1_2, SslProtocols.TLS_v1}; } @MethodSource("newTestParams") @ParameterizedTest public void testHandshakeCompletesWithNonContiguousProtocolsTLSv1_2CipherOnly(SSLEngineTestParam param) throws Exception { SelfSignedCertificate ssc = new SelfSignedCertificate(); // Select a mandatory cipher from the TLSv1.2 RFC https://www.ietf.org/rfc/rfc5246.txt so handshakes won't fail // due to no shared/supported cipher. final String sharedCipher = "TLS_RSA_WITH_AES_128_CBC_SHA"; clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .ciphers(Collections.singletonList(sharedCipher)) .protocols(nonContiguousProtocols(sslClientProvider())) .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .build()); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .ciphers(Collections.singletonList(sharedCipher)) .protocols(nonContiguousProtocols(sslServerProvider())) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } @MethodSource("newTestParams") @ParameterizedTest public void testHandshakeCompletesWithoutFilteringSupportedCipher(SSLEngineTestParam param) throws Exception { SelfSignedCertificate ssc = new SelfSignedCertificate(); // Select a mandatory cipher from the TLSv1.2 RFC https://www.ietf.org/rfc/rfc5246.txt so handshakes won't fail // due to no shared/supported cipher. final String sharedCipher = "TLS_RSA_WITH_AES_128_CBC_SHA"; clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .ciphers(Collections.singletonList(sharedCipher), SupportedCipherSuiteFilter.INSTANCE) .protocols(nonContiguousProtocols(sslClientProvider())) .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .build()); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .ciphers(Collections.singletonList(sharedCipher), SupportedCipherSuiteFilter.INSTANCE) .protocols(nonContiguousProtocols(sslServerProvider())) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } @MethodSource("newTestParams") @ParameterizedTest public void testPacketBufferSizeLimit(SSLEngineTestParam param) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .trustManager(cert.cert()) .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { // Allocate an buffer that is bigger then the max plain record size. ByteBuffer plainServerOut = allocateBuffer( param.type(), server.getSession().getApplicationBufferSize() * 2); handshake(param.type(), param.delegate(), client, server); // Fill the whole buffer and flip it. plainServerOut.position(plainServerOut.capacity()); plainServerOut.flip(); ByteBuffer encryptedServerToClient = allocateBuffer( param.type(), server.getSession().getPacketBufferSize()); int encryptedServerToClientPos = encryptedServerToClient.position(); int plainServerOutPos = plainServerOut.position(); SSLEngineResult result = server.wrap(plainServerOut, encryptedServerToClient); assertEquals(SSLEngineResult.Status.OK, result.getStatus()); assertEquals(plainServerOut.position() - plainServerOutPos, result.bytesConsumed()); assertEquals(encryptedServerToClient.position() - encryptedServerToClientPos, result.bytesProduced()); } finally { cleanupClientSslEngine(client); cleanupServerSslEngine(server); cert.delete(); } } @MethodSource("newTestParams") @ParameterizedTest public void testSSLEngineUnwrapNoSslRecord(SSLEngineTestParam param) throws Exception { clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); final SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { final ByteBuffer src = allocateBuffer(param.type(), client.getSession().getApplicationBufferSize()); final ByteBuffer dst = allocateBuffer(param.type(), client.getSession().getPacketBufferSize()); ByteBuffer empty = allocateBuffer(param.type(), 0); SSLEngineResult clientResult = client.wrap(empty, dst); assertEquals(SSLEngineResult.Status.OK, clientResult.getStatus()); assertEquals(SSLEngineResult.HandshakeStatus.NEED_UNWRAP, clientResult.getHandshakeStatus()); assertThrows(SSLException.class, new Executable() { @Override public void execute() throws Throwable { client.unwrap(src, dst); } }); } finally { cleanupClientSslEngine(client); } } @MethodSource("newTestParams") @ParameterizedTest public void testBeginHandshakeAfterEngineClosed(SSLEngineTestParam param) throws SSLException { clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { client.closeInbound(); client.closeOutbound(); try { client.beginHandshake(); fail(); } catch (SSLException expected) { // expected } catch (IllegalStateException e) { if (!Conscrypt.isEngineSupported(client)) { throw e; } // Workaround for conscrypt bug // See https://github.com/google/conscrypt/issues/840 } } finally { cleanupClientSslEngine(client); } } @MethodSource("newTestParams") @ParameterizedTest public void testBeginHandshakeCloseOutbound(SSLEngineTestParam param) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { testBeginHandshakeCloseOutbound(param, client); testBeginHandshakeCloseOutbound(param, server); } finally { cleanupClientSslEngine(client); cleanupServerSslEngine(server); cert.delete(); } } private void testBeginHandshakeCloseOutbound(SSLEngineTestParam param, SSLEngine engine) throws SSLException { ByteBuffer dst = allocateBuffer(param.type(), engine.getSession().getPacketBufferSize()); ByteBuffer empty = allocateBuffer(param.type(), 0); engine.beginHandshake(); engine.closeOutbound(); SSLEngineResult result; for (;;) { result = engine.wrap(empty, dst); dst.flip(); assertEquals(0, result.bytesConsumed()); assertEquals(dst.remaining(), result.bytesProduced()); if (result.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.NEED_WRAP) { break; } dst.clear(); } assertEquals(SSLEngineResult.Status.CLOSED, result.getStatus()); } @MethodSource("newTestParams") @ParameterizedTest public void testCloseInboundAfterBeginHandshake(SSLEngineTestParam param) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { testCloseInboundAfterBeginHandshake(client); testCloseInboundAfterBeginHandshake(server); } finally { cleanupClientSslEngine(client); cleanupServerSslEngine(server); cert.delete(); } } private static void testCloseInboundAfterBeginHandshake(SSLEngine engine) throws SSLException { engine.beginHandshake(); try { engine.closeInbound(); // Workaround for conscrypt bug // See https://github.com/google/conscrypt/issues/839 if (!Conscrypt.isEngineSupported(engine)) { fail(); } } catch (SSLException expected) { // expected } } @MethodSource("newTestParams") @ParameterizedTest public void testCloseNotifySequence(SSLEngineTestParam param) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .trustManager(cert.cert()) .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) // This test only works for non TLSv1.3 for now .protocols(SslProtocols.TLS_v1_2) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) // This test only works for non TLSv1.3 for now .protocols(SslProtocols.TLS_v1_2) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { ByteBuffer plainClientOut = allocateBuffer(param.type(), client.getSession().getApplicationBufferSize()); ByteBuffer plainServerOut = allocateBuffer(param.type(), server.getSession().getApplicationBufferSize()); ByteBuffer encryptedClientToServer = allocateBuffer(param.type(), client.getSession().getPacketBufferSize()); ByteBuffer encryptedServerToClient = allocateBuffer(param.type(), server.getSession().getPacketBufferSize()); ByteBuffer empty = allocateBuffer(param.type(), 0); handshake(param.type(), param.delegate(), client, server); // This will produce a close_notify client.closeOutbound(); // Something still pending in the outbound buffer. assertFalse(client.isOutboundDone()); assertFalse(client.isInboundDone()); // Now wrap and so drain the outbound buffer. SSLEngineResult result = client.wrap(empty, encryptedClientToServer); encryptedClientToServer.flip(); assertEquals(SSLEngineResult.Status.CLOSED, result.getStatus()); SSLEngineResult.HandshakeStatus hs = result.getHandshakeStatus(); // Need an UNWRAP to read the response of the close_notify if (sslClientProvider() == SslProvider.JDK || Conscrypt.isEngineSupported(client)) { assertTrue(hs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING || hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP); } else { assertEquals(SSLEngineResult.HandshakeStatus.NEED_UNWRAP, hs); } int produced = result.bytesProduced(); int consumed = result.bytesConsumed(); int closeNotifyLen = produced; assertTrue(produced > 0); assertEquals(0, consumed); assertEquals(produced, encryptedClientToServer.remaining()); // Outbound buffer should be drained now. assertTrue(client.isOutboundDone()); assertFalse(client.isInboundDone()); assertFalse(server.isOutboundDone()); assertFalse(server.isInboundDone()); result = server.unwrap(encryptedClientToServer, plainServerOut); plainServerOut.flip(); assertEquals(SSLEngineResult.Status.CLOSED, result.getStatus()); // Need a WRAP to respond to the close_notify assertEquals(SSLEngineResult.HandshakeStatus.NEED_WRAP, result.getHandshakeStatus()); produced = result.bytesProduced(); consumed = result.bytesConsumed(); assertEquals(closeNotifyLen, consumed); assertEquals(0, produced); // Should have consumed the complete close_notify assertEquals(0, encryptedClientToServer.remaining()); assertEquals(0, plainServerOut.remaining()); assertFalse(server.isOutboundDone()); assertTrue(server.isInboundDone()); result = server.wrap(empty, encryptedServerToClient); encryptedServerToClient.flip(); assertEquals(SSLEngineResult.Status.CLOSED, result.getStatus()); // UNWRAP/WRAP are not expected after this point assertEquals(SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, result.getHandshakeStatus()); produced = result.bytesProduced(); consumed = result.bytesConsumed(); assertEquals(closeNotifyLen, produced); assertEquals(0, consumed); assertEquals(produced, encryptedServerToClient.remaining()); assertTrue(server.isOutboundDone()); assertTrue(server.isInboundDone()); result = client.unwrap(encryptedServerToClient, plainClientOut); plainClientOut.flip(); assertEquals(SSLEngineResult.Status.CLOSED, result.getStatus()); // UNWRAP/WRAP are not expected after this point assertEquals(SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, result.getHandshakeStatus()); produced = result.bytesProduced(); consumed = result.bytesConsumed(); assertEquals(closeNotifyLen, consumed); assertEquals(0, produced); assertEquals(0, encryptedServerToClient.remaining()); assertTrue(client.isOutboundDone()); assertTrue(client.isInboundDone()); // Ensure that calling wrap or unwrap again will not produce an SSLException encryptedServerToClient.clear(); plainServerOut.clear(); result = server.wrap(plainServerOut, encryptedServerToClient); assertEngineRemainsClosed(result); encryptedClientToServer.clear(); plainServerOut.clear(); result = server.unwrap(encryptedClientToServer, plainServerOut); assertEngineRemainsClosed(result); encryptedClientToServer.clear(); plainClientOut.clear(); result = client.wrap(plainClientOut, encryptedClientToServer); assertEngineRemainsClosed(result); encryptedServerToClient.clear(); plainClientOut.clear(); result = client.unwrap(encryptedServerToClient, plainClientOut); assertEngineRemainsClosed(result); } finally { cert.delete(); cleanupClientSslEngine(client); cleanupServerSslEngine(server); } } private static void assertEngineRemainsClosed(SSLEngineResult result) { assertEquals(SSLEngineResult.Status.CLOSED, result.getStatus()); assertEquals(SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, result.getHandshakeStatus()); assertEquals(0, result.bytesConsumed()); assertEquals(0, result.bytesProduced()); } @MethodSource("newTestParams") @ParameterizedTest public void testWrapAfterCloseOutbound(SSLEngineTestParam param) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .trustManager(cert.cert()) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { ByteBuffer dst = allocateBuffer(param.type(), client.getSession().getPacketBufferSize()); ByteBuffer src = allocateBuffer(param.type(), 1024); handshake(param.type(), param.delegate(), client, server); // This will produce a close_notify client.closeOutbound(); SSLEngineResult result = client.wrap(src, dst); assertEquals(SSLEngineResult.Status.CLOSED, result.getStatus()); assertEquals(0, result.bytesConsumed()); assertTrue(result.bytesProduced() > 0); assertTrue(client.isOutboundDone()); assertFalse(client.isInboundDone()); } finally { cert.delete(); cleanupClientSslEngine(client); cleanupServerSslEngine(server); } } @MethodSource("newTestParams") @ParameterizedTest public void testMultipleRecordsInOneBufferWithNonZeroPosition(SSLEngineTestParam param) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .trustManager(cert.cert()) .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { // Choose buffer size small enough that we can put multiple buffers into one buffer and pass it into the // unwrap call without exceed MAX_ENCRYPTED_PACKET_LENGTH. ByteBuffer plainClientOut = allocateBuffer(param.type(), 1024); ByteBuffer plainServerOut = allocateBuffer(param.type(), server.getSession().getApplicationBufferSize()); ByteBuffer encClientToServer = allocateBuffer(param.type(), client.getSession().getPacketBufferSize()); int positionOffset = 1; // We need to be able to hold 2 records + positionOffset ByteBuffer combinedEncClientToServer = allocateBuffer( param.type(), encClientToServer.capacity() * 2 + positionOffset); combinedEncClientToServer.position(positionOffset); handshake(param.type(), param.delegate(), client, server); plainClientOut.limit(plainClientOut.capacity()); SSLEngineResult result = client.wrap(plainClientOut, encClientToServer); assertEquals(plainClientOut.capacity(), result.bytesConsumed()); assertTrue(result.bytesProduced() > 0); encClientToServer.flip(); // Copy the first record into the combined buffer combinedEncClientToServer.put(encClientToServer); plainClientOut.clear(); encClientToServer.clear(); result = client.wrap(plainClientOut, encClientToServer); assertEquals(plainClientOut.capacity(), result.bytesConsumed()); assertTrue(result.bytesProduced() > 0); encClientToServer.flip(); int encClientToServerLen = encClientToServer.remaining(); // Copy the first record into the combined buffer combinedEncClientToServer.put(encClientToServer); encClientToServer.clear(); combinedEncClientToServer.flip(); combinedEncClientToServer.position(positionOffset); // Ensure we have the first record and a tiny amount of the second record in the buffer combinedEncClientToServer.limit( combinedEncClientToServer.limit() - (encClientToServerLen - positionOffset)); result = server.unwrap(combinedEncClientToServer, plainServerOut); assertEquals(encClientToServerLen, result.bytesConsumed()); assertTrue(result.bytesProduced() > 0); } finally { cert.delete(); cleanupClientSslEngine(client); cleanupServerSslEngine(server); } } @MethodSource("newTestParams") @ParameterizedTest public void testMultipleRecordsInOneBufferBiggerThenPacketBufferSize(SSLEngineTestParam param) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .trustManager(cert.cert()) .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { ByteBuffer plainClientOut = allocateBuffer(param.type(), 4096); ByteBuffer plainServerOut = allocateBuffer(param.type(), server.getSession().getApplicationBufferSize()); ByteBuffer encClientToServer = allocateBuffer(param.type(), server.getSession().getPacketBufferSize() * 2); handshake(param.type(), param.delegate(), client, server); int srcLen = plainClientOut.remaining(); SSLEngineResult result; int count = 0; do { int plainClientOutPosition = plainClientOut.position(); int encClientToServerPosition = encClientToServer.position(); result = client.wrap(plainClientOut, encClientToServer); if (result.getStatus() == Status.BUFFER_OVERFLOW) { // We did not have enough room to wrap assertEquals(plainClientOutPosition, plainClientOut.position()); assertEquals(encClientToServerPosition, encClientToServer.position()); break; } assertEquals(SSLEngineResult.Status.OK, result.getStatus()); assertEquals(srcLen, result.bytesConsumed()); assertTrue(result.bytesProduced() > 0); plainClientOut.clear(); ++count; } while (encClientToServer.position() < server.getSession().getPacketBufferSize()); // Check that we were able to wrap multiple times. assertTrue(count >= 2); encClientToServer.flip(); result = server.unwrap(encClientToServer, plainServerOut); assertEquals(SSLEngineResult.Status.OK, result.getStatus()); assertTrue(result.bytesConsumed() > 0); assertTrue(result.bytesProduced() > 0); assertTrue(encClientToServer.hasRemaining()); } finally { cert.delete(); cleanupClientSslEngine(client); cleanupServerSslEngine(server); } } @MethodSource("newTestParams") @ParameterizedTest public void testBufferUnderFlow(SSLEngineTestParam param) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .trustManager(cert.cert()) .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { ByteBuffer plainClient = allocateBuffer(param.type(), 1024); plainClient.limit(plainClient.capacity()); ByteBuffer encClientToServer = allocateBuffer(param.type(), client.getSession().getPacketBufferSize()); ByteBuffer plainServer = allocateBuffer(param.type(), server.getSession().getApplicationBufferSize()); handshake(param.type(), param.delegate(), client, server); SSLEngineResult result = client.wrap(plainClient, encClientToServer); assertEquals(SSLEngineResult.Status.OK, result.getStatus()); assertEquals(result.bytesConsumed(), plainClient.capacity()); // Flip so we can read it. encClientToServer.flip(); int remaining = encClientToServer.remaining(); // We limit the buffer so we have less then the header to read, this should result in an BUFFER_UNDERFLOW. encClientToServer.limit(SSL_RECORD_HEADER_LENGTH - 1); result = server.unwrap(encClientToServer, plainServer); assertResultIsBufferUnderflow(result); // We limit the buffer so we can read the header but not the rest, this should result in an // BUFFER_UNDERFLOW. encClientToServer.limit(SSL_RECORD_HEADER_LENGTH); result = server.unwrap(encClientToServer, plainServer); assertResultIsBufferUnderflow(result); // We limit the buffer so we can read the header and partly the rest, this should result in an // BUFFER_UNDERFLOW. encClientToServer.limit(SSL_RECORD_HEADER_LENGTH + remaining - 1 - SSL_RECORD_HEADER_LENGTH); result = server.unwrap(encClientToServer, plainServer); assertResultIsBufferUnderflow(result); // Reset limit so we can read the full record. encClientToServer.limit(remaining); result = server.unwrap(encClientToServer, plainServer); assertEquals(SSLEngineResult.Status.OK, result.getStatus()); assertEquals(result.bytesConsumed(), remaining); assertTrue(result.bytesProduced() > 0); } finally { cert.delete(); cleanupClientSslEngine(client); cleanupServerSslEngine(server); } } private static void assertResultIsBufferUnderflow(SSLEngineResult result) { assertEquals(SSLEngineResult.Status.BUFFER_UNDERFLOW, result.getStatus()); assertEquals(0, result.bytesConsumed()); assertEquals(0, result.bytesProduced()); } @MethodSource("newTestParams") @ParameterizedTest public void testWrapDoesNotZeroOutSrc(SSLEngineTestParam param) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .trustManager(cert.cert()) .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { ByteBuffer plainServerOut = allocateBuffer(param.type(), server.getSession().getApplicationBufferSize() / 2); handshake(param.type(), param.delegate(), client, server); // Fill the whole buffer and flip it. for (int i = 0; i < plainServerOut.capacity(); i++) { plainServerOut.put(i, (byte) i); } plainServerOut.position(plainServerOut.capacity()); plainServerOut.flip(); ByteBuffer encryptedServerToClient = allocateBuffer(param.type(), server.getSession().getPacketBufferSize()); SSLEngineResult result = server.wrap(plainServerOut, encryptedServerToClient); assertEquals(SSLEngineResult.Status.OK, result.getStatus()); assertTrue(result.bytesConsumed() > 0); for (int i = 0; i < plainServerOut.capacity(); i++) { assertEquals((byte) i, plainServerOut.get(i)); } } finally { cleanupClientSslEngine(client); cleanupServerSslEngine(server); cert.delete(); } } @MethodSource("newTestParams") @ParameterizedTest public void testDisableProtocols(SSLEngineTestParam param) throws Exception { testDisableProtocols(param, SslProtocols.SSL_v2, SslProtocols.SSL_v2); testDisableProtocols(param, SslProtocols.SSL_v3, SslProtocols.SSL_v2, SslProtocols.SSL_v3); testDisableProtocols(param, SslProtocols.TLS_v1, SslProtocols.SSL_v2, SslProtocols.SSL_v3, SslProtocols.TLS_v1); testDisableProtocols(param, SslProtocols.TLS_v1_1, SslProtocols.SSL_v2, SslProtocols.SSL_v3, SslProtocols.TLS_v1, SslProtocols.TLS_v1_1); testDisableProtocols(param, SslProtocols.TLS_v1_2, SslProtocols.SSL_v2, SslProtocols.SSL_v3, SslProtocols.TLS_v1, SslProtocols.TLS_v1_1, SslProtocols.TLS_v1_2); } private void testDisableProtocols(SSLEngineTestParam param, String protocol, String... disabledProtocols) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); SslContext ctx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine server = wrapEngine(ctx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { Set supported = new HashSet(Arrays.asList(server.getSupportedProtocols())); if (supported.contains(protocol)) { server.setEnabledProtocols(server.getSupportedProtocols()); assertEquals(supported, new HashSet(Arrays.asList(server.getSupportedProtocols()))); for (String disabled : disabledProtocols) { supported.remove(disabled); } if (supported.contains(SslProtocols.SSL_v2_HELLO) && supported.size() == 1) { // It's not allowed to set only PROTOCOL_SSL_V2_HELLO if using JDK SSLEngine. return; } server.setEnabledProtocols(supported.toArray(new String[0])); assertEquals(supported, new HashSet(Arrays.asList(server.getEnabledProtocols()))); server.setEnabledProtocols(server.getSupportedProtocols()); } } finally { cleanupServerSslEngine(server); cleanupClientSslContext(ctx); cert.delete(); } } @MethodSource("newTestParams") @ParameterizedTest public void testUsingX509TrustManagerVerifiesHostname(SSLEngineTestParam param) throws Exception { if (clientSslContextProvider() != null) { // Not supported when using conscrypt return; } SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .trustManager(new TrustManagerFactory(new TrustManagerFactorySpi() { @Override protected void engineInit(KeyStore keyStore) { // NOOP } @Override protected TrustManager[] engineGetTrustManagers() { // Provide a custom trust manager, this manager trust all certificates return new TrustManager[] { new X509TrustManager() { @Override public void checkClientTrusted( java.security.cert.X509Certificate[] x509Certificates, String s) { // NOOP } @Override public void checkServerTrusted( java.security.cert.X509Certificate[] x509Certificates, String s) { // NOOP } @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { return EmptyArrays.EMPTY_X509_CERTIFICATES; } } }; } @Override protected void engineInit(ManagerFactoryParameters managerFactoryParameters) { } }, null, TrustManagerFactory.getDefaultAlgorithm()) { }) .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT, "netty.io", 1234)); SSLParameters sslParameters = client.getSSLParameters(); sslParameters.setEndpointIdentificationAlgorithm("HTTPS"); client.setSSLParameters(sslParameters); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { handshake(param.type(), param.delegate(), client, server); fail(); } catch (SSLException expected) { // expected as the hostname not matches. } finally { cleanupClientSslEngine(client); cleanupServerSslEngine(server); cert.delete(); } } @Test public void testInvalidCipher() throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); List cipherList = new ArrayList(); Collections.addAll(cipherList, ((SSLSocketFactory) SSLSocketFactory.getDefault()).getDefaultCipherSuites()); cipherList.add("InvalidCipher"); SSLEngine server = null; try { serverSslCtx = wrapContext(null, SslContextBuilder.forServer(cert.key(), cert.cert()) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .ciphers(cipherList).build()); server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); fail(); } catch (IllegalArgumentException expected) { // expected when invalid cipher is used. } catch (SSLException expected) { // expected when invalid cipher is used. } finally { cert.delete(); cleanupServerSslEngine(server); } } @MethodSource("newTestParams") @ParameterizedTest public void testGetCiphersuite(SSLEngineTestParam param) throws Exception { clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); String clientCipher = clientEngine.getSession().getCipherSuite(); String serverCipher = serverEngine.getSession().getCipherSuite(); assertEquals(clientCipher, serverCipher); assertEquals(param.protocolCipherCombo.cipher, clientCipher); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } @MethodSource("newTestParams") @ParameterizedTest public void testSessionCache(SSLEngineTestParam param) throws Exception { clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); try { doHandshakeVerifyReusedAndClose(param, "a.netty.io", 9999, false); doHandshakeVerifyReusedAndClose(param, "a.netty.io", 9999, true); doHandshakeVerifyReusedAndClose(param, "b.netty.io", 9999, false); invalidateSessionsAndAssert(serverSslCtx.sessionContext()); invalidateSessionsAndAssert(clientSslCtx.sessionContext()); } finally { ssc.delete(); } } protected void invalidateSessionsAndAssert(SSLSessionContext context) { Enumeration ids = context.getIds(); while (ids.hasMoreElements()) { byte[] id = ids.nextElement(); SSLSession session = context.getSession(id); if (session != null) { session.invalidate(); assertFalse(session.isValid()); assertNull(context.getSession(id)); } } } private static void assertSessionCache(SSLSessionContext sessionContext, int numSessions) { Enumeration ids = sessionContext.getIds(); int numIds = 0; while (ids.hasMoreElements()) { numIds++; byte[] id = ids.nextElement(); assertNotEquals(0, id.length); SSLSession session = sessionContext.getSession(id); assertArrayEquals(id, session.getId()); } assertEquals(numSessions, numIds); } private void doHandshakeVerifyReusedAndClose(SSLEngineTestParam param, String host, int port, boolean reuse) throws Exception { SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT, host, port)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); int clientSessions = currentSessionCacheSize(clientSslCtx.sessionContext()); int serverSessions = currentSessionCacheSize(serverSslCtx.sessionContext()); int nCSessions = clientSessions; int nSSessions = serverSessions; boolean clientSessionReused = false; boolean serverSessionReused = false; if (param.protocolCipherCombo == ProtocolCipherCombo.TLSV13) { // Allocate something which is big enough for sure ByteBuffer packetBuffer = allocateBuffer(param.type(), 32 * 1024); ByteBuffer appBuffer = allocateBuffer(param.type(), 32 * 1024); appBuffer.clear().position(4).flip(); packetBuffer.clear(); do { SSLEngineResult result; do { result = serverEngine.wrap(appBuffer, packetBuffer); } while (appBuffer.hasRemaining() || result.bytesProduced() > 0); appBuffer.clear(); packetBuffer.flip(); do { result = clientEngine.unwrap(packetBuffer, appBuffer); } while (packetBuffer.hasRemaining() || result.bytesProduced() > 0); packetBuffer.clear(); appBuffer.clear().position(4).flip(); do { result = clientEngine.wrap(appBuffer, packetBuffer); } while (appBuffer.hasRemaining() || result.bytesProduced() > 0); appBuffer.clear(); packetBuffer.flip(); do { result = serverEngine.unwrap(packetBuffer, appBuffer); } while (packetBuffer.hasRemaining() || result.bytesProduced() > 0); packetBuffer.clear(); appBuffer.clear().position(4).flip(); nCSessions = currentSessionCacheSize(clientSslCtx.sessionContext()); nSSessions = currentSessionCacheSize(serverSslCtx.sessionContext()); clientSessionReused = isSessionMaybeReused(clientEngine); serverSessionReused = isSessionMaybeReused(serverEngine); } while ((reuse && (!clientSessionReused || !serverSessionReused)) || (!reuse && (nCSessions < clientSessions || // server may use multiple sessions nSSessions < serverSessions))); } assertSessionReusedForEngine(clientEngine, serverEngine, reuse); closeOutboundAndInbound(param.type(), clientEngine, serverEngine); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); } } protected boolean isSessionMaybeReused(SSLEngine engine) { return true; } private static int currentSessionCacheSize(SSLSessionContext ctx) { Enumeration ids = ctx.getIds(); int i = 0; while (ids.hasMoreElements()) { i++; ids.nextElement(); } return i; } private void closeOutboundAndInbound( BufferType type, SSLEngine clientEngine, SSLEngine serverEngine) throws SSLException { assertFalse(clientEngine.isInboundDone()); assertFalse(clientEngine.isOutboundDone()); assertFalse(serverEngine.isInboundDone()); assertFalse(serverEngine.isOutboundDone()); ByteBuffer empty = allocateBuffer(type, 0); // Ensure we allocate a bit more so we can fit in multiple packets. This is needed as we may call multiple // time wrap / unwrap in a for loop before we drain the buffer we are writing in. ByteBuffer cTOs = allocateBuffer(type, clientEngine.getSession().getPacketBufferSize() * 4); ByteBuffer sTOs = allocateBuffer(type, serverEngine.getSession().getPacketBufferSize() * 4); ByteBuffer cApps = allocateBuffer(type, clientEngine.getSession().getApplicationBufferSize() * 4); ByteBuffer sApps = allocateBuffer(type, serverEngine.getSession().getApplicationBufferSize() * 4); clientEngine.closeOutbound(); for (;;) { // call wrap till we produced all data SSLEngineResult result = clientEngine.wrap(empty, cTOs); if (result.getStatus() == Status.CLOSED && result.bytesProduced() == 0) { break; } assertTrue(cTOs.hasRemaining()); } cTOs.flip(); for (;;) { // call unwrap till we consumed all data SSLEngineResult result = serverEngine.unwrap(cTOs, sApps); if (result.getStatus() == Status.CLOSED && result.bytesProduced() == 0) { break; } assertTrue(sApps.hasRemaining()); } serverEngine.closeOutbound(); for (;;) { // call wrap till we produced all data SSLEngineResult result = serverEngine.wrap(empty, sTOs); if (result.getStatus() == Status.CLOSED && result.bytesProduced() == 0) { break; } assertTrue(sTOs.hasRemaining()); } sTOs.flip(); for (;;) { // call unwrap till we consumed all data SSLEngineResult result = clientEngine.unwrap(sTOs, cApps); if (result.getStatus() == Status.CLOSED && result.bytesProduced() == 0) { break; } assertTrue(cApps.hasRemaining()); } // Now close the inbound as well clientEngine.closeInbound(); serverEngine.closeInbound(); } protected void assertSessionReusedForEngine(SSLEngine clientEngine, SSLEngine serverEngine, boolean reuse) { // NOOP } @MethodSource("newTestParams") @ParameterizedTest public void testSessionCacheTimeout(SSLEngineTestParam param) throws Exception { clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .sessionTimeout(1) .build()); SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .sessionTimeout(1) .build()); try { doHandshakeVerifyReusedAndClose(param, "a.netty.io", 9999, false); // Let's sleep for a bit more then 1 second so the cache should timeout the sessions. Thread.sleep(1500); assertSessionCache(serverSslCtx.sessionContext(), 0); assertSessionCache(clientSslCtx.sessionContext(), 0); } finally { ssc.delete(); } } @MethodSource("newTestParams") @ParameterizedTest public void testSessionCacheSize(SSLEngineTestParam param) throws Exception { clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .sessionCacheSize(1) .build()); SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); try { doHandshakeVerifyReusedAndClose(param, "a.netty.io", 9999, false); // As we have a cache size of 1 we should never have more then one session in the cache doHandshakeVerifyReusedAndClose(param, "b.netty.io", 9999, false); // We should at least reuse b.netty.io doHandshakeVerifyReusedAndClose(param, "b.netty.io", 9999, true); } finally { ssc.delete(); } } @MethodSource("newTestParams") @ParameterizedTest public void testSessionBindingEvent(SSLEngineTestParam param) throws Exception { clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); SSLSession session = clientEngine.getSession(); assertEquals(0, session.getValueNames().length); class SSLSessionBindingEventValue implements SSLSessionBindingListener { SSLSessionBindingEvent boundEvent; SSLSessionBindingEvent unboundEvent; @Override public void valueBound(SSLSessionBindingEvent sslSessionBindingEvent) { assertNull(boundEvent); boundEvent = sslSessionBindingEvent; } @Override public void valueUnbound(SSLSessionBindingEvent sslSessionBindingEvent) { assertNull(unboundEvent); unboundEvent = sslSessionBindingEvent; } } String name = "name"; String name2 = "name2"; SSLSessionBindingEventValue value1 = new SSLSessionBindingEventValue(); session.putValue(name, value1); assertSSLSessionBindingEventValue(name, session, value1.boundEvent); assertNull(value1.unboundEvent); assertEquals(1, session.getValueNames().length); session.putValue(name2, "value"); SSLSessionBindingEventValue value2 = new SSLSessionBindingEventValue(); session.putValue(name, value2); assertEquals(2, session.getValueNames().length); assertSSLSessionBindingEventValue(name, session, value1.unboundEvent); assertSSLSessionBindingEventValue(name, session, value2.boundEvent); assertNull(value2.unboundEvent); assertEquals(2, session.getValueNames().length); session.removeValue(name); assertSSLSessionBindingEventValue(name, session, value2.unboundEvent); assertEquals(1, session.getValueNames().length); session.removeValue(name2); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } private static void assertSSLSessionBindingEventValue( String name, SSLSession session, SSLSessionBindingEvent event) { assertEquals(name, event.getName()); assertEquals(session, event.getSession()); assertEquals(session, event.getSource()); } @MethodSource("newTestParams") @ParameterizedTest public void testSessionAfterHandshake(SSLEngineTestParam param) throws Exception { testSessionAfterHandshake0(param, false, false); } @MethodSource("newTestParams") @ParameterizedTest public void testSessionAfterHandshakeMutualAuth(SSLEngineTestParam param) throws Exception { testSessionAfterHandshake0(param, false, true); } @MethodSource("newTestParams") @ParameterizedTest public void testSessionAfterHandshakeKeyManagerFactory(SSLEngineTestParam param) throws Exception { testSessionAfterHandshake0(param, true, false); } @MethodSource("newTestParams") @ParameterizedTest public void testSessionAfterHandshakeKeyManagerFactoryMutualAuth(SSLEngineTestParam param) throws Exception { testSessionAfterHandshake0(param, true, true); } private void testSessionAfterHandshake0( SSLEngineTestParam param, boolean useKeyManagerFactory, boolean mutualAuth) throws Exception { SelfSignedCertificate ssc = new SelfSignedCertificate(); KeyManagerFactory kmf = useKeyManagerFactory ? SslContext.buildKeyManagerFactory( new java.security.cert.X509Certificate[] { ssc.cert()}, null, ssc.key(), null, null, null) : null; SslContextBuilder clientContextBuilder = SslContextBuilder.forClient(); if (mutualAuth) { if (kmf != null) { clientContextBuilder.keyManager(kmf); } else { clientContextBuilder.keyManager(ssc.key(), ssc.cert()); } } clientSslCtx = wrapContext(param, clientContextBuilder .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SslContextBuilder serverContextBuilder = kmf != null ? SslContextBuilder.forServer(kmf) : SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()); if (mutualAuth) { serverContextBuilder.clientAuth(ClientAuth.REQUIRE); } serverSslCtx = wrapContext(param, serverContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); SSLSession clientSession = clientEngine.getSession(); SSLSession serverSession = serverEngine.getSession(); assertNull(clientSession.getPeerHost()); assertNull(serverSession.getPeerHost()); assertEquals(-1, clientSession.getPeerPort()); assertEquals(-1, serverSession.getPeerPort()); assertTrue(clientSession.getCreationTime() > 0); assertTrue(serverSession.getCreationTime() > 0); assertTrue(clientSession.getLastAccessedTime() > 0); assertTrue(serverSession.getLastAccessedTime() > 0); assertEquals(param.combo().protocol, clientSession.getProtocol()); assertEquals(param.combo().protocol, serverSession.getProtocol()); assertEquals(param.combo().cipher, clientSession.getCipherSuite()); assertEquals(param.combo().cipher, serverSession.getCipherSuite()); assertNotNull(clientSession.getId()); assertNotNull(serverSession.getId()); assertTrue(clientSession.getApplicationBufferSize() > 0); assertTrue(serverSession.getApplicationBufferSize() > 0); assertTrue(clientSession.getPacketBufferSize() > 0); assertTrue(serverSession.getPacketBufferSize() > 0); assertNotNull(clientSession.getSessionContext()); // Workaround for JDK 14 regression. // See https://bugs.openjdk.java.net/browse/JDK-8242008 if (PlatformDependent.javaVersion() != 14) { assertNotNull(serverSession.getSessionContext()); } Object value = new Object(); assertEquals(0, clientSession.getValueNames().length); clientSession.putValue("test", value); assertEquals("test", clientSession.getValueNames()[0]); assertSame(value, clientSession.getValue("test")); clientSession.removeValue("test"); assertEquals(0, clientSession.getValueNames().length); assertEquals(0, serverSession.getValueNames().length); serverSession.putValue("test", value); assertEquals("test", serverSession.getValueNames()[0]); assertSame(value, serverSession.getValue("test")); serverSession.removeValue("test"); assertEquals(0, serverSession.getValueNames().length); Certificate[] serverLocalCertificates = serverSession.getLocalCertificates(); assertEquals(1, serverLocalCertificates.length); assertArrayEquals(ssc.cert().getEncoded(), serverLocalCertificates[0].getEncoded()); Principal serverLocalPrincipal = serverSession.getLocalPrincipal(); assertNotNull(serverLocalPrincipal); if (mutualAuth) { Certificate[] clientLocalCertificates = clientSession.getLocalCertificates(); assertEquals(1, clientLocalCertificates.length); Certificate[] serverPeerCertificates = serverSession.getPeerCertificates(); assertEquals(1, serverPeerCertificates.length); assertArrayEquals(clientLocalCertificates[0].getEncoded(), serverPeerCertificates[0].getEncoded()); try { X509Certificate[] serverPeerX509Certificates = serverSession.getPeerCertificateChain(); assertEquals(1, serverPeerX509Certificates.length); assertArrayEquals(clientLocalCertificates[0].getEncoded(), serverPeerX509Certificates[0].getEncoded()); } catch (UnsupportedOperationException e) { // See https://bugs.openjdk.java.net/browse/JDK-8241039 assertTrue(PlatformDependent.javaVersion() >= 15); } Principal clientLocalPrincipial = clientSession.getLocalPrincipal(); assertNotNull(clientLocalPrincipial); Principal serverPeerPrincipal = serverSession.getPeerPrincipal(); assertEquals(clientLocalPrincipial, serverPeerPrincipal); } else { assertNull(clientSession.getLocalCertificates()); assertNull(clientSession.getLocalPrincipal()); try { serverSession.getPeerCertificates(); fail(); } catch (SSLPeerUnverifiedException expected) { // As we did not use mutual auth this is expected } try { serverSession.getPeerCertificateChain(); fail(); } catch (SSLPeerUnverifiedException expected) { // As we did not use mutual auth this is expected } catch (UnsupportedOperationException e) { // See https://bugs.openjdk.java.net/browse/JDK-8241039 assertTrue(PlatformDependent.javaVersion() >= 15); } try { serverSession.getPeerPrincipal(); fail(); } catch (SSLPeerUnverifiedException expected) { // As we did not use mutual auth this is expected } } Certificate[] clientPeerCertificates = clientSession.getPeerCertificates(); assertEquals(1, clientPeerCertificates.length); assertArrayEquals(serverLocalCertificates[0].getEncoded(), clientPeerCertificates[0].getEncoded()); try { X509Certificate[] clientPeerX509Certificates = clientSession.getPeerCertificateChain(); assertEquals(1, clientPeerX509Certificates.length); assertArrayEquals(serverLocalCertificates[0].getEncoded(), clientPeerX509Certificates[0].getEncoded()); } catch (UnsupportedOperationException e) { // See https://bugs.openjdk.java.net/browse/JDK-8241039 assertTrue(PlatformDependent.javaVersion() >= 15); } Principal clientPeerPrincipal = clientSession.getPeerPrincipal(); assertEquals(serverLocalPrincipal, clientPeerPrincipal); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } @MethodSource("newTestParams") @ParameterizedTest public void testSupportedSignatureAlgorithms(SSLEngineTestParam param) throws Exception { final SelfSignedCertificate ssc = new SelfSignedCertificate(); final class TestKeyManagerFactory extends KeyManagerFactory { TestKeyManagerFactory(final KeyManagerFactory factory) { super(new KeyManagerFactorySpi() { private final KeyManager[] managers = factory.getKeyManagers(); @Override protected void engineInit(KeyStore keyStore, char[] chars) { throw new UnsupportedOperationException(); } @Override protected void engineInit(ManagerFactoryParameters managerFactoryParameters) { throw new UnsupportedOperationException(); } @Override protected KeyManager[] engineGetKeyManagers() { KeyManager[] array = new KeyManager[managers.length]; for (int i = 0 ; i < array.length; i++) { final X509ExtendedKeyManager x509ExtendedKeyManager = (X509ExtendedKeyManager) managers[i]; array[i] = new X509ExtendedKeyManager() { @Override public String[] getClientAliases(String s, Principal[] principals) { fail(); return null; } @Override public String chooseClientAlias( String[] strings, Principal[] principals, Socket socket) { fail(); return null; } @Override public String[] getServerAliases(String s, Principal[] principals) { fail(); return null; } @Override public String chooseServerAlias(String s, Principal[] principals, Socket socket) { fail(); return null; } @Override public String chooseEngineClientAlias( String[] strings, Principal[] principals, SSLEngine sslEngine) { assertNotEquals(0, ((ExtendedSSLSession) sslEngine.getHandshakeSession()) .getPeerSupportedSignatureAlgorithms().length); assertNotEquals(0, ((ExtendedSSLSession) sslEngine.getHandshakeSession()) .getLocalSupportedSignatureAlgorithms().length); return x509ExtendedKeyManager.chooseEngineClientAlias( strings, principals, sslEngine); } @Override public String chooseEngineServerAlias( String s, Principal[] principals, SSLEngine sslEngine) { assertNotEquals(0, ((ExtendedSSLSession) sslEngine.getHandshakeSession()) .getPeerSupportedSignatureAlgorithms().length); assertNotEquals(0, ((ExtendedSSLSession) sslEngine.getHandshakeSession()) .getLocalSupportedSignatureAlgorithms().length); return x509ExtendedKeyManager.chooseEngineServerAlias(s, principals, sslEngine); } @Override public java.security.cert.X509Certificate[] getCertificateChain(String s) { return x509ExtendedKeyManager.getCertificateChain(s); } @Override public PrivateKey getPrivateKey(String s) { return x509ExtendedKeyManager.getPrivateKey(s); } }; } return array; } }, factory.getProvider(), factory.getAlgorithm()); } } clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .keyManager(new TestKeyManagerFactory(newKeyManagerFactory(ssc))) .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); serverSslCtx = wrapContext(param, SslContextBuilder.forServer( new TestKeyManagerFactory(newKeyManagerFactory(ssc))) .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .clientAuth(ClientAuth.REQUIRE) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } @MethodSource("newTestParams") @ParameterizedTest public void testHandshakeSession(SSLEngineTestParam param) throws Exception { final SelfSignedCertificate ssc = new SelfSignedCertificate(); final TestTrustManagerFactory clientTmf = new TestTrustManagerFactory(ssc.cert()); final TestTrustManagerFactory serverTmf = new TestTrustManagerFactory(ssc.cert()); clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(new SimpleTrustManagerFactory() { @Override protected void engineInit(KeyStore keyStore) { // NOOP } @Override protected void engineInit(ManagerFactoryParameters managerFactoryParameters) { // NOOP } @Override protected TrustManager[] engineGetTrustManagers() { return new TrustManager[] { clientTmf }; } }) .keyManager(newKeyManagerFactory(ssc)) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(newKeyManagerFactory(ssc)) .trustManager(new SimpleTrustManagerFactory() { @Override protected void engineInit(KeyStore keyStore) { // NOOP } @Override protected void engineInit(ManagerFactoryParameters managerFactoryParameters) { // NOOP } @Override protected TrustManager[] engineGetTrustManagers() { return new TrustManager[] { serverTmf }; } }) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .clientAuth(ClientAuth.REQUIRE) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); assertTrue(clientTmf.isVerified()); assertTrue(serverTmf.isVerified()); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } @MethodSource("newTestParams") @ParameterizedTest public void testSessionLocalWhenNonMutualWithKeyManager(SSLEngineTestParam param) throws Exception { testSessionLocalWhenNonMutual(param, true); } @MethodSource("newTestParams") @ParameterizedTest public void testSessionLocalWhenNonMutualWithoutKeyManager(SSLEngineTestParam param) throws Exception { testSessionLocalWhenNonMutual(param, false); } private void testSessionLocalWhenNonMutual(SSLEngineTestParam param, boolean useKeyManager) throws Exception { final SelfSignedCertificate ssc = new SelfSignedCertificate(); SslContextBuilder clientSslCtxBuilder = SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()); if (useKeyManager) { clientSslCtxBuilder.keyManager(newKeyManagerFactory(ssc)); } else { clientSslCtxBuilder.keyManager(ssc.certificate(), ssc.privateKey()); } clientSslCtx = wrapContext(param, clientSslCtxBuilder.build()); final SslContextBuilder serverSslCtxBuilder; if (useKeyManager) { serverSslCtxBuilder = SslContextBuilder.forServer(newKeyManagerFactory(ssc)); } else { serverSslCtxBuilder = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()); } serverSslCtxBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .clientAuth(ClientAuth.NONE); serverSslCtx = wrapContext(param, serverSslCtxBuilder.build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); SSLSession clientSession = clientEngine.getSession(); assertNull(clientSession.getLocalCertificates()); assertNull(clientSession.getLocalPrincipal()); SSLSession serverSession = serverEngine.getSession(); assertNotNull(serverSession.getLocalCertificates()); assertNotNull(serverSession.getLocalPrincipal()); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } @MethodSource("newTestParams") @ParameterizedTest public void testEnabledProtocolsAndCiphers(SSLEngineTestParam param) throws Exception { clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); assertEnabledProtocolsAndCipherSuites(clientEngine); assertEnabledProtocolsAndCipherSuites(serverEngine); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } private static void assertEnabledProtocolsAndCipherSuites(SSLEngine engine) { String protocol = engine.getSession().getProtocol(); String cipherSuite = engine.getSession().getCipherSuite(); assertArrayContains(protocol, engine.getEnabledProtocols()); assertArrayContains(cipherSuite, engine.getEnabledCipherSuites()); } private static void assertArrayContains(String expected, String[] array) { for (String value: array) { if (expected.equals(value)) { return; } } fail("Array did not contain '" + expected + "':" + Arrays.toString(array)); } @MethodSource("newTestParams") @ParameterizedTest public void testMasterKeyLogging(final SSLEngineTestParam param) throws Exception { if (param.combo() != ProtocolCipherCombo.tlsv12()) { return; } /* * At the moment master key logging is not supported for conscrypt */ assumeFalse(serverSslContextProvider() instanceof OpenSSLProvider); /* * The JDK SSL engine master key retrieval relies on being able to set field access to true. * That is not available in JDK9+ */ assumeFalse(sslServerProvider() == SslProvider.JDK && PlatformDependent.javaVersion() > 8); String originalSystemPropertyValue = SystemPropertyUtil.get(SslMasterKeyHandler.SYSTEM_PROP_KEY); System.setProperty(SslMasterKeyHandler.SYSTEM_PROP_KEY, Boolean.TRUE.toString()); SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); Socket socket = null; try { sb = new ServerBootstrap(); sb.group(new NioEventLoopGroup(), new NioEventLoopGroup()); sb.channel(NioServerSocketChannel.class); final Promise promise = sb.config().group().next().newPromise(); serverChannel = sb.childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type())); SslHandler sslHandler = !param.delegate() ? serverSslCtx.newHandler(ch.alloc()) : serverSslCtx.newHandler(ch.alloc(), delegatingExecutor); ch.pipeline().addLast(sslHandler); ch.pipeline().addLast(new SslMasterKeyHandler() { @Override protected void accept(SecretKey masterKey, SSLSession session) { promise.setSuccess(masterKey); } }); serverConnectedChannel = ch; } }).bind(new InetSocketAddress(0)).sync().channel(); int port = ((InetSocketAddress) serverChannel.localAddress()).getPort(); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, InsecureTrustManagerFactory.INSTANCE.getTrustManagers(), null); socket = sslContext.getSocketFactory().createSocket(NetUtil.LOCALHOST, port); OutputStream out = socket.getOutputStream(); out.write(1); out.flush(); assertTrue(promise.await(10, TimeUnit.SECONDS)); SecretKey key = promise.get(); assertEquals(48, key.getEncoded().length, "AES secret key must be 48 bytes"); } finally { closeQuietly(socket); if (originalSystemPropertyValue != null) { System.setProperty(SslMasterKeyHandler.SYSTEM_PROP_KEY, originalSystemPropertyValue); } else { System.clearProperty(SslMasterKeyHandler.SYSTEM_PROP_KEY); } ssc.delete(); } } private static void closeQuietly(Closeable c) { if (c != null) { try { c.close(); } catch (IOException ignore) { // ignore } } } private static KeyManagerFactory newKeyManagerFactory(SelfSignedCertificate ssc) throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { return SslContext.buildKeyManagerFactory( new java.security.cert.X509Certificate[] { ssc.cert() }, null, ssc.key(), null, null, null); } private static final class TestTrustManagerFactory extends X509ExtendedTrustManager { private final Certificate localCert; private volatile boolean verified; TestTrustManagerFactory(Certificate localCert) { this.localCert = localCert; } boolean isVerified() { return verified; } @Override public void checkClientTrusted( java.security.cert.X509Certificate[] x509Certificates, String s, Socket socket) { fail(); } @Override public void checkServerTrusted( java.security.cert.X509Certificate[] x509Certificates, String s, Socket socket) { fail(); } @Override public void checkClientTrusted( java.security.cert.X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) { verified = true; assertFalse(sslEngine.getUseClientMode()); SSLSession session = sslEngine.getHandshakeSession(); assertNotNull(session); Certificate[] localCertificates = session.getLocalCertificates(); assertNotNull(localCertificates); assertEquals(1, localCertificates.length); assertEquals(localCert, localCertificates[0]); assertNotNull(session.getLocalPrincipal()); } @Override public void checkServerTrusted( java.security.cert.X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) { verified = true; assertTrue(sslEngine.getUseClientMode()); SSLSession session = sslEngine.getHandshakeSession(); assertNotNull(session); assertNull(session.getLocalCertificates()); assertNull(session.getLocalPrincipal()); } @Override public void checkClientTrusted( java.security.cert.X509Certificate[] x509Certificates, String s) { fail(); } @Override public void checkServerTrusted( java.security.cert.X509Certificate[] x509Certificates, String s) { fail(); } @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { return EmptyArrays.EMPTY_X509_CERTIFICATES; } } @MethodSource("newTestParams") @ParameterizedTest public void testDefaultProtocolsIncludeTLSv13(SSLEngineTestParam param) throws Exception { // Don't specify the protocols as we want to test the default selection clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .ciphers(param.ciphers()) .build()); SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .ciphers(param.ciphers()) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; String[] clientProtocols; String[] serverProtocols; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); clientProtocols = clientEngine.getEnabledProtocols(); serverProtocols = serverEngine.getEnabledProtocols(); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } assertEquals(SslProvider.isTlsv13EnabledByDefault(sslClientProvider(), clientSslContextProvider()), arrayContains(clientProtocols, SslProtocols.TLS_v1_3)); assertEquals(SslProvider.isTlsv13EnabledByDefault(sslServerProvider(), serverSslContextProvider()), arrayContains(serverProtocols, SslProtocols.TLS_v1_3)); } @MethodSource("newTestParams") @ParameterizedTest public void testRSASSAPSS(SSLEngineTestParam param) throws Exception { char[] password = "password".toCharArray(); final KeyStore serverKeyStore = KeyStore.getInstance("PKCS12"); serverKeyStore.load(getClass().getResourceAsStream("rsaValidations-server-keystore.p12"), password); final KeyStore clientKeyStore = KeyStore.getInstance("PKCS12"); clientKeyStore.load(getClass().getResourceAsStream("rsaValidation-user-certs.p12"), password); final KeyManagerFactory serverKeyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); serverKeyManagerFactory.init(serverKeyStore, password); final KeyManagerFactory clientKeyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); clientKeyManagerFactory.init(clientKeyStore, password); File commonChain = ResourcesUtil.getFile(getClass(), "rsapss-ca-cert.cert"); ClientAuth auth = ClientAuth.REQUIRE; mySetupMutualAuth(param, serverKeyManagerFactory, commonChain, clientKeyManagerFactory, commonChain, auth, false, true); assertTrue(clientLatch.await(10, TimeUnit.SECONDS)); rethrowIfNotNull(clientException); assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); rethrowIfNotNull(serverException); } protected SSLEngine wrapEngine(SSLEngine engine) { return engine; } protected SslContext wrapContext(SSLEngineTestParam param, SslContext context) { return context; } }