forked from I2P_Developers/i2p.i2p
i2ptunnel: New form for blinding info (WIP)
This commit is contained in:
@ -1163,7 +1163,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
|||||||
Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER))) {
|
Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER))) {
|
||||||
out.write(ERR_HELPER_DISABLED.getBytes("UTF-8"));
|
out.write(ERR_HELPER_DISABLED.getBytes("UTF-8"));
|
||||||
} else {
|
} else {
|
||||||
LocalHTTPServer.serveLocalFile(out, method, internalPath, internalRawQuery, _proxyNonce);
|
LocalHTTPServer.serveLocalFile(_context, sockMgr, out, method, internalPath, internalRawQuery, _proxyNonce);
|
||||||
}
|
}
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
// ignore
|
// ignore
|
||||||
@ -1267,26 +1267,13 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
|||||||
if (code != LookupResult.RESULT_SUCCESS) {
|
if (code != LookupResult.RESULT_SUCCESS) {
|
||||||
if (_log.shouldWarn())
|
if (_log.shouldWarn())
|
||||||
_log.warn("Unable to resolve b33 " + destination + " error code " + code);
|
_log.warn("Unable to resolve b33 " + destination + " error code " + code);
|
||||||
// TODO new form to supply missing data
|
|
||||||
if (code != LookupResult.RESULT_FAILURE) {
|
if (code != LookupResult.RESULT_FAILURE) {
|
||||||
String header = getErrorPage("b32", ERR_DESTINATION_UNKNOWN);
|
// form to supply missing data
|
||||||
String msg;
|
writeB32SaveForm(out, destination, code, targetRequest);
|
||||||
if (code == LookupResult.RESULT_SECRET_REQUIRED)
|
|
||||||
msg = "b32 address requires lookup password";
|
|
||||||
else if (code == LookupResult.RESULT_KEY_REQUIRED)
|
|
||||||
msg = "b32 address requires encryption key";
|
|
||||||
else if (code == LookupResult.RESULT_SECRET_AND_KEY_REQUIRED)
|
|
||||||
msg = "b32 address requires encryption key and lookup password";
|
|
||||||
else if (code == LookupResult.RESULT_DECRYPTION_FAILURE)
|
|
||||||
msg = "b32 address decryption failure, check encryption key";
|
|
||||||
else
|
|
||||||
msg = "lookup failure code " + code;
|
|
||||||
try {
|
|
||||||
writeErrorMessage(header, msg, out, targetRequest, false, destination);
|
|
||||||
} catch (IOException ioe) {}
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
// fall through to standard destination unreachable error page
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1504,6 +1491,63 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
|||||||
writeFooter(out);
|
writeFooter(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @since 0.9.43 */
|
||||||
|
private void writeB32SaveForm(OutputStream outs, String destination, int code,
|
||||||
|
String targetRequest) throws IOException {
|
||||||
|
if(outs == null)
|
||||||
|
return;
|
||||||
|
Writer out = new BufferedWriter(new OutputStreamWriter(outs, "UTF-8"));
|
||||||
|
String header = getErrorPage("b32", ERR_DESTINATION_UNKNOWN);
|
||||||
|
out.write(header);
|
||||||
|
out.write("<table id=\"proxyNewHost\">\n<tr><td align=\"right\">" + _t("Host") +
|
||||||
|
"</td><td>" + destination + "</td></tr>\n");
|
||||||
|
out.write("<tr><td align=\"right\">" + _t("Base 32") + "</td>" +
|
||||||
|
"<td><a href=\"http://" + destination + "/\">" + destination + "</a></td></tr>");
|
||||||
|
out.write("\n</table>\n" + "<hr>");
|
||||||
|
String msg;
|
||||||
|
if (code == LookupResult.RESULT_SECRET_REQUIRED)
|
||||||
|
msg = _t("b32 address requires lookup password");
|
||||||
|
else if (code == LookupResult.RESULT_KEY_REQUIRED)
|
||||||
|
msg = _t("b32 address requires encryption key");
|
||||||
|
else if (code == LookupResult.RESULT_SECRET_AND_KEY_REQUIRED)
|
||||||
|
msg = _t("b32 address requires encryption key and lookup password");
|
||||||
|
else if (code == LookupResult.RESULT_DECRYPTION_FAILURE)
|
||||||
|
msg = _t("b32 address decryption failure, check encryption key");
|
||||||
|
else
|
||||||
|
msg = "lookup failure code " + code;
|
||||||
|
out.write("<p><b>" + msg + "</b></p>");
|
||||||
|
out.write("<form method=\"GET\" action=\"http://" + LOCAL_SERVER + "/b32\">\n" +
|
||||||
|
"<input type=\"hidden\" name=\"host\" value=\"" + destination + "\">\n" +
|
||||||
|
"<input type=\"hidden\" name=\"url\" value=\"" + targetRequest + "\">\n" +
|
||||||
|
"<input type=\"hidden\" name=\"code\" value=\"" + code + "\">\n" +
|
||||||
|
"<input type=\"hidden\" name=\"nonce\" value=\"" + _proxyNonce + "\">\n");
|
||||||
|
|
||||||
|
if (code == LookupResult.RESULT_KEY_REQUIRED || code == LookupResult.RESULT_SECRET_AND_KEY_REQUIRED) {
|
||||||
|
String label = _t("Generate");
|
||||||
|
out.write("<h4>" + _t("Encryption key") + "</h4>\n<p>" +
|
||||||
|
"<p>" + _t("You must either enter a PSK encryption key provided by the server operator, or generate a DH encryption key and send that to the server operator.") +
|
||||||
|
' ' + _t("Ask the server operator for help.") +
|
||||||
|
"</p>\n" +
|
||||||
|
|
||||||
|
"<input type=\"text\" size=\"55\" name=\"privkey\" value=\"\"></p>\n" +
|
||||||
|
"<p>" + _t("Generate new DH encryption key") +
|
||||||
|
"<button type=\"submit\" class=\"accept\" name=\"action\" value=\"newdh\">" + label + "</button>\n");
|
||||||
|
//"<p>" + _t("Generate new PSK encryption key") +
|
||||||
|
//"<button type=\"submit\" class=\"accept\" name=\"action\" value=\"newpsk\">" + label + "</button>\n");
|
||||||
|
}
|
||||||
|
if (code == LookupResult.RESULT_SECRET_REQUIRED || code == LookupResult.RESULT_SECRET_AND_KEY_REQUIRED) {
|
||||||
|
out.write("<h4>" + _t("Lookup password") + "</h4>\n<p>" +
|
||||||
|
"<input type=\"text\" size=\"55\" name=\"secret\" value=\"\"></p>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME wasn't escaped
|
||||||
|
String label = _t("Save & continue").replace("&", "&");
|
||||||
|
out.write("<div class=\"formaction\"><button type=\"submit\" class=\"accept\" name=\"action\" value=\"save\">" +
|
||||||
|
label + "</button></div>\n" +
|
||||||
|
"</form>\n</div>\n");
|
||||||
|
writeFooter(out);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read the first line unbuffered.
|
* Read the first line unbuffered.
|
||||||
* After that, switch to a BufferedReader, unless the method is "POST".
|
* After that, switch to a BufferedReader, unless the method is "POST".
|
||||||
|
@ -12,10 +12,21 @@ import java.util.Properties;
|
|||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.client.I2PSession;
|
||||||
|
import net.i2p.client.I2PSessionException;
|
||||||
import net.i2p.client.naming.NamingService;
|
import net.i2p.client.naming.NamingService;
|
||||||
|
import net.i2p.client.streaming.I2PSocketManager;
|
||||||
|
import net.i2p.crypto.Blinding;
|
||||||
|
import net.i2p.crypto.EncType;
|
||||||
|
import net.i2p.crypto.KeyPair;
|
||||||
|
import net.i2p.crypto.SigType;
|
||||||
|
import net.i2p.data.Base64;
|
||||||
|
import net.i2p.data.BlindData;
|
||||||
import net.i2p.data.DataFormatException;
|
import net.i2p.data.DataFormatException;
|
||||||
import net.i2p.data.DataHelper;
|
import net.i2p.data.DataHelper;
|
||||||
import net.i2p.data.Destination;
|
import net.i2p.data.Destination;
|
||||||
|
import net.i2p.data.PrivateKey;
|
||||||
|
import net.i2p.data.SigningPublicKey;
|
||||||
import net.i2p.i2ptunnel.I2PTunnelHTTPClient;
|
import net.i2p.i2ptunnel.I2PTunnelHTTPClient;
|
||||||
import net.i2p.util.FileUtil;
|
import net.i2p.util.FileUtil;
|
||||||
import net.i2p.util.PortMapper;
|
import net.i2p.util.PortMapper;
|
||||||
@ -51,6 +62,14 @@ public abstract class LocalHTTPServer {
|
|||||||
"\r\n"+
|
"\r\n"+
|
||||||
"Add to addressbook failed - bad parameters";
|
"Add to addressbook failed - bad parameters";
|
||||||
|
|
||||||
|
private final static String ERR_B32 =
|
||||||
|
"HTTP/1.1 409 Bad\r\n"+
|
||||||
|
"Content-Type: text/plain\r\n"+
|
||||||
|
"Connection: close\r\n"+
|
||||||
|
"Proxy-Connection: close\r\n"+
|
||||||
|
"\r\n"+
|
||||||
|
"B32 update failed - bad parameters";
|
||||||
|
|
||||||
private final static String OK =
|
private final static String OK =
|
||||||
"HTTP/1.1 200 OK\r\n" +
|
"HTTP/1.1 200 OK\r\n" +
|
||||||
"Content-Type: text/plain\r\n" +
|
"Content-Type: text/plain\r\n" +
|
||||||
@ -79,10 +98,12 @@ public abstract class LocalHTTPServer {
|
|||||||
* uncaught vulnerabilities.
|
* uncaught vulnerabilities.
|
||||||
* Restrict to the /themes/ directory for now.
|
* Restrict to the /themes/ directory for now.
|
||||||
*
|
*
|
||||||
|
* @param sockMgr only for /b32, otherwise ignored
|
||||||
* @param targetRequest decoded path only, non-null
|
* @param targetRequest decoded path only, non-null
|
||||||
* @param query raw (encoded), may be null
|
* @param query raw (encoded), may be null
|
||||||
*/
|
*/
|
||||||
public static void serveLocalFile(OutputStream out, String method, String targetRequest,
|
public static void serveLocalFile(I2PAppContext context, I2PSocketManager sockMgr,
|
||||||
|
OutputStream out, String method, String targetRequest,
|
||||||
String query, String proxyNonce) throws IOException {
|
String query, String proxyNonce) throws IOException {
|
||||||
//System.err.println("targetRequest: \"" + targetRequest + "\"");
|
//System.err.println("targetRequest: \"" + targetRequest + "\"");
|
||||||
// a home page message for the curious...
|
// a home page message for the curious...
|
||||||
@ -102,8 +123,8 @@ public abstract class LocalHTTPServer {
|
|||||||
}
|
}
|
||||||
// theme hack
|
// theme hack
|
||||||
if (filename.startsWith("console/default/"))
|
if (filename.startsWith("console/default/"))
|
||||||
filename = filename.replaceFirst("default", I2PAppContext.getGlobalContext().getProperty("routerconsole.theme", "light"));
|
filename = filename.replaceFirst("default", context.getProperty("routerconsole.theme", "light"));
|
||||||
File themesDir = new File(I2PAppContext.getGlobalContext().getBaseDir(), "docs/themes");
|
File themesDir = new File(context.getBaseDir(), "docs/themes");
|
||||||
File file = new File(themesDir, filename);
|
File file = new File(themesDir, filename);
|
||||||
if (file.exists() && !file.isDirectory()) {
|
if (file.exists() && !file.isDirectory()) {
|
||||||
String type;
|
String type;
|
||||||
@ -167,7 +188,7 @@ public abstract class LocalHTTPServer {
|
|||||||
//System.err.println("book : \"" + book + "\"");
|
//System.err.println("book : \"" + book + "\"");
|
||||||
//System.err.println("nonce : \"" + nonce + "\"");
|
//System.err.println("nonce : \"" + nonce + "\"");
|
||||||
if (proxyNonce.equals(nonce) && url != null && host != null && dest != null) {
|
if (proxyNonce.equals(nonce) && url != null && host != null && dest != null) {
|
||||||
NamingService ns = I2PAppContext.getGlobalContext().namingService();
|
NamingService ns = context.namingService();
|
||||||
Properties nsOptions = new Properties();
|
Properties nsOptions = new Properties();
|
||||||
nsOptions.setProperty("list", book);
|
nsOptions.setProperty("list", book);
|
||||||
if (referer != null && referer.startsWith("http")) {
|
if (referer != null && referer.startsWith("http")) {
|
||||||
@ -182,6 +203,88 @@ public abstract class LocalHTTPServer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
out.write(ERR_ADD.getBytes("UTF-8"));
|
out.write(ERR_ADD.getBytes("UTF-8"));
|
||||||
|
|
||||||
|
} else if (targetRequest.equals("/b32")) {
|
||||||
|
// Send a blinding info message (form submit)
|
||||||
|
// Parameters are url, host, dest, nonce, and master | router | private.
|
||||||
|
// Do the add and redirect.
|
||||||
|
if (query == null) {
|
||||||
|
out.write(ERR_ADD.getBytes("UTF-8"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<String, String> opts = new HashMap<String, String>(8);
|
||||||
|
// FIXME
|
||||||
|
// this only works if all keys are followed by =value
|
||||||
|
StringTokenizer tok = new StringTokenizer(query, "=&;");
|
||||||
|
while (tok.hasMoreTokens()) {
|
||||||
|
String k = tok.nextToken();
|
||||||
|
if (!tok.hasMoreTokens())
|
||||||
|
break;
|
||||||
|
String v = tok.nextToken();
|
||||||
|
opts.put(decode(k), decode(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
String err = null;
|
||||||
|
String url = opts.get("url");
|
||||||
|
String host = opts.get("host");
|
||||||
|
String nonce = opts.get("nonce");
|
||||||
|
String code = opts.get("code");
|
||||||
|
String privkey = opts.get("privkey");
|
||||||
|
String secret = opts.get("secret");
|
||||||
|
String action = opts.get("action");
|
||||||
|
if (proxyNonce.equals(nonce) && url != null && host != null && code != null) {
|
||||||
|
boolean success = true;
|
||||||
|
PrivateKey privateKey = null;
|
||||||
|
if (!code.equals("2") && !code.equals("4")) {
|
||||||
|
secret = null;
|
||||||
|
} else if (secret == null || secret.length() == 0) {
|
||||||
|
err = "Missing lookup password";
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int authType = BlindData.AUTH_NONE;
|
||||||
|
if (!code.equals("3") && !code.equals("4")) {
|
||||||
|
privkey = null;
|
||||||
|
} else if ("newdh".equals(action) || "newpsk".equals(action)) {
|
||||||
|
// newpsk probably not required
|
||||||
|
KeyPair kp = context.keyGenerator().generatePKIKeys(EncType.ECIES_X25519);
|
||||||
|
privateKey = kp.getPrivate();
|
||||||
|
authType = action.equals("newdh") ? BlindData.AUTH_DH : BlindData.AUTH_PSK;
|
||||||
|
} else if (privkey == null || privkey.length() == 0) {
|
||||||
|
err = "Missing private key";
|
||||||
|
success = false;
|
||||||
|
} else {
|
||||||
|
byte[] data = Base64.decode(privkey);
|
||||||
|
if (data == null || data.length != 32) {
|
||||||
|
err = "Bad private key";
|
||||||
|
success = false;
|
||||||
|
} else {
|
||||||
|
privateKey = new PrivateKey(EncType.ECIES_X25519, data);
|
||||||
|
authType = BlindData.AUTH_PSK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
try {
|
||||||
|
// get spk and blind type
|
||||||
|
BlindData bd = Blinding.decode(context, host);
|
||||||
|
SigningPublicKey spk = bd.getUnblindedPubKey();
|
||||||
|
SigType bt = bd.getBlindedSigType();
|
||||||
|
bd = new BlindData(context, spk, bt, secret, authType, privateKey);
|
||||||
|
I2PSession sess = sockMgr.getSession();
|
||||||
|
sess.sendBlindingInfo(bd, 365*60*60*1000);
|
||||||
|
writeRedirectPage(out, success, host, "FIXME", url);
|
||||||
|
return;
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
err = iae.toString();
|
||||||
|
} catch (I2PSessionException ise) {
|
||||||
|
err = ise.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.write(ERR_B32.getBytes("UTF-8"));
|
||||||
|
if (err != null)
|
||||||
|
out.write(("\n\n" + err + "\n\nGo back and fix it").getBytes("UTF-8"));
|
||||||
} else {
|
} else {
|
||||||
out.write(ERR_404.getBytes("UTF-8"));
|
out.write(ERR_404.getBytes("UTF-8"));
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
|
2019-09-12 zzz
|
||||||
|
* I2CP: BlindingInfo fixes
|
||||||
|
* i2ptunnel: New form for blinding info
|
||||||
|
|
||||||
2019-09-10 zzz
|
2019-09-10 zzz
|
||||||
* I2CP: New Blinding Info message (proposal 123)
|
* I2CP: New Blinding Info message (proposal 123)
|
||||||
|
* i2ptunnel: New b32 error page
|
||||||
|
* Util: Fix AIOOBE on bad input to base 32 decode
|
||||||
|
|
||||||
2019-09-08 zzz
|
2019-09-08 zzz
|
||||||
* Transport:
|
* Transport:
|
||||||
|
@ -18,7 +18,7 @@ public class RouterVersion {
|
|||||||
/** deprecated */
|
/** deprecated */
|
||||||
public final static String ID = "Monotone";
|
public final static String ID = "Monotone";
|
||||||
public final static String VERSION = CoreVersion.VERSION;
|
public final static String VERSION = CoreVersion.VERSION;
|
||||||
public final static long BUILD = 6;
|
public final static long BUILD = 7;
|
||||||
|
|
||||||
/** for example "-test" */
|
/** for example "-test" */
|
||||||
public final static String EXTRA = "";
|
public final static String EXTRA = "";
|
||||||
|
Reference in New Issue
Block a user