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.bootstrap.Bootstrap;
19 import io.netty.buffer.ByteBuf;
20 import io.netty.buffer.Unpooled;
21 import io.netty.channel.ChannelFuture;
22 import io.netty.channel.ChannelFutureListener;
23 import io.netty.channel.ChannelInitializer;
24 import io.netty.channel.ChannelOption;
25 import io.netty.channel.ChannelPipeline;
26 import io.netty.channel.EventLoop;
27 import io.netty.channel.socket.SocketChannel;
28 import io.netty.handler.codec.http.DefaultFullHttpRequest;
29 import io.netty.handler.codec.http.FullHttpRequest;
30 import io.netty.handler.codec.http.HttpClientCodec;
31 import io.netty.handler.codec.http.HttpHeaderNames;
32 import io.netty.handler.codec.http.HttpObjectAggregator;
33 import io.netty.resolver.dns.DnsNameResolver;
34 import io.netty.util.concurrent.Future;
35 import io.netty.util.concurrent.FutureListener;
36 import io.netty.util.concurrent.GenericFutureListener;
37 import io.netty.util.concurrent.Promise;
38 import io.netty.util.internal.SystemPropertyUtil;
39 import io.netty.util.internal.logging.InternalLogger;
40 import io.netty.util.internal.logging.InternalLoggerFactory;
41 import org.bouncycastle.asn1.DEROctetString;
42 import org.bouncycastle.asn1.x509.AccessDescription;
43 import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
44 import org.bouncycastle.asn1.x509.Extension;
45 import org.bouncycastle.asn1.x509.Extensions;
46 import org.bouncycastle.cert.X509CertificateHolder;
47 import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
48 import org.bouncycastle.cert.ocsp.BasicOCSPResp;
49 import org.bouncycastle.cert.ocsp.CertificateID;
50 import org.bouncycastle.cert.ocsp.OCSPException;
51 import org.bouncycastle.cert.ocsp.OCSPReqBuilder;
52 import org.bouncycastle.cert.ocsp.OCSPResp;
53 import org.bouncycastle.operator.ContentVerifierProvider;
54 import org.bouncycastle.operator.OperatorCreationException;
55 import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
56 import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
57
58 import java.net.InetAddress;
59 import java.net.URL;
60 import java.security.SecureRandom;
61 import java.security.cert.CertificateEncodingException;
62 import java.security.cert.X509Certificate;
63
64 import static io.netty.handler.codec.http.HttpMethod.POST;
65 import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
66 import static io.netty.handler.ssl.ocsp.OcspHttpHandler.OCSP_REQUEST_TYPE;
67 import static io.netty.handler.ssl.ocsp.OcspHttpHandler.OCSP_RESPONSE_TYPE;
68 import static io.netty.util.internal.ObjectUtil.checkNotNull;
69 import static org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers.id_pkix_ocsp_nonce;
70 import static org.bouncycastle.asn1.x509.X509ObjectIdentifiers.id_ad_ocsp;
71 import static org.bouncycastle.cert.ocsp.CertificateID.HASH_SHA1;
72
73 final class OcspClient {
74
75 private static final InternalLogger logger = InternalLoggerFactory.getInstance(OcspClient.class);
76
77 private static final SecureRandom SECURE_RANDOM = new SecureRandom();
78 private static final int OCSP_RESPONSE_MAX_SIZE = SystemPropertyUtil.getInt(
79 "io.netty.ocsp.responseSize", 1024 * 10);
80
81 static {
82 logger.debug("-Dio.netty.ocsp.responseSize: {} bytes", OCSP_RESPONSE_MAX_SIZE);
83 }
84
85
86
87
88
89
90
91
92
93
94 static Promise<BasicOCSPResp> query(final X509Certificate x509Certificate,
95 final X509Certificate issuer, final boolean validateResponseNonce,
96 final IoTransport ioTransport, final DnsNameResolver dnsNameResolver) {
97 final EventLoop eventLoop = ioTransport.eventLoop();
98 final Promise<BasicOCSPResp> responsePromise = eventLoop.newPromise();
99 eventLoop.execute(new Runnable() {
100 @Override
101 public void run() {
102 try {
103 CertificateID certificateID = new CertificateID(new JcaDigestCalculatorProviderBuilder()
104 .build().get(HASH_SHA1), new JcaX509CertificateHolder(issuer),
105 x509Certificate.getSerialNumber());
106
107
108 OCSPReqBuilder builder = new OCSPReqBuilder();
109 builder.addRequest(certificateID);
110
111
112
113
114
115
116
117 byte[] nonce = new byte[16];
118 SECURE_RANDOM.nextBytes(nonce);
119 final DEROctetString derNonce = new DEROctetString(nonce);
120 builder.setRequestExtensions(new Extensions(new Extension(id_pkix_ocsp_nonce, false, derNonce)));
121
122
123 URL uri = new URL(parseOcspUrlFromCertificate(x509Certificate));
124
125
126 int port = uri.getPort();
127 if (port == -1) {
128 port = uri.getDefaultPort();
129 }
130
131
132 String path = uri.getPath();
133 if (path.isEmpty()) {
134 path = "/";
135 } else {
136 if (uri.getQuery() != null) {
137 path = path + '?' + uri.getQuery();
138 }
139 }
140
141 Promise<OCSPResp> ocspResponsePromise = query(eventLoop,
142 Unpooled.wrappedBuffer(builder.build().getEncoded()),
143 uri.getHost(), port, path, ioTransport, dnsNameResolver);
144
145
146 ocspResponsePromise.addListener(new GenericFutureListener<Future<OCSPResp>>() {
147 @Override
148 public void operationComplete(Future<OCSPResp> future) throws Exception {
149
150
151 if (future.isSuccess()) {
152 BasicOCSPResp resp = (BasicOCSPResp) future.get().getResponseObject();
153 validateResponse(responsePromise, resp, derNonce, issuer, validateResponseNonce);
154 } else {
155 responsePromise.tryFailure(future.cause());
156 }
157 }
158 });
159
160 } catch (Exception ex) {
161 responsePromise.tryFailure(ex);
162 }
163 }
164 });
165 return responsePromise;
166 }
167
168
169
170
171
172
173
174
175
176
177
178
179 private static Promise<OCSPResp> query(final EventLoop eventLoop, final ByteBuf ocspRequest,
180 final String host, final int port, final String path,
181 final IoTransport ioTransport, final DnsNameResolver dnsNameResolver) {
182 final Promise<OCSPResp> responsePromise = eventLoop.newPromise();
183
184 try {
185 final Bootstrap bootstrap = new Bootstrap()
186 .group(ioTransport.eventLoop())
187 .option(ChannelOption.TCP_NODELAY, true)
188 .channelFactory(ioTransport.socketChannel())
189 .attr(OcspServerCertificateValidator.OCSP_PIPELINE_ATTRIBUTE, Boolean.TRUE)
190 .handler(new Initializer(responsePromise));
191
192 dnsNameResolver.resolve(host).addListener(new FutureListener<InetAddress>() {
193 @Override
194 public void operationComplete(Future<InetAddress> future) throws Exception {
195
196
197
198 if (future.isSuccess()) {
199
200 InetAddress hostAddress = future.get();
201 final ChannelFuture channelFuture = bootstrap.connect(hostAddress, port);
202 channelFuture.addListener(new ChannelFutureListener() {
203 @Override
204 public void operationComplete(ChannelFuture future) {
205
206
207 if (future.isSuccess()) {
208 FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, POST, path,
209 ocspRequest);
210 request.headers().add(HttpHeaderNames.HOST, host);
211 request.headers().add(HttpHeaderNames.USER_AGENT, "Netty OCSP Client");
212 request.headers().add(HttpHeaderNames.CONTENT_TYPE, OCSP_REQUEST_TYPE);
213 request.headers().add(HttpHeaderNames.ACCEPT_ENCODING, OCSP_RESPONSE_TYPE);
214 request.headers().add(HttpHeaderNames.CONTENT_LENGTH, ocspRequest.readableBytes());
215
216
217 channelFuture.channel().writeAndFlush(request);
218 } else {
219 responsePromise.tryFailure(new IllegalStateException(
220 "Connection to OCSP Responder Failed", future.cause()));
221 }
222 }
223 });
224 } else {
225 responsePromise.tryFailure(future.cause());
226 }
227 }
228 });
229 } catch (Exception ex) {
230 responsePromise.tryFailure(ex);
231 }
232
233 return responsePromise;
234 }
235
236 private static void validateResponse(Promise<BasicOCSPResp> responsePromise, BasicOCSPResp basicResponse,
237 DEROctetString derNonce, X509Certificate issuer, boolean validateNonce) {
238 try {
239
240
241 int responses = basicResponse.getResponses().length;
242 if (responses != 1) {
243 throw new IllegalArgumentException("Expected number of responses was 1 but got: " + responses);
244 }
245
246 if (validateNonce) {
247 validateNonce(basicResponse, derNonce);
248 }
249 validateSignature(basicResponse, issuer);
250 responsePromise.trySuccess(basicResponse);
251 } catch (Exception ex) {
252 responsePromise.tryFailure(ex);
253 }
254 }
255
256
257
258
259 private static void validateNonce(BasicOCSPResp basicResponse, DEROctetString encodedNonce) throws OCSPException {
260 Extension nonceExt = basicResponse.getExtension(id_pkix_ocsp_nonce);
261 if (nonceExt != null) {
262 DEROctetString responseNonceString = (DEROctetString) nonceExt.getExtnValue();
263 if (!responseNonceString.equals(encodedNonce)) {
264 throw new OCSPException("Nonce does not match");
265 }
266 } else {
267 throw new IllegalArgumentException("Nonce is not present");
268 }
269 }
270
271
272
273
274 private static void validateSignature(BasicOCSPResp resp, X509Certificate certificate) throws OCSPException {
275 try {
276 ContentVerifierProvider verifier = new JcaContentVerifierProviderBuilder().build(certificate);
277 if (!resp.isSignatureValid(verifier)) {
278 throw new OCSPException("OCSP signature is not valid");
279 }
280 } catch (OperatorCreationException e) {
281 throw new OCSPException("Error validating OCSP-Signature", e);
282 }
283 }
284
285
286
287
288
289
290
291
292
293 private static String parseOcspUrlFromCertificate(X509Certificate cert) {
294 X509CertificateHolder holder;
295 try {
296 holder = new JcaX509CertificateHolder(cert);
297 } catch (CertificateEncodingException e) {
298
299 throw new IllegalArgumentException("Error while parsing X509Certificate into JcaX509CertificateHolder", e);
300 }
301
302 AuthorityInformationAccess aiaExtension = AuthorityInformationAccess.fromExtensions(holder.getExtensions());
303
304
305 for (AccessDescription accessDescription : aiaExtension.getAccessDescriptions()) {
306 if (accessDescription.getAccessMethod().equals(id_ad_ocsp)) {
307 return accessDescription.getAccessLocation().getName().toASN1Primitive().toString();
308 }
309 }
310
311 throw new NullPointerException("Unable to find OCSP responder URL in Certificate");
312 }
313
314 static final class Initializer extends ChannelInitializer<SocketChannel> {
315
316 private final Promise<OCSPResp> responsePromise;
317
318 Initializer(Promise<OCSPResp> responsePromise) {
319 this.responsePromise = checkNotNull(responsePromise, "ResponsePromise");
320 }
321
322 @Override
323 protected void initChannel(SocketChannel socketChannel) {
324 ChannelPipeline pipeline = socketChannel.pipeline();
325 pipeline.addLast(new HttpClientCodec());
326 pipeline.addLast(new HttpObjectAggregator(OCSP_RESPONSE_MAX_SIZE));
327 pipeline.addLast(new OcspHttpHandler(responsePromise));
328 }
329 }
330
331 private OcspClient() {
332
333 }
334 }