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