/* * Copyright (C) 2012-2016 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.facebook.nifty.ssl; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.Channels; import org.jboss.netty.handler.codec.frame.FrameDecoder; public class SslPlaintextHandler extends FrameDecoder { private final SslServerConfiguration serverConfiguration; private final String sslHandlerName; /** * Special message for a SessionAwareSslHandler that tells it to initiate a TLS handshake after the connection * has already been established, if this class determines that it's handling a TLS connection rather than a * plaintext connection. * * By using an enum with a single value, we enforce that it's a singleton and can use == rather than an * instanceof check. */ enum TLSConnectedEvent { SINGLETON }; public SslPlaintextHandler(SslServerConfiguration serverConfiguration, String sslHandlerName) { this.serverConfiguration = serverConfiguration; this.sslHandlerName = sslHandlerName; } @Override protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception { if (buffer.readableBytes() < 9) { return null; } if (looksLikeTLS(buffer)) { ctx.getPipeline().addAfter(ctx.getName(), sslHandlerName, serverConfiguration.createHandler()); // Tell the SessionAwareSslHandler to handle the TLS handshake. Channels.fireMessageReceived(ctx, TLSConnectedEvent.SINGLETON); } ctx.getPipeline().remove(this); return buffer.readBytes(buffer.readableBytes()); } // Requires 9 bytes of input. private static boolean looksLikeTLS(ChannelBuffer buffer) { // TLS starts as // 0: 0x16 - handshake protocol magic // 1: 0x03 - SSL version major // 2: 0x00 to 0x03 - SSL version minor (SSLv3 or TLS1.0 through TLS1.2) // 3-4: length (2 bytes) // 5: 0x01 - handshake type (ClientHello) // 6-8: handshake len (3 bytes), equals value from offset 3-4 minus 4 // Framed binary starts as // 0-3: frame len // 4: 0x80 - binary magic // 5: 0x01 - protocol version // 6-7: various // 8-11: method name len // Other Thrift transports/protocols can't conflict because they don't have // 16-03-01 at offsets 0-1-5. // Definitely not TLS if (buffer.getByte(0) != 0x16 || buffer.getByte(1) != 0x03 || buffer.getByte(5) != 0x01) { return false; } // This is most likely TLS, but could be framed binary, which has 80-01 // at offsets 4-5. if (buffer.getByte(4) == 0x80 && buffer.getByte(8) != 0x7c) { // Binary will have the method name length at offsets 8-11, which must be // smaller than the frame length at 0-3, so byte 8 is <= byte 0, // which is 0x16. // However, for TLS, bytes 6-8 (24 bits) are the length of the // handshake protocol and this value is 4 less than the record-layer // length at offset 3-4 (16 bits), so byte 8 equals 0x7c (0x80 - 4), // which is not smaller than 0x16 return false; } return true; } }