diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java index 7a7328c3c..3473a1831 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java @@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicLong; +import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; import net.i2p.I2PAppContext; @@ -36,6 +37,7 @@ import net.i2p.client.streaming.I2PSocketOptions; import net.i2p.data.Destination; import net.i2p.util.EventDispatcher; import net.i2p.util.I2PAppThread; +import net.i2p.util.I2PSSLSocketFactory; import net.i2p.util.Log; public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runnable { @@ -622,6 +624,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna } SSLServerSocketFactory fact = SSLClientUtil.initializeFactory(opts); ss = fact.createServerSocket(localPort, 0, addr); + I2PSSLSocketFactory.setProtocolsAndCiphers((SSLServerSocket) ss); } else { ss = new ServerSocket(localPort, 0, addr); } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java index b8799df16..92fbd1a06 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java @@ -37,6 +37,7 @@ import net.i2p.util.FileUtil; import net.i2p.util.I2PAppThread; import net.i2p.util.PortMapper; import net.i2p.util.SecureDirectory; +import net.i2p.util.I2PSSLSocketFactory; import net.i2p.util.SystemVersion; import org.eclipse.jetty.security.HashLoginService; import org.eclipse.jetty.security.ConstraintMapping; @@ -464,6 +465,10 @@ public class RouterConsoleRunner implements RouterApp { sslFactory.setKeyStorePassword(_context.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD)); // the X.509 cert password (if not present, verifyKeyStore() returned false) sslFactory.setKeyManagerPassword(_context.getProperty(PROP_KEY_PASSWORD, "thisWontWork")); + sslFactory.addExcludeProtocols(I2PSSLSocketFactory.EXCLUDE_PROTOCOLS.toArray( + new String[I2PSSLSocketFactory.EXCLUDE_PROTOCOLS.size()])); + sslFactory.addExcludeCipherSuites(I2PSSLSocketFactory.INCLUDE_CIPHERS.toArray( + new String[I2PSSLSocketFactory.EXCLUDE_CIPHERS.size()])); StringTokenizer tok = new StringTokenizer(_sslListenHost, " ,"); while (tok.hasMoreTokens()) { String host = tok.nextToken().trim(); diff --git a/core/java/src/net/i2p/util/I2PSSLSocketFactory.java b/core/java/src/net/i2p/util/I2PSSLSocketFactory.java index fffbb4f80..5f5981c8b 100644 --- a/core/java/src/net/i2p/util/I2PSSLSocketFactory.java +++ b/core/java/src/net/i2p/util/I2PSSLSocketFactory.java @@ -1,13 +1,42 @@ package net.i2p.util; +/* + * Contains code adapted from: + * Jetty SslContextFactory.java + * + * ======================================================================= + * Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd. + * ------------------------------------------------------------------------ + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + * ======================================================================== + */ + import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; import java.security.KeyStore; import java.security.GeneralSecurityException; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; @@ -22,6 +51,103 @@ import net.i2p.crypto.KeyStoreUtil; */ public class I2PSSLSocketFactory { + /** + * Unmodifiable. + * Public for RouterConsoleRunner. + * @since 0.9.16 + */ + public static final List EXCLUDE_PROTOCOLS = Collections.unmodifiableList(Arrays.asList(new String[] { + "SSLv2Hello", "SSLv3" + })); + + /** + * Java 7 does not enable 1.1 or 1.2 by default on the client side. + * Java 8 does enable 1.1 and 1.2 by default on the client side. + * ref: http://docs.oracle.com/javase/7/docs/technotes/guides/security/SunProviders.html + * Unmodifiable. + * Public for RouterConsoleRunner. + * @since 0.9.16 + */ + public static final List INCLUDE_PROTOCOLS = Collections.unmodifiableList(Arrays.asList(new String[] { + "TLSv1", "TLSv1.1", "TLSv1.2" + })); + + /** + * We exclude everything that Java 8 disables by default, plus some others. + * ref: http://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html + * Unmodifiable. + * Public for RouterConsoleRunner. + * @since 0.9.16 + */ + public static final List EXCLUDE_CIPHERS = Collections.unmodifiableList(Arrays.asList(new String[] { + // following are disabled by default in Java 8 + "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA", + "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5", + "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA", + "SSL_DH_anon_WITH_DES_CBC_SHA", + "SSL_DH_anon_WITH_RC4_128_MD5", + "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", + "SSL_DHE_DSS_WITH_DES_CBC_SHA", + "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", + "SSL_DHE_RSA_WITH_DES_CBC_SHA", + "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", + "SSL_RSA_EXPORT_WITH_RC4_40_MD5", + "SSL_RSA_WITH_DES_CBC_SHA", + "SSL_RSA_WITH_NULL_MD5", + "SSL_RSA_WITH_NULL_SHA", + "TLS_DH_anon_WITH_AES_128_CBC_SHA", + "TLS_DH_anon_WITH_AES_128_CBC_SHA256", + "TLS_DH_anon_WITH_AES_128_GCM_SHA256", + "TLS_DH_anon_WITH_AES_256_CBC_SHA", + "TLS_DH_anon_WITH_AES_256_CBC_SHA256", + "TLS_DH_anon_WITH_AES_256_GCM_SHA384", + "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", + "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", + "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", + "TLS_ECDH_anon_WITH_NULL_SHA", + "TLS_ECDH_anon_WITH_RC4_128_SHA", + "TLS_ECDH_ECDSA_WITH_NULL_SHA", + "TLS_ECDHE_ECDSA_WITH_NULL_SHA", + "TLS_ECDHE_RSA_WITH_NULL_SHA", + "TLS_ECDH_RSA_WITH_NULL_SHA", + "TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5", + "TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA", + "TLS_KRB5_EXPORT_WITH_RC4_40_MD5", + "TLS_KRB5_EXPORT_WITH_RC4_40_SHA", + "TLS_KRB5_WITH_3DES_EDE_CBC_MD5", + "TLS_KRB5_WITH_3DES_EDE_CBC_SHA", + "TLS_KRB5_WITH_DES_CBC_MD5", + "TLS_KRB5_WITH_DES_CBC_SHA", + "TLS_KRB5_WITH_RC4_128_MD5", + "TLS_KRB5_WITH_RC4_128_SHA", + "TLS_RSA_WITH_NULL_SHA256", + // following are disabled because they are SSLv3 + "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA", + "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + "SSL_RSA_WITH_3DES_EDE_CBC_SHA", + "SSL_RSA_WITH_RC4_128_MD5", + "SSL_RSA_WITH_RC4_128_SHA", + // following are disabled because they are RC4 + "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", + // following are disabled because they are 3DES + "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", + "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", + "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA" + })); + + /** + * Nothing for now. + * There's nothing disabled by default we would want to enable. + * Unmodifiable. + * Public for RouterConsoleRunner. + * @since 0.9.16 + */ + public static final List INCLUDE_CIPHERS = Collections.emptyList(); + private final SSLSocketFactory _factory; /** @@ -37,7 +163,9 @@ public class I2PSSLSocketFactory { * Returns a socket to the host. */ public Socket createSocket(String host, int port) throws IOException { - return _factory.createSocket(host, port); + SSLSocket rv = (SSLSocket) _factory.createSocket(host, port); + setProtocolsAndCiphers(rv); + return rv; } /** @@ -45,7 +173,9 @@ public class I2PSSLSocketFactory { * @since 0.9.9 */ public Socket createSocket(InetAddress host, int port) throws IOException { - return _factory.createSocket(host, port); + SSLSocket rv = (SSLSocket) _factory.createSocket(host, port); + setProtocolsAndCiphers(rv); + return rv; } /** @@ -104,4 +234,106 @@ public class I2PSSLSocketFactory { sslc.init(null, tmf.getTrustManagers(), context.random()); return sslc.getSocketFactory(); } + + /** + * Select protocols and cipher suites to be used + * based on configured inclusion and exclusion lists + * as well as enabled and supported protocols and cipher suites. + * + * Adapted from Jetty SslContextFactory.java + * + * @since 0.9.16 + */ + public static void setProtocolsAndCiphers(SSLSocket socket) { + socket.setEnabledProtocols(selectProtocols(socket.getEnabledProtocols(), + socket.getSupportedProtocols())); + socket.setEnabledCipherSuites(selectCipherSuites(socket.getEnabledCipherSuites(), + socket.getSupportedCipherSuites())); + } + + /** + * Select protocols and cipher suites to be used + * based on configured inclusion and exclusion lists + * as well as enabled and supported protocols and cipher suites. + * + * Adapted from Jetty SslContextFactory.java + * + * @since 0.9.16 + */ + public static void setProtocolsAndCiphers(SSLServerSocket socket) { + socket.setEnabledProtocols(selectProtocols(socket.getEnabledProtocols(), + socket.getSupportedProtocols())); + socket.setEnabledCipherSuites(selectCipherSuites(socket.getEnabledCipherSuites(), + socket.getSupportedCipherSuites())); + } + + /** + * Select protocols to be used + * based on configured inclusion and exclusion lists + * as well as enabled and supported protocols. + * + * Adapted from Jetty SslContextFactory.java + * + * @param enabledProtocols Array of enabled protocols + * @param supportedProtocols Array of supported protocols + * @return Array of protocols to enable + * @since 0.9.16 + */ + private static String[] selectProtocols(String[] enabledProtocols, String[] supportedProtocols) { + return select(enabledProtocols, supportedProtocols, INCLUDE_PROTOCOLS, EXCLUDE_PROTOCOLS); + } + + /** + * Select cipher suites to be used + * based on configured inclusion and exclusion lists + * as well as enabled and supported cipher suite lists. + * + * Adapted from Jetty SslContextFactory.java + * + * @param enabledCipherSuites Array of enabled cipher suites + * @param supportedCipherSuites Array of supported cipher suites + * @return Array of cipher suites to enable + * @since 0.9.16 + */ + private static String[] selectCipherSuites(String[] enabledCipherSuites, String[] supportedCipherSuites) { + return select(enabledCipherSuites, supportedCipherSuites, INCLUDE_CIPHERS, EXCLUDE_CIPHERS); + } + + /** + * Adapted from Jetty SslContextFactory.java + * + * @param toEnable Add all these to what is enabled, if supported + * @param toExclude Remove all these from what is enabled + * @since 0.9.16 + */ + private static String[] select(String[] enabledArr, String[] supportedArr, + List toEnable, List toExclude) { + Log log = I2PAppContext.getGlobalContext().logManager().getLog(I2PSSLSocketFactory.class); + Set selected = new HashSet(enabledArr.length); + selected.addAll(Arrays.asList(enabledArr)); + selected.removeAll(toExclude); + Set supported = new HashSet(supportedArr.length); + supported.addAll(Arrays.asList(supportedArr)); + for (String s : toEnable) { + if (supported.contains(s)) { + if (selected.add(s)) { + if (log.shouldLog(Log.INFO)) + log.info("Added, previously disabled: " + s); + } + } else if (log.shouldLog(Log.INFO)) { + log.info("Not supported in this JVM: " + s); + } + } + if (selected.isEmpty()) { + // shouldn't happen, Java 6 supports TLSv1 + log.logAlways(Log.WARN, "No TLS support for SSLEepGet, falling back"); + return enabledArr; + } + if (log.shouldLog(Log.DEBUG)) { + List foo = new ArrayList(selected); + Collections.sort(foo); + log.debug("Selected: " + foo); + } + return selected.toArray(new String[selected.size()]); + } } diff --git a/core/java/src/net/i2p/util/SSLEepGet.java b/core/java/src/net/i2p/util/SSLEepGet.java index 9236d6cae..4cf540089 100644 --- a/core/java/src/net/i2p/util/SSLEepGet.java +++ b/core/java/src/net/i2p/util/SSLEepGet.java @@ -50,9 +50,13 @@ import java.security.KeyStore; import java.security.GeneralSecurityException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.util.Arrays; import java.util.Locale; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; @@ -254,6 +258,50 @@ public class SSLEepGet extends EepGet { X509TrustManager defaultTrustManager = (X509TrustManager)tmf.getTrustManagers()[0]; _stm = new SavingTrustManager(defaultTrustManager); sslc.init(null, new TrustManager[] {_stm}, null); + if (_log.shouldLog(Log.DEBUG)) { + SSLEngine eng = sslc.createSSLEngine(); + SSLParameters params = sslc.getDefaultSSLParameters(); + String[] s = eng.getSupportedProtocols(); + Arrays.sort(s); + _log.debug("Supported protocols: " + s.length); + for (int i = 0; i < s.length; i++) { + _log.debug(s[i]); + } + s = eng.getEnabledProtocols(); + Arrays.sort(s); + _log.debug("Enabled protocols: " + s.length); + for (int i = 0; i < s.length; i++) { + _log.debug(s[i]); + } + s = params.getProtocols(); + if (s == null) + s = new String[0]; + _log.debug("Default protocols: " + s.length); + Arrays.sort(s); + for (int i = 0; i < s.length; i++) { + _log.debug(s[i]); + } + s = eng.getSupportedCipherSuites(); + Arrays.sort(s); + _log.debug("Supported ciphers: " + s.length); + for (int i = 0; i < s.length; i++) { + _log.debug(s[i]); + } + s = eng.getEnabledCipherSuites(); + Arrays.sort(s); + _log.debug("Enabled ciphers: " + s.length); + for (int i = 0; i < s.length; i++) { + _log.debug(s[i]); + } + s = params.getCipherSuites(); + if (s == null) + s = new String[0]; + _log.debug("Default ciphers: " + s.length); + Arrays.sort(s); + for (int i = 0; i < s.length; i++) { + _log.debug(s[i]); + } + } return sslc; } catch (GeneralSecurityException gse) { _log.error("Key Store update error", gse); @@ -505,6 +553,8 @@ public class SSLEepGet extends EepGet { _proxy = _sslContext.getSocketFactory().createSocket(host, port); else _proxy = SSLSocketFactory.getDefault().createSocket(host, port); + SSLSocket socket = (SSLSocket) _proxy; + I2PSSLSocketFactory.setProtocolsAndCiphers(socket); } else { throw new MalformedURLException("Only https supported: " + _actualURL); } diff --git a/history.txt b/history.txt index c88f78b35..6bba0801f 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,7 @@ +2014-10-15 zzz + * Console, I2CP, i2ptunnel, SSLEepGet: Set allowed SSL + protocols and ciphers + 2014-10-14 zzz * I2NP: Implement DatabaseLookupMessage search type field diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 7ca1a3ec2..5a58b19bc 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 10; + public final static long BUILD = 11; /** for example "-test" */ public final static String EXTRA = ""; diff --git a/router/java/src/net/i2p/router/client/SSLClientListenerRunner.java b/router/java/src/net/i2p/router/client/SSLClientListenerRunner.java index 948cb7527..33abd394b 100644 --- a/router/java/src/net/i2p/router/client/SSLClientListenerRunner.java +++ b/router/java/src/net/i2p/router/client/SSLClientListenerRunner.java @@ -13,12 +13,14 @@ import java.util.HashMap; import java.util.Map; import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLContext; import net.i2p.client.I2PClient; import net.i2p.crypto.KeyStoreUtil; import net.i2p.router.RouterContext; +import net.i2p.util.I2PSSLSocketFactory; import net.i2p.util.Log; import net.i2p.util.SecureDirectory; @@ -174,6 +176,7 @@ class SSLClientListenerRunner extends ClientListenerRunner { _log.info("Listening on port " + _port + " of the specific interface: " + listenInterface); rv = _factory.createServerSocket(_port, 0, InetAddress.getByName(listenInterface)); } + I2PSSLSocketFactory.setProtocolsAndCiphers((SSLServerSocket) rv); return rv; }