• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.conscrypt.tlswire;
2 
3 import static org.junit.Assert.assertEquals;
4 import static org.junit.Assert.fail;
5 
6 import java.io.ByteArrayInputStream;
7 import java.io.DataInputStream;
8 import java.io.EOFException;
9 import java.io.IOException;
10 import java.net.ServerSocket;
11 import java.net.Socket;
12 import java.util.Arrays;
13 import java.util.concurrent.Callable;
14 import java.util.concurrent.ExecutorService;
15 import java.util.concurrent.Future;
16 import java.util.concurrent.TimeUnit;
17 import javax.net.ServerSocketFactory;
18 import javax.net.ssl.SSLSocket;
19 import javax.net.ssl.SSLSocketFactory;
20 import org.conscrypt.tlswire.handshake.ClientHello;
21 import org.conscrypt.tlswire.handshake.HandshakeMessage;
22 import org.conscrypt.tlswire.record.TlsProtocols;
23 import org.conscrypt.tlswire.record.TlsRecord;
24 
25 public class TlsTester {
26 
TlsTester()27     private TlsTester() {}
28 
captureTlsHandshakeClientHello(ExecutorService executor, SSLSocketFactory sslSocketFactory)29     public static ClientHello captureTlsHandshakeClientHello(ExecutorService executor,
30             SSLSocketFactory sslSocketFactory) throws Exception {
31         TlsRecord record = captureTlsHandshakeFirstTlsRecord(executor, sslSocketFactory);
32         return parseClientHello(record);
33     }
34 
parseClientHello(byte[] data)35     public static ClientHello parseClientHello(byte[] data) throws Exception {
36         return parseClientHello(parseRecord(data));
37     }
38 
parseClientHello(TlsRecord record)39     private static ClientHello parseClientHello(TlsRecord record) throws Exception {
40         assertEquals("TLS record type", TlsProtocols.HANDSHAKE, record.type);
41         ByteArrayInputStream fragmentIn = new ByteArrayInputStream(record.fragment);
42         HandshakeMessage handshakeMessage = HandshakeMessage.read(new DataInputStream(fragmentIn));
43         assertEquals(
44                 "HandshakeMessage type", HandshakeMessage.TYPE_CLIENT_HELLO, handshakeMessage.type);
45         // Assert that the fragment does not contain any more messages
46         assertEquals(0, fragmentIn.available());
47         return (ClientHello) handshakeMessage;
48     }
49 
captureTlsHandshakeFirstTlsRecord(ExecutorService executor, SSLSocketFactory sslSocketFactory)50     public static TlsRecord captureTlsHandshakeFirstTlsRecord(ExecutorService executor,
51             SSLSocketFactory sslSocketFactory) throws Exception {
52         byte[] firstReceivedChunk = captureTlsHandshakeFirstTransmittedChunkBytes(executor, sslSocketFactory);
53         return parseRecord(firstReceivedChunk);
54     }
55 
parseRecord(byte[] data)56     public static TlsRecord parseRecord(byte[] data) throws Exception {
57         ByteArrayInputStream firstReceivedChunkIn = new ByteArrayInputStream(data);
58         TlsRecord record = TlsRecord.read(new DataInputStream(firstReceivedChunkIn));
59         // Assert that the chunk does not contain any more data
60         assertEquals(0, firstReceivedChunkIn.available());
61         return record;
62     }
63 
64     @SuppressWarnings("FutureReturnValueIgnored")
captureTlsHandshakeFirstTransmittedChunkBytes( ExecutorService executor, final SSLSocketFactory sslSocketFactory)65     private static byte[] captureTlsHandshakeFirstTransmittedChunkBytes(
66             ExecutorService executor, final SSLSocketFactory sslSocketFactory) throws Exception {
67         // Since there's no straightforward way to obtain a ClientHello from SSLSocket, this test
68         // does the following:
69         // 1. Creates a listening server socket (a plain one rather than a TLS/SSL one).
70         // 2. Creates a client SSLSocket, which connects to the server socket and initiates the
71         //    TLS/SSL handshake.
72         // 3. Makes the server socket accept an incoming connection on the server socket, and reads
73         //    the first chunk of data received. This chunk is assumed to be the ClientHello.
74         // NOTE: Steps 2 and 3 run concurrently.
75         ServerSocket listeningSocket = null;
76         // Some Socket operations are not interruptible via Thread.interrupt for some reason. To
77         // work around, we unblock these sockets using Socket.close.
78         final Socket[] sockets = new Socket[2];
79         try {
80             // 1. Create the listening server socket.
81             listeningSocket = ServerSocketFactory.getDefault().createServerSocket(0);
82             final ServerSocket finalListeningSocket = listeningSocket;
83             // 2. (in background) Wait for an incoming connection and read its first chunk.
84             final Future<byte[]>
85                     readFirstReceivedChunkFuture = executor.submit(new Callable<byte[]>() {
86                 @Override
87                 public byte[] call() throws Exception {
88                     Socket socket = finalListeningSocket.accept();
89                     sockets[1] = socket;
90                     try {
91                         byte[] buffer = new byte[64 * 1024];
92                         int bytesRead = socket.getInputStream().read(buffer);
93                         if (bytesRead == -1) {
94                             throw new EOFException("Failed to read anything");
95                         }
96                         return Arrays.copyOf(buffer, bytesRead);
97                     } finally {
98                         closeQuietly(socket);
99                     }
100                 }
101             });
102             // 3. Create a client socket, connect it to the server socket, and start the TLS/SSL
103             //    handshake.
104             executor.submit(new Callable<Void>() {
105                 @Override
106                 public Void call() throws Exception {
107                     Socket client = new Socket();
108                     sockets[0] = client;
109                     try {
110                         client.connect(finalListeningSocket.getLocalSocketAddress());
111                         // Initiate the TLS/SSL handshake which is expected to fail as soon as the
112                         // server socket receives a ClientHello.
113                         try {
114                             SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(client,
115                                     "localhost.localdomain", finalListeningSocket.getLocalPort(),
116                                     true);
117                             sslSocket.startHandshake();
118                             fail();
119                             return null;
120                         } catch (IOException expected) {
121                             // Ignored.
122                         }
123                         return null;
124                     } finally {
125                         closeQuietly(client);
126                     }
127                 }
128             });
129             // Wait for the ClientHello to arrive
130             return readFirstReceivedChunkFuture.get(10, TimeUnit.SECONDS);
131         } finally {
132             closeQuietly(listeningSocket);
133             closeQuietly(sockets[0]);
134             closeQuietly(sockets[1]);
135         }
136     }
137 
closeQuietly(Socket socket)138     private static void closeQuietly(Socket socket) {
139         if (socket != null) {
140             try {
141                 socket.close();
142             } catch (IOException ignored) {
143                 // Ignored.
144             }
145         }
146     }
147 
closeQuietly(ServerSocket serverSocket)148     private static void closeQuietly(ServerSocket serverSocket) {
149         if (serverSocket != null) {
150             try {
151                 serverSocket.close();
152             } catch (IOException ignored) {
153                 // Ignored.
154             }
155         }
156     }
157 }
158