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.ByteBufUtil;
19 import io.netty.channel.ChannelDuplexHandler;
20 import io.netty.channel.ChannelHandlerContext;
21 import io.netty.channel.ChannelPromise;
22 import io.netty.handler.codec.http.FullHttpResponse;
23 import io.netty.handler.codec.http.HttpHeaderNames;
24 import io.netty.util.concurrent.Future;
25 import io.netty.util.concurrent.GenericFutureListener;
26 import io.netty.util.concurrent.Promise;
27 import io.netty.util.internal.ObjectUtil;
28 import io.netty.util.internal.logging.InternalLogger;
29 import io.netty.util.internal.logging.InternalLoggerFactory;
30 import org.bouncycastle.cert.ocsp.OCSPException;
31 import org.bouncycastle.cert.ocsp.OCSPResp;
32
33 import java.nio.channels.ClosedChannelException;
34 import java.util.concurrent.TimeUnit;
35
36 import static io.netty.handler.codec.http.HttpResponseStatus.OK;
37 import static io.netty.util.internal.ObjectUtil.checkNotNull;
38
39 final class OcspHttpHandler extends ChannelDuplexHandler {
40
41 private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(OcspHttpHandler.class);
42 private final Promise<OCSPResp> responseFuture;
43 private final long timeoutMillis;
44 private Future<?> timeoutFuture;
45 static final String OCSP_REQUEST_TYPE = "application/ocsp-request";
46 static final String OCSP_RESPONSE_TYPE = "application/ocsp-response";
47
48
49
50
51
52
53
54 OcspHttpHandler(Promise<OCSPResp> responsePromise, long timeoutMillis) {
55 this.responseFuture = checkNotNull(responsePromise, "ResponsePromise");
56 this.timeoutMillis = ObjectUtil.checkPositive(timeoutMillis, "timeoutMillis");
57 this.responseFuture.addListener(new GenericFutureListener<Future<? super OCSPResp>>() {
58 @Override
59 public void operationComplete(Future<? super OCSPResp> future) throws Exception {
60 if (timeoutFuture != null) {
61 timeoutFuture.cancel(true);
62 }
63 }
64 });
65 }
66
67 @Override
68 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
69 FullHttpResponse response = (FullHttpResponse) msg;
70 try {
71
72 if (LOGGER.isDebugEnabled()) {
73 LOGGER.debug("Received OCSP HTTP Response: {}", response);
74 }
75
76
77 String contentType = response.headers().get(HttpHeaderNames.CONTENT_TYPE);
78 if (contentType == null) {
79 throw new OCSPException("HTTP Response does not contain 'CONTENT-TYPE' header");
80 }
81
82
83 if (!contentType.equalsIgnoreCase(OCSP_RESPONSE_TYPE)) {
84 throw new OCSPException("Response Content-Type was: " + contentType +
85 "; Expected: " + OCSP_RESPONSE_TYPE);
86 }
87
88
89 if (response.status() != OK) {
90 throw new IllegalArgumentException("HTTP Response Code was: " + response.status().code() +
91 "; Expected: 200");
92 }
93
94 responseFuture.trySuccess(new OCSPResp(ByteBufUtil.getBytes(response.content())));
95 } finally {
96 response.release();
97 ctx.close();
98 }
99 }
100
101 @Override
102 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
103 responseFuture.tryFailure(cause);
104 ctx.close();
105 }
106
107 @Override
108 public void write(final ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
109 super.write(ctx, msg, promise);
110 timeoutFuture = ctx.executor().schedule(new Runnable() {
111 @Override
112 public void run() {
113 if (!responseFuture.isDone()) {
114 responseFuture.tryFailure(new OCSPException("OCSP response was not received within "
115 + timeoutMillis + "ms"));
116 ctx.close();
117 }
118 }
119 }, timeoutMillis, TimeUnit.MILLISECONDS);
120 }
121
122 @Override
123 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
124 if (!responseFuture.isDone()) {
125 responseFuture.tryFailure(new ClosedChannelException());
126 }
127 super.channelInactive(ctx);
128 }
129 }