You are viewing a plain text version of this content. The canonical link for it is here.
Posted to common-commits@hadoop.apache.org by zh...@apache.org on 2016/03/30 23:13:33 UTC

hadoop git commit: HADOOP-12886. Exclude weak ciphers in SSLFactory through ssl-server.xml. Contributed by Wei-Chiu Chuang.

Repository: hadoop
Updated Branches:
  refs/heads/trunk 37e23ce45 -> e4fc609d5


HADOOP-12886. Exclude weak ciphers in SSLFactory through ssl-server.xml. Contributed by Wei-Chiu Chuang.


Project: http://git-wip-us.apache.org/repos/asf/hadoop/repo
Commit: http://git-wip-us.apache.org/repos/asf/hadoop/commit/e4fc609d
Tree: http://git-wip-us.apache.org/repos/asf/hadoop/tree/e4fc609d
Diff: http://git-wip-us.apache.org/repos/asf/hadoop/diff/e4fc609d

Branch: refs/heads/trunk
Commit: e4fc609d5d3739b7809057954c5233cfd1d1117b
Parents: 37e23ce
Author: Zhe ZHang <ze...@linkedin.com>
Authored: Wed Mar 30 14:13:11 2016 -0700
Committer: Zhe ZHang <ze...@linkedin.com>
Committed: Wed Mar 30 14:13:11 2016 -0700

----------------------------------------------------------------------
 .../apache/hadoop/security/ssl/SSLFactory.java  |  42 +++++-
 .../hadoop/security/ssl/TestSSLFactory.java     | 139 ++++++++++++++++++-
 2 files changed, 175 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/hadoop/blob/e4fc609d/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/SSLFactory.java
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/SSLFactory.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/SSLFactory.java
index ea65848..95cba80 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/SSLFactory.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/SSLFactory.java
@@ -23,6 +23,8 @@ import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.security.authentication.client.ConnectionConfigurator;
 import org.apache.hadoop.util.ReflectionUtils;
 import org.apache.hadoop.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import static org.apache.hadoop.util.PlatformName.IBM_JAVA;
 
 import javax.net.ssl.HostnameVerifier;
@@ -34,6 +36,11 @@ import javax.net.ssl.SSLSocketFactory;
 import java.io.IOException;
 import java.net.HttpURLConnection;
 import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
 
 /**
  * Factory that creates SSLEngine and SSLSocketFactory instances using
@@ -48,6 +55,7 @@ import java.security.GeneralSecurityException;
 @InterfaceAudience.Private
 @InterfaceStability.Evolving
 public class SSLFactory implements ConnectionConfigurator {
+  static final Logger LOG = LoggerFactory.getLogger(SSLFactory.class);
 
   @InterfaceAudience.Private
   public static enum Mode { CLIENT, SERVER }
@@ -60,7 +68,7 @@ public class SSLFactory implements ConnectionConfigurator {
     "hadoop.ssl.client.conf";
   public static final String SSL_SERVER_CONF_KEY =
     "hadoop.ssl.server.conf";
-  public static final String SSLCERTIFICATE = IBM_JAVA?"ibmX509":"SunX509"; 
+  public static final String SSLCERTIFICATE = IBM_JAVA?"ibmX509":"SunX509";
 
   public static final boolean DEFAULT_SSL_REQUIRE_CLIENT_CERT = false;
 
@@ -71,6 +79,8 @@ public class SSLFactory implements ConnectionConfigurator {
       "hadoop.ssl.enabled.protocols";
   public static final String DEFAULT_SSL_ENABLED_PROTOCOLS =
       "TLSv1,SSLv2Hello,TLSv1.1,TLSv1.2";
+  public static final String SSL_SERVER_EXCLUDE_CIPHER_LIST =
+      "ssl.server.exclude.cipher.list";
 
   private Configuration conf;
   private Mode mode;
@@ -80,6 +90,7 @@ public class SSLFactory implements ConnectionConfigurator {
   private KeyStoresFactory keystoresFactory;
 
   private String[] enabledProtocols = null;
+  private List<String> excludeCiphers;
 
   /**
    * Creates an SSLFactory.
@@ -105,6 +116,14 @@ public class SSLFactory implements ConnectionConfigurator {
 
     enabledProtocols = conf.getStrings(SSL_ENABLED_PROTOCOLS,
         DEFAULT_SSL_ENABLED_PROTOCOLS);
+    String excludeCiphersConf =
+        sslConf.get(SSL_SERVER_EXCLUDE_CIPHER_LIST, "");
+    if (excludeCiphersConf.isEmpty()) {
+      excludeCiphers = new LinkedList<String>();
+    } else {
+      LOG.debug("will exclude cipher suites: {}", excludeCiphersConf);
+      excludeCiphers = Arrays.asList(excludeCiphersConf.split(","));
+    }
   }
 
   private Configuration readSSLConfiguration(Mode mode) {
@@ -195,11 +214,32 @@ public class SSLFactory implements ConnectionConfigurator {
     } else {
       sslEngine.setUseClientMode(false);
       sslEngine.setNeedClientAuth(requireClientCert);
+      disableExcludedCiphers(sslEngine);
     }
     sslEngine.setEnabledProtocols(enabledProtocols);
     return sslEngine;
   }
 
+  private void disableExcludedCiphers(SSLEngine sslEngine) {
+    String[] cipherSuites = sslEngine.getEnabledCipherSuites();
+
+    ArrayList<String> defaultEnabledCipherSuites =
+        new ArrayList<String>(Arrays.asList(cipherSuites));
+    Iterator iterator = excludeCiphers.iterator();
+
+    while(iterator.hasNext()) {
+      String cipherName = (String)iterator.next();
+      if(defaultEnabledCipherSuites.contains(cipherName)) {
+        defaultEnabledCipherSuites.remove(cipherName);
+        LOG.debug("Disabling cipher suite {}.", cipherName);
+      }
+    }
+
+    cipherSuites = defaultEnabledCipherSuites.toArray(
+        new String[defaultEnabledCipherSuites.size()]);
+    sslEngine.setEnabledCipherSuites(cipherSuites);
+  }
+
   /**
    * Returns a configured SSLServerSocketFactory.
    *

http://git-wip-us.apache.org/repos/asf/hadoop/blob/e4fc609d/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/TestSSLFactory.java
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/TestSSLFactory.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/TestSSLFactory.java
index 004888c..b8a09ed 100644
--- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/TestSSLFactory.java
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/TestSSLFactory.java
@@ -17,24 +17,31 @@
  */
 package org.apache.hadoop.security.ssl;
 
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.FileUtil;
 import org.apache.hadoop.fs.Path;
-import org.apache.hadoop.security.alias.CredentialProvider;
 import org.apache.hadoop.security.alias.CredentialProviderFactory;
 import org.apache.hadoop.security.alias.JavaKeyStoreProvider;
+import org.apache.hadoop.test.GenericTestUtils;
+import org.apache.log4j.Level;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLSession;
 import java.io.File;
 import java.net.URL;
+import java.nio.ByteBuffer;
 import java.security.GeneralSecurityException;
 import java.security.KeyPair;
 import java.security.cert.X509Certificate;
@@ -42,13 +49,21 @@ import java.util.Collections;
 import java.util.Map;
 
 public class TestSSLFactory {
-
+  private static final Logger LOG = LoggerFactory
+      .getLogger(TestSSLFactory.class);
   private static final String BASEDIR =
     System.getProperty("test.build.dir", "target/test-dir") + "/" +
     TestSSLFactory.class.getSimpleName();
   private static final String KEYSTORES_DIR =
     new File(BASEDIR).getAbsolutePath();
   private String sslConfsDir;
+  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";
 
   @BeforeClass
   public static void setUp() throws Exception {
@@ -62,7 +77,7 @@ public class TestSSLFactory {
     throws Exception {
     Configuration conf = new Configuration();
     KeyStoreTestUtil.setupSSLConfig(KEYSTORES_DIR, sslConfsDir, conf,
-      clientCert, trustStore);
+      clientCert, trustStore, excludeCiphers);
     return conf;
   }
 
@@ -125,6 +140,120 @@ public class TestSSLFactory {
     serverMode(true, false);
   }
 
+  private void runDelegatedTasks(SSLEngineResult result, SSLEngine engine)
+    throws Exception {
+    Runnable runnable;
+    if (result.getHandshakeStatus() ==
+        SSLEngineResult.HandshakeStatus.NEED_TASK) {
+      while ((runnable = engine.getDelegatedTask()) != null) {
+        LOG.info("running delegated task...");
+        runnable.run();
+      }
+      SSLEngineResult.HandshakeStatus hsStatus = engine.getHandshakeStatus();
+      if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK) {
+        throw new Exception("handshake shouldn't need additional tasks");
+      }
+    }
+  }
+
+  private static boolean isEngineClosed(SSLEngine engine) {
+    return engine.isOutboundDone() && engine.isInboundDone();
+  }
+
+  private static void checkTransfer(ByteBuffer a, ByteBuffer b)
+    throws Exception {
+    a.flip();
+    b.flip();
+    assertTrue("transfer did not complete", a.equals(b));
+
+    a.position(a.limit());
+    b.position(b.limit());
+    a.limit(a.capacity());
+    b.limit(b.capacity());
+  }
+  @Test
+  public void testServerWeakCiphers() throws Exception {
+    // a simple test case to verify that SSL server rejects weak cipher suites,
+    // inspired by https://docs.oracle.com/javase/8/docs/technotes/guides/
+    //            security/jsse/samples/sslengine/SSLEngineSimpleDemo.java
+
+    // set up a client and a server SSLEngine object, and let them exchange
+    // data over ByteBuffer instead of network socket.
+    GenericTestUtils.setLogLevel(SSLFactory.LOG, Level.DEBUG);
+    final Configuration conf = createConfiguration(true, true);
+
+    SSLFactory serverSSLFactory = new SSLFactory(SSLFactory.Mode.SERVER, conf);
+    SSLFactory clientSSLFactory = new SSLFactory(SSLFactory.Mode.CLIENT, conf);
+
+    serverSSLFactory.init();
+    clientSSLFactory.init();
+
+    SSLEngine serverSSLEngine = serverSSLFactory.createSSLEngine();
+    SSLEngine clientSSLEngine = clientSSLFactory.createSSLEngine();
+    // client selects cipher suites excluded by server
+    clientSSLEngine.setEnabledCipherSuites(excludeCiphers.split(","));
+
+    // use the same buffer size for server and client.
+    SSLSession session = clientSSLEngine.getSession();
+    int appBufferMax = session.getApplicationBufferSize();
+    int netBufferMax = session.getPacketBufferSize();
+
+    ByteBuffer clientOut = ByteBuffer.wrap("client".getBytes());
+    ByteBuffer clientIn = ByteBuffer.allocate(appBufferMax);
+    ByteBuffer serverOut = ByteBuffer.wrap("server".getBytes());
+    ByteBuffer serverIn = ByteBuffer.allocate(appBufferMax);
+
+    // send data from client to server
+    ByteBuffer cTOs = ByteBuffer.allocateDirect(netBufferMax);
+    // send data from server to client
+    ByteBuffer sTOc = ByteBuffer.allocateDirect(netBufferMax);
+
+    boolean dataDone = false;
+    try {
+      /**
+       * Server and client engines call wrap()/unwrap() to perform handshaking,
+       * until both engines are closed.
+       */
+      while (!isEngineClosed(clientSSLEngine) ||
+          !isEngineClosed(serverSSLEngine)) {
+        LOG.info("client wrap " + wrap(clientSSLEngine, clientOut, cTOs));
+        LOG.info("server wrap " + wrap(serverSSLEngine, serverOut, sTOc));
+        cTOs.flip();
+        sTOc.flip();
+        LOG.info("client unwrap " + unwrap(clientSSLEngine, sTOc, clientIn));
+        LOG.info("server unwrap " + unwrap(serverSSLEngine, cTOs, serverIn));
+        cTOs.compact();
+        sTOc.compact();
+        if (!dataDone && (clientOut.limit() == serverIn.position()) &&
+            (serverOut.limit() == clientIn.position())) {
+          checkTransfer(serverOut, clientIn);
+          checkTransfer(clientOut, serverIn);
+
+          LOG.info("closing client");
+          clientSSLEngine.closeOutbound();
+          dataDone = true;
+        }
+      }
+      Assert.fail("The exception was not thrown");
+    } catch (SSLHandshakeException e) {
+      GenericTestUtils.assertExceptionContains("no cipher suites in common", e);
+    }
+  }
+
+  private SSLEngineResult wrap(SSLEngine engine, ByteBuffer from,
+      ByteBuffer to) throws Exception {
+    SSLEngineResult result = engine.wrap(from, to);
+    runDelegatedTasks(result, engine);
+    return result;
+  }
+
+  private SSLEngineResult unwrap(SSLEngine engine, ByteBuffer from,
+      ByteBuffer to) throws Exception {
+    SSLEngineResult result = engine.unwrap(from, to);
+    runDelegatedTasks(result, engine);
+    return result;
+  }
+
   @Test
   public void validHostnameVerifier() throws Exception {
     Configuration conf = createConfiguration(false, true);