forked from I2P_Developers/i2p.i2p
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:
@ -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 < 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())
|
||||
|
@ -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,
|
||||
|
@ -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 < 0
|
||||
* @since 0.9.12
|
||||
|
@ -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);
|
||||
|
@ -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?")%> <%=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?")%> <%=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>
|
||||
|
@ -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>
|
||||
|
19
history.txt
19
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
|
||||
|
||||
|
@ -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 = "";
|
||||
|
Reference in New Issue
Block a user