diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/GeneralHelper.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/GeneralHelper.java index a11894a1a1..6e3f163f8a 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/GeneralHelper.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/GeneralHelper.java @@ -569,11 +569,13 @@ public class GeneralHelper { int rv; String authType = getProperty(tunnel, "i2cp.leaseSetAuthType", "0"); if (authType.equals("2")) { - // shared PSK key - rv = 4; - // per-client PSK key - // TODO - //rv = 6; + if (getProperty(tunnel, "i2cp.leaseSetClient.psk.0", null) != null) { + // per-client PSK key + rv = 6; + } else { + // shared PSK key + rv = 4; + } } else if (authType.equals("1")) { rv = 8; } else { @@ -596,6 +598,25 @@ public class GeneralHelper { public String getBlindedPassword(int tunnel) { return getProperty(tunnel, "i2cp.leaseSetSecret", ""); } + + /** + * List of b64 name : b64key + * Pubkeys for DH, privkeys for PSK + * @param isDH true for DH, false for PSK + * @return non-null + * @since 0.9.41 + */ + public List getClientAuths(int tunnel, boolean isDH) { + List rv = new ArrayList(4); + String pfx = isDH ? "i2cp.leaseSetClient.dh." : "i2cp.leaseSetClient.psk."; + int i = 0; + String p; + while ((p = getProperty(tunnel, pfx + i, null)) != null) { + rv.add(p); + i++; + } + return rv; + } /** * @param newTunnelType used if tunnel < 0 @@ -868,6 +889,8 @@ public class GeneralHelper { if ((!isMD5Proxy) && TunnelConfig._nonProxyNoShowSet.contains(key)) continue; + if (key.startsWith("i2cp.leaseSetClient.")) + continue; sorted.put(key, (String)e.getValue()); } if (sorted.isEmpty()) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java index f3ecb2fffa..e14c39265f 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java @@ -2,7 +2,10 @@ package net.i2p.i2ptunnel.ui; import java.security.GeneralSecurityException; import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; @@ -14,6 +17,7 @@ import net.i2p.client.I2PClient; import net.i2p.crypto.KeyGenerator; import net.i2p.crypto.SigType; import net.i2p.data.Base64; +import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.data.SimpleDataStructure; import net.i2p.i2ptunnel.I2PTunnelClientBase; @@ -77,6 +81,13 @@ public class TunnelConfig { private Destination _dest; private String _filterDefinition; private int _encryptMode; + private String[] _clientNames; + private String[] _clientKeys; + private Set _clientRevocations; + // b64name : b64key + private String _newClientName; + private String _newClientKey; + private boolean _addClientAuth; public TunnelConfig() { _context = I2PAppContext.getGlobalContext(); @@ -271,6 +282,59 @@ public class TunnelConfig { _otherOptions.remove("i2cp.leaseSetSecret"); } + /** + * Multiple entries in form + * @since 0.9.41 + */ + public void addClientNames(String[] s) { + _clientNames = s; + } + + /** + * Multiple entries in form + * Handles either order addClientName/addClientKey + * @since 0.9.41 + */ + public void addClientKeys(String[] s) { + _clientKeys = s; + } + + /** + * Multiple entries in form + * @since 0.9.41 + */ + public void revokeClients(String[] s) { + _clientRevocations = new HashSet(4); + for (String k : s) { + try { + _clientRevocations.add(Integer.valueOf(Integer.parseInt(k))); + } catch (NumberFormatException nfe) {} + } + } + + /** + * Handles either order newClientName/newClientKey + * @since 0.9.41 + */ + public void newClientName(String s) { + _newClientName = s; + } + + /** + * Handles either order newClientName/newClientKey + * @since 0.9.41 + */ + public void newClientKey(String s) { + _newClientKey = s; + } + + /** + * @since 0.9.41 + */ + public void setAddClient(boolean val) { + _addClientAuth = val; + } + public void setDCC(boolean val) { if (val) _booleanOptions.add(I2PTunnelIRCClient.PROP_DCC); @@ -858,6 +922,74 @@ public class TunnelConfig { break; } + + // per-client keys + String pfx; + if (_encryptMode == 6 || _encryptMode == 7) { + pfx = OPT + "i2cp.leaseSetClient.psk."; + } else if (_encryptMode == 8 || _encryptMode == 9) { + pfx = OPT + "i2cp.leaseSetClient.dh."; + } else { + pfx = null; + } + List clientAuth = null; + if (pfx != null) { + if (_clientNames != null && _clientKeys != null && _clientNames.length == _clientKeys.length) { + clientAuth = new ArrayList(4); + } else if (_addClientAuth || _encryptMode == 6 || _encryptMode == 7) { + // force one client for per-client PSK + clientAuth = new ArrayList(1); + if (!_addClientAuth) { + _addClientAuth = true; + if (_newClientName == null || _newClientName.length() == 0) + _newClientName = Base64.encode(DataHelper.getUTF8(GeneralHelper._t("Client", _context) + " 1")); + } + } + } + if (pfx != null && clientAuth != null) { + if (_clientNames != null && _clientKeys != null && _clientNames.length == _clientKeys.length) { + for (int i = 0; i < _clientNames.length; i++) { + if (_clientRevocations != null && _clientRevocations.contains(Integer.valueOf(i))) + continue; + String name = _clientNames[i]; + String key = _clientKeys[i]; + byte[] b = Base64.decode(key); + if (b == null || b.length != 32) + continue; + if (name.length() > 0) + name = Base64.encode(DataHelper.getUTF8(name)); + else + name = Base64.encode(DataHelper.getUTF8(GeneralHelper._t("Client", _context) + ' ' + (i + 1))); + clientAuth.add(name + ':' + key); + } + } + if (_addClientAuth && _newClientName != null) { + String name = _newClientName; + if (name.length() > 0) + name = Base64.encode(DataHelper.getUTF8(name)); + else + name = Base64.encode(DataHelper.getUTF8(GeneralHelper._t("Client", _context) + ' ' + (clientAuth.size() + 1))); + String key; + if (_encryptMode == 6 || _encryptMode == 7) { + byte[] b = new byte[32]; + _context.random().nextBytes(b); + key = Base64.encode(b); + clientAuth.add(name + ':' + key); + } else if (_encryptMode == 8 || _encryptMode == 9) { + key = _newClientKey; + byte[] b = Base64.decode(key); + if (b != null && b.length == 32) { + // key required for DH + clientAuth.add(name + ':' + key); + } + } + } + int i = 0; + for (String auth : clientAuth) { + config.put(pfx + i, auth); + i++; + } + } } /** @@ -933,7 +1065,7 @@ public class TunnelConfig { PROP_MAX_STREAMS, I2PClient.PROP_SIGTYPE, "inbound.randomKey", "outbound.randomKey", "i2cp.leaseSetSigningPrivateKey", "i2cp.leaseSetPrivateKey", I2PTunnelServer.PROP_ALT_PKF, - "i2cp.leaseSetSecret", "i2cp.leaseSetType", "i2cp.leaseSetAuthType" + "i2cp.leaseSetSecret", "i2cp.leaseSetType", "i2cp.leaseSetAuthType", "i2cp.leaseSetPrivKey" }; private static final String _httpServerOpts[] = { I2PTunnelHTTPServer.OPT_POST_WINDOW, 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 831e7b3c9d..a3599e3fe3 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java @@ -226,6 +226,17 @@ public class EditBean extends IndexBean { return _helper.getBlindedPassword(tunnel); } + /** + * List of b64 name : b64key + * Pubkeys for DH, privkeys for PSK + * @param isDH true for DH, false for PSK + * @return non-null + * @since 0.9.41 + */ + public List getClientAuths(int tunnel, boolean isDH) { + return _helper.getClientAuths(tunnel, isDH); + } + /** * @param newTunnelType used if tunnel < 0 * @since 0.9.12 diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java index 0425e6e34d..7e45ca9dc4 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -872,6 +872,63 @@ public class IndexBean { _config.setBlindedPassword(s); } + /** + * Multiple entries in form + * @since 0.9.41 + */ + public void setNofilter_clientName(String[] s) { + if (s != null) { + _config.addClientNames(s); + } + } + + + /** + * Multiple entries in form + * @since 0.9.41 + */ + public void setclientKey(String[] s) { + if (s != null) { + _config.addClientKeys(s); + } + } + + /** + * Multiple entries in form + * Values are integers + * @since 0.9.41 + */ + public void setRevokeClient(String[] s) { + if (s != null) { + _config.revokeClients(s); + } + } + + /** + * @since 0.9.41 + */ + public void setNofilter_newClientName(String s) { + if (s != null) { + _config.newClientName(s.trim()); + } + } + + /** + * @since 0.9.41 + */ + public void setNewClientKey(String s) { + if (s != null) { + _config.newClientKey(s.trim()); + } + } + + /** + * @since 0.9.41 + */ + public void setAddClient(String moo) { + _config.setAddClient(true); + } + /** @since 0.8.9 */ public void setDCC(String moo) { _config.setDCC(true); diff --git a/apps/i2ptunnel/jsp/editServer.jsi b/apps/i2ptunnel/jsp/editServer.jsi index b353d9c3bd..c61e1e8e5f 100644 --- a/apps/i2ptunnel/jsp/editServer.jsi +++ b/apps/i2ptunnel/jsp/editServer.jsi @@ -155,7 +155,7 @@ -
" tabindex="0" onblur="resetScrollLeft(this)"><%=editBean.getDestinationBase64(curTunnel)%>
+
" tabindex="0" onblur="resetScrollLeft(this)"><%=editBean.getDestinationBase64(curTunnel)%>
<% String value3 = editBean.getPrivateKeyFile(curTunnel); if (value3 == null || "".equals(value3.trim())) { @@ -485,20 +485,27 @@ - <%=intl._t("Encryption Key")%> - <% if (allowBlinding && editBean.isAdvanced()) { %> <%=intl._t("Optional lookup password")%>: <% } // allowBlinding +%> + +<% + // even if not shown, we need to preserve the param as a hidden input below + // as it's the key we use to decrypt the PSK/DH LS on the router side + boolean showSharedKey = curEncryptMode.equals("1") || curEncryptMode.equals("4") || curEncryptMode.equals("5"); + if (showSharedKey) { +%> + <%=intl._t("Encryption Key")%> +<% + } // showSharedKey %> - - <% if (allowBlinding && editBean.isAdvanced()) { %> @@ -506,9 +513,71 @@ <% } // allowBlinding %> + + " style="user-select: all;" size="44" id="leasesetKey" name="encryptKey" title="<%=intl._t("Encryption key required to access this service")%>" readonly="readonly" value="<%=editBean.getEncryptKey(curTunnel)%>" class="freetext"/> <% + if (allowBlinding && editBean.isAdvanced()) { + boolean pskClient = curEncryptMode.equals("6") || curEncryptMode.equals("7"); + boolean dhClient = curEncryptMode.equals("8") || curEncryptMode.equals("9"); + if (pskClient || dhClient) { + // b64Name:b64Key + java.util.List clientAuths = editBean.getClientAuths(curTunnel, dhClient); + if (!clientAuths.isEmpty()) { +%> + <%=intl._t("Revoke?")%>    <%=intl._t("Client Name")%><%=intl._t("Client Key")%> +<% + int i = 0; + for (String clientAuth : clientAuths) { + String[] split = net.i2p.data.DataHelper.split(clientAuth, ":", 2); + String cname, ckey; + if (split.length == 2) { + cname = split[0]; + ckey = split[1]; + } else { + cname = ""; + ckey = split[0]; + } + if (cname.length() > 0) { + cname = net.i2p.data.DataHelper.escapeHTML(net.i2p.data.DataHelper.getUTF8(net.i2p.data.Base64.decode(cname))); + } else { + cname = intl._t("Client") + ' ' + (i + 1); + } +%> + + + + + +<% + i++; + } // for + } // isEmpty +%> + <%=intl._t("Add?")%>    <%=intl._t("Client Name")%> +<% + if (dhClient) { +%> + <%=intl._t("Client Key")%> +<% + } // dhClient +%> + + + " size="44" name="nofilter_newClientName" class="freetext" /> + +<% + if (dhClient) { +%> + +<% + } // dhClient +%> + +<% + } // pskClient || dhClient + } // allowBlinding } // !isOffline %> diff --git a/apps/i2ptunnel/jsp/register.jsp b/apps/i2ptunnel/jsp/register.jsp index 91bcf7b78c..d49e3004a2 100644 --- a/apps/i2ptunnel/jsp/register.jsp +++ b/apps/i2ptunnel/jsp/register.jsp @@ -150,7 +150,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; } -
"><% he.write(out); %>
+
"><% he.write(out); %>
@@ -171,7 +171,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; } -
"><% he.writeRemove(out); %>
+
"><% he.writeRemove(out); %>
@@ -211,7 +211,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; } %> -
"><% he.write(out); %>
+
"><% he.write(out); %>
@@ -244,7 +244,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; } %> -
"><% he.write(out); %>
+
"><% he.write(out); %>
@@ -281,7 +281,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; } -
"><% he.write(out); %>
+
"><% he.write(out); %>
@@ -318,7 +318,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; } %> -
"><% he.write(out); %>
+
"><% he.write(out); %>
@@ -351,7 +351,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; } props2.setProperty(HostTxtEntry.PROP_OLDDEST, b64); he2.signInner(spk); he2.sign(spk3); - %>
"><% he2.write(out); %>
+ %>
"><% he2.write(out); %>
<%=intl._t("This will add an alternate destination for {0}", name)%> <% } else { @@ -385,7 +385,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; } -
"><% he.write(out); %>
+
"><% he.write(out); %>
diff --git a/history.txt b/history.txt index c7e9ffaa85..fd9d801178 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,22 @@ +2019-05-24 zzz + * i2ptunnel: + - Per-client auth config + - Hide encryption key for per-client auth + - User-select: all for key fields + +2019-05-23 zzz + * i2ptunnel: + - Rework server encryption key UI in prep for blinded keys + - Remove generate button, automatically generate when required + - Refactor auto configuration + - Add LS2 option, change to select box + - Select box for sig type + +2019-05-22 zzz + * Crypto: Add X25519 DH class + * Data: Per-client auth for enc. LS2 (proposal 123) + * Transport: Use KeyGenerator for X25519 keys + 2019-05-21 zzz * Profiles: Omit comments from stored profiles diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 4e28a2c8d2..c10128fe62 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 = 5; + public final static long BUILD = 6; /** for example "-test" */ public final static String EXTRA = "";