diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java index 4c9d3f1264..8619c6ceb8 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java @@ -71,6 +71,7 @@ public class EditBean extends IndexBean { return _helper.getPrivateKeyFile(tunnel); } +/**** public String getNameSignature(int tunnel) { String spoof = getSpoofedHost(tunnel); if (spoof.length() <= 0) @@ -100,6 +101,26 @@ public class EditBean extends IndexBean { } return ""; } +****/ + + /** + * @since 0.9.26 + * @return key or null + */ + public SigningPrivateKey getSigningPrivateKey(int tunnel) { + TunnelController tun = getController(tunnel); + if (tun == null) + return null; + String keyFile = tun.getPrivKeyFile(); + if (keyFile != null && keyFile.trim().length() > 0) { + File f = new File(keyFile); + if (!f.isAbsolute()) + f = new File(_context.getConfigDir(), keyFile); + PrivateKeyFile pkf = new PrivateKeyFile(f); + return pkf.getSigningPrivKey(); + } + return null; + } public boolean startAutomatically(int tunnel) { return _helper.shouldStartAutomatically(tunnel); diff --git a/apps/i2ptunnel/jsp/editServer.jsp b/apps/i2ptunnel/jsp/editServer.jsp index 4b51b49795..fe31926e06 100644 --- a/apps/i2ptunnel/jsp/editServer.jsp +++ b/apps/i2ptunnel/jsp/editServer.jsp @@ -228,6 +228,9 @@ input.default { width: 1px; height: 1px; visibility: hidden; } +<% + /****** +%> <% if (("httpserver".equals(tunnelType)) || ("httpbidirserver".equals(tunnelType))) { String sig = editBean.getNameSignature(curTunnel); if (sig.length() > 0) { @@ -240,6 +243,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; } <% } // sig } // type + ****/ String b64 = editBean.getDestinationBase64(curTunnel); if (!"".equals(b64)) { @@ -256,11 +260,14 @@ input.default { width: 1px; height: 1px; visibility: hidden; } " href="/imagegen/qr?s=320&t=<%=name%>&c=http%3a%2f%2f<%=name%>%2f%3fi2paddresshelper%3d<%=b64%>" target="_top"><%=intl._t("Generate QR Code")%> <%=intl._t("Add to local addressbook")%> +      + <%=intl._t("Registration Authentication")%> <% } else { %> <%=intl._t("Set name with .i2p suffix to enable QR code generation")%> + <%=intl._t("Set name with .i2p suffix to enable registration authentication")%> <% } // name %> diff --git a/apps/i2ptunnel/jsp/register.jsp b/apps/i2ptunnel/jsp/register.jsp new file mode 100644 index 0000000000..f7f874a447 --- /dev/null +++ b/apps/i2ptunnel/jsp/register.jsp @@ -0,0 +1,191 @@ +<%@page contentType="text/html" import="net.i2p.i2ptunnel.web.EditBean,net.i2p.client.naming.HostTxtEntry,net.i2p.data.SigningPrivateKey,net.i2p.util.OrderedProperties" +%><%@page trimDirectiveWhitespaces="true" +%> + +<% + /* right now using EditBean instead of IndexBean for getSpoofedHost() */ + /* but might want to POST to it anyway ??? */ +%> + + +<% String tun = request.getParameter("tunnel"); + int curTunnel = -1; + if (tun != null) { + try { + curTunnel = Integer.parseInt(tun); + } catch (NumberFormatException nfe) { + curTunnel = -1; + } + } +%> + + + <%=intl._t("Hidden Services Manager")%> - <%=intl._t("Registration Helper")%> + + + + + <% if (editBean.allowCSS()) { + %> + + + <% } + %> + + + + +<% + + if (editBean.isInitialized()) { + +%> +
+ +
+
+<% + String tunnelTypeName; + String tunnelType; + boolean valid = false; + if (curTunnel >= 0) { + tunnelTypeName = editBean.getTunnelType(curTunnel); + tunnelType = editBean.getInternalType(curTunnel); + %>

<%=intl._t("Registration Helper")%>

<% + } else { + tunnelTypeName = "new"; + tunnelType = "new"; + %>

Fail

Tunnel not found

<% + } + String b64 = editBean.getDestinationBase64(curTunnel); + String name = editBean.getSpoofedHost(curTunnel); + if (name == null || name.equals("")) + name = editBean.getTunnelName(curTunnel); +%> + + + + +
+<% + if (!"new".equals(tunnelType)) { +%> +
+
+
+ +
+ + <%=editBean.getTunnelName(curTunnel)%> +
+
+ + <%=tunnelTypeName%> +
+ +<% + if (("httpserver".equals(tunnelType)) || ("httpbidirserver".equals(tunnelType))) { + %>
+ + <%=editBean.getSpoofedHost(curTunnel)%> +
+<% + } +%> +
+ + +
+
+
+
+<% + if (b64 == null || b64.length() < 516) { + %><%=intl._t("Local destination is not available. Start the tunnel.")%><% + } else if (name == null || name.equals("") || name.contains(" ") || !name.endsWith(".i2p")) { + if (("httpserver".equals(tunnelType)) || ("httpbidirserver".equals(tunnelType))) { + %><%=intl._t("To enable registration verification, edit tunnel and set name (or website name) to a valid host name ending in '.i2p'")%><% + } else { + %><%=intl._t("To enable registration verification, edit tunnel and set name to a valid host name ending in '.i2p'")%><% + } + } else { + SigningPrivateKey spk = editBean.getSigningPrivateKey(curTunnel); + if (spk == null) { + %><%=intl._t("Destination signing key is not available. Start the tunnel.")%><% + } else { + valid = true; + OrderedProperties props = new OrderedProperties(); + HostTxtEntry he = new HostTxtEntry(name, b64, props); + he.sign(spk); + %>
+ + <%=intl._t("Select and copy the entire contents of the appropriate box")%> +
+
+ + +
+<% + props.remove(HostTxtEntry.PROP_SIG); + props.setProperty(HostTxtEntry.PROP_ACTION, HostTxtEntry.ACTION_REMOVE); + he.signRemove(spk); + %>
+ + +
+ + + +<% + } // spk != null + } // valid b64 and name + } // !"new".equals(tunnelType) + if (!valid) { + %><%=intl._t("Go back and edit the tunnel")%><% + } +%> +
+ + +<% + if (false && valid) { +%> +
+
+ +
+<% + } // valid +%> +
+ +<% + + } else { + %>Tunnels are not initialized yet, please reload in two minutes.<% + } // isInitialized() + +%> + + diff --git a/apps/i2ptunnel/jsp/web.xml b/apps/i2ptunnel/jsp/web.xml index 0a74b68357..2387328683 100644 --- a/apps/i2ptunnel/jsp/web.xml +++ b/apps/i2ptunnel/jsp/web.xml @@ -32,6 +32,11 @@ /wizard + + net.i2p.i2ptunnel.jsp.register_jsp + /register + + diff --git a/core/java/src/net/i2p/client/naming/HostTxtEntry.java b/core/java/src/net/i2p/client/naming/HostTxtEntry.java index f510b03d78..d68a4c80c1 100644 --- a/core/java/src/net/i2p/client/naming/HostTxtEntry.java +++ b/core/java/src/net/i2p/client/naming/HostTxtEntry.java @@ -139,7 +139,7 @@ public class HostTxtEntry { out.write(KV_SEPARATOR); out.write(dest); } - writeProps(out, false, false); + writeProps(out); out.newLine(); } @@ -149,21 +149,38 @@ public class HostTxtEntry { * Includes newline. * Must have been constructed with non-null properties. */ - public void writeRemove(BufferedWriter out) throws IOException { + public void writeRemoveLine(BufferedWriter out) throws IOException { + writeRemove(out); + out.newLine(); + } + + /** + * Write as a "remove" line #!dest=dest#name=name#k1=v1#sig=sig...] + * This works whether constructed with name and dest, or just properties. + * Does not include newline. + * Must have been constructed with non-null properties. + */ + public void writeRemove(Writer out) throws IOException { if (props == null) throw new IllegalStateException(); if (name != null && dest != null) { props.setProperty(PROP_NAME, name); props.setProperty(PROP_DEST, dest); } - writeProps(out, false, false); - out.newLine(); + writeProps(out); if (name != null && dest != null) { props.remove(PROP_NAME); props.remove(PROP_DEST); } } + /** + * Write the props part (if any) only, without newline + */ + public void writeProps(Writer out) throws IOException { + writeProps(out, false, false); + } + /** * Write the props part (if any) only, without newline */ @@ -346,20 +363,23 @@ public class HostTxtEntry { /** * Sign and set the "sig" property + * Must have been constructed with non-null properties. */ - private void sign(SigningPrivateKey spk) { + public void sign(SigningPrivateKey spk) { signIt(spk, PROP_SIG); } /** * Sign and set the "oldsig" property + * Must have been constructed with non-null properties. */ - private void signInner(SigningPrivateKey spk) { + public void signInner(SigningPrivateKey spk) { signIt(spk, PROP_OLDSIG); } /** * Sign as a "remove" line #!dest=dest#name=name#k1=v1#sig=sig...] + * Must have been constructed with non-null properties. */ public void signRemove(SigningPrivateKey spk) { if (props == null) @@ -370,7 +390,7 @@ public class HostTxtEntry { props.setProperty(PROP_DEST, dest); StringWriter buf = new StringWriter(1024); try { - writeProps(buf, false, false); + writeProps(buf); } catch (IOException ioe) { throw new IllegalStateException(ioe); } @@ -395,7 +415,7 @@ public class HostTxtEntry { buf.append(KV_SEPARATOR); buf.append(dest); try { - writeProps(buf, false, false); + writeProps(buf); } catch (IOException ioe) { throw new IllegalStateException(ioe); } @@ -490,7 +510,7 @@ public class HostTxtEntry { //out.write("Remove entry:\n"); sw = new StringWriter(1024); buf = new BufferedWriter(sw); - he.writeRemove(buf); + he.writeRemoveLine(buf); buf.flush(); out.write(sw.toString()); out.flush(); diff --git a/core/java/src/net/i2p/data/PrivateKeyFile.java b/core/java/src/net/i2p/data/PrivateKeyFile.java index 428d3f2401..c4414cae78 100644 --- a/core/java/src/net/i2p/data/PrivateKeyFile.java +++ b/core/java/src/net/i2p/data/PrivateKeyFile.java @@ -455,11 +455,29 @@ public class PrivateKeyFile { return c; } + /** + * @return null on error or if not initialized + */ public PrivateKey getPrivKey() { + try { + // call this to force initialization + getDestination(); + } catch (Exception e) { + return null; + } return this.privKey; } + /** + * @return null on error or if not initialized + */ public SigningPrivateKey getSigningPrivKey() { + try { + // call this to force initialization + getDestination(); + } catch (Exception e) { + return null; + } return this.signingPrivKey; }