forked from I2P_Developers/i2p.i2p
SAM: Use the Destination cache
Comment out some unused methods SAM client: Add SSL forward support Handle header line in forwarded stream Name some threads, number some others
This commit is contained in:
@ -207,8 +207,8 @@ public class SAMBridge implements Runnable, ClientApp {
|
||||
*
|
||||
* @param name name of the destination
|
||||
* @return null if the name does not exist, or if it is improperly formatted
|
||||
* @deprecated unused
|
||||
*/
|
||||
/****
|
||||
public Destination getDestination(String name) {
|
||||
synchronized (nameToPrivKeys) {
|
||||
String val = nameToPrivKeys.get(name);
|
||||
@ -224,6 +224,7 @@ public class SAMBridge implements Runnable, ClientApp {
|
||||
}
|
||||
}
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* Retrieve the I2P private keystream for the given name, formatted
|
||||
|
@ -226,8 +226,7 @@ class SAMStreamSession {
|
||||
return false;
|
||||
}
|
||||
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(dest);
|
||||
Destination d = SAMUtils.getDest(dest);
|
||||
|
||||
I2PSocketOptions opts = socketMgr.buildOptions(props);
|
||||
if (props.getProperty(I2PSocketOptions.PROP_CONNECT_TIMEOUT) == null)
|
||||
|
@ -82,6 +82,7 @@ class SAMUtils {
|
||||
*
|
||||
* @return True if the destination is valid, false otherwise
|
||||
*/
|
||||
/****
|
||||
public static boolean checkDestination(String dest) {
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
@ -92,6 +93,7 @@ class SAMUtils {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* Check whether a base64-encoded {dest,privkey,signingprivkey} is valid
|
||||
@ -105,8 +107,7 @@ class SAMUtils {
|
||||
return false;
|
||||
ByteArrayInputStream destKeyStream = new ByteArrayInputStream(b);
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
d.readBytes(destKeyStream);
|
||||
Destination d = Destination.create(destKeyStream);
|
||||
new PrivateKey().readBytes(destKeyStream);
|
||||
SigningPrivateKey spk = new SigningPrivateKey(d.getSigningPublicKey().getType());
|
||||
spk.readBytes(destKeyStream);
|
||||
|
@ -105,29 +105,18 @@ class SAMv2StreamSession extends SAMStreamSession
|
||||
return false ;
|
||||
}
|
||||
|
||||
Destination d = new Destination();
|
||||
|
||||
d.fromBase64 ( dest );
|
||||
|
||||
Destination d = SAMUtils.getDest(dest);
|
||||
I2PSocketOptions opts = socketMgr.buildOptions ( props );
|
||||
|
||||
if ( props.getProperty ( I2PSocketOptions.PROP_CONNECT_TIMEOUT ) == null )
|
||||
opts.setConnectTimeout ( 60 * 1000 );
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug ( "Connecting new I2PSocket..." );
|
||||
|
||||
|
||||
// non-blocking connection (SAMv2)
|
||||
|
||||
StreamConnector connector ;
|
||||
|
||||
connector = new StreamConnector ( id, d, opts );
|
||||
|
||||
StreamConnector connector = new StreamConnector ( id, d, opts );
|
||||
I2PAppThread connectThread = new I2PAppThread ( connector, "StreamConnector" + id ) ;
|
||||
|
||||
connectThread.start() ;
|
||||
|
||||
return true ;
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PSession;
|
||||
@ -22,6 +23,7 @@ public class SAMReader {
|
||||
private final SAMClientEventListener _listener;
|
||||
private volatile boolean _live;
|
||||
private Thread _thread;
|
||||
private static final AtomicInteger _count = new AtomicInteger();
|
||||
|
||||
public SAMReader(I2PAppContext context, InputStream samIn, SAMClientEventListener listener) {
|
||||
_log = context.logManager().getLog(SAMReader.class);
|
||||
@ -33,7 +35,7 @@ public class SAMReader {
|
||||
if (_live)
|
||||
throw new IllegalStateException();
|
||||
_live = true;
|
||||
I2PAppThread t = new I2PAppThread(new Runner(), "SAM reader");
|
||||
I2PAppThread t = new I2PAppThread(new Runner(), "SAM reader " + _count.incrementAndGet());
|
||||
t.start();
|
||||
_thread = t;
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLServerSocket;
|
||||
import javax.net.ssl.SSLServerSocketFactory;
|
||||
|
||||
import gnu.getopt.Getopt;
|
||||
|
||||
@ -56,10 +57,13 @@ public class SAMStreamSink {
|
||||
private final Map<String, Sink> _remotePeers;
|
||||
private static I2PSSLSocketFactory _sslSocketFactory;
|
||||
|
||||
private static final int STREAM=0, DG=1, V1DG=2, RAW=3, V1RAW=4, RAWHDR = 5, FORWARD = 6;
|
||||
private static final String USAGE = "Usage: SAMStreamSink [-s] [-m mode] [-v version] [-b samHost] [-p samPort] [-o opt=val] [-u user] [-w password] myDestFile sinkDir\n" +
|
||||
" modes: stream: 0; datagram: 1; v1datagram: 2; raw: 3; v1raw: 4; raw-with-headers: 5; stream-forward: 6\n" +
|
||||
" -s: use SSL\n" +
|
||||
private static final int STREAM=0, DG=1, V1DG=2, RAW=3, V1RAW=4, RAWHDR = 5, FORWARD = 6, FORWARDSSL=7;
|
||||
private static final String USAGE = "Usage: SAMStreamSink [-s] [-m mode] [-v version] [-b samHost] [-p samPort]\n" +
|
||||
" [-o opt=val] [-u user] [-w password] myDestFile sinkDir\n" +
|
||||
" modes: stream: 0; datagram: 1; v1datagram: 2;\n" +
|
||||
" raw: 3; v1raw: 4; raw-with-headers: 5;\n" +
|
||||
" stream-forward: 6; stream-forward-ssl: 7\n" +
|
||||
" -s: use SSL to connect to bridge\n" +
|
||||
" multiple -o session options are allowed";
|
||||
private static final int V3FORWARDPORT=9998;
|
||||
private static final int V3DGPORT=9999;
|
||||
@ -83,7 +87,7 @@ public class SAMStreamSink {
|
||||
|
||||
case 'm':
|
||||
mode = Integer.parseInt(g.getOptarg());
|
||||
if (mode < 0 || mode > FORWARD) {
|
||||
if (mode < 0 || mode > FORWARDSSL) {
|
||||
System.err.println(USAGE);
|
||||
return;
|
||||
}
|
||||
@ -174,7 +178,7 @@ public class SAMStreamSink {
|
||||
Thread t = new Pinger(out);
|
||||
t.start();
|
||||
}
|
||||
if (_isV3 && (mode == STREAM || mode == FORWARD)) {
|
||||
if (_isV3 && (mode == STREAM || mode == FORWARD || mode == FORWARDSSL)) {
|
||||
// test multiple acceptors, only works in 3.2
|
||||
int acceptors = (_isV32 && mode == STREAM) ? 4 : 1;
|
||||
for (int i = 0; i < acceptors; i++) {
|
||||
@ -193,7 +197,18 @@ public class SAMStreamSink {
|
||||
}
|
||||
if (mode == FORWARD) {
|
||||
// set up a listening ServerSocket
|
||||
(new FwdRcvr(isSSL)).start();
|
||||
(new FwdRcvr(false, null)).start();
|
||||
} else if (mode == FORWARDSSL) {
|
||||
// set up a listening ServerSocket
|
||||
String scfile = SSLUtil.DEFAULT_SAMCLIENT_CONFIGFILE;
|
||||
File file = new File(scfile);
|
||||
Properties opts = new Properties();
|
||||
if (file.exists())
|
||||
DataHelper.loadProps(opts, file);
|
||||
boolean shouldSave = SSLUtil.verifyKeyStore(opts);
|
||||
if (shouldSave)
|
||||
DataHelper.storeProps(opts, file);
|
||||
(new FwdRcvr(true, opts)).start();
|
||||
}
|
||||
} else if (_isV3 && (mode == DG || mode == RAW || mode == RAWHDR)) {
|
||||
// set up a listening DatagramSocket
|
||||
@ -208,7 +223,10 @@ public class SAMStreamSink {
|
||||
private class DGRcvr extends I2PAppThread {
|
||||
private final int _mode;
|
||||
|
||||
public DGRcvr(int mode) { _mode = mode; }
|
||||
public DGRcvr(int mode) {
|
||||
super("SAM DG Rcvr");
|
||||
_mode = mode;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
byte[] buf = new byte[32768];
|
||||
@ -257,18 +275,23 @@ public class SAMStreamSink {
|
||||
|
||||
private class FwdRcvr extends I2PAppThread {
|
||||
private final boolean _isSSL;
|
||||
// for SSL only
|
||||
private final Properties _opts;
|
||||
|
||||
public FwdRcvr(boolean isSSL) {
|
||||
if (isSSL)
|
||||
throw new UnsupportedOperationException("TODO");
|
||||
public FwdRcvr(boolean isSSL, Properties opts) {
|
||||
super("SAM Fwd Rcvr");
|
||||
_isSSL = isSSL;
|
||||
_opts = opts;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
ServerSocket ss;
|
||||
if (_isSSL) {
|
||||
throw new UnsupportedOperationException("TODO");
|
||||
SSLServerSocketFactory fact = SSLUtil.initializeFactory(_opts);
|
||||
SSLServerSocket sock = (SSLServerSocket) fact.createServerSocket(V3FORWARDPORT);
|
||||
I2PSSLSocketFactory.setProtocolsAndCiphers(sock);
|
||||
ss = sock;
|
||||
} else {
|
||||
ss = new ServerSocket(V3FORWARDPORT);
|
||||
}
|
||||
@ -277,10 +300,40 @@ public class SAMStreamSink {
|
||||
Sink sink = new Sink("FAKE", "FAKEFROM");
|
||||
try {
|
||||
InputStream in = s.getInputStream();
|
||||
boolean gotDest = false;
|
||||
byte[] dest = new byte[1024];
|
||||
int dlen = 0;
|
||||
byte[] buf = new byte[32768];
|
||||
int len;
|
||||
while((len = in.read(buf)) >= 0) {
|
||||
sink.received(buf, 0, len);
|
||||
if (!gotDest) {
|
||||
// eat the dest line
|
||||
for (int i = 0; i < len; i++) {
|
||||
byte b = buf[i];
|
||||
if (b == (byte) '\n') {
|
||||
gotDest = true;
|
||||
if (_log.shouldInfo()) {
|
||||
try {
|
||||
_log.info("Got incoming accept from: \"" + new String(dest, 0, dlen, "ISO-8859-1") + '"');
|
||||
} catch (IOException uee) {}
|
||||
}
|
||||
// feed any remaining to the sink
|
||||
i++;
|
||||
if (i < len)
|
||||
sink.received(buf, i, len - i);
|
||||
break;
|
||||
} else {
|
||||
if (dlen < dest.length) {
|
||||
dest[dlen++] = b;
|
||||
} else if (dlen == dest.length) {
|
||||
dlen++;
|
||||
_log.error("first line overflow on accept");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sink.received(buf, 0, len);
|
||||
}
|
||||
}
|
||||
sink.closed();
|
||||
} catch (IOException ioe) {
|
||||
@ -534,13 +587,15 @@ public class SAMStreamSink {
|
||||
req = "STREAM ACCEPT SILENT=false TO_PORT=5678 ID=" + _v3ID + "\n";
|
||||
else if (mode == FORWARD)
|
||||
req = "STREAM FORWARD ID=" + _v3ID + " PORT=" + V3FORWARDPORT + '\n';
|
||||
else if (mode == FORWARDSSL)
|
||||
req = "STREAM FORWARD ID=" + _v3ID + " PORT=" + V3FORWARDPORT + " SSL=true\n";
|
||||
else
|
||||
throw new IllegalStateException("mode " + mode);
|
||||
samOut.write(req.getBytes("UTF-8"));
|
||||
samOut.flush();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("STREAM ACCEPT/FORWARD sent");
|
||||
if (mode == FORWARD) {
|
||||
if (mode == FORWARD || mode == FORWARDSSL) {
|
||||
// docs were wrong, we do not get a STREAM STATUS if SILENT=true for ACCEPT
|
||||
boolean ok = eventHandler.waitForStreamStatusReply();
|
||||
if (!ok)
|
||||
@ -587,7 +642,7 @@ public class SAMStreamSink {
|
||||
dest = _destFile;
|
||||
}
|
||||
String style;
|
||||
if (mode == STREAM || mode == FORWARD)
|
||||
if (mode == STREAM || mode == FORWARD || mode == FORWARDSSL)
|
||||
style = "STREAM";
|
||||
else if (mode == V1DG)
|
||||
style = "DATAGRAM";
|
||||
|
189
apps/sam/java/src/net/i2p/sam/client/SSLUtil.java
Normal file
189
apps/sam/java/src/net/i2p/sam/client/SSLUtil.java
Normal file
@ -0,0 +1,189 @@
|
||||
package net.i2p.sam.client;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyStore;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLServerSocketFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.KeyStoreUtil;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SecureDirectory;
|
||||
|
||||
/**
|
||||
* Utilities for SAM SSL server sockets.
|
||||
*
|
||||
* @since 0.9.24 copied from net.i2p.sam for testing SSL stream forwarding
|
||||
*/
|
||||
class SSLUtil {
|
||||
|
||||
public static final String DEFAULT_SAMCLIENT_CONFIGFILE = "samclient.config";
|
||||
private static final String PROP_KEYSTORE_PASSWORD = "samclient.keystorePassword";
|
||||
private static final String DEFAULT_KEYSTORE_PASSWORD = "changeit";
|
||||
private static final String PROP_KEY_PASSWORD = "samclient.keyPassword";
|
||||
private static final String PROP_KEY_ALIAS = "samclient.keyAlias";
|
||||
private static final String ASCII_KEYFILE_SUFFIX = ".local.crt";
|
||||
private static final String PROP_KS_NAME = "samclient.keystoreFile";
|
||||
private static final String KS_DIR = "keystore";
|
||||
private static final String PREFIX = "samclient-";
|
||||
private static final String KS_SUFFIX = ".ks";
|
||||
private static final String CERT_DIR = "certificates/samclient";
|
||||
|
||||
/**
|
||||
* Create a new selfsigned cert and keystore and pubkey cert if they don't exist.
|
||||
* May take a while.
|
||||
*
|
||||
* @param opts in/out, updated if rv is true
|
||||
* @return false if it already exists; if true, caller must save opts
|
||||
* @throws IOException on creation fail
|
||||
*/
|
||||
public static boolean verifyKeyStore(Properties opts) throws IOException {
|
||||
String name = opts.getProperty(PROP_KEY_ALIAS);
|
||||
if (name == null) {
|
||||
name = KeyStoreUtil.randomString();
|
||||
opts.setProperty(PROP_KEY_ALIAS, name);
|
||||
}
|
||||
String ksname = opts.getProperty(PROP_KS_NAME);
|
||||
if (ksname == null) {
|
||||
ksname = PREFIX + name + KS_SUFFIX;
|
||||
opts.setProperty(PROP_KS_NAME, ksname);
|
||||
}
|
||||
File ks = new File(ksname);
|
||||
if (!ks.isAbsolute()) {
|
||||
ks = new File(I2PAppContext.getGlobalContext().getConfigDir(), KS_DIR);
|
||||
ks = new File(ks, ksname);
|
||||
}
|
||||
if (ks.exists())
|
||||
return false;
|
||||
File dir = ks.getParentFile();
|
||||
if (!dir.exists()) {
|
||||
File sdir = new SecureDirectory(dir.getAbsolutePath());
|
||||
if (!sdir.mkdirs())
|
||||
throw new IOException("Unable to create keystore " + ks);
|
||||
}
|
||||
boolean rv = createKeyStore(ks, name, opts);
|
||||
if (!rv)
|
||||
throw new IOException("Unable to create keystore " + 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.
|
||||
exportCert(ks, name, opts);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Call out to keytool to create a new keystore with a keypair in it.
|
||||
*
|
||||
* @param name used in CNAME
|
||||
* @param opts in/out, updated if rv is true, must contain PROP_KEY_ALIAS
|
||||
* @return success, if true, opts will have password properties added to be saved
|
||||
*/
|
||||
private static boolean createKeyStore(File ks, String name, Properties opts) {
|
||||
// make a random 48 character password (30 * 8 / 5)
|
||||
String keyPassword = KeyStoreUtil.randomString();
|
||||
// and one for the cname
|
||||
String cname = name + ".sam.i2p.net";
|
||||
|
||||
String keyName = opts.getProperty(PROP_KEY_ALIAS);
|
||||
boolean success = KeyStoreUtil.createKeys(ks, keyName, cname, "SAM", keyPassword);
|
||||
if (success) {
|
||||
success = ks.exists();
|
||||
if (success) {
|
||||
opts.setProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
|
||||
opts.setProperty(PROP_KEY_PASSWORD, keyPassword);
|
||||
}
|
||||
}
|
||||
if (success) {
|
||||
logAlways("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 {
|
||||
error("Failed to create SAM SSL keystore.\n" +
|
||||
"If you create the keystore manually, you must add " + PROP_KEYSTORE_PASSWORD + " and " + PROP_KEY_PASSWORD +
|
||||
" to " + (new File(I2PAppContext.getGlobalContext().getConfigDir(), DEFAULT_SAMCLIENT_CONFIGFILE)).getAbsolutePath());
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull the cert back OUT of the keystore and save it as ascii
|
||||
* so the clients can get to it.
|
||||
*
|
||||
* @param name used to generate output file name
|
||||
* @param opts must contain PROP_KEY_ALIAS
|
||||
*/
|
||||
private static void exportCert(File ks, String name, Properties opts) {
|
||||
File sdir = new SecureDirectory(I2PAppContext.getGlobalContext().getConfigDir(), CERT_DIR);
|
||||
if (sdir.exists() || sdir.mkdirs()) {
|
||||
String keyAlias = opts.getProperty(PROP_KEY_ALIAS);
|
||||
String ksPass = opts.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
|
||||
File out = new File(sdir, PREFIX + name + ASCII_KEYFILE_SUFFIX);
|
||||
boolean success = KeyStoreUtil.exportCert(ks, ksPass, keyAlias, out);
|
||||
if (!success)
|
||||
error("Error getting SSL cert to save as ASCII");
|
||||
} else {
|
||||
error("Error saving ASCII SSL keys");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the SSLContext and sets the socket factory.
|
||||
* No option prefix allowed.
|
||||
*
|
||||
* @throws IOException; GeneralSecurityExceptions are wrapped in IOE for convenience
|
||||
* @return factory, throws on all errors
|
||||
*/
|
||||
public static SSLServerSocketFactory initializeFactory(Properties opts) throws IOException {
|
||||
String ksPass = opts.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
|
||||
String keyPass = opts.getProperty(PROP_KEY_PASSWORD);
|
||||
if (keyPass == null) {
|
||||
throw new IOException("No key password, set " + PROP_KEY_PASSWORD + " in " +
|
||||
(new File(I2PAppContext.getGlobalContext().getConfigDir(), DEFAULT_SAMCLIENT_CONFIGFILE)).getAbsolutePath());
|
||||
}
|
||||
String ksname = opts.getProperty(PROP_KS_NAME);
|
||||
if (ksname == null) {
|
||||
throw new IOException("No keystore, set " + PROP_KS_NAME + " in " +
|
||||
(new File(I2PAppContext.getGlobalContext().getConfigDir(), DEFAULT_SAMCLIENT_CONFIGFILE)).getAbsolutePath());
|
||||
}
|
||||
File ks = new File(ksname);
|
||||
if (!ks.isAbsolute()) {
|
||||
ks = new File(I2PAppContext.getGlobalContext().getConfigDir(), KS_DIR);
|
||||
ks = new File(ks, ksname);
|
||||
}
|
||||
|
||||
InputStream fis = null;
|
||||
try {
|
||||
SSLContext sslc = SSLContext.getInstance("TLS");
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
fis = new FileInputStream(ks);
|
||||
keyStore.load(fis, ksPass.toCharArray());
|
||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(keyStore, keyPass.toCharArray());
|
||||
sslc.init(kmf.getKeyManagers(), null, I2PAppContext.getGlobalContext().random());
|
||||
return sslc.getServerSocketFactory();
|
||||
} catch (GeneralSecurityException gse) {
|
||||
IOException ioe = new IOException("keystore error");
|
||||
ioe.initCause(gse);
|
||||
throw ioe;
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
private static void error(String s) {
|
||||
I2PAppContext.getGlobalContext().logManager().getLog(SSLUtil.class).error(s);
|
||||
}
|
||||
|
||||
private static void logAlways(String s) {
|
||||
I2PAppContext.getGlobalContext().logManager().getLog(SSLUtil.class).logAlways(Log.INFO, s);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user