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((FutureListener<T>) future1 -> {
276 releaseIfNotNull(finalClientHello);
277 try {
278 suppressRead = false;
279 try {
280 onLookupComplete(ctx, future1);
281 } catch (DecoderException err) {
282 ctx.fireExceptionCaught(err);
283 } catch (Exception cause) {
284 ctx.fireExceptionCaught(new DecoderException(cause));
285 } catch (Throwable cause) {
286 ctx.fireExceptionCaught(cause);
287 }
288 } finally {
289 if (readPending) {
290 readPending = false;
291 ctx.read();
292 }
293 }
294 });
295
296 // Ownership was transferred to the FutureListener.
297 clientHello = null;
298 }
299 } catch (Throwable cause) {
300 PlatformDependent.throwException(cause);
301 } finally {
302 releaseIfNotNull(clientHello);
303 }
304 }
305
306 @Override
307 protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
308 releaseHandshakeBuffer();
309
310 super.handlerRemoved0(ctx);
311 }
312
313 /**
314 * Kicks off a lookup for the given {@code ClientHello} and returns a {@link Future} which in turn will
315 * notify the {@link #onLookupComplete(ChannelHandlerContext, Future)} on completion.
316 *
317 * See https://tools.ietf.org/html/rfc5246#section-7.4.1.2
318 *
319 * <pre>
320 * struct {
321 * ProtocolVersion client_version;
322 * Random random;
323 * SessionID session_id;
324 * CipherSuite cipher_suites<2..2^16-2>;
325 * CompressionMethod compression_methods<1..2^8-1>;
326 * select (extensions_present) {
327 * case false:
328 * struct {};
329 * case true:
330 * Extension extensions<0..2^16-1>;
331 * };
332 * } ClientHello;
333 * </pre>
334 *
335 * @see #onLookupComplete(ChannelHandlerContext, Future)
336 */
337 protected abstract Future<T> lookup(ChannelHandlerContext ctx, ByteBuf clientHello) throws Exception;
338
339 /**
340 * Called upon completion of the {@link #lookup(ChannelHandlerContext, ByteBuf)} {@link Future}.
341 *
342 * @see #lookup(ChannelHandlerContext, ByteBuf)
343 */
344 protected abstract void onLookupComplete(ChannelHandlerContext ctx, Future<T> future) throws Exception;
345
346 @Override
347 public void read(ChannelHandlerContext ctx) throws Exception {
348 if (suppressRead) {
349 readPending = true;
350 } else {
351 ctx.read();
352 }
353 }
354
355 @Override
356 public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
357 ctx.bind(localAddress, promise);
358 }
359
360 @Override
361 public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress,
362 ChannelPromise promise) throws Exception {
363 ctx.connect(remoteAddress, localAddress, promise);
364 }
365
366 @Override
367 public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
368 ctx.disconnect(promise);
369 }
370
371 @Override
372 public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
373 ctx.close(promise);
374 }
375
376 @Override
377 public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
378 ctx.deregister(promise);
379 }
380
381 @Override
382 public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
383 ctx.write(msg, promise);
384 }
385
386 @Override
387 public void flush(ChannelHandlerContext ctx) throws Exception {
388 ctx.flush();
389 }
390 }