* I2CP over SSL, enable with i2cp.SSL=true

This commit is contained in:
zzz
2010-12-14 14:27:35 +00:00
parent 59af763dcd
commit 8efefeeb5b
6 changed files with 526 additions and 37 deletions

View File

@ -0,0 +1,183 @@
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 java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import net.i2p.I2PAppContext;
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 Log _log;
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) {
_log = ctx.logManager().getLog(I2CPSSLSocketFactory.class);
initSSLContext(ctx);
if (_factory == null)
throw new IOException("Unable to create SSL Context for I2CP Client");
_log.info("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) {
_log.error("Key Store init error", gse);
return;
} catch (IOException ioe) {
_log.error("Key Store init error", ioe);
return;
}
File dir = new File(context.getConfigDir(), CERT_DIR);
int adds = addCerts(dir, ks);
int totalAdds = adds;
if (adds > 0 && _log.shouldLog(Log.INFO))
_log.info("Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath());
File dir2 = new File(System.getProperty("user.dir"), CERT_DIR);
if (!dir.getAbsolutePath().equals(dir2.getAbsolutePath())) {
adds = addCerts(dir2, ks);
totalAdds += adds;
if (adds > 0 && _log.shouldLog(Log.INFO))
_log.info("Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath());
}
if (totalAdds > 0) {
if (_log.shouldLog(Log.INFO))
_log.info("Loaded total of " + totalAdds + " new trusted certificates");
} else {
_log.error("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.");
// 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) {
_log.error("SSL context init error", gse);
}
}
/**
* Load all X509 Certs from a directory and add them to the
* trusted set of certificates in the key store
*
* @return number successfully added
*/
private static int addCerts(File dir, KeyStore ks) {
if (_log.shouldLog(Log.INFO))
_log.info("Looking for X509 Certificates in " + dir.getAbsolutePath());
int added = 0;
if (dir.exists() && dir.isDirectory()) {
File[] files = dir.listFiles();
if (files != null) {
for (int i = 0; i < files.length; i++) {
File f = files[i];
if (!f.isFile())
continue;
// use file name as alias
String alias = f.getName().toLowerCase();
boolean success = addCert(f, alias, ks);
if (success)
added++;
}
}
}
return added;
}
/**
* Load an X509 Cert from a file and add it to the
* trusted set of certificates in the key store
*
* @return success
*/
private static boolean addCert(File file, String alias, KeyStore ks) {
InputStream fis = null;
try {
fis = new FileInputStream(file);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(fis);
if (_log.shouldLog(Log.INFO)) {
_log.info("Read X509 Certificate from " + file.getAbsolutePath() +
" Issuer: " + cert.getIssuerX500Principal() +
"; Valid From: " + cert.getNotBefore() +
" To: " + cert.getNotAfter());
}
try {
cert.checkValidity();
} catch (CertificateExpiredException cee) {
_log.error("Rejecting expired X509 Certificate: " + file.getAbsolutePath(), cee);
return false;
} catch (CertificateNotYetValidException cnyve) {
_log.error("Rejecting X509 Certificate not yet valid: " + file.getAbsolutePath(), cnyve);
return false;
}
ks.setCertificateEntry(alias, cert);
if (_log.shouldLog(Log.INFO))
_log.info("Now trusting X509 Certificate, Issuer: " + cert.getIssuerX500Principal());
} catch (GeneralSecurityException gse) {
_log.error("Error reading X509 Certificate: " + file.getAbsolutePath(), gse);
return false;
} catch (IOException ioe) {
_log.error("Error reading X509 Certificate: " + file.getAbsolutePath(), ioe);
return false;
} finally {
try { if (fis != null) fis.close(); } catch (IOException foo) {}
}
return true;
}
}

View File

@ -131,6 +131,9 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
private long _lastActivity;
private boolean _isReduced;
/** SSL interface (only) @since 0.8.3 */
protected static final String PROP_ENABLE_SSL = "i2cp.SSL";
void dateUpdated() {
_dateReceived = true;
synchronized (_dateReceivedLock) {
@ -181,7 +184,10 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
protected void loadConfig(Properties options) {
_options = new Properties();
_options.putAll(filter(options));
if (!_context.isRouterContext()) {
if (_context.isRouterContext()) {
// just for logging
_hostname = "[internal connection]";
} else {
_hostname = _options.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1");
String portNum = _options.getProperty(I2PClient.PROP_TCP_PORT, LISTEN_PORT + "");
try {
@ -195,6 +201,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
}
// auto-add auth if required, not set in the options, and we are in the same JVM
// TODO bypass this on router side for internal connections
if (_context.isRouterContext() &&
Boolean.valueOf(_context.getProperty("i2cp.auth")).booleanValue() &&
((!options.containsKey("i2cp.username")) || (!options.containsKey("i2cp.password")))) {
@ -302,7 +309,10 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
_queue = mgr.connect();
_reader = new QueuedI2CPMessageReader(_queue, this);
} else {
_socket = new Socket(_hostname, _portNum);
if (Boolean.valueOf(_options.getProperty(PROP_ENABLE_SSL)).booleanValue())
_socket = I2CPSSLSocketFactory.createSocket(_context, _hostname, _portNum);
else
_socket = new Socket(_hostname, _portNum);
// _socket.setSoTimeout(1000000); // Uhmmm we could really-really use a real timeout, and handle it.
_out = _socket.getOutputStream();
synchronized (_out) {

View File

@ -23,7 +23,6 @@ import net.i2p.internal.I2CPMessageQueue;
import net.i2p.internal.InternalClientManager;
import net.i2p.internal.QueuedI2CPMessageReader;
import net.i2p.util.I2PAppThread;
import net.i2p.util.InternalSocket;
/**
* Create a new session for doing naming and bandwidth queries only. Do not create a Destination.
@ -80,7 +79,10 @@ class I2PSimpleSession extends I2PSessionImpl2 {
_queue = mgr.connect();
_reader = new QueuedI2CPMessageReader(_queue, this);
} else {
_socket = new Socket(_hostname, _portNum);
if (Boolean.valueOf(getOptions().getProperty(PROP_ENABLE_SSL)).booleanValue())
_socket = I2CPSSLSocketFactory.createSocket(_context, _hostname, _portNum);
else
_socket = new Socket(_hostname, _portNum);
_out = _socket.getOutputStream();
synchronized (_out) {
_out.write(I2PClient.PROTOCOL_BYTE);

View File

@ -25,12 +25,12 @@ import net.i2p.util.Log;
* @author jrandom
*/
class ClientListenerRunner implements Runnable {
protected Log _log;
protected RouterContext _context;
protected ClientManager _manager;
protected final Log _log;
protected final RouterContext _context;
protected final ClientManager _manager;
protected ServerSocket _socket;
protected int _port;
private boolean _bindAllInterfaces;
protected final int _port;
protected final boolean _bindAllInterfaces;
protected boolean _running;
protected boolean _listening;
@ -38,18 +38,33 @@ class ClientListenerRunner implements Runnable {
public ClientListenerRunner(RouterContext context, ClientManager manager, int port) {
_context = context;
_log = _context.logManager().getLog(ClientListenerRunner.class);
_log = _context.logManager().getLog(getClass());
_manager = manager;
_port = port;
String val = context.getProperty(BIND_ALL_INTERFACES);
_bindAllInterfaces = Boolean.valueOf(val).booleanValue();
_bindAllInterfaces = context.getBooleanProperty(BIND_ALL_INTERFACES);
}
public void setPort(int port) { _port = port; }
public int getPort() { return _port; }
public boolean isListening() { return _running && _listening; }
/**
* Get a ServerSocket.
* Split out so it can be overridden for SSL.
* @since 0.8.3
*/
protected ServerSocket getServerSocket() throws IOException {
if (_bindAllInterfaces) {
if (_log.shouldLog(Log.INFO))
_log.info("Listening on port " + _port + " on all interfaces");
return new ServerSocket(_port);
} else {
String listenInterface = _context.getProperty(ClientManagerFacadeImpl.PROP_CLIENT_HOST,
ClientManagerFacadeImpl.DEFAULT_HOST);
if (_log.shouldLog(Log.INFO))
_log.info("Listening on port " + _port + " of the specific interface: " + listenInterface);
return new ServerSocket(_port, 0, InetAddress.getByName(listenInterface));
}
}
/**
* Start up the socket listener, listens for connections, and
* fires those connections off via {@link #runConnection runConnection}.
@ -62,18 +77,7 @@ class ClientListenerRunner implements Runnable {
int curDelay = 1000;
while (_running) {
try {
if (_bindAllInterfaces) {
if (_log.shouldLog(Log.INFO))
_log.info("Listening on port " + _port + " on all interfaces");
_socket = new ServerSocket(_port);
} else {
String listenInterface = _context.getProperty(ClientManagerFacadeImpl.PROP_CLIENT_HOST,
ClientManagerFacadeImpl.DEFAULT_HOST);
if (_log.shouldLog(Log.INFO))
_log.info("Listening on port " + _port + " of the specific interface: " + listenInterface);
_socket = new ServerSocket(_port, 0, InetAddress.getByName(listenInterface));
}
_socket = getServerSocket();
if (_log.shouldLog(Log.DEBUG))
_log.debug("ServerSocket created, before accept: " + _socket);
@ -131,7 +135,8 @@ class ClientListenerRunner implements Runnable {
}
/** give the i2cp client 5 seconds to show that they're really i2cp clients */
private final static int CONNECT_TIMEOUT = 5*1000;
protected final static int CONNECT_TIMEOUT = 5*1000;
private final static int LOOP_DELAY = 250;
/**
* Verify the first byte.
@ -141,16 +146,17 @@ class ClientListenerRunner implements Runnable {
protected boolean validate(Socket socket) {
try {
InputStream is = socket.getInputStream();
for (int i = 0; i < 20; i++) {
for (int i = 0; i < CONNECT_TIMEOUT / LOOP_DELAY; i++) {
if (is.available() > 0)
return is.read() == I2PClient.PROTOCOL_BYTE;
try { Thread.sleep(250); } catch (InterruptedException ie) {}
try { Thread.sleep(LOOP_DELAY); } catch (InterruptedException ie) {}
}
} catch (IOException ioe) {}
if (_log.shouldLog(Log.WARN))
_log.warn("Peer did not authenticate themselves as I2CP quickly enough, dropping");
return false;
}
/**
* Handle the connection by passing it off to a {@link ClientConnectionRunner ClientConnectionRunner}
*

View File

@ -53,6 +53,8 @@ class ClientManager {
/** Disable external interface, allow internal clients only @since 0.8.3 */
private static final String PROP_DISABLE_EXTERNAL = "i2cp.disableInterface";
/** SSL interface (only) @since 0.8.3 */
private static final String PROP_ENABLE_SSL = "i2cp.SSL";
/** ms to wait before rechecking for inbound messages to deliver to clients */
private final static int INBOUND_POLL_INTERVAL = 300;
@ -60,10 +62,10 @@ class ClientManager {
public ClientManager(RouterContext context, int port) {
_ctx = context;
_log = context.logManager().getLog(ClientManager.class);
_ctx.statManager().createRateStat("client.receiveMessageSize",
"How large are messages received by the client?",
"ClientMessages",
new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
//_ctx.statManager().createRateStat("client.receiveMessageSize",
// "How large are messages received by the client?",
// "ClientMessages",
// new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
_runners = new HashMap();
_pendingRunners = new HashSet();
startListeners(port);
@ -72,7 +74,11 @@ class ClientManager {
/** Todo: Start a 3rd listener for IPV6? */
private void startListeners(int port) {
if (!_ctx.getBooleanProperty(PROP_DISABLE_EXTERNAL)) {
_listener = new ClientListenerRunner(_ctx, this, port);
// there's no option to start both an SSL and non-SSL listener
if (_ctx.getBooleanProperty(PROP_ENABLE_SSL))
_listener = new SSLClientListenerRunner(_ctx, this, port);
else
_listener = new ClientListenerRunner(_ctx, this, port);
Thread t = new I2PThread(_listener, "ClientListener:" + port, true);
t.start();
}
@ -494,8 +500,8 @@ class ClientManager {
runner = getRunner(_msg.getDestinationHash());
if (runner != null) {
_ctx.statManager().addRateData("client.receiveMessageSize",
_msg.getPayload().getSize(), 0);
//_ctx.statManager().addRateData("client.receiveMessageSize",
// _msg.getPayload().getSize(), 0);
runner.receiveMessage(_msg.getDestination(), null, _msg.getPayload());
} else {
// no client connection...

View File

@ -0,0 +1,282 @@
package net.i2p.router.client;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.ServerSocket;
import java.security.KeyStore;
import java.security.GeneralSecurityException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.util.Arrays;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLContext;
import net.i2p.client.I2PClient;
import net.i2p.data.Base32;
import net.i2p.data.Base64;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;
import net.i2p.util.SecureDirectory;
import net.i2p.util.SecureFileOutputStream;
import net.i2p.util.ShellCommand;
/**
* SSL version of ClientListenerRunner
*
* @since 0.8.3
* @author zzz
*/
class SSLClientListenerRunner extends ClientListenerRunner {
private SSLServerSocketFactory _factory;
private static final String PROP_KEYSTORE_PASSWORD = "i2cp.keystorePassword";
private static final String DEFAULT_KEYSTORE_PASSWORD = "changeit";
private static final String PROP_KEY_PASSWORD = "i2cp.keyPassword";
private static final String KEY_ALIAS = "i2cp";
private static final String ASCII_KEYFILE = "i2cp.local.crt";
public SSLClientListenerRunner(RouterContext context, ClientManager manager, int port) {
super(context, manager, port);
}
/**
* @return success if it exists and we have a password, or it was created successfully.
*/
private boolean verifyKeyStore(File ks) {
if (ks.exists()) {
boolean rv = _context.getProperty(PROP_KEY_PASSWORD) != null;
if (!rv)
_log.error("I2CP SSL error, must set " + PROP_KEY_PASSWORD + " in " +
(new File(_context.getConfigDir(), "router.config")).getAbsolutePath());
return rv;
}
File dir = ks.getParentFile();
if (!dir.exists()) {
File sdir = new SecureDirectory(dir.getAbsolutePath());
if (!sdir.mkdir())
return false;
}
boolean rv = createKeyStore(ks);
// Now read it back out of the new keystore and save it in ascii form
// where the clients can get to it.
// Failure of this part is not fatal.
if (rv)
exportCert(ks);
return rv;
}
/**
* Call out to keytool to create a new keystore with a keypair in it.
* Trying to do this programatically is a nightmare, requiring either BouncyCastle
* libs or using proprietary Sun libs, and it's a huge mess.
* If successful, stores the keystore password and key password in router.config.
*
* @return success
*/
private boolean createKeyStore(File ks) {
// make a random 48 character password (30 * 8 / 5)
byte[] rand = new byte[30];
_context.random().nextBytes(rand);
String keyPassword = Base32.encode(rand);
// and one for the cname
_context.random().nextBytes(rand);
String cname = Base32.encode(rand) + ".i2cp.i2p.net";
String keytool = (new File(System.getProperty("java.home"), "bin/keytool")).getAbsolutePath();
String[] args = new String[] {
keytool,
"-genkey", // -genkeypair preferred in newer keytools, but this works with more
"-storetype", KeyStore.getDefaultType(),
"-keystore", ks.getAbsolutePath(),
"-storepass", DEFAULT_KEYSTORE_PASSWORD,
"-alias", KEY_ALIAS,
"-dname", "CN=" + cname + ",OU=I2CP,O=I2P Anonymous Network,L=XX,ST=XX,C=XX",
"-validity", "3652", // 10 years
"-keyalg", "DSA",
"-keysize", "1024",
"-keypass", keyPassword};
boolean success = (new ShellCommand()).executeSilentAndWaitTimed(args, 30); // 30 secs
if (success) {
success = ks.exists();
if (success) {
SecureFileOutputStream.setPerms(ks);
_context.router().setConfigSetting(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
_context.router().setConfigSetting(PROP_KEY_PASSWORD, keyPassword);
_context.router().saveConfig();
}
}
if (success) {
_log.logAlways(Log.INFO, "Created self-signed certificate for " + cname + " in keystore: " + ks.getAbsolutePath() + "\n" +
"The certificate name was generated randomly, and is not associated with your " +
"IP address, host name, router identity, or destination keys.");
} else {
_log.error("Failed to create I2CP SSL keystore using command line:");
StringBuilder buf = new StringBuilder(256);
for (int i = 0; i < args.length; i++) {
buf.append('"').append(args[i]).append("\" ");
}
_log.error(buf.toString());
_log.error("This is for the Sun/Oracle keytool, others may be incompatible.\n" +
"If you create the keystore manually, you must add " + PROP_KEYSTORE_PASSWORD + " and " + PROP_KEY_PASSWORD +
" to " + (new File(_context.getConfigDir(), "router.config")).getAbsolutePath());
}
return success;
}
/**
* Pull the cert back OUT of the keystore and save it as ascii
* so the clients can get to it.
*/
private void exportCert(File ks) {
File sdir = new SecureDirectory(_context.getConfigDir(), "certificates");
if (sdir.exists() || sdir.mkdir()) {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream fis = new FileInputStream(ks);
String ksPass = _context.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
keyStore.load(fis, ksPass.toCharArray());
fis.close();
Certificate cert = keyStore.getCertificate(KEY_ALIAS);
if (cert != null) {
File certFile = new File(sdir, ASCII_KEYFILE);
saveCert(cert, certFile);
} else {
_log.error("Error getting SSL cert to save as ASCII");
}
} catch (GeneralSecurityException gse) {
_log.error("Error saving ASCII SSL keys", gse);
} catch (IOException ioe) {
_log.error("Error saving ASCII SSL keys", ioe);
}
} else {
_log.error("Error saving ASCII SSL keys");
}
}
private static final int LINE_LENGTH = 64;
/**
* Modified from:
* http://www.exampledepot.com/egs/java.security.cert/ExportCert.html
*
* Write a certificate to a file in base64 format.
*/
private void saveCert(Certificate cert, File file) {
OutputStream os = null;
try {
// Get the encoded form which is suitable for exporting
byte[] buf = cert.getEncoded();
os = new SecureFileOutputStream(file);
PrintWriter wr = new PrintWriter(os);
wr.println("-----BEGIN CERTIFICATE-----");
String b64 = Base64.encode(buf, true); // true = use standard alphabet
for (int i = 0; i < b64.length(); i += LINE_LENGTH) {
wr.println(b64.substring(i, Math.min(i + LINE_LENGTH, b64.length())));
}
wr.println("-----END CERTIFICATE-----");
wr.flush();
} catch (CertificateEncodingException cee) {
_log.error("Error writing X509 Certificate " + file.getAbsolutePath(), cee);
} catch (IOException ioe) {
_log.error("Error writing X509 Certificate " + file.getAbsolutePath(), ioe);
} finally {
try { if (os != null) os.close(); } catch (IOException foo) {}
}
}
/**
* Sets up the SSLContext and sets the socket factory.
* @return success
*/
private boolean initializeFactory(File ks) {
String ksPass = _context.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
String keyPass = _context.getProperty(PROP_KEY_PASSWORD);
if (keyPass == null) {
_log.error("No key password, set " + PROP_KEY_PASSWORD +
" in " + (new File(_context.getConfigDir(), "router.config")).getAbsolutePath());
return false;
}
try {
SSLContext sslc = SSLContext.getInstance("TLS");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream fis = new FileInputStream(ks);
keyStore.load(fis, ksPass.toCharArray());
fis.close();
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, keyPass.toCharArray());
sslc.init(kmf.getKeyManagers(), null, _context.random());
_factory = sslc.getServerSocketFactory();
return true;
} catch (GeneralSecurityException gse) {
_log.error("Error loading SSL keys", gse);
} catch (IOException ioe) {
_log.error("Error loading SSL keys", ioe);
}
return false;
}
/**
* Get a SSLServerSocket.
*/
@Override
protected ServerSocket getServerSocket() throws IOException {
ServerSocket rv;
if (_bindAllInterfaces) {
if (_log.shouldLog(Log.INFO))
_log.info("Listening on port " + _port + " on all interfaces");
rv = _factory.createServerSocket(_port);
} else {
String listenInterface = _context.getProperty(ClientManagerFacadeImpl.PROP_CLIENT_HOST,
ClientManagerFacadeImpl.DEFAULT_HOST);
if (_log.shouldLog(Log.INFO))
_log.info("Listening on port " + _port + " of the specific interface: " + listenInterface);
rv = _factory.createServerSocket(_port, 0, InetAddress.getByName(listenInterface));
}
return rv;
}
/**
* Create (if necessary) and load the key store, then run.
*/
@Override
public void runServer() {
File keyStore = new File(_context.getConfigDir(), "keystore/i2cp.ks");
if (verifyKeyStore(keyStore) && initializeFactory(keyStore)) {
super.runServer();
} else {
_log.error("SSL I2CP server error - Failed to create or open key store");
}
}
/**
* Overridden because SSL handshake may need more time,
* and available() in super doesn't work.
* The handshake doesn't start until a read().
*/
@Override
protected boolean validate(Socket socket) {
try {
InputStream is = socket.getInputStream();
int oldTimeout = socket.getSoTimeout();
socket.setSoTimeout(4 * CONNECT_TIMEOUT);
boolean rv = is.read() == I2PClient.PROTOCOL_BYTE;
socket.setSoTimeout(oldTimeout);
return rv;
} catch (IOException ioe) {}
if (_log.shouldLog(Log.WARN))
_log.warn("Peer did not authenticate themselves as I2CP quickly enough, dropping");
return false;
}
}