View Javadoc
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 }