forked from I2P_Developers/i2p.i2p
* I2PTunnel:
- Display destination even when stopped - Enable key generation, dest modification, and hashcash estimation in the GUI - Add new CONNECT client
This commit is contained in:
@@ -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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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
|
||||||
*
|
*
|
||||||
|
@@ -0,0 +1,369 @@
|
|||||||
|
/* 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 {
|
|||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
_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;
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 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 @@
|
|||||||
</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 @@
|
|||||||
|
|
||||||
<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 @@
|
|||||||
<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 @@
|
|||||||
<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 @@
|
|||||||
<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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
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 {
|
|||||||
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.
|
||||||
|
Reference in New Issue
Block a user