package io.netty.example.ocsp;
import java.math.BigInteger;
import javax.net.ssl.SSLSession;
import javax.security.cert.X509Certificate;
import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import org.bouncycastle.cert.ocsp.CertificateStatus;
import org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.cert.ocsp.SingleResp;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.ssl.OpenSsl;
import io.netty.handler.ssl.ReferenceCountedOpenSslContext;
import io.netty.handler.ssl.ReferenceCountedOpenSslEngine;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.ocsp.OcspClientHandler;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.Promise;
public class OcspClientExample {
public static void main(String[] args) throws Exception {
if (!OpenSsl.isAvailable()) {
throw new IllegalStateException("OpenSSL is not available!");
}
if (!OpenSsl.isOcspSupported()) {
throw new IllegalStateException("OCSP is not supported!");
}
String host = "www.wikipedia.org";
ReferenceCountedOpenSslContext context
= (ReferenceCountedOpenSslContext) SslContextBuilder.forClient()
.sslProvider(SslProvider.OPENSSL)
.enableOcsp(true)
.build();
try {
EventLoopGroup group = new NioEventLoopGroup();
try {
Promise<FullHttpResponse> promise = group.next().newPromise();
Bootstrap bootstrap = new Bootstrap()
.channel(NioSocketChannel.class)
.group(group)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5 * 1000)
.handler(newClientHandler(context, host, promise));
Channel channel = bootstrap.connect(host, 443)
.syncUninterruptibly()
.channel();
try {
FullHttpResponse response = promise.get();
ReferenceCountUtil.release(response);
} finally {
channel.close();
}
} finally {
group.shutdownGracefully();
}
} finally {
context.release();
}
}
private static ChannelInitializer<Channel> newClientHandler(final ReferenceCountedOpenSslContext context,
final String host, final Promise<FullHttpResponse> promise) {
return new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
SslHandler sslHandler = context.newHandler(ch.alloc());
ReferenceCountedOpenSslEngine engine
= (ReferenceCountedOpenSslEngine) sslHandler.engine();
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(sslHandler);
pipeline.addLast(new ExampleOcspClientHandler(engine));
pipeline.addLast(new HttpClientCodec());
pipeline.addLast(new HttpObjectAggregator(1024 * 1024));
pipeline.addLast(new HttpClientHandler(host, promise));
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
if (!promise.isDone()) {
promise.tryFailure(new IllegalStateException("Connection closed and Promise was not done."));
}
ctx.fireChannelInactive();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (!promise.tryFailure(cause)) {
ctx.fireExceptionCaught(cause);
}
}
};
}
private static class HttpClientHandler extends ChannelInboundHandlerAdapter {
private final String host;
private final Promise<FullHttpResponse> promise;
public HttpClientHandler(String host, Promise<FullHttpResponse> promise) {
this.host = host;
this.promise = promise;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/");
request.headers().set(HttpHeaderNames.HOST, host);
request.headers().set(HttpHeaderNames.USER_AGENT, "netty-ocsp-example/1.0");
ctx.writeAndFlush(request).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
ctx.fireChannelActive();
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
if (!promise.isDone()) {
promise.tryFailure(new IllegalStateException("Connection closed and Promise was not done."));
}
ctx.fireChannelInactive();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof FullHttpResponse) {
if (!promise.trySuccess((FullHttpResponse) msg)) {
ReferenceCountUtil.release(msg);
}
return;
}
ctx.fireChannelRead(msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (!promise.tryFailure(cause)) {
ctx.fireExceptionCaught(cause);
}
}
}
private static class ExampleOcspClientHandler extends OcspClientHandler {
public ExampleOcspClientHandler(ReferenceCountedOpenSslEngine engine) {
super(engine);
}
@Override
protected boolean verify(ChannelHandlerContext ctx, ReferenceCountedOpenSslEngine engine) throws Exception {
byte[] staple = engine.getOcspResponse();
if (staple == null) {
throw new IllegalStateException("Server didn't provide an OCSP staple!");
}
OCSPResp response = new OCSPResp(staple);
if (response.getStatus() != OCSPResponseStatus.SUCCESSFUL) {
return false;
}
SSLSession session = engine.getSession();
X509Certificate[] chain = session.getPeerCertificateChain();
BigInteger certSerial = chain[0].getSerialNumber();
BasicOCSPResp basicResponse = (BasicOCSPResp) response.getResponseObject();
SingleResp first = basicResponse.getResponses()[0];
CertificateStatus status = first.getCertStatus();
BigInteger ocspSerial = first.getCertID().getSerialNumber();
String message = new StringBuilder()
.append("OCSP status of ").append(ctx.channel().remoteAddress())
.append("\n Status: ").append(status == CertificateStatus.GOOD ? "Good" : status)
.append("\n This Update: ").append(first.getThisUpdate())
.append("\n Next Update: ").append(first.getNextUpdate())
.append("\n Cert Serial: ").append(certSerial)
.append("\n OCSP Serial: ").append(ocspSerial)
.toString();
System.out.println(message);
return status == CertificateStatus.GOOD && certSerial.equals(ocspSerial);
}
}
}