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);