I2P Address: [http://git.idk.i2p]

Skip to content
Snippets Groups Projects
Commit ad810de7 authored by zzz's avatar zzz
Browse files

i2ptunnel: Add subsession support to servers, no UI yet

Update subsession javadocs
parent b57d7c69
No related branches found
No related tags found
No related merge requests found
......@@ -313,8 +313,9 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
}
/**
* Add a subsession to a shared client if necessary.
* Add a DSA_SHA1 subsession to the shared client if necessary.
*
* @return subsession, or null if none was added
* @since 0.9.20
*/
protected static synchronized I2PSession addSubsession(I2PTunnel tunnel) {
......
......@@ -18,6 +18,7 @@ import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
......@@ -29,12 +30,14 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.ThreadFactory;
import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketManagerFactory;
import net.i2p.crypto.SigType;
import net.i2p.data.Base64;
import net.i2p.data.Hash;
import net.i2p.util.EventDispatcher;
......@@ -67,6 +70,8 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
private static final boolean DEFAULT_USE_POOL = true;
public static final String PROP_USE_SSL = "useSSL";
public static final String PROP_UNIQUE_LOCAL = "enableUniqueLocal";
/** @since 0.9.30 */
public static final String PROP_ALT_PKF = "altPrivKeyFile";
/** 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 */
......@@ -217,6 +222,9 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
portNum, props);
rv.setName("I2PTunnel Server");
getTunnel().addSession(rv.getSession());
String alt = props.getProperty(PROP_ALT_PKF);
if (alt != null)
addSubsession(rv, alt);
return rv;
} catch (I2PSessionException ise) {
throw new IllegalArgumentException("Can't create socket manager", ise);
......@@ -225,6 +233,44 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
}
}
/**
* Add a non-DSA_SHA1 subsession to the DSA_SHA1 server if necessary.
*
* @return subsession, or null if none was added
* @since 0.9.30
*/
private I2PSession addSubsession(I2PSocketManager sMgr, String alt) {
File altFile = TunnelController.filenameToFile(alt);
if (alt == null)
return null;
I2PSession sess = sMgr.getSession();
if (sess.getMyDestination().getSigType() != SigType.DSA_SHA1)
return null;
Properties props = new Properties();
props.putAll(getTunnel().getClientOptions());
// fixme get actual sig type
String name = props.getProperty("inbound.nickname");
if (name != null)
props.setProperty("inbound.nickname", name + " (EdDSA)");
name = props.getProperty("outbound.nickname");
if (name != null)
props.setProperty("outbound.nickname", name + " (EdDSA)");
props.setProperty(I2PClient.PROP_SIGTYPE, "EdDSA_SHA512_Ed25519");
FileInputStream privData = null;
try {
privData = new FileInputStream(altFile);
return sMgr.addSubsession(privData, props);
} catch (IOException ioe) {
_log.error("Failed to add subssession", ioe);
return null;
} catch (I2PSessionException ise) {
_log.error("Failed to add subssession", ise);
return null;
} finally {
if (privData != null) try { privData.close(); } catch (IOException ioe) {}
}
}
/**
* Warning, blocks while connecting to router and building tunnels;
......@@ -238,6 +284,22 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
while (sockMgr.getSession().isClosed()) {
try {
sockMgr.getSession().connect();
// Now connect the subsessions, if any
List<I2PSession> subs = sockMgr.getSubsessions();
if (!subs.isEmpty()) {
for (I2PSession sub : subs) {
try {
sub.connect();
if (_log.shouldInfo())
_log.info("Connected subsession " + sub);
} catch (I2PSessionException ise) {
// not fatal?
String msg = "Unable to connect subsession " + sub;
this.l.log(msg);
_log.error(msg, ise);
}
}
}
} catch (I2PSessionException ise) {
// try to make this error sensible as it will happen...
String portNum = getTunnel().port;
......
......@@ -3,6 +3,7 @@ package net.i2p.i2ptunnel;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
......@@ -16,12 +17,22 @@ import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PClientFactory;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.crypto.KeyGenerator;
import net.i2p.crypto.SigType;
import net.i2p.data.Destination;
import net.i2p.data.KeyCertificate;
import net.i2p.data.PrivateKey;
import net.i2p.data.PrivateKeyFile;
import net.i2p.data.PublicKey;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey;
import net.i2p.data.SimpleDataStructure;
import net.i2p.i2ptunnel.socks.I2PSOCKSTunnel;
import net.i2p.util.FileUtil;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
import net.i2p.util.RandomSource;
import net.i2p.util.SecureFile;
import net.i2p.util.SecureFileOutputStream;
import net.i2p.util.SystemVersion;
......@@ -83,6 +94,8 @@ public class TunnelController implements Logging {
private static final String OPT_TAGS_SEND = PFX_OPTION + "crypto.tagsToSend";
private static final String OPT_LOW_TAGS = PFX_OPTION + "crypto.lowTagThreshold";
private static final String OPT_SIG_TYPE = PFX_OPTION + I2PClient.PROP_SIGTYPE;
/** @since 0.9.30 */
private static final String OPT_ALT_PKF = PFX_OPTION + I2PTunnelServer.PROP_ALT_PKF;
/** all of these @since 0.9.14 */
public static final String TYPE_CONNECT = "connectclient";
......@@ -106,7 +119,7 @@ public class TunnelController implements Logging {
*/
public static final SigType PREFERRED_SIGTYPE;
static {
if (SystemVersion.isARM() || SystemVersion.isGNU() || SystemVersion.isAndroid()) {
if (SystemVersion.isGNU() || SystemVersion.isAndroid()) {
if (SigType.ECDSA_SHA256_P256.isAvailable())
PREFERRED_SIGTYPE = SigType.ECDSA_SHA256_P256;
else
......@@ -146,8 +159,13 @@ public class TunnelController implements Logging {
setConfig(config, prefix);
_messages = new ArrayList<String>(4);
boolean keyOK = true;
if (createKey && (getType().endsWith("server") || getPersistentClientKey()))
if (createKey && (getType().endsWith("server") || getPersistentClientKey())) {
keyOK = createPrivateKey();
if (keyOK && getType().endsWith("server") && !getType().equals(TYPE_STREAMR_SERVER)) {
// check rv?
createAltPrivateKey();
}
}
_state = keyOK && getStartOnLoad() ? TunnelState.START_ON_LOAD : TunnelState.STOPPED;
}
......@@ -186,7 +204,7 @@ public class TunnelController implements Logging {
String destStr = dest.toBase64();
log("Private key created and saved in " + keyFile.getAbsolutePath());
log("You should backup this file in a secure place.");
log("New destination: " + destStr);
log("New alternate destination: " + destStr);
String b32 = dest.toBase32();
log("Base32: " + b32);
File backupDir = new SecureFile(I2PAppContext.getGlobalContext().getConfigDir(), KEY_BACKUP_DIR);
......@@ -214,6 +232,99 @@ public class TunnelController implements Logging {
return true;
}
/**
* Creates alternate Destination with the same encryption keys as the primary Destination,
* but a different signing key.
*
* Must have already called createPrivateKey() successfully.
* Does nothing unless option OPT_ALT_PKF is set with the privkey file name.
* Does nothing if the file already exists.
*
* @return success
* @since 0.9.30
*/
private boolean createAltPrivateKey() {
if (PREFERRED_SIGTYPE == SigType.DSA_SHA1)
return false;
File keyFile = getPrivateKeyFile();
if (keyFile == null)
return false;
if (!keyFile.exists())
return false;
File altFile = getAlternatePrivateKeyFile();
if (altFile == null)
return false;
if (altFile.exists())
return true;
PrivateKeyFile pkf = new PrivateKeyFile(keyFile);
FileOutputStream out = null;
try {
Destination dest = pkf.getDestination();
if (dest == null)
return false;
if (dest.getSigType() != SigType.DSA_SHA1)
return false;
PublicKey pub = dest.getPublicKey();
PrivateKey priv = pkf.getPrivKey();
SimpleDataStructure[] signingKeys = KeyGenerator.getInstance().generateSigningKeys(PREFERRED_SIGTYPE);
SigningPublicKey signingPubKey = (SigningPublicKey) signingKeys[0];
SigningPrivateKey signingPrivKey = (SigningPrivateKey) signingKeys[1];
KeyCertificate cert = new KeyCertificate(signingPubKey);
Destination d = new Destination();
d.setPublicKey(pub);
d.setSigningPublicKey(signingPubKey);
d.setCertificate(cert);
int len = signingPubKey.length();
if (len < 128) {
byte[] pad = new byte[128 - len];
RandomSource.getInstance().nextBytes(pad);
d.setPadding(pad);
} else if (len > 128) {
// copy of excess data handled in KeyCertificate constructor
}
out = new SecureFileOutputStream(altFile);
d.writeBytes(out);
priv.writeBytes(out);
signingPrivKey.writeBytes(out);
try { out.close(); } catch (IOException ioe) {}
String destStr = d.toBase64();
log("Alternate private key created and saved in " + altFile.getAbsolutePath());
log("You should backup this file in a secure place.");
log("New destination: " + destStr);
String b32 = d.toBase32();
log("Base32: " + b32);
File backupDir = new SecureFile(I2PAppContext.getGlobalContext().getConfigDir(), KEY_BACKUP_DIR);
if (backupDir.isDirectory() || backupDir.mkdir()) {
String name = b32 + '-' + I2PAppContext.getGlobalContext().clock().now() + ".dat";
File backup = new File(backupDir, name);
if (FileUtil.copy(altFile, backup, false, true)) {
SecureFileOutputStream.setPerms(backup);
log("Alternate private key backup saved to " + backup.getAbsolutePath());
}
}
return true;
} catch (GeneralSecurityException e) {
log("Error creating keys " + e);
return false;
} catch (I2PSessionException e) {
log("Error creating keys " + e);
return false;
} catch (I2PException e) {
log("Error creating keys " + e);
return false;
} catch (IOException e) {
log("Error creating keys " + e);
return false;
} catch (RuntimeException e) {
log("Error creating keys " + e);
return false;
} finally {
if (out != null) try { out.close(); } catch (IOException ioe) {}
}
}
public void startTunnelBackground() {
synchronized (this) {
if (_state != TunnelState.STOPPED && _state != TunnelState.START_ON_LOAD)
......@@ -797,7 +908,25 @@ public class TunnelController implements Logging {
* @since 0.9.17
*/
public File getPrivateKeyFile() {
String f = getPrivKeyFile();
return filenameToFile(getPrivKeyFile());
}
/**
* Does not necessarily exist.
* @return absolute path or null if unset
* @since 0.9.30
*/
public File getAlternatePrivateKeyFile() {
return filenameToFile(_config.getProperty(OPT_ALT_PKF));
}
/**
* Does not necessarily exist.
* @param f relative or absolute path, may be null
* @return absolute path or null
* @since 0.9.30
*/
static File filenameToFile(String f) {
if (f == null)
return null;
f = f.trim();
......
......@@ -38,6 +38,10 @@ public interface I2PSocketManager {
public I2PSession getSession();
/**
* For a server, you must call connect() on the returned object.
* Connecting the primary session does NOT connect any subsessions.
* If the primary session is not connected, connecting a subsession will connect the primary session first.
*
* @return a new subsession, non-null
* @param privateKeyStream null for transient, if non-null must have same encryption keys as primary session
* and different signing keys
......
......@@ -236,6 +236,10 @@ public class I2PSocketManagerFull implements I2PSocketManager {
}
/**
* For a server, you must call connect() on the returned object.
* Connecting the primary session does NOT connect any subsessions.
* If the primary session is not connected, connecting a subsession will connect the primary session first.
*
* @return a new subsession, non-null
* @param privateKeyStream null for transient, if non-null must have same encryption keys as primary session
* and different signing keys
......
......@@ -272,8 +272,10 @@ public interface I2PSession {
public List<I2PSession> getSubsessions();
/**
* Actually connect the session and start receiving/sending messages
*
* Actually connect the session and start receiving/sending messages.
* Connecting a primary session will not automatically connect subsessions.
* Connecting a subsession will automatically connect the primary session
* if not previously connected.
*/
public void connect() throws I2PSessionException;
......
......@@ -548,6 +548,10 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
* Disconnect / destroy from another thread may be called simultaneously and
* will (should?) interrupt the connect.
*
* Connecting a primary session will not automatically connect subsessions.
* Connecting a subsession will automatically connect the primary session
* if not previously connected.
*
* @throws I2PSessionException if there is a configuration error or the router is
* not reachable
*/
......
......@@ -93,6 +93,9 @@ class SubSession extends I2PSessionMuxedImpl {
* Disconnect / destroy from another thread may be called simultaneously and
* will (should?) interrupt the connect.
*
* Connecting a subsession will automatically connect the primary session
* if not previously connected.
*
* @throws I2PSessionException if there is a configuration error or the router is
* not reachable
*/
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment