Return-Path: X-Original-To: apmail-hadoop-common-commits-archive@www.apache.org Delivered-To: apmail-hadoop-common-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 0FE3B19D80 for ; Tue, 5 Apr 2016 19:30:38 +0000 (UTC) Received: (qmail 61465 invoked by uid 500); 5 Apr 2016 19:30:36 -0000 Delivered-To: apmail-hadoop-common-commits-archive@hadoop.apache.org Received: (qmail 60602 invoked by uid 500); 5 Apr 2016 19:30:35 -0000 Mailing-List: contact common-commits-help@hadoop.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: common-dev@hadoop.apache.org Delivered-To: mailing list common-commits@hadoop.apache.org Received: (qmail 59550 invoked by uid 99); 5 Apr 2016 19:30:35 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 05 Apr 2016 19:30:35 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id E93A0E2EF4; Tue, 5 Apr 2016 19:30:34 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: aengineer@apache.org To: common-commits@hadoop.apache.org Date: Tue, 05 Apr 2016 19:30:40 -0000 Message-Id: In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [07/50] [abbrv] hadoop git commit: HADOOP-12886. Exclude weak ciphers in SSLFactory through ssl-server.xml. Contributed by Wei-Chiu Chuang. 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/HDFS-1312 Commit: e4fc609d5d3739b7809057954c5233cfd1d1117b Parents: 37e23ce Author: Zhe ZHang Authored: Wed Mar 30 14:13:11 2016 -0700 Committer: Zhe ZHang 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 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(); + } 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 defaultEnabledCipherSuites = + new ArrayList(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);