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.channel.SimpleChannelInboundHandler;
23 import io.netty.handler.codec.http.FullHttpResponse;
24 import io.netty.handler.codec.http.HttpHeaderNames;
25 import io.netty.util.concurrent.Future;
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(f -> {
58 if (timeoutFuture != null) {
59 timeoutFuture.cancel(true);
60 }
61 });
62 }
63
64 @Override
65 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
66 FullHttpResponse response = (FullHttpResponse) msg;
67 try {
68
69 if (LOGGER.isDebugEnabled()) {
70 LOGGER.debug("Received OCSP HTTP Response: {}", response);
71 }
72
73
74 String contentType = response.headers().get(HttpHeaderNames.CONTENT_TYPE);
75 if (contentType == null) {
76 throw new OCSPException("HTTP Response does not contain 'CONTENT-TYPE' header");
77 }
78
79
80 if (!contentType.equalsIgnoreCase(OCSP_RESPONSE_TYPE)) {
81 throw new OCSPException("Response Content-Type was: " + contentType +
82 "; Expected: " + OCSP_RESPONSE_TYPE);
83 }
84
85
86 if (response.status() != OK) {
87 throw new IllegalArgumentException("HTTP Response Code was: " + response.status().code() +
88 "; Expected: 200");
89 }
90
91 responseFuture.trySuccess(new OCSPResp(ByteBufUtil.getBytes(response.content())));
92 } finally {
93 response.release();
94 ctx.close();
95 }
96 }
97
98 @Override
99 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
100 responseFuture.tryFailure(cause);
101 ctx.close();
102 }
103
104 @Override
105 public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
106 super.write(ctx, msg, promise);
107 timeoutFuture = ctx.executor().schedule(() -> {
108 if (!responseFuture.isDone()) {
109 responseFuture.tryFailure(new OCSPException("OCSP response was not received within "
110 + timeoutMillis + "ms"));
111 ctx.close();
112 }
113 }, timeoutMillis, TimeUnit.MILLISECONDS);
114 }
115
116 @Override
117 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
118 if (!responseFuture.isDone()) {
119 responseFuture.tryFailure(new ClosedChannelException());
120 }
121 super.channelInactive(ctx);
122 }
123 }