netty5/handler/src/test/java/io/netty/handler/ssl/ocsp/OcspTest.java
Norman Maurer cd3254df88
Update to new checkstyle plugin (#8777) (#8780)
Motivation:

We need to update to a new checkstyle plugin to allow the usage of lambdas.

Modifications:

- Update to new plugin version.
- Fix checkstyle problems.

Result:

Be able to use checkstyle plugin which supports new Java syntax.
2019-01-25 11:58:42 +01:00

504 lines
18 KiB
Java

/*
* Copyright 2017 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:
*
* http://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.ocsp;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.local.LocalAddress;
import io.netty.channel.local.LocalChannel;
import io.netty.channel.local.LocalServerChannel;
import io.netty.handler.ssl.OpenSsl;
import io.netty.handler.ssl.ReferenceCountedOpenSslEngine;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
import java.net.SocketAddress;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLHandshakeException;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
public class OcspTest {
@BeforeClass
public static void checkOcspSupported() {
assumeTrue(OpenSsl.isOcspSupported());
}
@Test(expected = IllegalArgumentException.class)
public void testJdkClientEnableOcsp() throws Exception {
SslContextBuilder.forClient()
.sslProvider(SslProvider.JDK)
.enableOcsp(true)
.build();
}
@Test(expected = IllegalArgumentException.class)
public void testJdkServerEnableOcsp() throws Exception {
SelfSignedCertificate ssc = new SelfSignedCertificate();
try {
SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
.sslProvider(SslProvider.JDK)
.enableOcsp(true)
.build();
} finally {
ssc.delete();
}
}
@Test(expected = IllegalStateException.class)
public void testClientOcspNotEnabledOpenSsl() throws Exception {
testClientOcspNotEnabled(SslProvider.OPENSSL);
}
@Test(expected = IllegalStateException.class)
public void testClientOcspNotEnabledOpenSslRefCnt() throws Exception {
testClientOcspNotEnabled(SslProvider.OPENSSL_REFCNT);
}
private static void testClientOcspNotEnabled(SslProvider sslProvider) throws Exception {
SslContext context = SslContextBuilder.forClient()
.sslProvider(sslProvider)
.build();
try {
SslHandler sslHandler = context.newHandler(ByteBufAllocator.DEFAULT);
ReferenceCountedOpenSslEngine engine = (ReferenceCountedOpenSslEngine) sslHandler.engine();
try {
engine.getOcspResponse();
} finally {
engine.release();
}
} finally {
ReferenceCountUtil.release(context);
}
}
@Test(expected = IllegalStateException.class)
public void testServerOcspNotEnabledOpenSsl() throws Exception {
testServerOcspNotEnabled(SslProvider.OPENSSL);
}
@Test(expected = IllegalStateException.class)
public void testServerOcspNotEnabledOpenSslRefCnt() throws Exception {
testServerOcspNotEnabled(SslProvider.OPENSSL_REFCNT);
}
private static void testServerOcspNotEnabled(SslProvider sslProvider) throws Exception {
SelfSignedCertificate ssc = new SelfSignedCertificate();
try {
SslContext context = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
.sslProvider(sslProvider)
.build();
try {
SslHandler sslHandler = context.newHandler(ByteBufAllocator.DEFAULT);
ReferenceCountedOpenSslEngine engine = (ReferenceCountedOpenSslEngine) sslHandler.engine();
try {
engine.setOcspResponse(new byte[] { 1, 2, 3 });
} finally {
engine.release();
}
} finally {
ReferenceCountUtil.release(context);
}
} finally {
ssc.delete();
}
}
@Test(timeout = 10000L)
public void testClientAcceptingOcspStapleOpenSsl() throws Exception {
testClientAcceptingOcspStaple(SslProvider.OPENSSL);
}
@Test(timeout = 10000L)
public void testClientAcceptingOcspStapleOpenSslRefCnt() throws Exception {
testClientAcceptingOcspStaple(SslProvider.OPENSSL_REFCNT);
}
/**
* The Server provides an OCSP staple and the Client accepts it.
*/
private static void testClientAcceptingOcspStaple(SslProvider sslProvider) throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
ChannelInboundHandlerAdapter serverHandler = new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.wrappedBuffer("Hello, World!".getBytes()));
ctx.fireChannelActive();
}
};
ChannelInboundHandlerAdapter clientHandler = new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
ReferenceCountUtil.release(msg);
} finally {
latch.countDown();
}
}
};
byte[] response = newOcspResponse();
TestClientOcspContext callback = new TestClientOcspContext(true);
handshake(sslProvider, latch, serverHandler, response, clientHandler, callback);
byte[] actual = callback.response();
assertNotNull(actual);
assertNotSame(response, actual);
assertArrayEquals(response, actual);
}
@Test(timeout = 10000L)
public void testClientRejectingOcspStapleOpenSsl() throws Exception {
testClientRejectingOcspStaple(SslProvider.OPENSSL);
}
@Test(timeout = 10000L)
public void testClientRejectingOcspStapleOpenSslRefCnt() throws Exception {
testClientRejectingOcspStaple(SslProvider.OPENSSL_REFCNT);
}
/**
* The Server provides an OCSP staple and the Client rejects it.
*/
private static void testClientRejectingOcspStaple(SslProvider sslProvider) throws Exception {
final AtomicReference<Throwable> causeRef = new AtomicReference<Throwable>();
final CountDownLatch latch = new CountDownLatch(1);
ChannelInboundHandlerAdapter clientHandler = new ChannelInboundHandlerAdapter() {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
try {
causeRef.set(cause);
} finally {
latch.countDown();
}
}
};
byte[] response = newOcspResponse();
TestClientOcspContext callback = new TestClientOcspContext(false);
handshake(sslProvider, latch, null, response, clientHandler, callback);
byte[] actual = callback.response();
assertNotNull(actual);
assertNotSame(response, actual);
assertArrayEquals(response, actual);
Throwable cause = causeRef.get();
assertTrue("" + cause, cause instanceof SSLHandshakeException);
}
@Test(timeout = 10000L)
public void testServerHasNoStapleOpenSsl() throws Exception {
testServerHasNoStaple(SslProvider.OPENSSL);
}
@Test(timeout = 10000L)
public void testServerHasNoStapleOpenSslRefCnt() throws Exception {
testServerHasNoStaple(SslProvider.OPENSSL_REFCNT);
}
/**
* The server has OCSP stapling enabled but doesn't provide a staple.
*/
private static void testServerHasNoStaple(SslProvider sslProvider) throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
ChannelInboundHandlerAdapter serverHandler = new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.wrappedBuffer("Hello, World!".getBytes()));
ctx.fireChannelActive();
}
};
ChannelInboundHandlerAdapter clientHandler = new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
ReferenceCountUtil.release(msg);
} finally {
latch.countDown();
}
}
};
byte[] response = null;
TestClientOcspContext callback = new TestClientOcspContext(true);
handshake(sslProvider, latch, serverHandler, response, clientHandler, callback);
byte[] actual = callback.response();
assertNull(response);
assertNull(actual);
}
@Test(timeout = 10000L)
public void testClientExceptionOpenSsl() throws Exception {
testClientException(SslProvider.OPENSSL);
}
@Test(timeout = 10000L)
public void testClientExceptionOpenSslRefCnt() throws Exception {
testClientException(SslProvider.OPENSSL_REFCNT);
}
/**
* Testing what happens if the {@link OcspClientCallback} throws an {@link Exception}.
*
* The exception should bubble up on the client side and the connection should get closed.
*/
private static void testClientException(SslProvider sslProvider) throws Exception {
final AtomicReference<Throwable> causeRef = new AtomicReference<Throwable>();
final CountDownLatch latch = new CountDownLatch(1);
ChannelInboundHandlerAdapter clientHandler = new ChannelInboundHandlerAdapter() {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
try {
causeRef.set(cause);
} finally {
latch.countDown();
}
}
};
final OcspTestException clientException = new OcspTestException("testClientException");
byte[] response = newOcspResponse();
OcspClientCallback callback = new OcspClientCallback() {
@Override
public boolean verify(byte[] response) throws Exception {
throw clientException;
}
};
handshake(sslProvider, latch, null, response, clientHandler, callback);
assertSame(clientException, causeRef.get());
}
private static void handshake(SslProvider sslProvider, CountDownLatch latch, ChannelHandler serverHandler,
byte[] response, ChannelHandler clientHandler, OcspClientCallback callback) throws Exception {
SelfSignedCertificate ssc = new SelfSignedCertificate();
try {
SslContext serverSslContext = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
.sslProvider(sslProvider)
.enableOcsp(true)
.build();
try {
SslContext clientSslContext = SslContextBuilder.forClient()
.sslProvider(sslProvider)
.enableOcsp(true)
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build();
try {
EventLoopGroup group = new DefaultEventLoopGroup();
try {
LocalAddress address = new LocalAddress("handshake-" + Math.random());
Channel server = newServer(group, address, serverSslContext, response, serverHandler);
Channel client = newClient(group, address, clientSslContext, callback, clientHandler);
try {
assertTrue("Something went wrong.", latch.await(10L, TimeUnit.SECONDS));
} finally {
client.close().syncUninterruptibly();
server.close().syncUninterruptibly();
}
} finally {
group.shutdownGracefully(1L, 1L, TimeUnit.SECONDS);
}
} finally {
ReferenceCountUtil.release(clientSslContext);
}
} finally {
ReferenceCountUtil.release(serverSslContext);
}
} finally {
ssc.delete();
}
}
private static Channel newServer(EventLoopGroup group, SocketAddress address,
SslContext context, byte[] response, ChannelHandler handler) {
ServerBootstrap bootstrap = new ServerBootstrap()
.channel(LocalServerChannel.class)
.group(group)
.childHandler(newServerHandler(context, response, handler));
return bootstrap.bind(address)
.syncUninterruptibly()
.channel();
}
private static Channel newClient(EventLoopGroup group, SocketAddress address,
SslContext context, OcspClientCallback callback, ChannelHandler handler) {
Bootstrap bootstrap = new Bootstrap()
.channel(LocalChannel.class)
.group(group)
.handler(newClientHandler(context, callback, handler));
return bootstrap.connect(address)
.syncUninterruptibly()
.channel();
}
private static ChannelHandler newServerHandler(final SslContext context,
final byte[] response, final ChannelHandler handler) {
return new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
SslHandler sslHandler = context.newHandler(ch.alloc());
if (response != null) {
ReferenceCountedOpenSslEngine engine = (ReferenceCountedOpenSslEngine) sslHandler.engine();
engine.setOcspResponse(response);
}
pipeline.addLast(sslHandler);
if (handler != null) {
pipeline.addLast(handler);
}
}
};
}
private static ChannelHandler newClientHandler(final SslContext context,
final OcspClientCallback callback, final ChannelHandler handler) {
return new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
SslHandler sslHandler = context.newHandler(ch.alloc());
ReferenceCountedOpenSslEngine engine = (ReferenceCountedOpenSslEngine) sslHandler.engine();
pipeline.addLast(sslHandler);
pipeline.addLast(new OcspClientCallbackHandler(engine, callback));
if (handler != null) {
pipeline.addLast(handler);
}
}
};
}
private static byte[] newOcspResponse() {
// Assume we got the OCSP staple from somewhere. Using a bogus byte[]
// in the test because getting a true staple from the CA is quite involved.
// It requires HttpCodec and Bouncycastle and the test may be very unreliable
// because the OCSP responder servers are basically being DDoS'd by the
// Internet.
return "I am a bogus OCSP staple. OpenSSL does not care about the format of the byte[]!"
.getBytes(CharsetUtil.US_ASCII);
}
private interface OcspClientCallback {
boolean verify(byte[] staple) throws Exception;
}
private static final class TestClientOcspContext implements OcspClientCallback {
private final CountDownLatch latch = new CountDownLatch(1);
private final boolean valid;
private volatile byte[] response;
TestClientOcspContext(boolean valid) {
this.valid = valid;
}
public byte[] response() throws InterruptedException, TimeoutException {
assertTrue(latch.await(10L, TimeUnit.SECONDS));
return response;
}
@Override
public boolean verify(byte[] response) throws Exception {
this.response = response;
latch.countDown();
return valid;
}
}
private static final class OcspClientCallbackHandler extends OcspClientHandler {
private final OcspClientCallback callback;
OcspClientCallbackHandler(ReferenceCountedOpenSslEngine engine, OcspClientCallback callback) {
super(engine);
this.callback = callback;
}
@Override
protected boolean verify(ChannelHandlerContext ctx, ReferenceCountedOpenSslEngine engine) throws Exception {
byte[] response = engine.getOcspResponse();
return callback.verify(response);
}
}
private static final class OcspTestException extends IllegalStateException {
private static final long serialVersionUID = 4516426833250228159L;
OcspTestException(String message) {
super(message);
}
}
}