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