1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.ssl.ocsp;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.channel.ChannelHandlerContext;
20 import io.netty.channel.ChannelOutboundHandler;
21 import io.netty.channel.ChannelPromise;
22 import io.netty.handler.codec.ByteToMessageDecoder;
23 import io.netty.handler.ssl.SslHandler;
24 import io.netty.handler.ssl.SslHandshakeCompletionEvent;
25 import io.netty.resolver.dns.DnsNameResolver;
26 import io.netty.resolver.dns.DnsNameResolverBuilder;
27 import io.netty.util.AttributeKey;
28 import io.netty.util.ReferenceCountUtil;
29 import io.netty.util.concurrent.Future;
30 import io.netty.util.concurrent.GenericFutureListener;
31 import io.netty.util.concurrent.Promise;
32 import io.netty.util.internal.SystemPropertyUtil;
33 import org.bouncycastle.cert.ocsp.BasicOCSPResp;
34 import org.bouncycastle.cert.ocsp.OCSPException;
35 import org.bouncycastle.cert.ocsp.RevokedStatus;
36 import org.bouncycastle.cert.ocsp.SingleResp;
37
38 import java.net.SocketAddress;
39 import java.security.cert.Certificate;
40 import java.security.cert.X509Certificate;
41 import java.util.Date;
42 import java.util.List;
43 import java.util.concurrent.TimeUnit;
44
45 import static io.netty.util.internal.ObjectUtil.checkNotNull;
46
47
48
49
50
51
52 public class OcspServerCertificateValidator extends ByteToMessageDecoder implements ChannelOutboundHandler {
53
54
55
56 public static final AttributeKey<Boolean> OCSP_PIPELINE_ATTRIBUTE =
57 AttributeKey.newInstance("io.netty.handler.ssl.ocsp.pipeline");
58
59
60
61
62 private static final long CLOCK_SKEW_TOLERANCE_MILLIS = getClockSkewTolerance();
63
64 private static long getClockSkewTolerance() {
65 long defaultToleranceSeconds = TimeUnit.MINUTES.toSeconds(15);
66 long maxToleranceSeconds = TimeUnit.DAYS.toSeconds(2);
67 long configuredToleranceSeconds = SystemPropertyUtil.getLong("io.netty.handler.ssl.ocsp.clockSkew",
68 SystemPropertyUtil.getLong("com.sun.security.ocsp.clockSkew", defaultToleranceSeconds));
69 if (configuredToleranceSeconds < 0 || configuredToleranceSeconds > maxToleranceSeconds) {
70
71 configuredToleranceSeconds = defaultToleranceSeconds;
72 }
73 return TimeUnit.SECONDS.toMillis(configuredToleranceSeconds);
74 }
75
76 private final boolean closeAndThrowIfNotValid;
77 private final boolean validateNonce;
78 private final IoTransport ioTransport;
79 private final DnsNameResolver dnsNameResolver;
80 private boolean ocspQueryInProgress;
81 private boolean readPending;
82 private boolean discardInbound;
83
84
85
86
87
88
89
90 public OcspServerCertificateValidator() {
91 this(false);
92 }
93
94
95
96
97
98
99
100
101
102 public OcspServerCertificateValidator(boolean validateNonce) {
103 this(validateNonce, IoTransport.DEFAULT);
104 }
105
106
107
108
109
110
111
112
113 public OcspServerCertificateValidator(boolean validateNonce, IoTransport ioTransport) {
114 this(validateNonce, ioTransport, createDefaultResolver(ioTransport));
115 }
116
117
118
119
120
121
122
123
124
125 public OcspServerCertificateValidator(boolean validateNonce, IoTransport ioTransport,
126 DnsNameResolver dnsNameResolver) {
127 this(true, validateNonce, ioTransport, dnsNameResolver);
128 }
129
130
131
132
133
134
135
136
137
138
139
140
141
142 public OcspServerCertificateValidator(boolean closeAndThrowIfNotValid, boolean validateNonce,
143 IoTransport ioTransport, DnsNameResolver dnsNameResolver) {
144 this.closeAndThrowIfNotValid = closeAndThrowIfNotValid;
145 this.validateNonce = validateNonce;
146 this.ioTransport = checkNotNull(ioTransport, "IoTransport");
147 this.dnsNameResolver = checkNotNull(dnsNameResolver, "DnsNameResolver");
148 }
149
150 protected static DnsNameResolver createDefaultResolver(final IoTransport ioTransport) {
151 return new DnsNameResolverBuilder()
152 .eventLoop(ioTransport.eventLoop())
153 .datagramChannelFactory(ioTransport.datagramChannel())
154 .socketChannelFactory(ioTransport.socketChannel())
155
156 .caseRandomizationEnabled(true)
157 .dnsCookiesEnabled(true)
158 .build();
159 }
160
161 @Override
162 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
163
164 }
165
166 @Override
167 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
168 if (discardInbound) {
169 ReferenceCountUtil.release(msg);
170 return;
171 }
172 super.channelRead(ctx, msg);
173 }
174
175 @Override
176 public void userEventTriggered(final ChannelHandlerContext ctx, final Object evt) throws Exception {
177 if (evt instanceof SslHandshakeCompletionEvent) {
178 SslHandshakeCompletionEvent sslHandshakeCompletionEvent = (SslHandshakeCompletionEvent) evt;
179
180
181
182 if (sslHandshakeCompletionEvent.isSuccess()) {
183 Certificate[] certificates = ctx.pipeline().get(SslHandler.class)
184 .engine()
185 .getSession()
186 .getPeerCertificates();
187
188 assert certificates.length >= 2 : "There must an end-entity certificate and issuer certificate";
189
190 Promise<BasicOCSPResp> ocspRespPromise = ctx.executor().newPromise();
191 OcspClient.query((X509Certificate) certificates[0], (X509Certificate) certificates[1],
192 validateNonce, ioTransport, dnsNameResolver, ocspRespPromise);
193 ocspQueryInProgress = true;
194 ocspRespPromise.addListener(new GenericFutureListener<Future<BasicOCSPResp>>() {
195 @Override
196 public void operationComplete(Future<BasicOCSPResp> future) throws Exception {
197 ocspQueryInProgress = false;
198
199 OcspResponse.Status status = OcspResponse.Status.REVOKED;
200 try {
201
202
203 if (future.isSuccess()) {
204 SingleResp response = future.getNow().getResponses()[0];
205
206 Date thisUpdate = response.getThisUpdate();
207 Date nextUpdate = response.getNextUpdate();
208 long now = System.currentTimeMillis();
209 Date nowLower = new Date(now - CLOCK_SKEW_TOLERANCE_MILLIS);
210 Date nowUpper = new Date(now + CLOCK_SKEW_TOLERANCE_MILLIS);
211 if (thisUpdate == null || nowUpper.before(thisUpdate) ||
212 nowLower.after(nextUpdate == null ? thisUpdate : nextUpdate)) {
213 ctx.fireExceptionCaught(new IllegalStateException("OCSP Response is out-of-date"));
214 if (closeAndThrowIfNotValid) {
215 ctx.close();
216 }
217 return;
218 }
219
220 if (response.getCertStatus() == null) {
221
222 status = OcspResponse.Status.VALID;
223 } else if (response.getCertStatus() instanceof RevokedStatus) {
224 status = OcspResponse.Status.REVOKED;
225 } else {
226 status = OcspResponse.Status.UNKNOWN;
227 }
228
229 ctx.fireUserEventTriggered(new OcspValidationEvent(
230 new OcspResponse(status, thisUpdate, nextUpdate)));
231
232
233
234 if (status != OcspResponse.Status.VALID && closeAndThrowIfNotValid) {
235
236 ctx.fireExceptionCaught(new OCSPException(
237 "Certificate not valid. Status: " + status));
238 ctx.close();
239 }
240 } else {
241 ctx.fireExceptionCaught(future.cause());
242 if (closeAndThrowIfNotValid) {
243 ctx.close();
244 }
245 }
246 } catch (Throwable th) {
247 ctx.fireExceptionCaught(th);
248 if (closeAndThrowIfNotValid) {
249 ctx.close();
250 }
251 } finally {
252 if (status != OcspResponse.Status.VALID && closeAndThrowIfNotValid) {
253
254
255
256
257 discardInbound = true;
258 internalBuffer().clear();
259 ctx.fireUserEventTriggered(evt);
260 } else {
261 ctx.fireUserEventTriggered(evt);
262
263
264 ctx.pipeline().remove(OcspServerCertificateValidator.this);
265 }
266 if (readPending) {
267 readPending = false;
268 ctx.read();
269 }
270 }
271 }
272 });
273 } else {
274 ctx.fireUserEventTriggered(evt);
275 }
276 } else {
277 ctx.fireUserEventTriggered(evt);
278 }
279 }
280
281 @Override
282 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
283 ctx.close();
284 }
285
286 @Override
287 public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
288 ctx.bind(localAddress, promise);
289 }
290
291 @Override
292 public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
293 SocketAddress localAddress, ChannelPromise promise) throws Exception {
294 ctx.connect(remoteAddress, localAddress, promise);
295 }
296
297 @Override
298 public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
299 ctx.disconnect(promise);
300 }
301
302 @Override
303 public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
304 ctx.close(promise);
305 }
306
307 @Override
308 public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
309 ctx.deregister(promise);
310 }
311
312 @Override
313 public void read(ChannelHandlerContext ctx) throws Exception {
314
315 if (ocspQueryInProgress) {
316 readPending = true;
317 } else {
318 readPending = false;
319 ctx.read();
320 }
321 }
322
323 @Override
324 public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
325 ctx.write(msg, promise);
326 }
327
328 @Override
329 public void flush(ChannelHandlerContext ctx) throws Exception {
330 ctx.flush();
331 }
332 }