diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java index 57f6ec870..1e8ea3f62 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java @@ -148,7 +148,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { setEntry(headers, "Accept-encoding", ""); socket.setReadTimeout(readTimeout); - Socket s = new Socket(remoteHost, remotePort); + Socket s = getSocket(remoteHost, remotePort); long afterSocket = getTunnel().getContext().clock().now(); // instead of i2ptunnelrunner, use something that reads the HTTP // request from the socket, modifies the headers, sends the request to the diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java index 36f998242..1b651c34a 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java @@ -137,7 +137,7 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable { buf.append("\r\n"); modifiedRegistration = buf.toString(); } - Socket s = new Socket(remoteHost, remotePort); + Socket s = getSocket(remoteHost, remotePort); new I2PTunnelRunner(s, socket, slock, null, modifiedRegistration.getBytes(), null); } catch (SocketException ex) { try { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java index 1b16007b7..47cea2bb3 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java @@ -14,6 +14,7 @@ import java.net.InetAddress; import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; +import java.security.GeneralSecurityException; import java.util.Iterator; import java.util.Properties; import java.util.concurrent.Executors; @@ -33,6 +34,7 @@ import net.i2p.client.streaming.I2PSocketManagerFactory; import net.i2p.data.Base64; import net.i2p.util.EventDispatcher; import net.i2p.util.I2PAppThread; +import net.i2p.util.I2PSSLSocketFactory; import net.i2p.util.Log; public class I2PTunnelServer extends I2PTunnelTask implements Runnable { @@ -43,11 +45,13 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { private final Object lock = new Object(); protected final Object slock = new Object(); + protected final Object sslLock = new Object(); protected final InetAddress remoteHost; protected final int remotePort; private final boolean _usePool; protected final Logging l; + private I2PSSLSocketFactory _sslFactory; private static final long DEFAULT_READ_TIMEOUT = 5*60*1000; /** default timeout to 5 minutes - override if desired */ @@ -56,6 +60,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { /** do we use threads? default true (ignored for standard servers, always false) */ private static final String PROP_USE_POOL = "i2ptunnel.usePool"; private static final boolean DEFAULT_USE_POOL = true; + public static final String PROP_USE_SSL = "useSSL"; /** apparently unused */ protected static volatile long __serverId = 0; /** max number of threads - this many slowlorisses will DOS this server, but too high could OOM the JVM */ @@ -462,17 +467,17 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { if (_log.shouldLog(Log.INFO)) _log.info("Incoming connection to '" + toString() + "' port " + socket.getLocalPort() + " from: " + socket.getPeerDestination().calculateHash() + " port " + socket.getPort()); - long afterAccept = I2PAppContext.getGlobalContext().clock().now(); + long afterAccept = getTunnel().getContext().clock().now(); long afterSocket = -1; //local is fast, so synchronously. Does not need that many //threads. try { socket.setReadTimeout(readTimeout); - Socket s = new Socket(remoteHost, remotePort); - afterSocket = I2PAppContext.getGlobalContext().clock().now(); + Socket s = getSocket(remoteHost, remotePort); + afterSocket = getTunnel().getContext().clock().now(); new I2PTunnelRunner(s, socket, slock, null, null); - long afterHandle = I2PAppContext.getGlobalContext().clock().now(); + long afterHandle = getTunnel().getContext().clock().now(); long timeToHandle = afterHandle - afterAccept; if ( (timeToHandle > 1000) && (_log.shouldLog(Log.WARN)) ) _log.warn("Took a while to handle the request for " + remoteHost + ':' + remotePort + @@ -487,5 +492,31 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { _log.error("Error while waiting for I2PConnections", ex); } } + + /** + * Get a regular or SSL socket depending on config + * + * @since 0.9.9 + */ + protected Socket getSocket(InetAddress remoteHost, int remotePort) throws IOException { + String opt = getTunnel().getClientOptions().getProperty(PROP_USE_SSL); + if (Boolean.parseBoolean(opt)) { + synchronized(sslLock) { + if (_sslFactory == null) { + try { + _sslFactory = new I2PSSLSocketFactory(getTunnel().getContext(), + true, "certificates/i2ptunnel"); + } catch (GeneralSecurityException gse) { + IOException ioe = new IOException("SSL Fail"); + ioe.initCause(gse); + throw ioe; + } + } + } + return _sslFactory.createSocket(remoteHost, remotePort); + } else { + return new Socket(remoteHost, remotePort); + } + } } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java index 3a5c001f6..f5fefdbd0 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java @@ -407,7 +407,7 @@ public class TunnelController implements Logging { /** * These are the ones stored with a prefix of "option." * - * @return keys with the "option." prefix stripped + * @return keys with the "option." prefix stripped, non-null * @since 0.9.1 Much better than getClientOptions() */ public Properties getClientOptionProps() { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java index 5d8ec7dcc..10316f999 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -31,6 +31,7 @@ import net.i2p.i2ptunnel.I2PTunnelConnectClient; import net.i2p.i2ptunnel.I2PTunnelHTTPClient; import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase; import net.i2p.i2ptunnel.I2PTunnelIRCClient; +import net.i2p.i2ptunnel.I2PTunnelServer; import net.i2p.i2ptunnel.TunnelController; import net.i2p.i2ptunnel.TunnelControllerGroup; import net.i2p.util.Addresses; @@ -770,6 +771,21 @@ public class IndexBean { _booleanOptions.add(I2PTunnelIRCClient.PROP_DCC); } + /** @since 0.9.9 */ + public void setUseSSL(String moo) { + _booleanOptions.add(I2PTunnelServer.PROP_USE_SSL); + } + + /** @since 0.9.9 */ + public boolean isSSLEnabled(int tunnel) { + TunnelController tun = getController(tunnel); + if (tun != null) { + Properties opts = tun.getClientOptionProps(); + return Boolean.parseBoolean(opts.getProperty(I2PTunnelServer.PROP_USE_SSL)); + } + return false; + } + protected static final String PROP_ENABLE_ACCESS_LIST = "i2cp.enableAccessList"; protected static final String PROP_ENABLE_BLACKLIST = "i2cp.enableBlackList"; @@ -1149,7 +1165,8 @@ public class IndexBean { I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH }; private static final String _booleanServerOpts[] = { - "i2cp.reduceOnIdle", "i2cp.encryptLeaseSet", PROP_ENABLE_ACCESS_LIST, PROP_ENABLE_BLACKLIST + "i2cp.reduceOnIdle", "i2cp.encryptLeaseSet", PROP_ENABLE_ACCESS_LIST, PROP_ENABLE_BLACKLIST, + I2PTunnelServer.PROP_USE_SSL }; private static final String _otherClientOpts[] = { "i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.closeIdleTime", diff --git a/apps/i2ptunnel/jsp/editServer.jsp b/apps/i2ptunnel/jsp/editServer.jsp index dce9e1f79..ea212eefb 100644 --- a/apps/i2ptunnel/jsp/editServer.jsp +++ b/apps/i2ptunnel/jsp/editServer.jsp @@ -121,6 +121,14 @@ input.default { width: 1px; height: 1px; visibility: hidden; } + <% if (!"streamrserver".equals(tunnelType)) { %> +
+ + class="tickbox" /> +
+ <% } /* !streamrserver */ %> <% if ("httpbidirserver".equals(tunnelType)) { %>
diff --git a/apps/i2ptunnel/jsp/index.jsp b/apps/i2ptunnel/jsp/index.jsp index 7524e4445..1845c2f31 100644 --- a/apps/i2ptunnel/jsp/index.jsp +++ b/apps/i2ptunnel/jsp/index.jsp @@ -114,12 +114,19 @@ <% if (indexBean.isServerTargetLinkValid(curServer)) { - %> - <%=indexBean.getServerTarget(curServer)%> + if (indexBean.isSSLEnabled(curServer)) { %> + <%=indexBean.getServerTarget(curServer)%> SSL + <% } else { %> + <%=indexBean.getServerTarget(curServer)%> <% + } } else { %><%=indexBean.getServerTarget(curServer)%> <% + if (indexBean.isSSLEnabled(curServer)) { %> + SSL + <% + } } %>
diff --git a/core/java/src/net/i2p/client/I2CPSSLSocketFactory.java b/core/java/src/net/i2p/client/I2CPSSLSocketFactory.java deleted file mode 100644 index d6bba72a5..000000000 --- a/core/java/src/net/i2p/client/I2CPSSLSocketFactory.java +++ /dev/null @@ -1,119 +0,0 @@ -package net.i2p.client; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.Socket; -import java.security.KeyStore; -import java.security.GeneralSecurityException; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManagerFactory; - -import net.i2p.I2PAppContext; -import net.i2p.crypto.KeyStoreUtil; -import net.i2p.util.Log; - -/** - * Loads trusted ASCII certs from ~/.i2p/certificates/ and $CWD/certificates/. - * Keeps a single static SSLContext for the whole JVM. - * - * @author zzz - * @since 0.8.3 - */ -class I2CPSSLSocketFactory { - - private static final Object _initLock = new Object(); - private static SSLSocketFactory _factory; - - private static final String CERT_DIR = "certificates"; - - /** - * Initializes the static SSL Context if required, then returns a socket - * to the host. - * - * @param ctx just for logging - * @throws IOException on init error or usual socket errors - */ - public static Socket createSocket(I2PAppContext ctx, String host, int port) throws IOException { - synchronized(_initLock) { - if (_factory == null) { - initSSLContext(ctx); - if (_factory == null) - throw new IOException("Unable to create SSL Context for I2CP Client"); - info(ctx, "I2CP Client-side SSL Context initialized"); - } - } - return _factory.createSocket(host, port); - } - - /** - * Loads certs from - * the ~/.i2p/certificates/ and $CWD/certificates/ directories. - */ - private static void initSSLContext(I2PAppContext context) { - KeyStore ks; - try { - ks = KeyStore.getInstance(KeyStore.getDefaultType()); - ks.load(null, "".toCharArray()); - } catch (GeneralSecurityException gse) { - error(context, "Key Store init error", gse); - return; - } catch (IOException ioe) { - error(context, "Key Store init error", ioe); - return; - } - - File dir = new File(context.getConfigDir(), CERT_DIR); - int adds = KeyStoreUtil.addCerts(dir, ks); - int totalAdds = adds; - if (adds > 0) - info(context, "Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath()); - - File dir2 = new File(System.getProperty("user.dir"), CERT_DIR); - if (!dir.getAbsolutePath().equals(dir2.getAbsolutePath())) { - adds = KeyStoreUtil.addCerts(dir2, ks); - totalAdds += adds; - if (adds > 0) - info(context, "Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath()); - } - if (totalAdds > 0) { - info(context, "Loaded total of " + totalAdds + " new trusted certificates"); - } else { - error(context, "No trusted certificates loaded (looked in " + - dir.getAbsolutePath() + (dir.getAbsolutePath().equals(dir2.getAbsolutePath()) ? "" : (" and " + dir2.getAbsolutePath())) + - ", I2CP SSL client connections will fail. " + - "Copy the file certificates/i2cp.local.crt from the router to the directory.", null); - // don't continue, since we didn't load the system keystore, we have nothing. - return; - } - - try { - SSLContext sslc = SSLContext.getInstance("TLS"); - TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - tmf.init(ks); - sslc.init(null, tmf.getTrustManagers(), context.random()); - _factory = sslc.getSocketFactory(); - } catch (GeneralSecurityException gse) { - error(context, "SSL context init error", gse); - } - } - - /** @since 0.9.8 */ - private static void info(I2PAppContext ctx, String msg) { - log(ctx, Log.INFO, msg, null); - } - - /** @since 0.9.8 */ - private static void error(I2PAppContext ctx, String msg, Throwable t) { - log(ctx, Log.ERROR, msg, t); - } - - /** @since 0.9.8 */ - private static void log(I2PAppContext ctx, int level, String msg, Throwable t) { - Log l = ctx.logManager().getLog(I2CPSSLSocketFactory.class); - l.log(level, msg, t); - } -} diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java index 29389bdea..ab81dd8fa 100644 --- a/core/java/src/net/i2p/client/I2PSessionImpl.java +++ b/core/java/src/net/i2p/client/I2PSessionImpl.java @@ -16,6 +16,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.UnknownHostException; +import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -43,6 +44,7 @@ import net.i2p.internal.I2CPMessageQueue; import net.i2p.internal.InternalClientManager; import net.i2p.internal.QueuedI2CPMessageReader; import net.i2p.util.I2PAppThread; +import net.i2p.util.I2PSSLSocketFactory; import net.i2p.util.LHMCache; import net.i2p.util.Log; import net.i2p.util.SimpleScheduler; @@ -438,10 +440,18 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa _queue = mgr.connect(); _reader = new QueuedI2CPMessageReader(_queue, this); } else { - if (Boolean.parseBoolean(_options.getProperty(PROP_ENABLE_SSL))) - _socket = I2CPSSLSocketFactory.createSocket(_context, _hostname, _portNum); - else + if (Boolean.parseBoolean(_options.getProperty(PROP_ENABLE_SSL))) { + try { + I2PSSLSocketFactory fact = new I2PSSLSocketFactory(_context, false, "certificates/i2cp"); + _socket = fact.createSocket(_hostname, _portNum); + } catch (GeneralSecurityException gse) { + IOException ioe = new IOException("SSL Fail"); + ioe.initCause(gse); + throw ioe; + } + } else { _socket = new Socket(_hostname, _portNum); + } // _socket.setSoTimeout(1000000); // Uhmmm we could really-really use a real timeout, and handle it. OutputStream out = _socket.getOutputStream(); out.write(I2PClient.PROTOCOL_BYTE); diff --git a/core/java/src/net/i2p/client/I2PSimpleSession.java b/core/java/src/net/i2p/client/I2PSimpleSession.java index 35b6422e2..e3a911e19 100644 --- a/core/java/src/net/i2p/client/I2PSimpleSession.java +++ b/core/java/src/net/i2p/client/I2PSimpleSession.java @@ -11,6 +11,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.UnknownHostException; +import java.security.GeneralSecurityException; import java.util.Properties; import net.i2p.I2PAppContext; @@ -19,6 +20,7 @@ import net.i2p.data.i2cp.DestReplyMessage; import net.i2p.data.i2cp.I2CPMessageReader; import net.i2p.internal.InternalClientManager; import net.i2p.internal.QueuedI2CPMessageReader; +import net.i2p.util.I2PSSLSocketFactory; /** * Create a new session for doing naming and bandwidth queries only. Do not create a Destination. @@ -67,10 +69,18 @@ class I2PSimpleSession extends I2PSessionImpl2 { _queue = mgr.connect(); _reader = new QueuedI2CPMessageReader(_queue, this); } else { - if (Boolean.parseBoolean(getOptions().getProperty(PROP_ENABLE_SSL))) - _socket = I2CPSSLSocketFactory.createSocket(_context, _hostname, _portNum); - else + if (Boolean.parseBoolean(getOptions().getProperty(PROP_ENABLE_SSL))) { + try { + I2PSSLSocketFactory fact = new I2PSSLSocketFactory(_context, false, "certificates/i2cp"); + _socket = fact.createSocket(_hostname, _portNum); + } catch (GeneralSecurityException gse) { + IOException ioe = new IOException("SSL Fail"); + ioe.initCause(gse); + throw ioe; + } + } else { _socket = new Socket(_hostname, _portNum); + } OutputStream out = _socket.getOutputStream(); out.write(I2PClient.PROTOCOL_BYTE); out.flush(); diff --git a/core/java/src/net/i2p/util/I2PSSLSocketFactory.java b/core/java/src/net/i2p/util/I2PSSLSocketFactory.java new file mode 100644 index 000000000..0751cdc28 --- /dev/null +++ b/core/java/src/net/i2p/util/I2PSSLSocketFactory.java @@ -0,0 +1,109 @@ +package net.i2p.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.security.KeyStore; +import java.security.GeneralSecurityException; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManagerFactory; + +import net.i2p.I2PAppContext; +import net.i2p.crypto.KeyStoreUtil; + +/** + * Loads trusted ASCII certs from ~/.i2p/certificates/ and $I2P/certificates/. + * + * @author zzz + * @since 0.9.9 moved from ../client, original since 0.8.3 + */ +public class I2PSSLSocketFactory { + + private final SSLSocketFactory _factory; + + /** + * @param relativeCertPath e.g. "certificates/i2cp" + * @since 0.9.9 was static + */ + public I2PSSLSocketFactory(I2PAppContext context, boolean loadSystemCerts, String relativeCertPath) + throws GeneralSecurityException { + _factory = initSSLContext(context, loadSystemCerts, relativeCertPath); + } + + /** + * Returns a socket to the host. + */ + public Socket createSocket(String host, int port) throws IOException { + return _factory.createSocket(host, port); + } + + /** + * Returns a socket to the host. + * @since 0.9.9 + */ + public Socket createSocket(InetAddress host, int port) throws IOException { + return _factory.createSocket(host, port); + } + + /** + * Loads certs from + * the ~/.i2p/certificates/ and $I2P/certificates/ directories. + */ + private SSLSocketFactory initSSLContext(I2PAppContext context, boolean loadSystemCerts, String relativeCertPath) + throws GeneralSecurityException { + Log log = context.logManager().getLog(I2PSSLSocketFactory.class); + KeyStore ks; + if (loadSystemCerts) { + ks = KeyStoreUtil.loadSystemKeyStore(); + if (ks == null) + throw new GeneralSecurityException("Key Store init error"); + } else { + try { + ks = KeyStore.getInstance(KeyStore.getDefaultType()); + ks.load(null, "".toCharArray()); + } catch (IOException ioe) { + throw new GeneralSecurityException("Key Store init error", ioe); + } + } + + File dir = new File(context.getConfigDir(), relativeCertPath); + int adds = KeyStoreUtil.addCerts(dir, ks); + int totalAdds = adds; + if (adds > 0) { + if (log.shouldLog(Log.INFO)) + log.info("Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath()); + } + + File dir2 = new File(context.getBaseDir(), relativeCertPath); + if (!dir.getAbsolutePath().equals(dir2.getAbsolutePath())) { + adds = KeyStoreUtil.addCerts(dir2, ks); + totalAdds += adds; + if (adds > 0) { + if (log.shouldLog(Log.INFO)) + log.info("Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath()); + } + } + if (totalAdds > 0 || loadSystemCerts) { + if (log.shouldLog(Log.INFO)) + log.info("Loaded total of " + totalAdds + " new trusted certificates"); + } else { + String msg = "No trusted certificates loaded (looked in " + + dir.getAbsolutePath() + (dir.getAbsolutePath().equals(dir2.getAbsolutePath()) ? "" : (" and " + dir2.getAbsolutePath())) + + ", SSL connections will fail. " + + "Copy the cert in " + relativeCertPath + " from the router to the directory."; + // don't continue, since we didn't load the system keystore, we have nothing. + throw new GeneralSecurityException(msg); + } + + SSLContext sslc = SSLContext.getInstance("TLS"); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(ks); + sslc.init(null, tmf.getTrustManagers(), context.random()); + return sslc.getSocketFactory(); + } +} diff --git a/router/java/src/net/i2p/router/client/SSLClientListenerRunner.java b/router/java/src/net/i2p/router/client/SSLClientListenerRunner.java index 9e8ba08ce..d65f75bce 100644 --- a/router/java/src/net/i2p/router/client/SSLClientListenerRunner.java +++ b/router/java/src/net/i2p/router/client/SSLClientListenerRunner.java @@ -118,8 +118,8 @@ class SSLClientListenerRunner extends ClientListenerRunner { * so the clients can get to it. */ private void exportCert(File ks) { - File sdir = new SecureDirectory(_context.getConfigDir(), "certificates"); - if (sdir.exists() || sdir.mkdir()) { + File sdir = new SecureDirectory(_context.getConfigDir(), "certificates/i2cp"); + if (sdir.exists() || sdir.mkdirs()) { String ksPass = _context.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD); File out = new File(sdir, ASCII_KEYFILE); boolean success = KeyStoreUtil.exportCert(ks, ksPass, KEY_ALIAS, out);