|
@@ -19,11 +19,18 @@ package org.apache.hadoop.http;
|
|
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
|
import java.io.File;
|
|
|
+import java.io.IOException;
|
|
|
import java.io.InputStream;
|
|
|
+import java.net.InetAddress;
|
|
|
+import java.net.Socket;
|
|
|
import java.net.URI;
|
|
|
import java.net.URL;
|
|
|
+import java.net.UnknownHostException;
|
|
|
|
|
|
import javax.net.ssl.HttpsURLConnection;
|
|
|
+import javax.net.ssl.SSLHandshakeException;
|
|
|
+import javax.net.ssl.SSLSocket;
|
|
|
+import javax.net.ssl.SSLSocketFactory;
|
|
|
|
|
|
import org.apache.commons.logging.Log;
|
|
|
import org.apache.commons.logging.LogFactory;
|
|
@@ -43,6 +50,7 @@ import org.junit.Test;
|
|
|
* corresponding HTTPS URL.
|
|
|
*/
|
|
|
public class TestSSLHttpServer extends HttpServerFunctionalTest {
|
|
|
+
|
|
|
private static final String BASEDIR = System.getProperty("test.build.dir",
|
|
|
"target/test-dir") + "/" + TestSSLHttpServer.class.getSimpleName();
|
|
|
|
|
@@ -52,6 +60,23 @@ public class TestSSLHttpServer extends HttpServerFunctionalTest {
|
|
|
private static String keystoresDir;
|
|
|
private static String sslConfDir;
|
|
|
private static SSLFactory clientSslFactory;
|
|
|
+ private static final String excludeCiphers = "TLS_ECDHE_RSA_WITH_RC4_128_SHA,"
|
|
|
+ + "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA,"
|
|
|
+ + "SSL_RSA_WITH_DES_CBC_SHA,"
|
|
|
+ + "SSL_DHE_RSA_WITH_DES_CBC_SHA,"
|
|
|
+ + "SSL_RSA_EXPORT_WITH_RC4_40_MD5,"
|
|
|
+ + "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA,"
|
|
|
+ + "SSL_RSA_WITH_RC4_128_MD5";
|
|
|
+ private static final String oneEnabledCiphers = excludeCiphers
|
|
|
+ + ",TLS_RSA_WITH_AES_128_CBC_SHA";
|
|
|
+ private static final String exclusiveEnabledCiphers
|
|
|
+ = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,"
|
|
|
+ + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,"
|
|
|
+ + "TLS_RSA_WITH_AES_128_CBC_SHA,"
|
|
|
+ + "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,"
|
|
|
+ + "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,"
|
|
|
+ + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA,"
|
|
|
+ + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA";
|
|
|
|
|
|
@BeforeClass
|
|
|
public static void setup() throws Exception {
|
|
@@ -64,7 +89,8 @@ public class TestSSLHttpServer extends HttpServerFunctionalTest {
|
|
|
keystoresDir = new File(BASEDIR).getAbsolutePath();
|
|
|
sslConfDir = KeyStoreTestUtil.getClasspathDir(TestSSLHttpServer.class);
|
|
|
|
|
|
- KeyStoreTestUtil.setupSSLConfig(keystoresDir, sslConfDir, conf, false);
|
|
|
+ KeyStoreTestUtil.setupSSLConfig(keystoresDir, sslConfDir, conf, false, true,
|
|
|
+ excludeCiphers);
|
|
|
Configuration sslConf = new Configuration(false);
|
|
|
sslConf.addResource("ssl-server.xml");
|
|
|
sslConf.addResource("ssl-client.xml");
|
|
@@ -82,7 +108,9 @@ public class TestSSLHttpServer extends HttpServerFunctionalTest {
|
|
|
sslConf.get("ssl.server.keystore.type", "jks"))
|
|
|
.trustStore(sslConf.get("ssl.server.truststore.location"),
|
|
|
sslConf.get("ssl.server.truststore.password"),
|
|
|
- sslConf.get("ssl.server.truststore.type", "jks")).build();
|
|
|
+ sslConf.get("ssl.server.truststore.type", "jks"))
|
|
|
+ .excludeCiphers(
|
|
|
+ sslConf.get("ssl.server.exclude.cipher.list")).build();
|
|
|
server.addServlet("echo", "/echo", TestHttpServer.EchoServlet.class);
|
|
|
server.addServlet("longheader", "/longheader", LongHeaderServlet.class);
|
|
|
server.start();
|
|
@@ -107,10 +135,10 @@ public class TestSSLHttpServer extends HttpServerFunctionalTest {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Test that verifies headers can be up to 64K long.
|
|
|
- * The test adds a 63K header leaving 1K for other headers.
|
|
|
- * This is because the header buffer setting is for ALL headers,
|
|
|
- * names and values included. */
|
|
|
+ * Test that verifies headers can be up to 64K long. The test adds a 63K
|
|
|
+ * header leaving 1K for other headers. This is because the header buffer
|
|
|
+ * setting is for ALL headers, names and values included.
|
|
|
+ */
|
|
|
@Test
|
|
|
public void testLongHeader() throws Exception {
|
|
|
URL url = new URL(baseUrl, "/longheader");
|
|
@@ -128,4 +156,162 @@ public class TestSSLHttpServer extends HttpServerFunctionalTest {
|
|
|
return out.toString();
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Test that verifies that excluded ciphers (SSL_RSA_WITH_RC4_128_SHA,
|
|
|
+ * TLS_ECDH_ECDSA_WITH_RC4_128_SHA,TLS_ECDH_RSA_WITH_RC4_128_SHA,
|
|
|
+ * TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,TLS_ECDHE_RSA_WITH_RC4_128_SHA) are not
|
|
|
+ * available for negotiation during SSL connection.
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testExcludedCiphers() throws Exception {
|
|
|
+ URL url = new URL(baseUrl, "/echo?a=b&c=d");
|
|
|
+ HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
|
|
|
+ SSLSocketFactory sslSocketF = clientSslFactory.createSSLSocketFactory();
|
|
|
+ PrefferedCipherSSLSocketFactory testPreferredCipherSSLSocketF
|
|
|
+ = new PrefferedCipherSSLSocketFactory(sslSocketF,
|
|
|
+ excludeCiphers.split(","));
|
|
|
+ conn.setSSLSocketFactory(testPreferredCipherSSLSocketF);
|
|
|
+ assertFalse("excludedCipher list is empty", excludeCiphers.isEmpty());
|
|
|
+ try {
|
|
|
+ InputStream in = conn.getInputStream();
|
|
|
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
|
+ IOUtils.copyBytes(in, out, 1024);
|
|
|
+ fail("No Ciphers in common, SSLHandshake must fail.");
|
|
|
+ } catch (SSLHandshakeException ex) {
|
|
|
+ LOG.info("No Ciphers in common, expected succesful test result.", ex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Test that verified that additionally included cipher
|
|
|
+ * TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA is only available cipher for working
|
|
|
+ * TLS connection from client to server disabled for all other common ciphers.
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testOneEnabledCiphers() throws Exception {
|
|
|
+ URL url = new URL(baseUrl, "/echo?a=b&c=d");
|
|
|
+ HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
|
|
|
+ SSLSocketFactory sslSocketF = clientSslFactory.createSSLSocketFactory();
|
|
|
+ PrefferedCipherSSLSocketFactory testPreferredCipherSSLSocketF
|
|
|
+ = new PrefferedCipherSSLSocketFactory(sslSocketF,
|
|
|
+ oneEnabledCiphers.split(","));
|
|
|
+ conn.setSSLSocketFactory(testPreferredCipherSSLSocketF);
|
|
|
+ assertFalse("excludedCipher list is empty", oneEnabledCiphers.isEmpty());
|
|
|
+ try {
|
|
|
+ InputStream in = conn.getInputStream();
|
|
|
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
|
+ IOUtils.copyBytes(in, out, 1024);
|
|
|
+ assertEquals(out.toString(), "a:b\nc:d\n");
|
|
|
+ LOG.info("Atleast one additional enabled cipher than excluded ciphers,"
|
|
|
+ + " expected successful test result.");
|
|
|
+ } catch (SSLHandshakeException ex) {
|
|
|
+ fail("Atleast one additional cipher available for successful handshake."
|
|
|
+ + " Unexpected test failure: " + ex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Test verifies that mutually exclusive server's disabled cipher suites and
|
|
|
+ * client's enabled cipher suites can successfully establish TLS connection.
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testExclusiveEnabledCiphers() throws Exception {
|
|
|
+ URL url = new URL(baseUrl, "/echo?a=b&c=d");
|
|
|
+ HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
|
|
|
+ SSLSocketFactory sslSocketF = clientSslFactory.createSSLSocketFactory();
|
|
|
+ PrefferedCipherSSLSocketFactory testPreferredCipherSSLSocketF
|
|
|
+ = new PrefferedCipherSSLSocketFactory(sslSocketF,
|
|
|
+ exclusiveEnabledCiphers.split(","));
|
|
|
+ conn.setSSLSocketFactory(testPreferredCipherSSLSocketF);
|
|
|
+ assertFalse("excludedCipher list is empty",
|
|
|
+ exclusiveEnabledCiphers.isEmpty());
|
|
|
+ try {
|
|
|
+ InputStream in = conn.getInputStream();
|
|
|
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
|
+ IOUtils.copyBytes(in, out, 1024);
|
|
|
+ assertEquals(out.toString(), "a:b\nc:d\n");
|
|
|
+ LOG.info("Atleast one additional enabled cipher than excluded ciphers,"
|
|
|
+ + " expected successful test result.");
|
|
|
+ } catch (SSLHandshakeException ex) {
|
|
|
+ fail("Atleast one additional cipher available for successful handshake."
|
|
|
+ + " Unexpected test failure: " + ex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private class PrefferedCipherSSLSocketFactory extends SSLSocketFactory {
|
|
|
+ private final SSLSocketFactory delegateSocketFactory;
|
|
|
+ private final String[] enabledCipherSuites;
|
|
|
+
|
|
|
+ public PrefferedCipherSSLSocketFactory(SSLSocketFactory sslSocketFactory,
|
|
|
+ String[] pEnabledCipherSuites) {
|
|
|
+ delegateSocketFactory = sslSocketFactory;
|
|
|
+ if (null != pEnabledCipherSuites && pEnabledCipherSuites.length > 0) {
|
|
|
+ enabledCipherSuites = pEnabledCipherSuites;
|
|
|
+ } else {
|
|
|
+ enabledCipherSuites = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String[] getDefaultCipherSuites() {
|
|
|
+ return delegateSocketFactory.getDefaultCipherSuites();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String[] getSupportedCipherSuites() {
|
|
|
+ return delegateSocketFactory.getSupportedCipherSuites();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Socket createSocket(Socket socket, String string, int i, boolean bln)
|
|
|
+ throws IOException {
|
|
|
+ SSLSocket sslSocket = (SSLSocket) delegateSocketFactory.createSocket(
|
|
|
+ socket, string, i, bln);
|
|
|
+ if (null != enabledCipherSuites) {
|
|
|
+ sslSocket.setEnabledCipherSuites(enabledCipherSuites);
|
|
|
+ }
|
|
|
+ return sslSocket;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Socket createSocket(String string, int i) throws IOException,
|
|
|
+ UnknownHostException {
|
|
|
+ SSLSocket sslSocket = (SSLSocket) delegateSocketFactory.createSocket(
|
|
|
+ string, i);
|
|
|
+ if (null != enabledCipherSuites) {
|
|
|
+ sslSocket.setEnabledCipherSuites(enabledCipherSuites);
|
|
|
+ }
|
|
|
+ return sslSocket;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Socket createSocket(String string, int i, InetAddress ia, int i1)
|
|
|
+ throws IOException, UnknownHostException {
|
|
|
+ SSLSocket sslSocket = (SSLSocket) delegateSocketFactory.createSocket(
|
|
|
+ string, i, ia, i1);
|
|
|
+ if (null != enabledCipherSuites) {
|
|
|
+ sslSocket.setEnabledCipherSuites(enabledCipherSuites);
|
|
|
+ }
|
|
|
+ return sslSocket;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Socket createSocket(InetAddress ia, int i) throws IOException {
|
|
|
+ SSLSocket sslSocket = (SSLSocket) delegateSocketFactory.createSocket(ia,
|
|
|
+ i);
|
|
|
+ if (null != enabledCipherSuites) {
|
|
|
+ sslSocket.setEnabledCipherSuites(enabledCipherSuites);
|
|
|
+ }
|
|
|
+ return sslSocket;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Socket createSocket(InetAddress ia, int i, InetAddress ia1, int i1)
|
|
|
+ throws IOException {
|
|
|
+ SSLSocket sslSocket = (SSLSocket) delegateSocketFactory.createSocket(ia,
|
|
|
+ i, ia1, i1);
|
|
|
+ if (null != enabledCipherSuites) {
|
|
|
+ sslSocket.setEnabledCipherSuites(enabledCipherSuites);
|
|
|
+ }
|
|
|
+ return sslSocket;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|