i2ptunnel: Per-client auth config

Hide encryption key for per-client auth
User-select: all for dest and reg fields
This commit is contained in:
zzz
2019-05-24 17:42:11 +00:00
parent 47c64c2eef
commit 2abd59e6d4
8 changed files with 331 additions and 20 deletions

View File

@ -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<String> getClientAuths(int tunnel, boolean isDH) {
List<String> rv = new ArrayList<String>(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 &lt; 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())

View File

@ -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<Integer> _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<Integer>(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<String> clientAuth = null;
if (pfx != null) {
if (_clientNames != null && _clientKeys != null && _clientNames.length == _clientKeys.length) {
clientAuth = new ArrayList<String>(4);
} else if (_addClientAuth || _encryptMode == 6 || _encryptMode == 7) {
// force one client for per-client PSK
clientAuth = new ArrayList<String>(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,

View File

@ -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<String> getClientAuths(int tunnel, boolean isDH) {
return _helper.getClientAuths(tunnel, isDH);
}
/**
* @param newTunnelType used if tunnel &lt; 0
* @since 0.9.12

View File

@ -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);

View File

@ -155,7 +155,7 @@
</th>
</tr><tr>
<td>
<div class="displayText" title="<%=intl._t("Read Only: Local Destination (if known)")%>" tabindex="0" onblur="resetScrollLeft(this)"><%=editBean.getDestinationBase64(curTunnel)%></div>
<div class="displayText" style="user-select: all;" title="<%=intl._t("Read Only: Local Destination (if known)")%>" tabindex="0" onblur="resetScrollLeft(this)"><%=editBean.getDestinationBase64(curTunnel)%></div>
</td><td>
<% String value3 = editBean.getPrivateKeyFile(curTunnel);
if (value3 == null || "".equals(value3.trim())) {
@ -485,20 +485,27 @@
</td>
</tr><tr>
<td>
<b><%=intl._t("Encryption Key")%></b>
</td><td>
<%
if (allowBlinding && editBean.isAdvanced()) {
%>
<b><%=intl._t("Optional lookup password")%>:</b>
<%
} // allowBlinding
%>
</td><td>
<%
// 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) {
%>
<b><%=intl._t("Encryption Key")%></b>
<%
} // showSharedKey
%>
</td>
</tr><tr>
<td>
<textarea rows="1" style="height: 3em;" cols="44" id="leasesetKey" name="encryptKey" title="<%=intl._t("Encryption key required to access this service")%>" wrap="off" readonly="readonly"><%=editBean.getEncryptKey(curTunnel)%></textarea>
</td><td>
<%
if (allowBlinding && editBean.isAdvanced()) {
%>
@ -506,9 +513,71 @@
<%
} // allowBlinding
%>
</td><td>
<input type="<%=showSharedKey ? "text" : "hidden"%>" 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"/>
</td>
</tr>
<%
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<String> clientAuths = editBean.getClientAuths(curTunnel, dhClient);
if (!clientAuths.isEmpty()) {
%>
<tr><td><b><%=intl._t("Revoke?")%>&nbsp;&nbsp;&nbsp;&nbsp;<%=intl._t("Client Name")%></b></td><td><b><%=intl._t("Client Key")%></b></td></tr>
<%
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);
}
%>
<tr>
<td><input value="<%=i%>" type="checkbox" name="revokeClient" class="tickbox" />
<input type="text" value="<%=cname%>" size="44" name="nofilter_clientName" class="freetext" /></td>
<td><input type="text" id="leasesetKey" style="user-select: all;" value="<%=ckey%>" size="44" name="clientKey" readonly="readonly" class="freetext" />
</tr>
<%
i++;
} // for
} // isEmpty
%>
<tr><td><b><%=intl._t("Add?")%>&nbsp;&nbsp;&nbsp;&nbsp;<%=intl._t("Client Name")%></b></td><td>
<%
if (dhClient) {
%>
<b><%=intl._t("Client Key")%></b>
<%
} // dhClient
%>
</td></tr><tr>
<td><input value="1" type="checkbox" name="addClient" class="tickbox" />
<input type="text" value="<%=intl._t("Client") + ' ' + (clientAuths.size() + 1)%>" size="44" name="nofilter_newClientName" class="freetext" /></td>
<td>
<%
if (dhClient) {
%>
<input type="text" id="leasesetKey" value="" size="44" maxlength="44" name="newClientKey" class="freetext" />
<%
} // dhClient
%>
</td></tr>
<%
} // pskClient || dhClient
} // allowBlinding
} // !isOffline
%>
<tr>

View File

@ -150,7 +150,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
</tr>
<tr>
<td>
<div class="displayText" tabindex="0" title="<%=intl._t("Copy and paste this to the registration site")%>"><% he.write(out); %></div>
<div class="displayText" tabindex="0" style="user-select: all; "title="<%=intl._t("Copy and paste this to the registration site")%>"><% he.write(out); %></div>
</td>
</tr>
</table>
@ -171,7 +171,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
</tr>
<tr>
<td>
<div class="displayText" tabindex="0" title="<%=intl._t("Copy and paste this to the registration site")%>"><% he.writeRemove(out); %></div>
<div class="displayText" tabindex="0" style="user-select: all;" title="<%=intl._t("Copy and paste this to the registration site")%>"><% he.writeRemove(out); %></div>
</td>
</tr>
@ -211,7 +211,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
%>
<tr>
<td>
<div class="displayText" tabindex="0" title="<%=intl._t("Copy and paste this to the registration site")%>"><% he.write(out); %></div>
<div class="displayText" tabindex="0" style="user-select: all;" title="<%=intl._t("Copy and paste this to the registration site")%>"><% he.write(out); %></div>
</td>
</tr>
<tr>
@ -244,7 +244,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
%>
<tr>
<td>
<div class="displayText" tabindex="0" title="<%=intl._t("Copy and paste this to the registration site")%>"><% he.write(out); %></div>
<div class="displayText" tabindex="0" style="user-select: all;" title="<%=intl._t("Copy and paste this to the registration site")%>"><% he.write(out); %></div>
</td>
</tr>
<tr>
@ -281,7 +281,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
<tr>
<td>
<div class="displayText" tabindex="0" title="<%=intl._t("Copy and paste this to the registration site")%>"><% he.write(out); %></div>
<div class="displayText" tabindex="0" style="user-select: all;" title="<%=intl._t("Copy and paste this to the registration site")%>"><% he.write(out); %></div>
</td>
</tr>
<tr>
@ -318,7 +318,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
%>
<tr>
<td>
<div class="displayText" tabindex="0" title="<%=intl._t("Copy and paste this to the registration site")%>"><% he.write(out); %></div>
<div class="displayText" tabindex="0" style="user-select: all;" title="<%=intl._t("Copy and paste this to the registration site")%>"><% he.write(out); %></div>
</td>
</tr>
<tr>
@ -351,7 +351,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
props2.setProperty(HostTxtEntry.PROP_OLDDEST, b64);
he2.signInner(spk);
he2.sign(spk3);
%><tr><td><div class="displayText" tabindex="0" title="<%=intl._t("Copy and paste this to the registration site")%>"><% he2.write(out); %></div></td></tr>
%><tr><td><div class="displayText" tabindex="0" style="user-select: all;" title="<%=intl._t("Copy and paste this to the registration site")%>"><% he2.write(out); %></div></td></tr>
<tr><td class="infohelp"><%=intl._t("This will add an alternate destination for {0}", name)%></td></tr>
<%
} else {
@ -385,7 +385,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
<tr>
<td>
<div class="displayText" tabindex="0" title="<%=intl._t("Copy and paste this to the registration site")%>"><% he.write(out); %></div>
<div class="displayText" tabindex="0" style="user-select: all;" title="<%=intl._t("Copy and paste this to the registration site")%>"><% he.write(out); %></div>
</td>
</tr>
<tr>

View File

@ -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

View File

@ -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 = "";