netty5/handler/src/test/java/io/netty/handler/ssl/SSLEngineTest.java
Chris Vest 0cb4cc4e49
Make Promise not extend Future (#11634)
Motivation:
We wish to separate these two into clearer write/read interfaces.
In particular, we don't want to be able to add listeners to promises, because it makes it easy to add them out of order.
We can't prevent it entirely, because any promise can be freely converted to a future where listeners can be added.
We can, however, discourage this in the API.

Modification:
The Promise interface no longer extends the Future interface.
Numerous changes to make the project compile and its tests run.

Result:
Clearer separation of concerns in the code.
2021-09-02 10:46:54 +02:00

4206 lines
189 KiB
Java

/*
* 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.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.MultithreadEventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioHandler;
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.Future;
import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.Promise;
import io.netty.util.concurrent.UnaryPromiseNotifier;
import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.ResourcesUtil;
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 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 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.Random;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import static io.netty.handler.ssl.SslUtils.SSL_RECORD_HEADER_LENGTH;
import static io.netty.handler.ssl.SslUtils.isValidHostNameForSNI;
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<ByteBuf> {
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 messageReceived(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<String> protocols() {
return Collections.singletonList(protocolCipherCombo.protocol);
}
final List<String> ciphers() {
return Collections.singletonList(protocolCipherCombo.cipher);
}
}
protected List<SSLEngineTestParam> newTestParams() {
List<SSLEngineTestParam> params = new ArrayList<SSLEngineTestParam>();
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 ThreadLocalRandom.current().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 ThreadLocalRandom.current().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 ThreadLocalRandom.current().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 ThreadLocalRandom.current().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 ((Random) ThreadLocalRandom.current()).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 ((Random) ThreadLocalRandom.current()).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 {
Future<Void> clientCloseFuture = null;
Future<Void> serverConnectedCloseFuture = null;
Future<Void> 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 = { 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 Exception {
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 MultithreadEventLoopGroup(NioHandler.newFactory()),
new MultithreadEventLoopGroup(NioHandler.newFactory()));
sb.channel(NioServerSocketChannel.class);
sb.childHandler(new ChannelInitializer<Channel>() {
@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 ChannelHandler() {
@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 MultithreadEventLoopGroup(NioHandler.newFactory()));
cb.channel(NioSocketChannel.class);
cb.handler(new ChannelInitializer<Channel>() {
@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 ChannelHandler() {
@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)).get();
int port = ((InetSocketAddress) serverChannel.localAddress()).getPort();
Future<Channel> ccf = cb.connect(new InetSocketAddress(NetUtil.LOCALHOST, port));
assertTrue(ccf.awaitUninterruptibly().isSuccess());
clientChannel = ccf.get();
}
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<Void> 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<Void> mySetupClientHostnameValidation(final SSLEngineTestParam param, File serverCrtFile,
File serverKeyFile,
File clientTrustCrtFile,
final boolean failureExpected)
throws Exception {
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 MultithreadEventLoopGroup(NioHandler.newFactory()),
new MultithreadEventLoopGroup(NioHandler.newFactory()));
sb.channel(NioServerSocketChannel.class);
sb.childHandler(new ChannelInitializer<Channel>() {
@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 ChannelHandler() {
@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<Void> clientWritePromise = ImmediateEventExecutor.INSTANCE.newPromise();
cb.group(new MultithreadEventLoopGroup(NioHandler.newFactory()));
cb.channel(NioSocketChannel.class);
cb.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type));
ChannelPipeline p = ch.pipeline();
SslHandler sslHandler = !param.delegate ?
clientSslCtx.newHandler(ch.alloc(), expectedHost, 0) :
clientSslCtx.newHandler(ch.alloc(), expectedHost, 0, delegatingExecutor);
SSLParameters parameters = sslHandler.engine().getSSLParameters();
if (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 ChannelHandler() {
@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) {
ctx.write(ctx.alloc().buffer(1).writeByte(1))
.addListener(new UnaryPromiseNotifier<Void>(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)).get();
final int port = ((InetSocketAddress) serverChannel.localAddress()).getPort();
Future<Channel> ccf = cb.connect(new InetSocketAddress(expectedHost, port));
assertTrue(ccf.awaitUninterruptibly().isSuccess());
clientChannel = ccf.get();
return clientWritePromise.asFuture();
}
private void mySetupMutualAuth(SSLEngineTestParam param, File keyFile, File crtFile, String keyPassword)
throws Exception {
mySetupMutualAuth(param, crtFile, keyFile, crtFile, keyPassword, crtFile, keyFile, crtFile, keyPassword);
}
private static 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 Exception {
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 MultithreadEventLoopGroup(NioHandler.newFactory()),
new MultithreadEventLoopGroup(NioHandler.newFactory()));
sb.channel(NioServerSocketChannel.class);
sb.childHandler(new ChannelInitializer<Channel>() {
@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 ChannelHandler() {
@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 MultithreadEventLoopGroup(NioHandler.newFactory()));
cb.channel(NioSocketChannel.class);
cb.handler(new ChannelInitializer<Channel>() {
@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 ChannelHandler() {
@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)).get();
int port = ((InetSocketAddress) serverChannel.localAddress()).getPort();
Future<Channel> ccf = cb.connect(new InetSocketAddress(NetUtil.LOCALHOST, port));
assertTrue(ccf.awaitUninterruptibly().isSuccess());
clientChannel = ccf.get();
}
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<ByteBuf> dataCapture = null;
try {
assertTrue(sendChannel.writeAndFlush(message).await(10, TimeUnit.SECONDS));
receiverLatch.await(5, TimeUnit.SECONDS);
message.readerIndex(0);
ArgumentCaptor<ByteBuf> 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 MultithreadEventLoopGroup(1, NioHandler.newFactory()))
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@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 ChannelHandler() {
@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().executor().schedule(() -> {
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)).get();
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 MultithreadEventLoopGroup(1, NioHandler.newFactory()))
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@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 ChannelHandler() {
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();
}
});
}
});
Future<Channel> ccf = cb.connect(serverChannel.localAddress());
assertTrue(ccf.syncUninterruptibly().isSuccess());
clientChannel = ccf.get();
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 Exception {
setupHandlers(param, apn, apn);
}
protected void setupHandlers(SSLEngineTestParam param,
ApplicationProtocolConfig serverApn, ApplicationProtocolConfig clientApn)
throws Exception {
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 Exception {
serverSslCtx = serverCtx;
clientSslCtx = clientCtx;
serverConnectedChannel = null;
sb = new ServerBootstrap();
cb = new Bootstrap();
sb.group(new MultithreadEventLoopGroup(NioHandler.newFactory()),
new MultithreadEventLoopGroup(NioHandler.newFactory()));
sb.channel(NioServerSocketChannel.class);
sb.childHandler(new ChannelInitializer<Channel>() {
@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 ChannelHandler() {
@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 MultithreadEventLoopGroup(NioHandler.newFactory()));
cb.channel(NioSocketChannel.class);
cb.handler(new ChannelInitializer<Channel>() {
@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 ChannelHandler() {
@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)).get();
Future<Channel> ccf = cb.connect(serverChannel.localAddress());
assertTrue(ccf.syncUninterruptibly().isSuccess());
clientChannel = ccf.get();
}
@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 MultithreadEventLoopGroup(NioHandler.newFactory()),
new MultithreadEventLoopGroup(NioHandler.newFactory()));
sb.channel(NioServerSocketChannel.class);
final Promise<String> promise = sb.config().group().next().newPromise();
serverChannel = sb.childHandler(new ChannelInitializer<Channel>() {
@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 ChannelHandler() {
@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 (Certificate peerCertificate : peerCertificates) {
if (peerCertificate == null) {
promise.setFailure(
new IllegalStateException("Certificate in chain is null"));
return;
}
}
promise.setSuccess(null);
}
} else {
promise.setFailure(cause);
}
}
}
});
serverConnectedChannel = ch;
}
}).bind(new InetSocketAddress(0)).get();
// 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 MultithreadEventLoopGroup(NioHandler.newFactory()));
cb.channel(NioSocketChannel.class);
clientChannel = cb.handler(new ChannelInitializer<Channel>() {
@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()).get();
promise.asFuture().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 static 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<String> 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(EmptyArrays.EMPTY_STRINGS));
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<String> 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 | 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<byte[]> 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<byte[]> 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<byte[]> 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 MultithreadEventLoopGroup(NioHandler.newFactory()),
new MultithreadEventLoopGroup(NioHandler.newFactory()));
sb.channel(NioServerSocketChannel.class);
final Promise<SecretKey> promise = sb.config().group().next().newPromise();
serverChannel = sb.childHandler(new ChannelInitializer<Channel>() {
@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)).get();
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();
Future<SecretKey> future = promise.asFuture();
assertTrue(future.await(10, TimeUnit.SECONDS));
SecretKey key = future.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()),
SslUtils.arrayContains(clientProtocols, SslProtocols.TLS_v1_3));
assertEquals(SslProvider.isTlsv13EnabledByDefault(sslServerProvider(), serverSslContextProvider()),
SslUtils.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;
}
}