From 9c804921de475f55f71aaf7ac870e49ccc7dbef0 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 18 Nov 2011 11:54:31 +0100 Subject: [PATCH] Added more SSL tests for split boundary handshake and renegotiation in the middle of content. --- .../jetty/client/SslBytesServerTest.java | 294 ++++++++++++++++-- 1 file changed, 266 insertions(+), 28 deletions(-) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesServerTest.java index fb4a48af315..e8dfd6214a6 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesServerTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesServerTest.java @@ -201,6 +201,115 @@ public class SslBytesServerTest closeClient(client); } + @Test + public void testHandshakeWithSplitBoundary() throws Exception + { + final SSLSocket client = newClient(); + + Future handshake = threadPool.submit(new Callable() + { + public Object call() throws Exception + { + client.startHandshake(); + return null; + } + }); + + // Client Hello + TLSRecord record = proxy.readFromClient(); + byte[] bytes = record.getBytes(); + byte[] chunk1 = new byte[2 * bytes.length / 3]; + System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); + byte[] chunk2 = new byte[bytes.length - chunk1.length]; + System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); + proxy.flushToServer(chunk1); + TimeUnit.MILLISECONDS.sleep(100); + proxy.flushToServer(chunk2); + TimeUnit.MILLISECONDS.sleep(100); + + // Server Hello + Certificate + Server Done + record = proxy.readFromServer(); + proxy.flushToClient(record); + + // Client Key Exchange + record = proxy.readFromClient(); + bytes = record.getBytes(); + chunk1 = new byte[2 * bytes.length / 3]; + System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); + chunk2 = new byte[bytes.length - chunk1.length]; + System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); + proxy.flushToServer(chunk1); + TimeUnit.MILLISECONDS.sleep(100); + proxy.flushToServer(chunk2); + TimeUnit.MILLISECONDS.sleep(100); + + // Change Cipher Spec + record = proxy.readFromClient(); + bytes = record.getBytes(); + chunk1 = new byte[2 * bytes.length / 3]; + System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); + chunk2 = new byte[bytes.length - chunk1.length]; + System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); + proxy.flushToServer(chunk1); + TimeUnit.MILLISECONDS.sleep(100); + proxy.flushToServer(chunk2); + TimeUnit.MILLISECONDS.sleep(100); + + // Client Done + record = proxy.readFromClient(); + bytes = record.getBytes(); + chunk1 = new byte[2 * bytes.length / 3]; + System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); + chunk2 = new byte[bytes.length - chunk1.length]; + System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); + proxy.flushToServer(chunk1); + TimeUnit.MILLISECONDS.sleep(100); + proxy.flushToServer(chunk2); + TimeUnit.MILLISECONDS.sleep(100); + + // Change Cipher Spec + record = proxy.readFromServer(); + Assert.assertNotNull(record); + proxy.flushToClient(record); + + // Server Done + record = proxy.readFromServer(); + Assert.assertNotNull(record); + proxy.flushToClient(record); + + Assert.assertNull(handshake.get(5, TimeUnit.SECONDS)); + + // Check that we did not spin + Assert.assertThat(sslHandles.get(), lessThan(20)); + Assert.assertThat(httpParses.get(), lessThan(50)); + + client.close(); + + // Close Alert + record = proxy.readFromClient(); + bytes = record.getBytes(); + chunk1 = new byte[2 * bytes.length / 3]; + System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); + chunk2 = new byte[bytes.length - chunk1.length]; + System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); + proxy.flushToServer(chunk1); + TimeUnit.MILLISECONDS.sleep(100); + proxy.flushToServer(chunk2); + TimeUnit.MILLISECONDS.sleep(100); + // Socket close + record = proxy.readFromClient(); + Assert.assertNull("" + record, record); + proxy.flushToServer(record); + + // Close Alert + record = proxy.readFromServer(); + proxy.flushToClient(record); + // Socket close + record = proxy.readFromServer(); + Assert.assertNull("" + record, record); + proxy.flushToClient(record); + } + @Test public void testRequestResponse() throws Exception { @@ -237,7 +346,7 @@ public class SslBytesServerTest BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")); String line = reader.readLine(); Assert.assertNotNull(line); - Assert.assertTrue(line.contains(" 200 ")); + Assert.assertTrue(line.startsWith("HTTP/1.1 200 ")); while ((line = reader.readLine()) != null) { if (line.trim().length() == 0) @@ -338,13 +447,17 @@ public class SslBytesServerTest BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")); String line = reader.readLine(); Assert.assertNotNull(line); - Assert.assertTrue(line.contains(" 200 ")); + Assert.assertTrue(line.startsWith("HTTP/1.1 200 ")); while ((line = reader.readLine()) != null) { if (line.trim().length() == 0) break; } + // Check that we did not spin + Assert.assertThat(sslHandles.get(), lessThan(500)); + Assert.assertThat(httpParses.get(), lessThan(150)); + client.close(); // Close Alert @@ -356,7 +469,7 @@ public class SslBytesServerTest } // Socket close record = proxy.readFromClient(); - Assert.assertNull(record); + Assert.assertNull("" + record, record); proxy.flushToServer(record); // Close Alert @@ -364,7 +477,7 @@ public class SslBytesServerTest proxy.flushToClient(record); // Socket close record = proxy.readFromServer(); - Assert.assertNull(record); + Assert.assertNull("" + record, record); proxy.flushToClient(record); } @@ -414,7 +527,7 @@ public class SslBytesServerTest proxy.flushToServer(record); // Socket close record = proxy.readFromClient(); - Assert.assertNull(record); + Assert.assertNull("" + record, record); proxy.flushToServer(record); // Expect response from server @@ -433,12 +546,12 @@ public class SslBytesServerTest // Socket close record = proxy.readFromClient(); - Assert.assertNull(record); + Assert.assertNull("" + record, record); proxy.flushToServer(record); // Socket close record = proxy.readFromServer(); - Assert.assertNull(record); + Assert.assertNull("" + record, record); proxy.flushToClient(record); } @@ -494,12 +607,12 @@ public class SslBytesServerTest // Socket close record = proxy.readFromClient(); - Assert.assertNull(record); + Assert.assertNull("" + record, record); proxy.flushToServer(record); // Socket close record = proxy.readFromServer(); - Assert.assertNull(record); + Assert.assertNull("" + record, record); proxy.flushToClient(record); } @@ -538,7 +651,7 @@ public class SslBytesServerTest // Expect raw close from server record = proxy.readFromServer(); - Assert.assertNull(record); + Assert.assertNull("" + record, record); proxy.flushToClient(record); client.close(); @@ -608,12 +721,12 @@ public class SslBytesServerTest // Socket close record = proxy.readFromClient(); - Assert.assertNull(record); + Assert.assertNull("" + record, record); proxy.flushToServer(record); // Socket close record = proxy.readFromServer(); - Assert.assertNull(record); + Assert.assertNull("" + record, record); proxy.flushToClient(record); } @@ -665,7 +778,7 @@ public class SslBytesServerTest BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")); String line = reader.readLine(); Assert.assertNotNull(line); - Assert.assertTrue(line.contains(" 200 ")); + Assert.assertTrue(line.startsWith("HTTP/1.1 200 ")); while ((line = reader.readLine()) != null) { if (line.trim().length() == 0) @@ -706,8 +819,7 @@ public class SslBytesServerTest } }); - // The data is 8 times bigger than the TLS record max length, - // therefore there will be generated 9 TLSRecord + // Nine TLSRecords will be generated for the request for (int i = 0; i < 9; ++i) { // Application data @@ -715,21 +827,20 @@ public class SslBytesServerTest byte[] bytes = record.getBytes(); byte[] chunk1 = new byte[2 * bytes.length / 3]; System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); - proxy.flushToServer(chunk1); - - TimeUnit.MILLISECONDS.sleep(100); - byte[] chunk2 = new byte[bytes.length - chunk1.length]; System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); + proxy.flushToServer(chunk1); + TimeUnit.MILLISECONDS.sleep(100); proxy.flushToServer(chunk2); - TimeUnit.MILLISECONDS.sleep(100); } // Check that we did not spin - Assert.assertThat(sslHandles.get(), lessThan(10)); + Assert.assertThat(sslHandles.get(), lessThan(20)); Assert.assertThat(httpParses.get(), lessThan(50)); + Assert.assertNull(request.get(5, TimeUnit.SECONDS)); + TLSRecord record = proxy.readFromServer(); Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType()); proxy.flushToClient(record); @@ -737,7 +848,127 @@ public class SslBytesServerTest BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")); String line = reader.readLine(); Assert.assertNotNull(line); - Assert.assertTrue(line.contains(" 200 ")); + Assert.assertTrue(line.startsWith("HTTP/1.1 200 ")); + while ((line = reader.readLine()) != null) + { + if (line.trim().length() == 0) + break; + } + + closeClient(client); + } + + @Test + public void testRequestWithBigContentWithRenegotiationInMiddleOfContent() throws Exception + { + final SSLSocket client = newClient(); + final OutputStream clientOutput = client.getOutputStream(); + + SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); + client.startHandshake(); + Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); + + // Use a content that is larger than the TLS record which is 2^14 (around 16k) + byte[] data1 = new byte[80 * 1024]; + Arrays.fill(data1, (byte)'X'); + String content1 = new String(data1, "UTF-8"); + byte[] data2 = new byte[48 * 1024]; + Arrays.fill(data2, (byte)'Y'); + final String content2 = new String(data2, "UTF-8"); + + // Write only part of the body + automaticProxyFlow = proxy.startAutomaticFlow(); + clientOutput.write(("" + + "POST / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Content-Type: text/plain\r\n" + + "Content-Length: " + (content1.length() + content2.length()) + "\r\n" + + "\r\n" + + content1).getBytes("UTF-8")); + clientOutput.flush(); + Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); + + // Renegotiate + Future renegotiation = threadPool.submit(new Callable() + { + public Object call() throws Exception + { + client.startHandshake(); + return null; + } + }); + + // Renegotiation Handshake + TLSRecord record = proxy.readFromClient(); + proxy.flushToServer(record); + + // Renegotiation Handshake + record = proxy.readFromServer(); + proxy.flushToClient(record); + + // Renegotiation Change Cipher + record = proxy.readFromServer(); + Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType()); + proxy.flushToClient(record); + + // Renegotiation Handshake + record = proxy.readFromServer(); + proxy.flushToClient(record); + + // Trigger a read to have the client write the final renegotiation steps + client.setSoTimeout(100); + try + { + client.getInputStream().read(); + Assert.fail(); + } + catch (SocketTimeoutException x) + { + // Expected + } + + // Renegotiation Change Cipher + record = proxy.readFromClient(); + Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType()); + proxy.flushToServer(record); + + // Renegotiation Handshake + record = proxy.readFromClient(); + proxy.flushToServer(record); + + Assert.assertNull(renegotiation.get(5, TimeUnit.SECONDS)); + + // Write the rest of the request + Future request = threadPool.submit(new Callable() + { + public Object call() throws Exception + { + clientOutput.write(content2.getBytes("UTF-8")); + clientOutput.flush(); + return null; + } + }); + + // Three TLSRecords will be generated for the remainder of the content + for (int i = 0; i < 3; ++i) + { + // Application data + record = proxy.readFromClient(); + proxy.flushToServer(record); + } + + Assert.assertNull(request.get(5, TimeUnit.SECONDS)); + + // Read response + // Application Data + record = proxy.readFromServer(); + Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType()); + proxy.flushToClient(record); + + BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")); + String line = reader.readLine(); + Assert.assertNotNull(line); + Assert.assertTrue(line.startsWith("HTTP/1.1 200 ")); while ((line = reader.readLine()) != null) { if (line.trim().length() == 0) @@ -764,7 +995,7 @@ public class SslBytesServerTest proxy.flushToServer(record); // Socket close record = proxy.readFromClient(); - Assert.assertNull(record); + Assert.assertNull("" + record, record); proxy.flushToServer(record); // Close Alert @@ -772,7 +1003,7 @@ public class SslBytesServerTest proxy.flushToClient(record); // Socket close record = proxy.readFromServer(); - Assert.assertNull(record); + Assert.assertNull("" + record, record); proxy.flushToClient(record); } @@ -833,7 +1064,7 @@ public class SslBytesServerTest { InputStream input = socket.getInputStream(); int first = -2; - while (!Thread.currentThread().isInterrupted()) + while (true) { try { @@ -843,7 +1074,8 @@ public class SslBytesServerTest } catch (SocketTimeoutException x) { - // Ignore + if (Thread.currentThread().isInterrupted()) + break; } } if (first == -2) @@ -910,9 +1142,9 @@ public class SslBytesServerTest } } - public void flushToServer(byte... datum) throws IOException + public void flushToServer(byte... bytes) throws IOException { - flush(server, datum); + flush(server, bytes); } private void flush(Socket socket, byte... bytes) throws IOException @@ -1040,6 +1272,12 @@ public class SslBytesServerTest return bytes; } + @Override + public String toString() + { + return "TLSRecord [" + type + "] " + bytes.length + " bytes"; + } + public enum Type { CHANGE_CIPHER_SPEC(20), ALERT(21), HANDSHAKE(22), APPLICATION(23);