1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.netty.example.ocsp;
18
19 import java.math.BigInteger;
20
21 import javax.net.ssl.SSLSession;
22 import javax.security.cert.X509Certificate;
23
24 import io.netty.buffer.Unpooled;
25 import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
26 import org.bouncycastle.cert.ocsp.BasicOCSPResp;
27 import org.bouncycastle.cert.ocsp.CertificateStatus;
28 import org.bouncycastle.cert.ocsp.OCSPResp;
29 import org.bouncycastle.cert.ocsp.SingleResp;
30
31 import io.netty.bootstrap.Bootstrap;
32 import io.netty.channel.Channel;
33 import io.netty.channel.ChannelFutureListener;
34 import io.netty.channel.ChannelHandlerContext;
35 import io.netty.channel.ChannelInboundHandlerAdapter;
36 import io.netty.channel.ChannelInitializer;
37 import io.netty.channel.ChannelOption;
38 import io.netty.channel.ChannelPipeline;
39 import io.netty.channel.EventLoopGroup;
40 import io.netty.channel.nio.NioEventLoopGroup;
41 import io.netty.channel.socket.nio.NioSocketChannel;
42 import io.netty.handler.codec.http.DefaultFullHttpRequest;
43 import io.netty.handler.codec.http.FullHttpRequest;
44 import io.netty.handler.codec.http.FullHttpResponse;
45 import io.netty.handler.codec.http.HttpClientCodec;
46 import io.netty.handler.codec.http.HttpHeaderNames;
47 import io.netty.handler.codec.http.HttpMethod;
48 import io.netty.handler.codec.http.HttpObjectAggregator;
49 import io.netty.handler.codec.http.HttpVersion;
50 import io.netty.handler.ssl.OpenSsl;
51 import io.netty.handler.ssl.ReferenceCountedOpenSslContext;
52 import io.netty.handler.ssl.ReferenceCountedOpenSslEngine;
53 import io.netty.handler.ssl.SslContextBuilder;
54 import io.netty.handler.ssl.SslHandler;
55 import io.netty.handler.ssl.SslProvider;
56 import io.netty.handler.ssl.ocsp.OcspClientHandler;
57 import io.netty.util.ReferenceCountUtil;
58 import io.netty.util.concurrent.Promise;
59
60
61
62
63
64
65 public class OcspClientExample {
66 public static void main(String[] args) throws Exception {
67 if (!OpenSsl.isAvailable()) {
68 throw new IllegalStateException("OpenSSL is not available!");
69 }
70
71 if (!OpenSsl.isOcspSupported()) {
72 throw new IllegalStateException("OCSP is not supported!");
73 }
74
75
76
77
78
79
80
81
82 String host = "www.wikipedia.org";
83
84 ReferenceCountedOpenSslContext context
85 = (ReferenceCountedOpenSslContext) SslContextBuilder.forClient()
86 .sslProvider(SslProvider.OPENSSL)
87 .enableOcsp(true)
88 .build();
89
90 try {
91 EventLoopGroup group = new NioEventLoopGroup();
92 try {
93 Promise<FullHttpResponse> promise = group.next().newPromise();
94
95 Bootstrap bootstrap = new Bootstrap()
96 .channel(NioSocketChannel.class)
97 .group(group)
98 .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5 * 1000)
99 .handler(newClientHandler(context, host, promise));
100
101 Channel channel = bootstrap.connect(host, 443)
102 .syncUninterruptibly()
103 .channel();
104
105 try {
106 FullHttpResponse response = promise.get();
107 ReferenceCountUtil.release(response);
108 } finally {
109 channel.close();
110 }
111 } finally {
112 group.shutdownGracefully();
113 }
114 } finally {
115 context.release();
116 }
117 }
118
119 private static ChannelInitializer<Channel> newClientHandler(final ReferenceCountedOpenSslContext context,
120 final String host, final Promise<FullHttpResponse> promise) {
121
122 return new ChannelInitializer<Channel>() {
123 @Override
124 protected void initChannel(Channel ch) throws Exception {
125 SslHandler sslHandler = context.newHandler(ch.alloc());
126 ReferenceCountedOpenSslEngine engine
127 = (ReferenceCountedOpenSslEngine) sslHandler.engine();
128
129 ChannelPipeline pipeline = ch.pipeline();
130 pipeline.addLast(sslHandler);
131 pipeline.addLast(new ExampleOcspClientHandler(engine));
132
133 pipeline.addLast(new HttpClientCodec());
134 pipeline.addLast(new HttpObjectAggregator(1024 * 1024));
135 pipeline.addLast(new HttpClientHandler(host, promise));
136 }
137
138 @Override
139 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
140 if (!promise.isDone()) {
141 promise.tryFailure(new IllegalStateException("Connection closed and Promise was not done."));
142 }
143 ctx.fireChannelInactive();
144 }
145
146 @Override
147 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
148 if (!promise.tryFailure(cause)) {
149 ctx.fireExceptionCaught(cause);
150 }
151 }
152 };
153 }
154
155 private static class HttpClientHandler extends ChannelInboundHandlerAdapter {
156
157 private final String host;
158
159 private final Promise<FullHttpResponse> promise;
160
161 HttpClientHandler(String host, Promise<FullHttpResponse> promise) {
162 this.host = host;
163 this.promise = promise;
164 }
165
166 @Override
167 public void channelActive(ChannelHandlerContext ctx) throws Exception {
168 FullHttpRequest request = new DefaultFullHttpRequest(
169 HttpVersion.HTTP_1_1, HttpMethod.GET, "/", Unpooled.EMPTY_BUFFER);
170 request.headers().set(HttpHeaderNames.HOST, host);
171 request.headers().set(HttpHeaderNames.USER_AGENT, "netty-ocsp-example/1.0");
172
173 ctx.writeAndFlush(request).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
174
175 ctx.fireChannelActive();
176 }
177
178 @Override
179 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
180 if (!promise.isDone()) {
181 promise.tryFailure(new IllegalStateException("Connection closed and Promise was not done."));
182 }
183 ctx.fireChannelInactive();
184 }
185
186 @Override
187 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
188 if (msg instanceof FullHttpResponse) {
189 if (!promise.trySuccess((FullHttpResponse) msg)) {
190 ReferenceCountUtil.release(msg);
191 }
192 return;
193 }
194
195 ctx.fireChannelRead(msg);
196 }
197
198 @Override
199 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
200 if (!promise.tryFailure(cause)) {
201 ctx.fireExceptionCaught(cause);
202 }
203 }
204 }
205
206 private static class ExampleOcspClientHandler extends OcspClientHandler {
207
208 ExampleOcspClientHandler(ReferenceCountedOpenSslEngine engine) {
209 super(engine);
210 }
211
212 @Override
213 protected boolean verify(ChannelHandlerContext ctx, ReferenceCountedOpenSslEngine engine) throws Exception {
214 byte[] staple = engine.getOcspResponse();
215 if (staple == null) {
216 throw new IllegalStateException("Server didn't provide an OCSP staple!");
217 }
218
219 OCSPResp response = new OCSPResp(staple);
220 if (response.getStatus() != OCSPResponseStatus.SUCCESSFUL) {
221 return false;
222 }
223
224 SSLSession session = engine.getSession();
225 X509Certificate[] chain = session.getPeerCertificateChain();
226 BigInteger certSerial = chain[0].getSerialNumber();
227
228 BasicOCSPResp basicResponse = (BasicOCSPResp) response.getResponseObject();
229 SingleResp first = basicResponse.getResponses()[0];
230
231
232
233 CertificateStatus status = first.getCertStatus();
234 BigInteger ocspSerial = first.getCertID().getSerialNumber();
235 String message = new StringBuilder()
236 .append("OCSP status of ").append(ctx.channel().remoteAddress())
237 .append("\n Status: ").append(status == CertificateStatus.GOOD ? "Good" : status)
238 .append("\n This Update: ").append(first.getThisUpdate())
239 .append("\n Next Update: ").append(first.getNextUpdate())
240 .append("\n Cert Serial: ").append(certSerial)
241 .append("\n OCSP Serial: ").append(ocspSerial)
242 .toString();
243 System.out.println(message);
244
245 return status == CertificateStatus.GOOD && certSerial.equals(ocspSerial);
246 }
247 }
248 }