From 8d104f7fea392302532198d6b7362ad8075a5738 Mon Sep 17 00:00:00 2001
From: zzz
Date: Thu, 12 Sep 2019 17:42:44 +0000
Subject: [PATCH] i2ptunnel: New form for blinding info (WIP)
---
.../i2p/i2ptunnel/I2PTunnelHTTPClient.java | 78 +++++++++---
.../localServer/LocalHTTPServer.java | 111 +++++++++++++++++-
core/java/src/net/i2p/data/BlindData.java | 2 +-
history.txt | 6 +
.../src/net/i2p/router/RouterVersion.java | 2 +-
5 files changed, 176 insertions(+), 23 deletions(-)
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java
index c73589e60..63df4acce 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java
@@ -1163,7 +1163,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
Boolean.parseBoolean(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER))) {
out.write(ERR_HELPER_DISABLED.getBytes("UTF-8"));
} else {
- LocalHTTPServer.serveLocalFile(out, method, internalPath, internalRawQuery, _proxyNonce);
+ LocalHTTPServer.serveLocalFile(_context, sockMgr, out, method, internalPath, internalRawQuery, _proxyNonce);
}
} catch (IOException ioe) {
// ignore
@@ -1267,26 +1267,13 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
if (code != LookupResult.RESULT_SUCCESS) {
if (_log.shouldWarn())
_log.warn("Unable to resolve b33 " + destination + " error code " + code);
- // TODO new form to supply missing data
if (code != LookupResult.RESULT_FAILURE) {
- String header = getErrorPage("b32", ERR_DESTINATION_UNKNOWN);
- String msg;
- 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) {}
+ // form to supply missing data
+ writeB32SaveForm(out, destination, code, targetRequest);
return;
}
+ // fall through to standard destination unreachable error page
}
}
} else {
@@ -1504,6 +1491,63 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
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("\n| " + _t("Host") +
+ " | " + destination + " |
\n");
+ out.write("| " + _t("Base 32") + " | " +
+ "" + destination + " |
");
+ out.write("\n
\n" + "
");
+ 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("" + msg + "
");
+ out.write("
\n" +
+ "" + _t("Generate new DH encryption key") +
+ "\n");
+ //"
" + _t("Generate new PSK encryption key") +
+ //"\n");
+ }
+ if (code == LookupResult.RESULT_SECRET_REQUIRED || code == LookupResult.RESULT_SECRET_AND_KEY_REQUIRED) {
+ out.write("
" + _t("Lookup password") + "
\n" +
+ "
\n");
+ }
+
+ // FIXME wasn't escaped
+ String label = _t("Save & continue").replace("&", "&");
+ out.write("\n" +
+ "\n\n");
+ writeFooter(out);
+ }
+
/**
* Read the first line unbuffered.
* After that, switch to a BufferedReader, unless the method is "POST".
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/localServer/LocalHTTPServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/localServer/LocalHTTPServer.java
index c8146c97d..a123701a6 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/localServer/LocalHTTPServer.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/localServer/LocalHTTPServer.java
@@ -12,10 +12,21 @@ import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
+import net.i2p.client.I2PSession;
+import net.i2p.client.I2PSessionException;
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.DataHelper;
import net.i2p.data.Destination;
+import net.i2p.data.PrivateKey;
+import net.i2p.data.SigningPublicKey;
import net.i2p.i2ptunnel.I2PTunnelHTTPClient;
import net.i2p.util.FileUtil;
import net.i2p.util.PortMapper;
@@ -51,6 +62,14 @@ public abstract class LocalHTTPServer {
"\r\n"+
"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 =
"HTTP/1.1 200 OK\r\n" +
"Content-Type: text/plain\r\n" +
@@ -79,10 +98,12 @@ public abstract class LocalHTTPServer {
* uncaught vulnerabilities.
* Restrict to the /themes/ directory for now.
*
+ * @param sockMgr only for /b32, otherwise ignored
* @param targetRequest decoded path only, non-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 {
//System.err.println("targetRequest: \"" + targetRequest + "\"");
// a home page message for the curious...
@@ -102,8 +123,8 @@ public abstract class LocalHTTPServer {
}
// theme hack
if (filename.startsWith("console/default/"))
- filename = filename.replaceFirst("default", I2PAppContext.getGlobalContext().getProperty("routerconsole.theme", "light"));
- File themesDir = new File(I2PAppContext.getGlobalContext().getBaseDir(), "docs/themes");
+ filename = filename.replaceFirst("default", context.getProperty("routerconsole.theme", "light"));
+ File themesDir = new File(context.getBaseDir(), "docs/themes");
File file = new File(themesDir, filename);
if (file.exists() && !file.isDirectory()) {
String type;
@@ -167,7 +188,7 @@ public abstract class LocalHTTPServer {
//System.err.println("book : \"" + book + "\"");
//System.err.println("nonce : \"" + nonce + "\"");
if (proxyNonce.equals(nonce) && url != null && host != null && dest != null) {
- NamingService ns = I2PAppContext.getGlobalContext().namingService();
+ NamingService ns = context.namingService();
Properties nsOptions = new Properties();
nsOptions.setProperty("list", book);
if (referer != null && referer.startsWith("http")) {
@@ -182,6 +203,88 @@ public abstract class LocalHTTPServer {
return;
}
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 opts = new HashMap(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 {
out.write(ERR_404.getBytes("UTF-8"));
}
diff --git a/core/java/src/net/i2p/data/BlindData.java b/core/java/src/net/i2p/data/BlindData.java
index 1959e670f..7ac2332b1 100644
--- a/core/java/src/net/i2p/data/BlindData.java
+++ b/core/java/src/net/i2p/data/BlindData.java
@@ -291,7 +291,7 @@ public class BlindData {
else
buf.append("none");
if (_authKey != null)
- buf.append("\n\tAuth Key : ").append(_authKey);
+ buf.append("\n\tAuth Key : ").append(_authKey);
else
buf.append("\n\tAuth Required : ").append(_authRequired);
if (_dest != null)
diff --git a/history.txt b/history.txt
index d76739f45..8a28a5783 100644
--- a/history.txt
+++ b/history.txt
@@ -1,5 +1,11 @@
+2019-09-12 zzz
+ * I2CP: BlindingInfo fixes
+ * i2ptunnel: New form for blinding info
+
2019-09-10 zzz
* 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
* Transport:
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index c10128fe6..fecba78d6 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -18,7 +18,7 @@ public class RouterVersion {
/** deprecated */
public final static String ID = "Monotone";
public final static String VERSION = CoreVersion.VERSION;
- public final static long BUILD = 6;
+ public final static long BUILD = 7;
/** for example "-test" */
public final static String EXTRA = "";