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