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.channel.ChannelHandlerContext;
19 import io.netty.channel.ChannelInboundHandlerAdapter;
20 import io.netty.handler.ssl.SslHandler;
21 import io.netty.handler.ssl.SslHandshakeCompletionEvent;
22 import io.netty.resolver.dns.DnsNameResolver;
23 import io.netty.resolver.dns.DnsNameResolverBuilder;
24 import io.netty.util.AttributeKey;
25 import io.netty.util.concurrent.Future;
26 import io.netty.util.concurrent.GenericFutureListener;
27 import io.netty.util.concurrent.Promise;
28 import org.bouncycastle.cert.ocsp.BasicOCSPResp;
29 import org.bouncycastle.cert.ocsp.OCSPException;
30 import org.bouncycastle.cert.ocsp.RevokedStatus;
31 import org.bouncycastle.cert.ocsp.SingleResp;
32
33 import java.security.cert.Certificate;
34 import java.security.cert.X509Certificate;
35 import java.util.Date;
36
37 import static io.netty.util.internal.ObjectUtil.checkNotNull;
38
39
40
41
42
43
44 public class OcspServerCertificateValidator extends ChannelInboundHandlerAdapter {
45
46
47
48 public static final AttributeKey<Boolean> OCSP_PIPELINE_ATTRIBUTE =
49 AttributeKey.newInstance("io.netty.handler.ssl.ocsp.pipeline");
50
51 private final boolean closeAndThrowIfNotValid;
52 private final boolean validateNonce;
53 private final IoTransport ioTransport;
54 private final DnsNameResolver dnsNameResolver;
55
56
57
58
59
60
61
62 public OcspServerCertificateValidator() {
63 this(false);
64 }
65
66
67
68
69
70
71
72
73
74 public OcspServerCertificateValidator(boolean validateNonce) {
75 this(validateNonce, IoTransport.DEFAULT);
76 }
77
78
79
80
81
82
83
84
85 public OcspServerCertificateValidator(boolean validateNonce, IoTransport ioTransport) {
86 this(validateNonce, ioTransport, createDefaultResolver(ioTransport));
87 }
88
89
90
91
92
93
94
95
96
97 public OcspServerCertificateValidator(boolean validateNonce, IoTransport ioTransport,
98 DnsNameResolver dnsNameResolver) {
99 this(true, validateNonce, ioTransport, dnsNameResolver);
100 }
101
102
103
104
105
106
107
108
109
110
111
112
113
114 public OcspServerCertificateValidator(boolean closeAndThrowIfNotValid, boolean validateNonce,
115 IoTransport ioTransport, DnsNameResolver dnsNameResolver) {
116 this.closeAndThrowIfNotValid = closeAndThrowIfNotValid;
117 this.validateNonce = validateNonce;
118 this.ioTransport = checkNotNull(ioTransport, "IoTransport");
119 this.dnsNameResolver = checkNotNull(dnsNameResolver, "DnsNameResolver");
120 }
121
122 protected static DnsNameResolver createDefaultResolver(final IoTransport ioTransport) {
123 return new DnsNameResolverBuilder()
124 .eventLoop(ioTransport.eventLoop())
125 .datagramChannelFactory(ioTransport.datagramChannel())
126 .socketChannelFactory(ioTransport.socketChannel())
127 .build();
128 }
129
130 @Override
131 public void userEventTriggered(final ChannelHandlerContext ctx, final Object evt) throws Exception {
132 ctx.fireUserEventTriggered(evt);
133
134 if (evt instanceof SslHandshakeCompletionEvent) {
135 SslHandshakeCompletionEvent sslHandshakeCompletionEvent = (SslHandshakeCompletionEvent) evt;
136
137
138
139 if (sslHandshakeCompletionEvent.isSuccess()) {
140 Certificate[] certificates = ctx.pipeline().get(SslHandler.class)
141 .engine()
142 .getSession()
143 .getPeerCertificates();
144
145 assert certificates.length >= 2 : "There must an end-entity certificate and issuer certificate";
146
147 Promise<BasicOCSPResp> ocspRespPromise = OcspClient.query((X509Certificate) certificates[0],
148 (X509Certificate) certificates[1], validateNonce, ioTransport, dnsNameResolver);
149
150 ocspRespPromise.addListener(new GenericFutureListener<Future<BasicOCSPResp>>() {
151 @Override
152 public void operationComplete(Future<BasicOCSPResp> future) throws Exception {
153
154
155 if (future.isSuccess()) {
156 SingleResp response = future.get().getResponses()[0];
157
158 Date current = new Date();
159 if (!(current.after(response.getThisUpdate()) &&
160 current.before(response.getNextUpdate()))) {
161 ctx.fireExceptionCaught(new IllegalStateException("OCSP Response is out-of-date"));
162 }
163
164 OcspResponse.Status status;
165 if (response.getCertStatus() == null) {
166
167 status = OcspResponse.Status.VALID;
168 } else if (response.getCertStatus() instanceof RevokedStatus) {
169 status = OcspResponse.Status.REVOKED;
170 } else {
171 status = OcspResponse.Status.UNKNOWN;
172 }
173
174 ctx.fireUserEventTriggered(new OcspValidationEvent(
175 new OcspResponse(status, response.getThisUpdate(), response.getNextUpdate())));
176
177
178
179 if (status != OcspResponse.Status.VALID && closeAndThrowIfNotValid) {
180 ctx.channel().close();
181
182 ctx.fireExceptionCaught(new OCSPException(
183 "Certificate not valid. Status: " + status));
184 }
185 } else {
186 ctx.fireExceptionCaught(future.cause());
187 }
188 }
189 });
190 }
191
192 ctx.pipeline().remove(this);
193 }
194 }
195
196 @Override
197 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
198 ctx.channel().close();
199 }
200 }