1 /*
2 * Copyright 2017 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package io.netty.handler.ssl;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.buffer.ByteBufUtil;
20 import io.netty.channel.ChannelHandlerContext;
21 import io.netty.channel.ChannelOutboundHandler;
22 import io.netty.channel.ChannelPromise;
23 import io.netty.handler.codec.ByteToMessageDecoder;
24 import io.netty.handler.codec.DecoderException;
25 import io.netty.handler.codec.TooLongFrameException;
26 import io.netty.util.concurrent.Future;
27 import io.netty.util.concurrent.FutureListener;
28 import io.netty.util.internal.ObjectUtil;
29 import io.netty.util.internal.PlatformDependent;
30 import io.netty.util.internal.logging.InternalLogger;
31 import io.netty.util.internal.logging.InternalLoggerFactory;
32
33 import java.net.SocketAddress;
34 import java.util.List;
35
36 /**
37 * {@link ByteToMessageDecoder} which allows to be notified once a full {@code ClientHello} was received.
38 */
39 public abstract class SslClientHelloHandler<T> extends ByteToMessageDecoder implements ChannelOutboundHandler {
40
41 /**
42 * The maximum length of client hello message as defined by
43 * <a href="https://www.rfc-editor.org/rfc/rfc5246#section-6.2.1">RFC5246</a>.
44 */
45 public static final int MAX_CLIENT_HELLO_LENGTH = 0xFFFFFF;
46
47 // Let's use a default limit of 64kb which should be big enough for almost everything in practice but still
48 // small enough to not allocate to much memory.
49 static final int DEFAULT_MAX_CLIENT_HELLO_LENGTH = 64 * 1024;
50
51 private static final InternalLogger logger =
52 InternalLoggerFactory.getInstance(SslClientHelloHandler.class);
53
54 private final int maxClientHelloLength;
55 private boolean handshakeFailed;
56 private boolean suppressRead;
57 private boolean readPending;
58 private ByteBuf handshakeBuffer;
59 private int aggregatedBytes;
60 private int handshakeLength = -1;
61
62 public SslClientHelloHandler() {
63 this(DEFAULT_MAX_CLIENT_HELLO_LENGTH);
64 }
65
66 protected SslClientHelloHandler(int maxClientHelloLength) {
67 // 16MB is the maximum as per RFC:
68 // See https://www.rfc-editor.org/rfc/rfc5246#section-6.2.1
69 this.maxClientHelloLength =
70 ObjectUtil.checkInRange(maxClientHelloLength, 0, MAX_CLIENT_HELLO_LENGTH, "maxClientHelloLength");
71 }
72
73 @Override
74 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
75 if (!suppressRead && !handshakeFailed) {
76 try {
77 int readerIndex = in.readerIndex() + aggregatedBytes;
78 int readableBytes = in.readableBytes() - aggregatedBytes;
79
80 // Check if we have enough data to determine the record type and length.
81 while (readableBytes >= SslUtils.SSL_RECORD_HEADER_LENGTH) {
82 final int contentType = in.getUnsignedByte(readerIndex);
83 switch (contentType) {
84 case SslUtils.SSL_CONTENT_TYPE_CHANGE_CIPHER_SPEC:
85 // fall-through
86 case SslUtils.SSL_CONTENT_TYPE_ALERT:
87 final int len = SslUtils.getEncryptedPacketLength(in, readerIndex, true);
88
89 // Not an SSL/TLS packet
90 if (len == SslUtils.NOT_ENCRYPTED) {
91 handshakeFailed = true;
92 NotSslRecordException e = new NotSslRecordException(
93 "not an SSL/TLS record: " + ByteBufUtil.hexDump(in));
94 in.skipBytes(in.readableBytes());
95 ctx.fireUserEventTriggered(new SniCompletionEvent(e));
96 SslUtils.handleHandshakeFailure(ctx, e, true);
97 throw e;
98 }
99 if (len == SslUtils.NOT_ENOUGH_DATA) {
100 // Not enough data
101 return;
102 }
103 // No ClientHello
104 select(ctx, null);
105 return;
106 case SslUtils.SSL_CONTENT_TYPE_HANDSHAKE:
107 final int majorVersion = in.getUnsignedByte(readerIndex + 1);
108 // SSLv3 or TLS
109 if (majorVersion == 3) {
110 int packetLength = in.getUnsignedShort(readerIndex + 3) +
111 SslUtils.SSL_RECORD_HEADER_LENGTH;
112
113 if (readableBytes < packetLength) {
114 // client hello incomplete; try again to decode once more data is ready.
115 return;
116 }
117
118 if (packetLength == SslUtils.SSL_RECORD_HEADER_LENGTH) {
119 // Zero-length handshake records are forbidden by
120 // https://www.rfc-editor.org/rfc/rfc8446#section-5.1 and we can't select
121 // the default context for them: some TLS implementations skip the empty
122 // record and still process a ClientHello that follows it, which would allow
123 // to bypass the SNI based selection. We also can't just skip them as this
124 // would allow to buffer an unbounded amount of data. Fail closed instead.
125 handshakeFailed = true;
126 NotSslRecordException e = new NotSslRecordException(
127 "zero-length handshake record: " + ByteBufUtil.hexDump(in));
128 in.skipBytes(in.readableBytes());
129 ctx.fireUserEventTriggered(new SniCompletionEvent(e));
130 SslUtils.handleHandshakeFailure(ctx, e, true);
131 throw e;
132 }
133
134 final int endOffset = readerIndex + packetLength;
135
136 // Let's check if we already parsed the handshake length or not.
137 if (handshakeLength == -1) {
138 if (handshakeBuffer == null &&
139 readerIndex + SslUtils.SSL_RECORD_HEADER_LENGTH + 4 <= endOffset) {
140 final int handshakeType = in.getUnsignedByte(readerIndex +
141 SslUtils.SSL_RECORD_HEADER_LENGTH);
142
143 // Check if this is a clientHello(1)
144 // See https://tools.ietf.org/html/rfc5246#section-7.4
145 if (handshakeType != 1) {
146 select(ctx, null);
147 return;
148 }
149
150 // Read the length of the handshake as it may arrive in fragments
151 // See https://tools.ietf.org/html/rfc5246#section-7.4
152 handshakeLength = in.getUnsignedMedium(readerIndex +
153 SslUtils.SSL_RECORD_HEADER_LENGTH + 1);
154
155 if (handshakeLength > maxClientHelloLength && maxClientHelloLength != 0) {
156 TooLongFrameException e = new TooLongFrameException(
157 "ClientHello length exceeds " + maxClientHelloLength +
158 ": " + handshakeLength);
159 in.skipBytes(in.readableBytes());
160 ctx.fireUserEventTriggered(new SniCompletionEvent(e));
161 SslUtils.handleHandshakeFailure(ctx, e, true);
162 throw e;
163 }
164
165 if (handshakeLength + 4 + SslUtils.SSL_RECORD_HEADER_LENGTH <= packetLength) {
166 // We have everything we need in one packet.
167 // Skip the record header and handshake header (this sums up as 4 bytes)
168 readerIndex += SslUtils.SSL_RECORD_HEADER_LENGTH + 4;
169 final int clientHelloLength = handshakeLength;
170 handshakeLength = -1;
171 select(ctx, in.retainedSlice(readerIndex, clientHelloLength));
172 return;
173 }
174 }
175 }
176
177 if (handshakeBuffer == null) {
178 handshakeBuffer = ctx.alloc().buffer();
179 }
180
181 // Combine the encapsulated data in one buffer but not include the SSL_RECORD_HEADER
182 handshakeBuffer.writeBytes(in, readerIndex + SslUtils.SSL_RECORD_HEADER_LENGTH,
183 packetLength - SslUtils.SSL_RECORD_HEADER_LENGTH);
184 readerIndex += packetLength;
185 readableBytes -= packetLength;
186 aggregatedBytes += packetLength;
187 if (handshakeLength == -1) {
188 if (handshakeBuffer.readableBytes() < 4) {
189 continue;
190 }
191
192 final int handshakeType = handshakeBuffer.getUnsignedByte(0);
193 handshakeLength = handshakeBuffer.getUnsignedMedium(1);
194
195 // Check if this is a clientHello(1)
196 // See https://tools.ietf.org/html/rfc5246#section-7.4
197 if (handshakeType != 1) {
198 select(ctx, null);
199 return;
200 }
201
202 if (handshakeLength > maxClientHelloLength && maxClientHelloLength != 0) {
203 TooLongFrameException e = new TooLongFrameException(
204 "ClientHello length exceeds " + maxClientHelloLength +
205 ": " + handshakeLength);
206 in.skipBytes(in.readableBytes());
207 ctx.fireUserEventTriggered(new SniCompletionEvent(e));
208 SslUtils.handleHandshakeFailure(ctx, e, true);
209 throw e;
210 }
211 }
212
213 if (handshakeBuffer.readableBytes() >= handshakeLength + 4) {
214 ByteBuf clientHello = handshakeBuffer.setIndex(4, handshakeLength + 4).slice();
215 handshakeBuffer = null;
216 handshakeLength = -1;
217
218 select(ctx, clientHello);
219 return;
220 }
221 break;
222 }
223 // fall-through
224 default:
225 // not tls, ssl or application data
226 select(ctx, null);
227 return;
228 }
229 }
230 } catch (NotSslRecordException e) {
231 // Just rethrow as in this case we also closed the channel and this is consistent with SslHandler.
232 throw e;
233 } catch (TooLongFrameException e) {
234 // Just rethrow as in this case we also closed the channel
235 throw e;
236 } catch (Exception e) {
237 // unexpected encoding, ignore sni and use default
238 if (logger.isDebugEnabled()) {
239 logger.debug("Unexpected client hello packet: " + ByteBufUtil.hexDump(in), e);
240 }
241 select(ctx, null);
242 }
243 }
244 }
245
246 private void releaseHandshakeBuffer() {
247 releaseIfNotNull(handshakeBuffer);
248 handshakeBuffer = null;
249 handshakeLength = -1;
250 }
251
252 private static void releaseIfNotNull(ByteBuf buffer) {
253 if (buffer != null) {
254 buffer.release();
255 }
256 }
257
258 private void select(final ChannelHandlerContext ctx, ByteBuf clientHello) throws Exception {
259 final Future<T> future;
260 try {
261 future = lookup(ctx, clientHello);
262 if (future.isDone()) {
263 try {
264 onLookupComplete(ctx, future);
265 } catch (DecoderException err) {
266 ctx.fireExceptionCaught(err);
267 } catch (Exception cause) {
268 ctx.fireExceptionCaught(new DecoderException(cause));
269 } catch (Throwable cause) {
270 ctx.fireExceptionCaught(cause);
271 }
272 } else {
273 suppressRead = true;
274 final ByteBuf finalClientHello = clientHello;
275 future.addListener(new FutureListener<T>() {
276 @Override
277 public void operationComplete(Future<T> future) {
278 releaseIfNotNull(finalClientHello);
279 try {
280 suppressRead = false;
281 try {
282 onLookupComplete(ctx, future);
283 } catch (DecoderException err) {
284 ctx.fireExceptionCaught(err);
285 } catch (Exception cause) {
286 ctx.fireExceptionCaught(new DecoderException(cause));
287 } catch (Throwable cause) {
288 ctx.fireExceptionCaught(cause);
289 }
290 } finally {
291 if (readPending) {
292 readPending = false;
293 ctx.read();
294 }
295 }
296 }
297 });
298
299 // Ownership was transferred to the FutureListener.
300 clientHello = null;
301 }
302 } catch (Throwable cause) {
303 PlatformDependent.throwException(cause);
304 } finally {
305 releaseIfNotNull(clientHello);
306 }
307 }
308
309 @Override
310 protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
311 releaseHandshakeBuffer();
312
313 super.handlerRemoved0(ctx);
314 }
315
316 /**
317 * Kicks off a lookup for the given {@code ClientHello} and returns a {@link Future} which in turn will
318 * notify the {@link #onLookupComplete(ChannelHandlerContext, Future)} on completion.
319 *
320 * See https://tools.ietf.org/html/rfc5246#section-7.4.1.2
321 *
322 * <pre>
323 * struct {
324 * ProtocolVersion client_version;
325 * Random random;
326 * SessionID session_id;
327 * CipherSuite cipher_suites<2..2^16-2>;
328 * CompressionMethod compression_methods<1..2^8-1>;
329 * select (extensions_present) {
330 * case false:
331 * struct {};
332 * case true:
333 * Extension extensions<0..2^16-1>;
334 * };
335 * } ClientHello;
336 * </pre>
337 *
338 * @see #onLookupComplete(ChannelHandlerContext, Future)
339 */
340 protected abstract Future<T> lookup(ChannelHandlerContext ctx, ByteBuf clientHello) throws Exception;
341
342 /**
343 * Called upon completion of the {@link #lookup(ChannelHandlerContext, ByteBuf)} {@link Future}.
344 *
345 * @see #lookup(ChannelHandlerContext, ByteBuf)
346 */
347 protected abstract void onLookupComplete(ChannelHandlerContext ctx, Future<T> future) throws Exception;
348
349 @Override
350 public void read(ChannelHandlerContext ctx) throws Exception {
351 if (suppressRead) {
352 readPending = true;
353 } else {
354 ctx.read();
355 }
356 }
357
358 @Override
359 public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
360 ctx.bind(localAddress, promise);
361 }
362
363 @Override
364 public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress,
365 ChannelPromise promise) throws Exception {
366 ctx.connect(remoteAddress, localAddress, promise);
367 }
368
369 @Override
370 public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
371 ctx.disconnect(promise);
372 }
373
374 @Override
375 public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
376 ctx.close(promise);
377 }
378
379 @Override
380 public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
381 ctx.deregister(promise);
382 }
383
384 @Override
385 public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
386 ctx.write(msg, promise);
387 }
388
389 @Override
390 public void flush(ChannelHandlerContext ctx) throws Exception {
391 ctx.flush();
392 }
393 }