From 2a34d1c44afb2a0963630d88d1633461131e776e Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 24 Apr 2016 18:10:10 +0000 Subject: [PATCH] HostTxtEntry: Fixups for use by i2ptunnel i2ptunnel: Add new registration authentication page - Remove old, unused hostname signature generation PrivateKeyFile: Ensure initialization before returning private keys --- .../src/net/i2p/i2ptunnel/web/EditBean.java | 21 ++ apps/i2ptunnel/jsp/editServer.jsp | 7 + apps/i2ptunnel/jsp/register.jsp | 191 ++++++++++++++++++ apps/i2ptunnel/jsp/web.xml | 5 + .../net/i2p/client/naming/HostTxtEntry.java | 38 +++- .../java/src/net/i2p/data/PrivateKeyFile.java | 18 ++ 6 files changed, 271 insertions(+), 9 deletions(-) create mode 100644 apps/i2ptunnel/jsp/register.jsp 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; } <textarea rows="1" style="height: 3em;" cols="60" readonly="readonly" id="localDestination" title="Read Only: Local Destination (if known)" wrap="off" spellcheck="false"><%=editBean.getDestinationBase64(curTunnel)%></textarea> </div> +<% + /****** +%> <% 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; } <a class="control" title="<%=intl._t("Generate QR Code")%>" href="/imagegen/qr?s=320&t=<%=name%>&c=http%3a%2f%2f<%=name%>%2f%3fi2paddresshelper%3d<%=b64%>" target="_top"><%=intl._t("Generate QR Code")%></a> </label> <a class="control" href="/susidns/addressbook.jsp?book=private&hostname=<%=name%>&destination=<%=b64%>#add"><%=intl._t("Add to local addressbook")%></a> + + <a class="control" href="register?tunnel=<%=curTunnel%>"><%=intl._t("Registration Authentication")%></a> <% } else { %> <label> </label> <span class="comment"><%=intl._t("Set name with .i2p suffix to enable QR code generation")%></span> + <span class="comment"><%=intl._t("Set name with .i2p suffix to enable registration authentication")%></span> <% } // 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" +%><?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<% + /* right now using EditBean instead of IndexBean for getSpoofedHost() */ + /* but might want to POST to it anyway ??? */ +%> +<jsp:useBean class="net.i2p.i2ptunnel.web.EditBean" id="editBean" scope="request" /> +<jsp:useBean class="net.i2p.i2ptunnel.web.Messages" id="intl" scope="request" /> +<% String tun = request.getParameter("tunnel"); + int curTunnel = -1; + if (tun != null) { + try { + curTunnel = Integer.parseInt(tun); + } catch (NumberFormatException nfe) { + curTunnel = -1; + } + } +%> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> + <title><%=intl._t("Hidden Services Manager")%> - <%=intl._t("Registration Helper")%></title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" /> + <link href="/themes/console/images/favicon.ico" type="image/x-icon" rel="shortcut icon" /> + + <% if (editBean.allowCSS()) { + %><link rel="icon" href="<%=editBean.getTheme()%>images/favicon.ico" /> + <link href="<%=editBean.getTheme()%>default.css" rel="stylesheet" type="text/css" /> + <link href="<%=editBean.getTheme()%>i2ptunnel.css" rel="stylesheet" type="text/css" /> + <% } + %> +<style type='text/css'> +input.default { width: 1px; height: 1px; visibility: hidden; } +</style> +</head> +<body id="tunnelEditPage"> + <div id="pageHeader"> + </div> +<% + + if (editBean.isInitialized()) { + +%> + <form method="post" action="authenticate"> + + <div id="tunnelEditPanel" class="panel"> + <div class="header"> +<% + String tunnelTypeName; + String tunnelType; + boolean valid = false; + if (curTunnel >= 0) { + tunnelTypeName = editBean.getTunnelType(curTunnel); + tunnelType = editBean.getInternalType(curTunnel); + %><h4><%=intl._t("Registration Helper")%></h4><% + } else { + tunnelTypeName = "new"; + tunnelType = "new"; + %><h4>Fail</h4><p>Tunnel not found</p><% + } + String b64 = editBean.getDestinationBase64(curTunnel); + String name = editBean.getSpoofedHost(curTunnel); + if (name == null || name.equals("")) + name = editBean.getTunnelName(curTunnel); +%> + <input type="hidden" name="tunnel" value="<%=curTunnel%>" /> + <input type="hidden" name="nonce" value="<%=net.i2p.i2ptunnel.web.IndexBean.getNextNonce()%>" /> + <input type="hidden" name="type" value="<%=tunnelType%>" /> + <input type="submit" class="default" name="action" value="Save changes" /> + </div> +<% + if (!"new".equals(tunnelType)) { +%> + <div class="separator"> + <hr /> + </div> + + <div id="nameField" class="rowItem"> + <label for="name" accesskey="N"> + <%=intl._t("Name")%>(<span class="accessKey">N</span>): + </label> + <span class="text"><%=editBean.getTunnelName(curTunnel)%></span> + </div> + <div id="typeField" class="rowItem"> + <label><%=intl._t("Type")%>:</label> + <span class="text"><%=tunnelTypeName%></span> + </div> + +<% + if (("httpserver".equals(tunnelType)) || ("httpbidirserver".equals(tunnelType))) { + %><div id="websiteField" class="rowItem"> + <label for="spoofedHost" accesskey="W"> + <%=intl._t("Website name")%>(<span class="accessKey">W</span>): + </label> + <span class="text"><%=editBean.getSpoofedHost(curTunnel)%></span> + </div> +<% + } +%> + <div id="destinationField" class="rowItem"> + <label for="localDestination" accesskey="L"> + <%=intl._t("Local destination")%>(<span class="accessKey">L</span>): + </label> + <textarea rows="1" style="height: 3em;" cols="60" readonly="readonly" id="localDestination" title="Read Only: Local Destination (if known)" wrap="off" spellcheck="false"><%=editBean.getDestinationBase64(curTunnel)%></textarea> + </div> + <div class="subdivider"> + <hr /> + </div> +<% + 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); + %><div id="destinationField" class="rowItem"> + <label><%=intl._t("Authentication Strings")%>:</label> + <span class="text"><%=intl._t("Select and copy the entire contents of the appropriate box")%></span> + </div> + <div id="sigField" class="rowItem"> + <label for="signature"> + <%=intl._t("Authentication for adding host")%> + </label> + <textarea rows="1" style="height: 3em;" cols="60" readonly="readonly" id="localDestination" title="Copy and paste this to the registration site" wrap="off" spellcheck="false"><% he.writeProps(out); %></textarea> + </div> +<% + props.remove(HostTxtEntry.PROP_SIG); + props.setProperty(HostTxtEntry.PROP_ACTION, HostTxtEntry.ACTION_REMOVE); + he.signRemove(spk); + %><div id="sigField" class="rowItem"> + <label for="signature"> + <%=intl._t("Authentication for removing host")%> + </label> + <textarea rows="1" style="height: 3em;" cols="60" readonly="readonly" id="localDestination" title="Copy and paste this to the registration site" wrap="off" spellcheck="false"><% he.writeRemove(out); %></textarea> + </div> + + + <div class="footer"> + </div> +<% + } // spk != null + } // valid b64 and name + } // !"new".equals(tunnelType) + if (!valid) { + %><a href="edit?tunnel=<%=curTunnel%>"><%=intl._t("Go back and edit the tunnel")%></a><% + } +%> + </div> + + +<% + if (false && valid) { +%> + <div id="globalOperationsPanel" class="panel"> + <div class="header"></div> + <div class="footer"> + <div class="toolbox"> + <input type="hidden" value="true" name="removeConfirm" /> + <button id="controlCancel" class="control" type="submit" name="action" value="" title="Cancel"><%=intl._t("Cancel")%></button> + <button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="authenticate" title="Generate Authentication"><%=intl._t("Generate")%>(<span class="accessKey">S</span>)</button> + </div> + </div> + </div> +<% + } // valid +%> + </form> + <div id="pageFooter"> + </div> +<% + + } else { + %>Tunnels are not initialized yet, please reload in two minutes.<% + } // isInitialized() + +%> +</body> +</html> 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 @@ <url-pattern>/wizard</url-pattern> </servlet-mapping> + <servlet-mapping> + <servlet-name>net.i2p.i2ptunnel.jsp.register_jsp</servlet-name> + <url-pattern>/register</url-pattern> + </servlet-mapping> + <!-- this webapp doesn't actually use sessions or cookies --> <session-config> <session-timeout> 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; } -- GitLab