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                                 } else if (packetLength == SslUtils.SSL_RECORD_HEADER_LENGTH) {
117                                     select(ctx, null);
118                                     return;
119                                 }
120 
121                                 final int endOffset = readerIndex + packetLength;
122 
123                                 // Let's check if we already parsed the handshake length or not.
124                                 if (handshakeLength == -1) {
125                                     if (handshakeBuffer == null &&
126                                             readerIndex + SslUtils.SSL_RECORD_HEADER_LENGTH + 4 <= endOffset) {
127                                         final int handshakeType = in.getUnsignedByte(readerIndex +
128                                                 SslUtils.SSL_RECORD_HEADER_LENGTH);
129 
130                                         // Check if this is a clientHello(1)
131                                         // See https://tools.ietf.org/html/rfc5246#section-7.4
132                                         if (handshakeType != 1) {
133                                             select(ctx, null);
134                                             return;
135                                         }
136 
137                                         // Read the length of the handshake as it may arrive in fragments
138                                         // See https://tools.ietf.org/html/rfc5246#section-7.4
139                                         handshakeLength = in.getUnsignedMedium(readerIndex +
140                                                 SslUtils.SSL_RECORD_HEADER_LENGTH + 1);
141 
142                                         if (handshakeLength > maxClientHelloLength && maxClientHelloLength != 0) {
143                                             TooLongFrameException e = new TooLongFrameException(
144                                                     "ClientHello length exceeds " + maxClientHelloLength +
145                                                             ": " + handshakeLength);
146                                             in.skipBytes(in.readableBytes());
147                                             ctx.fireUserEventTriggered(new SniCompletionEvent(e));
148                                             SslUtils.handleHandshakeFailure(ctx, e, true);
149                                             throw e;
150                                         }
151 
152                                         if (handshakeLength + 4 + SslUtils.SSL_RECORD_HEADER_LENGTH <= packetLength) {
153                                             // We have everything we need in one packet.
154                                             // Skip the record header and handshake header (this sums up as 4 bytes)
155                                             readerIndex += SslUtils.SSL_RECORD_HEADER_LENGTH + 4;
156                                             final int clientHelloLength = handshakeLength;
157                                             handshakeLength = -1;
158                                             select(ctx, in.retainedSlice(readerIndex, clientHelloLength));
159                                             return;
160                                         }
161                                     }
162                                 }
163 
164                                 if (handshakeBuffer == null) {
165                                     handshakeBuffer = ctx.alloc().buffer();
166                                 }
167 
168                                 // Combine the encapsulated data in one buffer but not include the SSL_RECORD_HEADER
169                                 handshakeBuffer.writeBytes(in, readerIndex + SslUtils.SSL_RECORD_HEADER_LENGTH,
170                                         packetLength - SslUtils.SSL_RECORD_HEADER_LENGTH);
171                                 readerIndex += packetLength;
172                                 readableBytes -= packetLength;
173                                 aggregatedBytes += packetLength;
174                                 if (handshakeLength == -1) {
175                                     if (handshakeBuffer.readableBytes() < 4) {
176                                         continue;
177                                     }
178 
179                                     final int handshakeType = handshakeBuffer.getUnsignedByte(0);
180                                     handshakeLength = handshakeBuffer.getUnsignedMedium(1);
181 
182                                     // Check if this is a clientHello(1)
183                                     // See https://tools.ietf.org/html/rfc5246#section-7.4
184                                     if (handshakeType != 1) {
185                                         select(ctx, null);
186                                         return;
187                                     }
188 
189                                     if (handshakeLength > maxClientHelloLength && maxClientHelloLength != 0) {
190                                         TooLongFrameException e = new TooLongFrameException(
191                                                 "ClientHello length exceeds " + maxClientHelloLength +
192                                                         ": " + handshakeLength);
193                                         in.skipBytes(in.readableBytes());
194                                         ctx.fireUserEventTriggered(new SniCompletionEvent(e));
195                                         SslUtils.handleHandshakeFailure(ctx, e, true);
196                                         throw e;
197                                     }
198                                 }
199 
200                                 if (handshakeBuffer.readableBytes() >= handshakeLength + 4) {
201                                     ByteBuf clientHello = handshakeBuffer.setIndex(4, handshakeLength + 4).slice();
202                                     handshakeBuffer = null;
203                                     handshakeLength = -1;
204 
205                                     select(ctx, clientHello);
206                                     return;
207                                 }
208                                 break;
209                             }
210                             // fall-through
211                         default:
212                             // not tls, ssl or application data
213                             select(ctx, null);
214                             return;
215                     }
216                 }
217             } catch (NotSslRecordException e) {
218                 // Just rethrow as in this case we also closed the channel and this is consistent with SslHandler.
219                 throw e;
220             } catch (TooLongFrameException e) {
221                 // Just rethrow as in this case we also closed the channel
222                 throw e;
223             } catch (Exception e) {
224                 // unexpected encoding, ignore sni and use default
225                 if (logger.isDebugEnabled()) {
226                     logger.debug("Unexpected client hello packet: " + ByteBufUtil.hexDump(in), e);
227                 }
228                 select(ctx, null);
229             }
230         }
231     }
232 
233     private void releaseHandshakeBuffer() {
234         releaseIfNotNull(handshakeBuffer);
235         handshakeBuffer = null;
236         handshakeLength = -1;
237     }
238 
239     private static void releaseIfNotNull(ByteBuf buffer) {
240         if (buffer != null) {
241             buffer.release();
242         }
243     }
244 
245     private void select(final ChannelHandlerContext ctx, ByteBuf clientHello) throws Exception {
246         final Future<T> future;
247         try {
248             future = lookup(ctx, clientHello);
249             if (future.isDone()) {
250                 try {
251                     onLookupComplete(ctx, future);
252                 } catch (DecoderException err) {
253                     ctx.fireExceptionCaught(err);
254                 } catch (Exception cause) {
255                     ctx.fireExceptionCaught(new DecoderException(cause));
256                 } catch (Throwable cause) {
257                     ctx.fireExceptionCaught(cause);
258                 }
259             } else {
260                 suppressRead = true;
261                 final ByteBuf finalClientHello = clientHello;
262                 future.addListener((FutureListener<T>) future1 -> {
263                     releaseIfNotNull(finalClientHello);
264                     try {
265                         suppressRead = false;
266                         try {
267                             onLookupComplete(ctx, future1);
268                         } catch (DecoderException err) {
269                             ctx.fireExceptionCaught(err);
270                         } catch (Exception cause) {
271                             ctx.fireExceptionCaught(new DecoderException(cause));
272                         } catch (Throwable cause) {
273                             ctx.fireExceptionCaught(cause);
274                         }
275                     } finally {
276                         if (readPending) {
277                             readPending = false;
278                             ctx.read();
279                         }
280                     }
281                 });
282 
283                 // Ownership was transferred to the FutureListener.
284                 clientHello = null;
285             }
286         } catch (Throwable cause) {
287             PlatformDependent.throwException(cause);
288         } finally {
289             releaseIfNotNull(clientHello);
290         }
291     }
292 
293     @Override
294     protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
295         releaseHandshakeBuffer();
296 
297         super.handlerRemoved0(ctx);
298     }
299 
300     /**
301      * Kicks off a lookup for the given {@code ClientHello} and returns a {@link Future} which in turn will
302      * notify the {@link #onLookupComplete(ChannelHandlerContext, Future)} on completion.
303      *
304      * See https://tools.ietf.org/html/rfc5246#section-7.4.1.2
305      *
306      * <pre>
307      * struct {
308      *    ProtocolVersion client_version;
309      *    Random random;
310      *    SessionID session_id;
311      *    CipherSuite cipher_suites<2..2^16-2>;
312      *    CompressionMethod compression_methods<1..2^8-1>;
313      *    select (extensions_present) {
314      *        case false:
315      *            struct {};
316      *        case true:
317      *            Extension extensions<0..2^16-1>;
318      *    };
319      * } ClientHello;
320      * </pre>
321      *
322      * @see #onLookupComplete(ChannelHandlerContext, Future)
323      */
324     protected abstract Future<T> lookup(ChannelHandlerContext ctx, ByteBuf clientHello) throws Exception;
325 
326     /**
327      * Called upon completion of the {@link #lookup(ChannelHandlerContext, ByteBuf)} {@link Future}.
328      *
329      * @see #lookup(ChannelHandlerContext, ByteBuf)
330      */
331     protected abstract void onLookupComplete(ChannelHandlerContext ctx, Future<T> future) throws Exception;
332 
333     @Override
334     public void read(ChannelHandlerContext ctx) throws Exception {
335         if (suppressRead) {
336             readPending = true;
337         } else {
338             ctx.read();
339         }
340     }
341 
342     @Override
343     public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
344         ctx.bind(localAddress, promise);
345     }
346 
347     @Override
348     public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress,
349                         ChannelPromise promise) throws Exception {
350         ctx.connect(remoteAddress, localAddress, promise);
351     }
352 
353     @Override
354     public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
355         ctx.disconnect(promise);
356     }
357 
358     @Override
359     public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
360         ctx.close(promise);
361     }
362 
363     @Override
364     public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
365         ctx.deregister(promise);
366     }
367 
368     @Override
369     public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
370         ctx.write(msg, promise);
371     }
372 
373     @Override
374     public void flush(ChannelHandlerContext ctx) throws Exception {
375         ctx.flush();
376     }
377 }