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