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