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

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

* I2PTunnel:

      - Display destination even when stopped
      - Enable key generation, dest modification, and
        hashcash estimation in the GUI
      - Add new CONNECT client
parent 374360c7
No related branches found
No related tags found
No related merge requests found
...@@ -244,6 +244,8 @@ public class I2PTunnel implements Logging, EventDispatcher { ...@@ -244,6 +244,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
runIrcClient(args, l); runIrcClient(args, l);
} else if ("sockstunnel".equals(cmdname)) { } else if ("sockstunnel".equals(cmdname)) {
runSOCKSTunnel(args, l); runSOCKSTunnel(args, l);
} else if ("connectclient".equals(cmdname)) {
runConnectClient(args, l);
} else if ("config".equals(cmdname)) { } else if ("config".equals(cmdname)) {
runConfig(args, l); runConfig(args, l);
} else if ("listen_on".equals(cmdname)) { } else if ("listen_on".equals(cmdname)) {
...@@ -296,6 +298,7 @@ public class I2PTunnel implements Logging, EventDispatcher { ...@@ -296,6 +298,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
l.log("client <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]"); l.log("client <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]");
l.log("ircclient <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]"); l.log("ircclient <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]");
l.log("httpclient <port> [<sharedClient>] [<proxy>]"); l.log("httpclient <port> [<sharedClient>] [<proxy>]");
l.log("connectclient <port> [<sharedClient>] [<proxy>]");
l.log("lookup <name>"); l.log("lookup <name>");
l.log("quit"); l.log("quit");
l.log("close [forced] <jobnumber>|all"); l.log("close [forced] <jobnumber>|all");
...@@ -555,7 +558,7 @@ public class I2PTunnel implements Logging, EventDispatcher { ...@@ -555,7 +558,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
return; return;
} }
String proxy = "squid.i2p"; String proxy = "";
boolean isShared = true; boolean isShared = true;
if (args.length > 1) { if (args.length > 1) {
if ("true".equalsIgnoreCase(args[1].trim())) { if ("true".equalsIgnoreCase(args[1].trim())) {
...@@ -595,11 +598,66 @@ public class I2PTunnel implements Logging, EventDispatcher { ...@@ -595,11 +598,66 @@ public class I2PTunnel implements Logging, EventDispatcher {
l.log(" <sharedClient> (optional) indicates if this client shares tunnels with other clients (true of false)"); l.log(" <sharedClient> (optional) indicates if this client shares tunnels with other clients (true of false)");
l.log(" <proxy> (optional) indicates a proxy server to be used"); l.log(" <proxy> (optional) indicates a proxy server to be used");
l.log(" when trying to access an address out of the .i2p domain"); l.log(" when trying to access an address out of the .i2p domain");
l.log(" (the default proxy is squid.i2p).");
notifyEvent("httpclientTaskId", Integer.valueOf(-1)); notifyEvent("httpclientTaskId", Integer.valueOf(-1));
} }
} }
/**
* Run a CONNECT client on the given port number
*
* @param args {portNumber[, sharedClient][, proxy to be used for the WWW]}
* @param l logger to receive events and output
*/
public void runConnectClient(String args[], Logging l) {
if (args.length >= 1 && args.length <= 3) {
int port = -1;
try {
port = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe) {
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
return;
}
String proxy = "";
boolean isShared = true;
if (args.length > 1) {
if ("true".equalsIgnoreCase(args[1].trim())) {
isShared = true;
if (args.length == 3)
proxy = args[2];
} else if ("false".equalsIgnoreCase(args[1].trim())) {
_log.warn("args[1] == [" + args[1] + "] and rejected explicitly");
isShared = false;
if (args.length == 3)
proxy = args[2];
} else if (args.length == 3) {
isShared = false; // not "true"
proxy = args[2];
_log.warn("args[1] == [" + args[1] + "] but rejected");
} else {
// isShared not specified, default to true
isShared = true;
proxy = args[1];
}
}
I2PTunnelTask task;
ownDest = !isShared;
try {
task = new I2PTunnelConnectClient(port, l, ownDest, proxy, (EventDispatcher) this, this);
addtask(task);
} catch (IllegalArgumentException iae) {
_log.error(getPrefix() + "Invalid I2PTunnel config to create an httpclient [" + host + ":"+ port + "]", iae);
}
} else {
l.log("connectclient <port> [<sharedClient>] [<proxy>]");
l.log(" creates a client that for SSL/HTTPS requests.");
l.log(" <sharedClient> (optional) indicates if this client shares tunnels with other clients (true of false)");
l.log(" <proxy> (optional) indicates a proxy server to be used");
l.log(" when trying to access an address out of the .i2p domain");
}
}
/** /**
* Run an IRC client on the given port number * Run an IRC client on the given port number
* *
......
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.util.EventDispatcher;
import net.i2p.util.FileUtil;
import net.i2p.util.Log;
/**
* Supports the following:
* (where protocol is generally HTTP/1.1 but is ignored)
* (where host is one of:
* example.i2p
* 52chars.b32.i2p
* 516+charsbase64
* example.com (sent to one of the configured proxies)
* )
*
* (port and protocol are ignored for i2p destinations)
* CONNECT host
* CONNECT host protocol
* CONNECT host:port
* CONNECT host:port protocol (this is the standard)
*
* Additional lines after the CONNECT line but before the blank line are ignored and stripped.
* The CONNECT line is removed for .i2p accesses
* but passed along for outproxy accesses.
*
* Ref:
* INTERNET-DRAFT Ari Luotonen
* Expires: September 26, 1997 Netscape Communications Corporation
* <draft-luotonen-ssl-tunneling-03.txt> March 26, 1997
* Tunneling SSL Through a WWW Proxy
*
* @author zzz a stripped-down I2PTunnelHTTPClient
*/
public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runnable {
private static final Log _log = new Log(I2PTunnelConnectClient.class);
private List<String> _proxyList;
private final static byte[] ERR_DESTINATION_UNKNOWN =
("HTTP/1.1 503 Service Unavailable\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
"Cache-control: no-cache\r\n"+
"\r\n"+
"<html><body><H1>I2P ERROR: DESTINATION NOT FOUND</H1>"+
"That I2P Destination was not found. "+
"The host (or the outproxy, if you're using one) could also "+
"be temporarily offline. You may want to <b>retry</b>. "+
"Could not find the following Destination:<BR><BR><div>")
.getBytes();
private final static byte[] ERR_NO_OUTPROXY =
("HTTP/1.1 503 Service Unavailable\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
"Cache-control: no-cache\r\n"+
"\r\n"+
"<html><body><H1>I2P ERROR: No outproxy found</H1>"+
"Your request was for a site outside of I2P, but you have no "+
"HTTP outproxy configured. Please configure an outproxy in I2PTunnel")
.getBytes();
private final static byte[] ERR_BAD_PROTOCOL =
("HTTP/1.1 405 Bad Method\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
"Cache-control: no-cache\r\n"+
"\r\n"+
"<html><body><H1>I2P ERROR: METHOD NOT ALLOWED</H1>"+
"The request uses a bad protocol. "+
"The Connect Proxy supports CONNECT requests ONLY. Other methods such as GET are not allowed - Maybe you wanted the HTTP Proxy?.<BR>")
.getBytes();
private final static byte[] ERR_LOCALHOST =
("HTTP/1.1 403 Access Denied\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
"Cache-control: no-cache\r\n"+
"\r\n"+
"<html><body><H1>I2P ERROR: REQUEST DENIED</H1>"+
"Your browser is misconfigured. Do not use the proxy to access the router console or other localhost destinations.<BR>")
.getBytes();
private final static byte[] SUCCESS_RESPONSE =
("HTTP/1.1 200 Connection Established\r\n"+
"Proxy-agent: I2P\r\n"+
"\r\n")
.getBytes();
/** used to assign unique IDs to the threads / clients. no logic or functionality */
private static volatile long __clientId = 0;
/**
* @throws IllegalArgumentException if the I2PTunnel does not contain
* valid config to contact the router
*/
public I2PTunnelConnectClient(int localPort, Logging l, boolean ownDest,
String wwwProxy, EventDispatcher notifyThis,
I2PTunnel tunnel) throws IllegalArgumentException {
super(localPort, ownDest, l, notifyThis, "HTTPHandler " + (++__clientId), tunnel);
if (waitEventValue("openBaseClientResult").equals("error")) {
notifyEvent("openConnectClientResult", "error");
return;
}
_proxyList = new ArrayList();
if (wwwProxy != null) {
StringTokenizer tok = new StringTokenizer(wwwProxy, ",");
while (tok.hasMoreTokens())
_proxyList.add(tok.nextToken().trim());
}
setName(getLocalPort() + " -> ConnectClient [Outproxy list: " + wwwProxy + "]");
startRunning();
}
private String getPrefix(long requestId) { return "Client[" + _clientId + "/" + requestId + "]: "; }
private String selectProxy() {
synchronized (_proxyList) {
int size = _proxyList.size();
if (size <= 0)
return null;
int index = I2PAppContext.getGlobalContext().random().nextInt(size);
return _proxyList.get(index);
}
}
private static final int DEFAULT_READ_TIMEOUT = 60*1000;
/**
* create the default options (using the default timeout, etc)
*
*/
protected I2PSocketOptions getDefaultOptions() {
Properties defaultOpts = getTunnel().getClientOptions();
if (!defaultOpts.contains(I2PSocketOptions.PROP_READ_TIMEOUT))
defaultOpts.setProperty(I2PSocketOptions.PROP_READ_TIMEOUT, ""+DEFAULT_READ_TIMEOUT);
if (!defaultOpts.contains("i2p.streaming.inactivityTimeout"))
defaultOpts.setProperty("i2p.streaming.inactivityTimeout", ""+DEFAULT_READ_TIMEOUT);
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT))
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
return opts;
}
private static long __requestId = 0;
protected void clientConnectionRun(Socket s) {
InputStream in = null;
OutputStream out = null;
String targetRequest = null;
boolean usingWWWProxy = false;
String currentProxy = null;
long requestId = ++__requestId;
try {
out = s.getOutputStream();
in = s.getInputStream();
String line, method = null, host = null, destination = null, restofline = null;
StringBuffer newRequest = new StringBuffer();
int ahelper = 0;
while (true) {
// Use this rather than BufferedReader because we can't have readahead,
// since we are passing the stream on to I2PTunnelRunner
line = DataHelper.readLine(in);
line = line.trim();
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix(requestId) + "Line=[" + line + "]");
if (method == null) { // first line CONNECT blah.i2p:80 HTTP/1.1
int pos = line.indexOf(" ");
if (pos == -1) break; // empty first line
method = line.substring(0, pos);
String request = line.substring(pos + 1);
pos = request.indexOf(":");
if (pos == -1)
pos = request.indexOf(" ");
if (pos == -1) {
host = request;
restofline = "";
} else {
host = request.substring(0, pos);
restofline = request.substring(pos); // ":80 HTTP/1.1" or " HTTP/1.1"
}
if (host.toLowerCase().endsWith(".i2p")) {
// Destination gets the host name
destination = host;
} else if (host.indexOf(".") != -1) {
// The request must be forwarded to a outproxy
currentProxy = selectProxy();
if (currentProxy == null) {
if (_log.shouldLog(Log.WARN))
_log.warn(getPrefix(requestId) + "Host wants to be outproxied, but we dont have any!");
writeErrorMessage(ERR_NO_OUTPROXY, out);
s.close();
return;
}
destination = currentProxy;
usingWWWProxy = true;
newRequest.append("CONNECT ").append(host).append(restofline).append("\r\n\r\n"); // HTTP spec
} else if (host.toLowerCase().equals("localhost")) {
writeErrorMessage(ERR_LOCALHOST, out);
s.close();
return;
} else { // full b64 address (hopefully)
destination = host;
}
targetRequest = host;
if (_log.shouldLog(Log.DEBUG)) {
_log.debug(getPrefix(requestId) + "METHOD:" + method + ":");
_log.debug(getPrefix(requestId) + "HOST :" + host + ":");
_log.debug(getPrefix(requestId) + "REST :" + restofline + ":");
_log.debug(getPrefix(requestId) + "DEST :" + destination + ":");
}
} else if (line.length() > 0) {
// Additional lines - shouldn't be too many. Firefox sends:
// User-Agent: blabla
// Proxy-Connection: keep-alive
// Host: blabla.i2p
//
// We could send these (filtered like in HTTPClient) on to the outproxy,
// but for now just chomp them all.
line = null;
} else {
// do it
break;
}
}
if (destination == null || !"CONNECT".equalsIgnoreCase(method)) {
writeErrorMessage(ERR_BAD_PROTOCOL, out);
s.close();
return;
}
Destination dest = I2PTunnel.destFromName(destination);
if (dest == null) {
String str;
byte[] header;
if (usingWWWProxy)
str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
else
str = FileUtil.readTextFile("docs/dnfh-header.ht", 100, true);
if (str != null)
header = str.getBytes();
else
header = ERR_DESTINATION_UNKNOWN;
writeErrorMessage(header, out, targetRequest, usingWWWProxy, destination);
s.close();
return;
}
I2PSocket i2ps = createI2PSocket(dest, getDefaultOptions());
byte[] data = null;
byte[] response = null;
if (usingWWWProxy)
data = newRequest.toString().getBytes("ISO-8859-1");
else
response = SUCCESS_RESPONSE;
Runnable onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
I2PTunnelRunner runner = new I2PTunnelRunner(s, i2ps, sockLock, data, response, mySockets, onTimeout);
} catch (SocketException ex) {
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
handleConnectClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
closeSocket(s);
} catch (IOException ex) {
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
handleConnectClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
closeSocket(s);
} catch (I2PException ex) {
_log.info("getPrefix(requestId) + Error trying to connect", ex);
handleConnectClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
closeSocket(s);
} catch (OutOfMemoryError oom) {
IOException ex = new IOException("OOM");
_log.info("getPrefix(requestId) + Error trying to connect", ex);
handleConnectClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
closeSocket(s);
}
}
private static class OnTimeout implements Runnable {
private Socket _socket;
private OutputStream _out;
private String _target;
private boolean _usingProxy;
private String _wwwProxy;
private long _requestId;
public OnTimeout(Socket s, OutputStream out, String target, boolean usingProxy, String wwwProxy, long id) {
_socket = s;
_out = out;
_target = target;
_usingProxy = usingProxy;
_wwwProxy = wwwProxy;
_requestId = id;
}
public void run() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Timeout occured requesting " + _target);
handleConnectClientException(new RuntimeException("Timeout"), _out,
_target, _usingProxy, _wwwProxy, _requestId);
closeSocket(_socket);
}
}
private static void writeErrorMessage(byte[] errMessage, OutputStream out) throws IOException {
if (out == null)
return;
out.write(errMessage);
out.write("\n</body></html>\n".getBytes());
out.flush();
}
private static void writeErrorMessage(byte[] errMessage, OutputStream out, String targetRequest,
boolean usingWWWProxy, String wwwProxy) throws IOException {
if (out != null) {
out.write(errMessage);
if (targetRequest != null) {
out.write(targetRequest.getBytes());
if (usingWWWProxy)
out.write(("<br>WWW proxy: " + wwwProxy).getBytes());
}
out.write("</div>".getBytes());
out.write("\n</body></html>\n".getBytes());
out.flush();
}
}
private static void handleConnectClientException(Exception ex, OutputStream out, String targetRequest,
boolean usingWWWProxy, String wwwProxy, long requestId) {
if (out == null)
return;
try {
String str;
byte[] header;
if (usingWWWProxy)
str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
else
str = FileUtil.readTextFile("docs/dnf-header.ht", 100, true);
if (str != null)
header = str.getBytes();
else
header = ERR_DESTINATION_UNKNOWN;
writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy);
} catch (IOException ioe) {}
}
}
...@@ -73,7 +73,7 @@ public class TunnelController implements Logging { ...@@ -73,7 +73,7 @@ public class TunnelController implements Logging {
File keyFile = new File(getPrivKeyFile()); File keyFile = new File(getPrivKeyFile());
if (keyFile.exists()) { if (keyFile.exists()) {
log("Not overwriting existing private keys in " + keyFile.getAbsolutePath()); //log("Not overwriting existing private keys in " + keyFile.getAbsolutePath());
return; return;
} else { } else {
File parent = keyFile.getParentFile(); File parent = keyFile.getParentFile();
...@@ -87,6 +87,7 @@ public class TunnelController implements Logging { ...@@ -87,6 +87,7 @@ public class TunnelController implements Logging {
String destStr = dest.toBase64(); String destStr = dest.toBase64();
log("Private key created and saved in " + keyFile.getAbsolutePath()); log("Private key created and saved in " + keyFile.getAbsolutePath());
log("New destination: " + destStr); log("New destination: " + destStr);
log("Base32: " + Base32.encode(dest.calculateHash().getData()) + ".b32.i2p");
} catch (I2PException ie) { } catch (I2PException ie) {
if (_log.shouldLog(Log.ERROR)) if (_log.shouldLog(Log.ERROR))
_log.error("Error creating new destination", ie); _log.error("Error creating new destination", ie);
...@@ -139,6 +140,8 @@ public class TunnelController implements Logging { ...@@ -139,6 +140,8 @@ public class TunnelController implements Logging {
startIrcClient(); startIrcClient();
} else if("sockstunnel".equals(type)) { } else if("sockstunnel".equals(type)) {
startSocksClient(); startSocksClient();
} else if("connectclient".equals(type)) {
startConnectClient();
} else if ("client".equals(type)) { } else if ("client".equals(type)) {
startClient(); startClient();
} else if ("server".equals(type)) { } else if ("server".equals(type)) {
...@@ -166,6 +169,21 @@ public class TunnelController implements Logging { ...@@ -166,6 +169,21 @@ public class TunnelController implements Logging {
_running = true; _running = true;
} }
private void startConnectClient() {
setI2CPOptions();
setSessionOptions();
setListenOn();
String listenPort = getListenPort();
String proxyList = getProxyList();
String sharedClient = getSharedClient();
if (proxyList == null)
_tunnel.runConnectClient(new String[] { listenPort, sharedClient }, this);
else
_tunnel.runConnectClient(new String[] { listenPort, sharedClient, proxyList }, this);
acquire();
_running = true;
}
private void startIrcClient() { private void startIrcClient() {
setI2CPOptions(); setI2CPOptions();
setSessionOptions(); setSessionOptions();
......
...@@ -18,6 +18,11 @@ import java.util.Set; ...@@ -18,6 +18,11 @@ import java.util.Set;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.data.Base32;
import net.i2p.data.Certificate;
import net.i2p.data.Destination;
import net.i2p.data.PrivateKeyFile;
import net.i2p.data.SessionKey;
import net.i2p.i2ptunnel.TunnelController; import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup; import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.util.ConcurrentHashSet; import net.i2p.util.ConcurrentHashSet;
...@@ -65,6 +70,9 @@ public class IndexBean { ...@@ -65,6 +70,9 @@ public class IndexBean {
private boolean _removeConfirmed; private boolean _removeConfirmed;
private Set<String> _booleanOptions; private Set<String> _booleanOptions;
private Map<String, String> _otherOptions; private Map<String, String> _otherOptions;
private int _hashCashValue;
private int _certType;
private String _certSigner;
public static final int RUNNING = 1; public static final int RUNNING = 1;
public static final int STARTING = 2; public static final int STARTING = 2;
...@@ -156,6 +164,12 @@ public class IndexBean { ...@@ -156,6 +164,12 @@ public class IndexBean {
else if ("Delete this proxy".equals(_action) || // IE workaround: else if ("Delete this proxy".equals(_action) || // IE workaround:
(_action.toLowerCase().indexOf("d</span>elete") >= 0)) (_action.toLowerCase().indexOf("d</span>elete") >= 0))
return deleteTunnel(); return deleteTunnel();
else if ("Estimate".equals(_action))
return PrivateKeyFile.estimateHashCashTime(_hashCashValue);
else if ("Modify".equals(_action))
return modifyDestination();
else if ("Generate".equals(_action))
return generateNewEncryptionKey();
else else
return "Action " + _action + " unknown"; return "Action " + _action + " unknown";
} }
...@@ -370,7 +384,7 @@ public class IndexBean { ...@@ -370,7 +384,7 @@ public class IndexBean {
else if ("ircclient".equals(internalType)) return "IRC client"; else if ("ircclient".equals(internalType)) return "IRC client";
else if ("server".equals(internalType)) return "Standard server"; else if ("server".equals(internalType)) return "Standard server";
else if ("httpserver".equals(internalType)) return "HTTP server"; else if ("httpserver".equals(internalType)) return "HTTP server";
else if ("sockstunnel".equals(internalType)) return "SOCKS proxy"; else if ("sockstunnel".equals(internalType)) return "SOCKS 5 proxy";
else if ("connectclient".equals(internalType)) return "CONNECT/SSL/HTTPS proxy"; else if ("connectclient".equals(internalType)) return "CONNECT/SSL/HTTPS proxy";
else return internalType; else return internalType;
} }
...@@ -440,6 +454,16 @@ public class IndexBean { ...@@ -440,6 +454,16 @@ public class IndexBean {
String rv = tun.getMyDestination(); String rv = tun.getMyDestination();
if (rv != null) if (rv != null)
return rv; return rv;
// if not running, do this the hard way
String keyFile = tun.getPrivKeyFile();
if (keyFile != null && keyFile.trim().length() > 0) {
PrivateKeyFile pkf = new PrivateKeyFile(keyFile);
try {
Destination d = pkf.getDestination();
if (d != null)
return d.toBase64();
} catch (Exception e) {}
}
} }
return ""; return "";
} }
...@@ -616,6 +640,115 @@ public class IndexBean { ...@@ -616,6 +640,115 @@ public class IndexBean {
} }
} }
/** params needed for hashcash and dest modification */
public void setEffort(String val) {
if (val != null) {
try {
_hashCashValue = Integer.parseInt(val.trim());
} catch (NumberFormatException nfe) {}
}
}
public void setCert(String val) {
if (val != null) {
try {
_certType = Integer.parseInt(val.trim());
} catch (NumberFormatException nfe) {}
}
}
public void setSigner(String val) {
_certSigner = val;
}
/** Modify or create a destination */
private String modifyDestination() {
if (_privKeyFile == null || _privKeyFile.trim().length() <= 0)
return "Private Key File not specified";
TunnelController tun = getController(_tunnel);
Properties config = getConfig();
if (config == null)
return "Invalid params";
if (tun == null) {
// creating new
tun = new TunnelController(config, "", true);
_group.addController(tun);
saveChanges();
} else if (tun.getIsRunning() || tun.getIsStarting()) {
return "Tunnel must be stopped before modifying destination";
}
PrivateKeyFile pkf = new PrivateKeyFile(_privKeyFile);
try {
pkf.createIfAbsent();
} catch (Exception e) {
return "Create private key file failed: " + e;
}
switch (_certType) {
case Certificate.CERTIFICATE_TYPE_NULL:
case Certificate.CERTIFICATE_TYPE_HIDDEN:
pkf.setCertType(_certType);
break;
case Certificate.CERTIFICATE_TYPE_HASHCASH:
pkf.setHashCashCert(_hashCashValue);
break;
case Certificate.CERTIFICATE_TYPE_SIGNED:
if (_certSigner == null || _certSigner.trim().length() <= 0)
return "No signing destination specified";
// find the signer's key file...
String signerPKF = null;
for (int i = 0; i < getTunnelCount(); i++) {
TunnelController c = getController(i);
if (_certSigner.equals(c.getConfig("").getProperty("name")) ||
_certSigner.equals(c.getConfig("").getProperty("spoofedHost"))) {
signerPKF = c.getConfig("").getProperty("privKeyFile");
break;
}
}
if (signerPKF == null || signerPKF.length() <= 0)
return "Signing destination " + _certSigner + " not found";
if (_privKeyFile.equals(signerPKF))
return "Self-signed destinations not allowed";
Certificate c = pkf.setSignedCert(new PrivateKeyFile(signerPKF));
if (c == null)
return "Signing failed - does signer destination exist?";
break;
default:
return "Unknown certificate type";
}
Destination newdest;
try {
pkf.write();
newdest = pkf.getDestination();
} catch (Exception e) {
return "Modification failed: " + e;
}
return "Destination modified - " +
"New Base32 is " + Base32.encode(newdest.calculateHash().getData()) + ".b32.i2p " +
"New Destination is " + newdest.toBase64();
}
/** New key */
private String generateNewEncryptionKey() {
TunnelController tun = getController(_tunnel);
Properties config = getConfig();
if (config == null)
return "Invalid params";
if (tun == null) {
// creating new
tun = new TunnelController(config, "", true);
_group.addController(tun);
saveChanges();
} else if (tun.getIsRunning() || tun.getIsStarting()) {
return "Tunnel must be stopped before modifying leaseset encryption key";
}
byte[] data = new byte[SessionKey.KEYSIZE_BYTES];
_context.random().nextBytes(data);
SessionKey sk = new SessionKey(data);
setEncryptKey(sk.toBase64());
setEncrypt("");
saveChanges();
return "New Leaseset Encryption Key: " + sk.toBase64();
}
/** /**
* Based on all provided data, create a set of configuration parameters * Based on all provided data, create a set of configuration parameters
* suitable for use in a TunnelController. This will replace (not add to) * suitable for use in a TunnelController. This will replace (not add to)
......
...@@ -254,12 +254,18 @@ ...@@ -254,12 +254,18 @@
</label> </label>
<input value="1" type="checkbox" id="startOnLoad" name="encrypt" title="Encrypt LeaseSet"<%=(editBean.getEncrypt(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" /> <input value="1" type="checkbox" id="startOnLoad" name="encrypt" title="Encrypt LeaseSet"<%=(editBean.getEncrypt(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
</div> </div>
<div id="hostField" class="rowItem"> <div id="portField" class="rowItem">
<label for="encrypt" accesskey="e"> <label for="encrypt" accesskey="e">
Leaseset Encryption Key: Leaseset Encryption Key:
</label> </label>
<input type="text" id="hostField" name="encryptKey" size="60" title="Encrypt Key" value="<%=editBean.getEncryptKey(curTunnel)%>" class="freetext" /> <textarea rows="1" cols="44" id="portField" name="encryptKey" title="Encrypt Key" wrap="off"><%=editBean.getEncryptKey(curTunnel)%></textarea>
<span class="comment">(Users will require this key)</span> </div>
<div id="portField" class="rowItem">
<label for="force" accesskey="c">
Generate Key:
</label>
<button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Generate" title="Generate New Key Now">Generate New Key</button>
<span class="comment">(Tunnel must be stopped first)</span>
</div> </div>
<div class="subdivider"> <div class="subdivider">
...@@ -319,7 +325,7 @@ ...@@ -319,7 +325,7 @@
<div id="tunnelOptionsField" class="rowItem"> <div id="tunnelOptionsField" class="rowItem">
<label for="cert" accesskey="c"> <label for="cert" accesskey="c">
<span class="accessKey">C</span>ertificate type: New <span class="accessKey">C</span>ertificate type:
</label> </label>
</div> </div>
<div id="hostField" class="rowItem"> <div id="hostField" class="rowItem">
...@@ -331,14 +337,14 @@ ...@@ -331,14 +337,14 @@
<div id="portField" class="rowItem"> <div id="portField" class="rowItem">
<label>Hashcash (effort)</label> <label>Hashcash (effort)</label>
<input value="1" type="radio" id="startOnLoad" name="cert" title="Hashcash Certificate"<%=(editBean.getCert(curTunnel)==1 ? " checked=\"checked\"" : "")%> class="tickbox" /> <input value="1" type="radio" id="startOnLoad" name="cert" title="Hashcash Certificate"<%=(editBean.getCert(curTunnel)==1 ? " checked=\"checked\"" : "")%> class="tickbox" />
<input type="text" id="port" name="effort" size="2" title="Hashcash Effort" value="<%=editBean.getEffort(curTunnel)%>" class="freetext" /> <input type="text" id="port" name="effort" size="2" maxlength="2" title="Hashcash Effort" value="<%=editBean.getEffort(curTunnel)%>" class="freetext" />
</div> </div>
</div> </div>
<div id="portField" class="rowItem"> <div id="portField" class="rowItem">
<label for="force" accesskey="c"> <label for="force" accesskey="c">
Estimate Hashcash Calc Time: Estimate Hashcash Calc Time:
</label> </label>
<button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Estimate Calculation Time" title="Estimate Calculation Time">Estimate</button> <button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Estimate" title="Estimate Calculation Time">Estimate</button>
</div> </div>
<div id="hostField" class="rowItem"> <div id="hostField" class="rowItem">
<div id="portField" class="rowItem"> <div id="portField" class="rowItem">
...@@ -359,7 +365,7 @@ ...@@ -359,7 +365,7 @@
<label for="force" accesskey="c"> <label for="force" accesskey="c">
Modify Certificate: Modify Certificate:
</label> </label>
<button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Modify Cert Now" title="Force New Cert Now">Modify</button> <button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Modify" title="Force New Cert Now">Modify</button>
<span class="comment">(Tunnel must be stopped first)</span> <span class="comment">(Tunnel must be stopped first)</span>
</div> </div>
......
...@@ -148,7 +148,7 @@ ...@@ -148,7 +148,7 @@
<option value="client">Standard</option> <option value="client">Standard</option>
<option value="httpclient">HTTP</option> <option value="httpclient">HTTP</option>
<option value="ircclient">IRC</option> <option value="ircclient">IRC</option>
<option value="sockstunnel">SOCKS</option> <option value="sockstunnel">SOCKS 5</option>
<option value="connectclient">CONNECT</option> <option value="connectclient">CONNECT</option>
</select> </select>
<input class="control" type="submit" value="Create" /> <input class="control" type="submit" value="Create" />
......
...@@ -77,74 +77,25 @@ public class PrivateKeyFile { ...@@ -77,74 +77,25 @@ public class PrivateKeyFile {
verifySignature(d); verifySignature(d);
if (args.length == 1) if (args.length == 1)
return; return;
Certificate c = new Certificate();
if (args[0].equals("-n")) { if (args[0].equals("-n")) {
// Cert constructor generates a null cert // Cert constructor generates a null cert
pkf.setCertType(Certificate.CERTIFICATE_TYPE_NULL);
} else if (args[0].equals("-u")) { } else if (args[0].equals("-u")) {
c.setCertificateType(99); pkf.setCertType(99);
} else if (args[0].equals("-x")) { } else if (args[0].equals("-x")) {
c.setCertificateType(Certificate.CERTIFICATE_TYPE_HIDDEN); pkf.setCertType(Certificate.CERTIFICATE_TYPE_HIDDEN);
} else if (args[0].equals("-h")) { } else if (args[0].equals("-h")) {
int hashEffort = HASH_EFFORT; int hashEffort = HASH_EFFORT;
if (args.length == 3) if (args.length == 3)
hashEffort = Integer.parseInt(args[1]); hashEffort = Integer.parseInt(args[1]);
System.out.println("Estimating hashcash generation time, stand by..."); System.out.println("Estimating hashcash generation time, stand by...");
// takes a lot longer than the estimate usually... System.out.println(estimateHashCashTime(hashEffort));
// maybe because the resource string is much longer than used in the estimate? pkf.setHashCashCert(hashEffort);
long low = HashCash.estimateTime(hashEffort);
System.out.println("It is estimated this will take " + DataHelper.formatDuration(low) +
" to " + DataHelper.formatDuration(4*low));
long begin = System.currentTimeMillis();
System.out.println("Starting hashcash generation now...");
String resource = d.getPublicKey().toBase64() + d.getSigningPublicKey().toBase64();
HashCash hc = HashCash.mintCash(resource, hashEffort);
System.out.println("Generation took: " + DataHelper.formatDuration(System.currentTimeMillis() - begin));
System.out.println("Full Hashcash is: " + hc);
// Take the resource out of the stamp
String hcs = hc.toString();
int end1 = 0;
for (int i = 0; i < 3; i++) {
end1 = 1 + hcs.indexOf(':', end1);
if (end1 < 0) {
System.out.println("Bad hashcash");
return;
}
}
int start2 = hcs.indexOf(':', end1);
if (start2 < 0) {
System.out.println("Bad hashcash");
return;
}
hcs = hcs.substring(0, end1) + hcs.substring(start2);
System.out.println("Short Hashcash is: " + hcs);
c.setCertificateType(Certificate.CERTIFICATE_TYPE_HASHCASH);
c.setPayload(hcs.getBytes());
} else if (args.length == 3 && args[0].equals("-s")) { } else if (args.length == 3 && args[0].equals("-s")) {
// Sign dest1 with dest2's Signing Private Key // Sign dest1 with dest2's Signing Private Key
File f2 = new File(args[2]); PrivateKeyFile pkf2 = new PrivateKeyFile(args[2]);
I2PClient client2 = I2PClientFactory.createClient(); pkf.setSignedCert(pkf2);
PrivateKeyFile pkf2 = new PrivateKeyFile(f2, client2);
Destination d2 = pkf2.getDestination();
SigningPrivateKey spk2 = pkf2.getSigningPrivKey();
System.out.println("Signing With Dest:");
System.out.println(pkf2.toString());
int len = PublicKey.KEYSIZE_BYTES + SigningPublicKey.KEYSIZE_BYTES; // no cert
byte[] data = new byte[len];
System.arraycopy(d.getPublicKey().getData(), 0, data, 0, PublicKey.KEYSIZE_BYTES);
System.arraycopy(d.getSigningPublicKey().getData(), 0, data, PublicKey.KEYSIZE_BYTES, SigningPublicKey.KEYSIZE_BYTES);
byte[] payload = new byte[Hash.HASH_LENGTH + Signature.SIGNATURE_BYTES];
byte[] sig = DSAEngine.getInstance().sign(new ByteArrayInputStream(data), spk2).getData();
System.arraycopy(sig, 0, payload, 0, Signature.SIGNATURE_BYTES);
// Add dest2's Hash for reference
byte[] h2 = d2.calculateHash().getData();
System.arraycopy(h2, 0, payload, Signature.SIGNATURE_BYTES, Hash.HASH_LENGTH);
c.setCertificateType(Certificate.CERTIFICATE_TYPE_SIGNED);
c.setPayload(payload);
} }
d.setCertificate(c); // do this rather than just change the existing cert so the hash is recalculated
System.out.println("New signed destination is:"); System.out.println("New signed destination is:");
System.out.println(pkf); System.out.println(pkf);
pkf.write(); pkf.write();
...@@ -154,7 +105,10 @@ public class PrivateKeyFile { ...@@ -154,7 +105,10 @@ public class PrivateKeyFile {
} }
} }
public PrivateKeyFile(String file) {
this(new File(file), I2PClientFactory.createClient());
}
public PrivateKeyFile(File file, I2PClient client) { public PrivateKeyFile(File file, I2PClient client) {
this.file = file; this.file = file;
this.client = client; this.client = client;
...@@ -176,7 +130,7 @@ public class PrivateKeyFile { ...@@ -176,7 +130,7 @@ public class PrivateKeyFile {
return getDestination(); return getDestination();
} }
/** Also sets the local privKay and signingPrivKey */ /** Also sets the local privKey and signingPrivKey */
public Destination getDestination() throws I2PSessionException, IOException, DataFormatException { public Destination getDestination() throws I2PSessionException, IOException, DataFormatException {
if (dest == null) { if (dest == null) {
I2PSession s = open(); I2PSession s = open();
...@@ -188,6 +142,86 @@ public class PrivateKeyFile { ...@@ -188,6 +142,86 @@ public class PrivateKeyFile {
} }
return this.dest; return this.dest;
} }
public void setDestination(Destination d) {
this.dest = d;
}
/** change cert type - caller must also call write() */
public Certificate setCertType(int t) {
if (this.dest == null)
throw new IllegalArgumentException("Dest is null");
Certificate c = new Certificate();
c.setCertificateType(t);
this.dest.setCertificate(c);
return c;
}
/** change to hashcash cert - caller must also call write() */
public Certificate setHashCashCert(int effort) {
Certificate c = setCertType(Certificate.CERTIFICATE_TYPE_HASHCASH);
long begin = System.currentTimeMillis();
System.out.println("Starting hashcash generation now...");
String resource = this.dest.getPublicKey().toBase64() + this.dest.getSigningPublicKey().toBase64();
HashCash hc;
try {
hc = HashCash.mintCash(resource, effort);
} catch (Exception e) {
return null;
}
System.out.println("Generation took: " + DataHelper.formatDuration(System.currentTimeMillis() - begin));
System.out.println("Full Hashcash is: " + hc);
// Take the resource out of the stamp
String hcs = hc.toString();
int end1 = 0;
for (int i = 0; i < 3; i++) {
end1 = 1 + hcs.indexOf(':', end1);
if (end1 < 0) {
System.out.println("Bad hashcash");
return null;
}
}
int start2 = hcs.indexOf(':', end1);
if (start2 < 0) {
System.out.println("Bad hashcash");
return null;
}
hcs = hcs.substring(0, end1) + hcs.substring(start2);
System.out.println("Short Hashcash is: " + hcs);
c.setPayload(hcs.getBytes());
return c;
}
/** sign this dest by dest found in pkf2 - caller must also call write() */
public Certificate setSignedCert(PrivateKeyFile pkf2) {
Certificate c = setCertType(Certificate.CERTIFICATE_TYPE_SIGNED);
Destination d2;
try {
d2 = pkf2.getDestination();
} catch (Exception e) {
return null;
}
if (d2 == null)
return null;
SigningPrivateKey spk2 = pkf2.getSigningPrivKey();
System.out.println("Signing With Dest:");
System.out.println(pkf2.toString());
int len = PublicKey.KEYSIZE_BYTES + SigningPublicKey.KEYSIZE_BYTES; // no cert
byte[] data = new byte[len];
System.arraycopy(this.dest.getPublicKey().getData(), 0, data, 0, PublicKey.KEYSIZE_BYTES);
System.arraycopy(this.dest.getSigningPublicKey().getData(), 0, data, PublicKey.KEYSIZE_BYTES, SigningPublicKey.KEYSIZE_BYTES);
byte[] payload = new byte[Hash.HASH_LENGTH + Signature.SIGNATURE_BYTES];
byte[] sig = DSAEngine.getInstance().sign(new ByteArrayInputStream(data), spk2).getData();
System.arraycopy(sig, 0, payload, 0, Signature.SIGNATURE_BYTES);
// Add dest2's Hash for reference
byte[] h2 = d2.calculateHash().getData();
System.arraycopy(h2, 0, payload, Signature.SIGNATURE_BYTES, Hash.HASH_LENGTH);
c.setCertificateType(Certificate.CERTIFICATE_TYPE_SIGNED);
c.setPayload(payload);
return c;
}
public PrivateKey getPrivKey() { public PrivateKey getPrivKey() {
return this.privKey; return this.privKey;
...@@ -238,7 +272,25 @@ public class PrivateKeyFile { ...@@ -238,7 +272,25 @@ public class PrivateKeyFile {
return s.toString(); return s.toString();
} }
public static String estimateHashCashTime(int hashEffort) {
if (hashEffort <= 0 || hashEffort > 160)
return "Bad HashCash value: " + hashEffort;
long low = Long.MAX_VALUE;
try {
low = HashCash.estimateTime(hashEffort);
} catch (Exception e) {}
// takes a lot longer than the estimate usually...
// maybe because the resource string is much longer than used in the estimate?
return "It is estimated that generating a HashCash Certificate with value " + hashEffort +
" for the Destination will take " +
((low < 1000l * 24l * 60l * 60l * 1000l)
?
"approximately " + DataHelper.formatDuration(low) +
" to " + DataHelper.formatDuration(4*low)
:
"longer than three years!"
);
}
/** /**
* Sample code to verify a 3rd party signature. * Sample code to verify a 3rd party signature.
......
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