1717package org .apache .tomcat .util .net ;
1818
1919import 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
2126public 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}
0 commit comments