diff options
author | Håvard Pettersen <havardpe@oath.com> | 2018-09-25 11:29:20 +0000 |
---|---|---|
committer | Håvard Pettersen <havardpe@oath.com> | 2018-09-25 12:15:04 +0000 |
commit | eaf61679b8989895eb183332f92b430fab9d3dfd (patch) | |
tree | f332cd545c70635c148b260c17b523aa104a67ce /jrt/src/com/yahoo | |
parent | 1f6c71298d0f7655d266627ca49f554019e5bd13 (diff) |
added support for auto-detecting tls for incoming connections
Diffstat (limited to 'jrt/src/com/yahoo')
-rw-r--r-- | jrt/src/com/yahoo/jrt/MaybeTlsCryptoEngine.java | 34 | ||||
-rw-r--r-- | jrt/src/com/yahoo/jrt/MaybeTlsCryptoSocket.java | 126 | ||||
-rw-r--r-- | jrt/src/com/yahoo/jrt/TlsCryptoSocket.java | 5 |
3 files changed, 165 insertions, 0 deletions
diff --git a/jrt/src/com/yahoo/jrt/MaybeTlsCryptoEngine.java b/jrt/src/com/yahoo/jrt/MaybeTlsCryptoEngine.java new file mode 100644 index 00000000000..8cb560246e8 --- /dev/null +++ b/jrt/src/com/yahoo/jrt/MaybeTlsCryptoEngine.java @@ -0,0 +1,34 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jrt; + +import java.nio.channels.SocketChannel; + +/** + * A crypto engine that supports both tls encrypted connections and + * unencrypted connections. The use of tls for incoming connections is + * auto-detected using clever heuristics. The use of tls for outgoing + * connections is controlled by the useTls flag given to the + * constructor. + **/ +public class MaybeTlsCryptoEngine implements CryptoEngine { + + private final TlsCryptoEngine tlsEngine; + private final boolean useTls; + + public MaybeTlsCryptoEngine(TlsCryptoEngine tlsEngine, boolean useTls) { + this.tlsEngine = tlsEngine; + this.useTls = useTls; + } + + @Override public CryptoSocket createCryptoSocket(SocketChannel channel, boolean isServer) { + if (isServer) { + return new MaybeTlsCryptoSocket(channel, tlsEngine); + } else if (useTls) { + return tlsEngine.createCryptoSocket(channel, false); + } else { + return new NullCryptoSocket(channel); + } + } + + @Override public String toString() { return "MaybeTlsCryptoEngine(useTls:" + useTls + ")"; } +} diff --git a/jrt/src/com/yahoo/jrt/MaybeTlsCryptoSocket.java b/jrt/src/com/yahoo/jrt/MaybeTlsCryptoSocket.java new file mode 100644 index 00000000000..7cedbcda9a1 --- /dev/null +++ b/jrt/src/com/yahoo/jrt/MaybeTlsCryptoSocket.java @@ -0,0 +1,126 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jrt; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; + +/** + * A crypto socket for the server side of a connection that + * auto-detects whether the connection is tls encrypted or unencrypted + * using clever heuristics. The assumption is that the client side + * will send at least one RPC request before expecting anything from + * the server. The first 9 bytes are inspected to see if they look + * like part of a tls handshake or not (RPC packet headers are 12 + * bytes). + **/ +public class MaybeTlsCryptoSocket implements CryptoSocket { + + private static final int snoop_size = 9; + + private CryptoSocket socket; + + // 'data' is the first 9 bytes received from the client + public static boolean looksLikeTlsToMe(byte[] data) { + if (data.length != snoop_size) { + return false; // wrong data size for tls detection + } + if (data[0] != 22) { + return false; // not tagged as tls handshake + } + if (data[1] != 3) { + return false; // unknown major version + } + if ((data[2] != 1) && (data[2] != 3)) { + return false; // unknown minor version + } + int frame_len = (data[3] & 0xff); + frame_len = ((frame_len << 8) | (data[4] & 0xff)); + if (frame_len > (16384 + 2048)) { + return false; // frame too large + } + if (frame_len < 4) { + return false; // frame too small + } + if (data[5] != 0x1) { + return false; // not tagges as client hello + } + int hello_len = (data[6] & 0xff); + hello_len = ((hello_len << 8) | (data[7] & 0xff)); + hello_len = ((hello_len << 8) | (data[8] & 0xff)); + if ((frame_len - 4) != hello_len) { + return false; // inconsistent sizes; frame vs client hello + } + return true; + } + + private class MyCryptoSocket extends NullCryptoSocket { + + private TlsCryptoEngine factory; + private Buffer buffer; + + MyCryptoSocket(SocketChannel channel, TlsCryptoEngine factory) { + super(channel); + this.factory = factory; + this.buffer = new Buffer(4096); + } + + @Override public HandshakeResult handshake() throws IOException { + if (factory != null) { + channel().read(buffer.getWritable(snoop_size)); + if (buffer.bytes() < snoop_size) { + return HandshakeResult.NEED_READ; + } + byte[] data = new byte[snoop_size]; + ByteBuffer src = buffer.getReadable(); + for (int i = 0; i < snoop_size; i++) { + data[i] = src.get(i); + } + if (looksLikeTlsToMe(data)) { + TlsCryptoSocket tlsSocket = factory.createCryptoSocket(channel(), true); + tlsSocket.injectReadData(buffer); + socket = tlsSocket; + return socket.handshake(); + } else { + factory = null; + } + } + return HandshakeResult.DONE; + } + + @Override public int read(ByteBuffer dst) throws IOException { + int drainResult = drain(dst); + if (drainResult != 0) { + return drainResult; + } + return super.read(dst); + } + + @Override public int drain(ByteBuffer dst) throws IOException { + int cnt = 0; + if (buffer != null) { + ByteBuffer src = buffer.getReadable(); + while (src.hasRemaining() && dst.hasRemaining()) { + dst.put(src.get()); + cnt++; + } + if (buffer.bytes() == 0) { + buffer = null; + } + } + return cnt; + } + } + + public MaybeTlsCryptoSocket(SocketChannel channel, TlsCryptoEngine factory) { + this.socket = new MyCryptoSocket(channel, factory); + } + + @Override public SocketChannel channel() { return socket.channel(); } + @Override public HandshakeResult handshake() throws IOException { return socket.handshake(); } + @Override public int getMinimumReadBufferSize() { return socket.getMinimumReadBufferSize(); } + @Override public int read(ByteBuffer dst) throws IOException { return socket.read(dst); } + @Override public int drain(ByteBuffer dst) throws IOException { return socket.drain(dst); } + @Override public int write(ByteBuffer src) throws IOException { return socket.write(src); } + @Override public FlushResult flush() throws IOException { return socket.flush(); } +} diff --git a/jrt/src/com/yahoo/jrt/TlsCryptoSocket.java b/jrt/src/com/yahoo/jrt/TlsCryptoSocket.java index 3db54811f9e..96aca622af4 100644 --- a/jrt/src/com/yahoo/jrt/TlsCryptoSocket.java +++ b/jrt/src/com/yahoo/jrt/TlsCryptoSocket.java @@ -46,6 +46,11 @@ public class TlsCryptoSocket implements CryptoSocket { this.handshakeState = HandshakeState.NOT_STARTED; } + // inject pre-read data into the read pipeline (typically called by MaybeTlsCryptoSocket) + public void injectReadData(Buffer data) { + unwrapBuffer.getWritable(data.bytes()).put(data.getReadable()); + } + @Override public SocketChannel channel() { return channel; |