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