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