Skip to content

Commit 6cec4d4

Browse files
committed
SNI basically working with NIO.
Debug code still present. Needs to be made more robust. git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@1672631 13f79535-47bb-0310-9956-ffa450edef68
1 parent 40ad62e commit 6cec4d4

3 files changed

Lines changed: 135 additions & 14 deletions

File tree

java/org/apache/tomcat/util/net/LocalStrings.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ channel.nio.ssl.invalidBuffer=You can only read using the application read buffe
9797
channel.nio.ssl.expandNetInBuffer=Expanding network input buffer to [{0}] bytes
9898
channel.nio.ssl.expandNetOutBuffer=Expanding network input buffer to [{0}] bytes
9999

100+
sniExtractor.clientHelloTooBig=The ClientHello was not presented in a single TLS record so no SNI information could be extracted
101+
100102
socket.closed=The socket associated with this connection has been closed.
101103

102104
socket.apr.clientAbort=The client aborted the connection.

java/org/apache/tomcat/util/net/SNIExtractor.java

Lines changed: 128 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,17 @@
1717
package org.apache.tomcat.util.net;
1818

1919
import java.nio.ByteBuffer;
20+
import java.nio.charset.StandardCharsets;
21+
22+
import org.apache.juli.logging.Log;
23+
import org.apache.juli.logging.LogFactory;
24+
import org.apache.tomcat.util.res.StringManager;
2025

2126
public class SNIExtractor {
2227

28+
private static final Log log = LogFactory.getLog(SNIExtractor.class);
29+
private static final StringManager sm = StringManager.getManager(SNIExtractor.class);
30+
2331
private final SNIResult result;
2432
private final String sniValue;
2533

@@ -41,18 +49,60 @@ public SNIExtractor(ByteBuffer netInBuffer) {
4149
return;
4250
}
4351

44-
if (!isTLSClientHello(netInBuffer)) {
52+
if (!isTLSHandshake(netInBuffer)) {
4553
return;
4654
}
4755

48-
if (!isAllRecordPresent(netInBuffer)) {
49-
result = SNIResult.UNDERFLOW;
56+
int recordSizeToRead = recordSizeToRead(netInBuffer);
57+
if (recordSizeToRead == -1) {
58+
// Not enough data in the buffer for the full record
59+
if (netInBuffer.limit() == netInBuffer.capacity()) {
60+
// Buffer too small
61+
result = SNIResult.UNDERFLOW;
62+
return;
63+
} else {
64+
// Need to read more data
65+
result = SNIResult.NEED_READ;
66+
return;
67+
}
68+
69+
}
70+
71+
if (!isClientHello(netInBuffer)) {
5072
return;
5173
}
5274

53-
System.out.println("Looking good so far to find some SNI data");
54-
// TODO Parse the remainder of the data
75+
int clientHelloSizeToRead = clientHelloSize(netInBuffer);
76+
if (clientHelloSizeToRead == -1) {
77+
// Client hello can't have fitted into single TLS record.
78+
// Treat this as not present.
79+
log.warn(sm.getString("sniExtractor.clientHelloTooBig"));
80+
return;
81+
}
82+
83+
// Protocol Version (2 bytes)
84+
netInBuffer.getChar();
85+
swallowRandom(netInBuffer);
86+
// Session ID
87+
swallowUnit8Vector(netInBuffer);
88+
swallowCipherSuites(netInBuffer);
89+
// Compression methods
90+
swallowUnit8Vector(netInBuffer);
91+
92+
if (!netInBuffer.hasRemaining()) {
93+
// No more data means no extensions present
94+
return;
95+
}
5596

97+
// Extension length
98+
netInBuffer.getChar();
99+
// Read th eextensions until we run out of data or find the SNI
100+
while (netInBuffer.hasRemaining() && sniValue == null) {
101+
sniValue = readSniExtension(netInBuffer);
102+
}
103+
if (sniValue != null) {
104+
result = SNIResult.FOUND;
105+
}
56106
} finally {
57107
this.result = result;
58108
this.sniValue = sniValue;
@@ -85,7 +135,7 @@ private static boolean isRecordHeaderComplete(ByteBuffer bb) {
85135
}
86136

87137

88-
private static boolean isTLSClientHello(ByteBuffer bb) {
138+
private static boolean isTLSHandshake(ByteBuffer bb) {
89139
// For a TLS client hello the first byte must be 22 - handshake
90140
if (bb.get() != 22) {
91141
return false;
@@ -100,19 +150,86 @@ private static boolean isTLSClientHello(ByteBuffer bb) {
100150
}
101151

102152

103-
private static boolean isAllRecordPresent(ByteBuffer bb) {
153+
private static int recordSizeToRead(ByteBuffer bb) {
104154
// Next two bytes (unsigned) are the size of the record. We need all of
105155
// it.
106-
if (bb.getChar() > bb.remaining()) {
107-
return false;
156+
int size = bb.getChar();
157+
if (bb.remaining() < size) {
158+
return -1;
108159
}
109-
return true;
160+
return size;
161+
}
162+
163+
164+
private static boolean isClientHello(ByteBuffer bb) {
165+
// Client hello is handshake type 1
166+
if (bb.get() == 1) {
167+
return true;
168+
}
169+
return false;
170+
}
171+
172+
173+
private static int clientHelloSize(ByteBuffer bb) {
174+
// Next three bytes (unsigned) are the size of the client hello. We need
175+
// all of it.
176+
int size = ((bb.get() & 0xFF) << 16) + ((bb.get() & 0xFF) << 8) + (bb.get() & 0xFF);
177+
if (bb.remaining() < size) {
178+
return -1;
179+
}
180+
return size;
181+
}
182+
183+
private static void swallowRandom(ByteBuffer bb) {
184+
// 32 bytes total
185+
for (int i = 0; i < 4; i++) {
186+
bb.getLong();
187+
}
188+
}
189+
190+
private static void swallowCipherSuites(ByteBuffer bb) {
191+
char c = bb.getChar();
192+
for (int i = 0; i < c; i++) {
193+
bb.get();
194+
}
195+
}
196+
197+
198+
private static void swallowUnit8Vector(ByteBuffer bb) {
199+
int b = bb.get() & 0xFF;
200+
for (int i = 0; i < b; i++) {
201+
bb.get();
202+
}
203+
}
204+
205+
206+
private static String readSniExtension(ByteBuffer bb) {
207+
// SNI extension is type 0
208+
char extensionType = bb.getChar();
209+
// Next byte is data size
210+
char extensionDataSize = bb.getChar();
211+
if (extensionType == 0) {
212+
// First 2 bytes are size of server name list (only expecting one)
213+
bb.getChar();
214+
// Next byte is type (0 for hostname)
215+
bb.get();
216+
// Next 2 bytes are length of host name
217+
char serverNameSize = bb.getChar();
218+
byte[] serverNameBytes = new byte[serverNameSize];
219+
bb.get(serverNameBytes);
220+
return new String(serverNameBytes, StandardCharsets.UTF_8);
221+
} else {
222+
for (int i = 0; i < extensionDataSize; i++) {
223+
bb.get();
224+
}
225+
}
226+
return null;
110227
}
111228

112229
public static enum SNIResult {
113230
FOUND,
114231
NOT_PRESENT,
115232
UNDERFLOW,
116-
ERROR
233+
NEED_READ
117234
}
118235
}

java/org/apache/tomcat/util/net/SecureNioChannel.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,14 +239,16 @@ private int processSNI() throws IOException {
239239
case FOUND:
240240
hostName = extractor.getSNIValue();
241241
break;
242-
case ERROR:
243-
// NO-OP Let the handshake continue and deal with whatever was wrong
244-
break;
245242
case NOT_PRESENT:
246243
// NO-OP
247244
break;
248245
case UNDERFLOW:
246+
// Need to expand buffer
247+
break;
248+
case NEED_READ:
249249
return SelectionKey.OP_READ;
250+
default:
251+
break;
250252
}
251253

252254
System.out.println("SNI hostname was [" + hostName + "]");

0 commit comments

Comments
 (0)