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&amp;t=<%=name%>&amp;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&amp;hostname=<%=name%>&amp;destination=<%=b64%>#add"><%=intl._t("Add to local addressbook")%></a>    
+              &nbsp;&nbsp;&nbsp;&nbsp;
+              <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