* Router: Add a keyring for decrypting leases
* Routerconsole: Add configkeyring.jsp
This commit is contained in:
@ -0,0 +1,55 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.SessionKey;
|
||||
|
||||
/**
|
||||
* Support additions via B64 Destkey, B64 Desthash, or blahblah.i2p
|
||||
*/
|
||||
public class ConfigKeyringHandler extends FormHandler {
|
||||
private String _peer;
|
||||
private String _key;
|
||||
|
||||
protected void processForm() {
|
||||
if ("Add key".equals(_action)) {
|
||||
if (_peer == null || _key == null) {
|
||||
addFormError("You must enter a destination and a key");
|
||||
return;
|
||||
}
|
||||
Hash h = new Hash();
|
||||
try {
|
||||
h.fromBase64(_peer);
|
||||
} catch (DataFormatException dfe) {}
|
||||
if (h.getData() == null) {
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(_peer);
|
||||
h = d.calculateHash();
|
||||
} catch (DataFormatException dfe) {}
|
||||
}
|
||||
if (h.getData() == null) {
|
||||
Destination d = _context.namingService().lookup(_peer);
|
||||
if (d != null)
|
||||
h = d.calculateHash();
|
||||
}
|
||||
SessionKey sk = new SessionKey();
|
||||
try {
|
||||
sk.fromBase64(_key);
|
||||
} catch (DataFormatException dfe) {}
|
||||
if (h.getData() != null && sk.getData() != null) {
|
||||
_context.keyRing().put(h, sk);
|
||||
addFormNotice("Key for " + h.toBase64() + " added to keyring");
|
||||
} else {
|
||||
addFormError("Invalid destination or key");
|
||||
}
|
||||
} else {
|
||||
addFormError("Unsupported");
|
||||
}
|
||||
}
|
||||
|
||||
public void setPeer(String peer) { _peer = peer; }
|
||||
public void setKey(String peer) { _key = peer; }
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
public class ConfigKeyringHelper {
|
||||
private RouterContext _context;
|
||||
/**
|
||||
* Configure this bean to query a particular router context
|
||||
*
|
||||
* @param contextId begging few characters of the routerHash, or null to pick
|
||||
* the first one we come across.
|
||||
*/
|
||||
public void setContextId(String contextId) {
|
||||
try {
|
||||
_context = ContextHelper.getContext(contextId);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public ConfigKeyringHelper() {}
|
||||
|
||||
public String getSummary() {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(4*1024);
|
||||
try {
|
||||
_context.keyRing().renderStatusHTML(new OutputStreamWriter(baos));
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
return new String(baos.toByteArray());
|
||||
}
|
||||
}
|
58
apps/routerconsole/jsp/configkeyring.jsp
Normal file
58
apps/routerconsole/jsp/configkeyring.jsp
Normal file
@ -0,0 +1,58 @@
|
||||
<%@page contentType="text/html"%>
|
||||
<%@page pageEncoding="UTF-8"%>
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
|
||||
<html><head>
|
||||
<title>I2P Router Console - config keyring</title>
|
||||
<link rel="stylesheet" href="default.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
<%@include file="nav.jsp" %>
|
||||
<%@include file="summary.jsp" %>
|
||||
|
||||
<div class="main" id="main">
|
||||
<%@include file="confignav.jsp" %>
|
||||
|
||||
<jsp:useBean class="net.i2p.router.web.ConfigKeyringHandler" id="formhandler" scope="request" />
|
||||
<jsp:setProperty name="formhandler" property="*" />
|
||||
<jsp:setProperty name="formhandler" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
|
||||
<font color="red"><jsp:getProperty name="formhandler" property="errors" /></font>
|
||||
<i><jsp:getProperty name="formhandler" property="notices" /></i>
|
||||
|
||||
|
||||
|
||||
<jsp:useBean class="net.i2p.router.web.ConfigKeyringHelper" id="keyringhelper" scope="request" />
|
||||
<jsp:setProperty name="keyringhelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
|
||||
|
||||
<p>
|
||||
<h2>Keyring</h2>
|
||||
The router keyring is used to decrypt encrypted leaseSets.
|
||||
The keyring may contain keys for local or remote encrypted destinations.
|
||||
<p><jsp:getProperty name="keyringhelper" property="summary" />
|
||||
</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<form action="configkeyring.jsp" method="POST">
|
||||
<% String prev = System.getProperty("net.i2p.router.web.ConfigKeyringHandler.nonce");
|
||||
if (prev != null) System.setProperty("net.i2p.router.web.ConfigKeyringHandler.noncePrev", prev);
|
||||
System.setProperty("net.i2p.router.web.ConfigKeyringHandler.nonce", new java.util.Random().nextLong()+""); %>
|
||||
<input type="hidden" name="nonce" value="<%=System.getProperty("net.i2p.router.web.ConfigKeyringHandler.nonce")%>" />
|
||||
<h2>Manual Keyring Addition</h2>
|
||||
Enter keys for encrypted remote destinations here.
|
||||
Keys for local destinations must be entered on the <a href="i2ptunnel/index.jsp">I2PTunnel page</a>.
|
||||
<p>
|
||||
<table>
|
||||
<tr><td>Dest. name, hash, or full key:
|
||||
<td><textarea name="peer" cols="44" rows="1" wrap="off"></textarea>
|
||||
<tr><td align="right">Session Key:
|
||||
<td><input type="text" size="55" name="key" />
|
||||
<tr><td><td><input type="submit" name="action" value="Add key" />
|
||||
</table>
|
||||
</form>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -10,6 +10,8 @@
|
||||
%>Clients | <% } else { %><a href="configclients.jsp">Clients</a> | <% }
|
||||
if (request.getRequestURI().indexOf("configpeer.jsp") != -1) {
|
||||
%>Peers | <% } else { %><a href="configpeer.jsp">Peers</a> | <% }
|
||||
if (request.getRequestURI().indexOf("configkeyring.jsp") != -1) {
|
||||
%>Keyring | <% } else { %><a href="configkeyring.jsp">Keyring</a> | <% }
|
||||
if (request.getRequestURI().indexOf("configlogging.jsp") != -1) {
|
||||
%>Logging | <% } else { %><a href="configlogging.jsp">Logging</a> | <% }
|
||||
if (request.getRequestURI().indexOf("configstats.jsp") != -1) {
|
||||
|
@ -24,6 +24,7 @@ import net.i2p.data.RoutingKeyGenerator;
|
||||
import net.i2p.stat.StatManager;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.FortunaRandomSource;
|
||||
import net.i2p.util.KeyRing;
|
||||
import net.i2p.util.LogManager;
|
||||
import net.i2p.util.PooledRandomSource;
|
||||
import net.i2p.util.RandomSource;
|
||||
@ -75,6 +76,7 @@ public class I2PAppContext {
|
||||
private RoutingKeyGenerator _routingKeyGenerator;
|
||||
private RandomSource _random;
|
||||
private KeyGenerator _keyGenerator;
|
||||
protected KeyRing _keyRing; // overridden in RouterContext
|
||||
private volatile boolean _statManagerInitialized;
|
||||
private volatile boolean _sessionKeyManagerInitialized;
|
||||
private volatile boolean _namingServiceInitialized;
|
||||
@ -91,6 +93,7 @@ public class I2PAppContext {
|
||||
private volatile boolean _routingKeyGeneratorInitialized;
|
||||
private volatile boolean _randomInitialized;
|
||||
private volatile boolean _keyGeneratorInitialized;
|
||||
protected volatile boolean _keyRingInitialized; // used in RouterContext
|
||||
|
||||
|
||||
/**
|
||||
@ -141,12 +144,14 @@ public class I2PAppContext {
|
||||
_elGamalEngine = null;
|
||||
_elGamalAESEngine = null;
|
||||
_logManager = null;
|
||||
_keyRing = null;
|
||||
_statManagerInitialized = false;
|
||||
_sessionKeyManagerInitialized = false;
|
||||
_namingServiceInitialized = false;
|
||||
_elGamalEngineInitialized = false;
|
||||
_elGamalAESEngineInitialized = false;
|
||||
_logManagerInitialized = false;
|
||||
_keyRingInitialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -512,6 +517,23 @@ public class I2PAppContext {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic hash map
|
||||
*/
|
||||
public KeyRing keyRing() {
|
||||
if (!_keyRingInitialized)
|
||||
initializeKeyRing();
|
||||
return _keyRing;
|
||||
}
|
||||
|
||||
protected void initializeKeyRing() {
|
||||
synchronized (this) {
|
||||
if (_keyRing == null)
|
||||
_keyRing = new KeyRing();
|
||||
_keyRingInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [insert snarky comment here]
|
||||
*
|
||||
|
20
core/java/src/net/i2p/util/KeyRing.java
Normal file
20
core/java/src/net/i2p/util/KeyRing.java
Normal file
@ -0,0 +1,20 @@
|
||||
package net.i2p.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.SessionKey;
|
||||
|
||||
/**
|
||||
* simple
|
||||
*/
|
||||
public class KeyRing extends ConcurrentHashMap<Hash, SessionKey> {
|
||||
public KeyRing() {
|
||||
super(0);
|
||||
}
|
||||
|
||||
public void renderStatusHTML(Writer out) throws IOException {}
|
||||
}
|
103
router/java/src/net/i2p/router/PersistentKeyRing.java
Normal file
103
router/java/src/net/i2p/router/PersistentKeyRing.java
Normal file
@ -0,0 +1,103 @@
|
||||
package net.i2p.router;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.router.TunnelPoolSettings;
|
||||
import net.i2p.util.KeyRing;
|
||||
|
||||
/**
|
||||
* ConcurrentHashMap with backing in the router.config file.
|
||||
* router.keyring.key.{base64 hash, with = replaced with $}={base64 session key}
|
||||
* Caution - not all HashMap methods are overridden.
|
||||
*/
|
||||
public class PersistentKeyRing extends KeyRing {
|
||||
private RouterContext _ctx;
|
||||
private static final String PROP_PFX = "router.keyring.key.";
|
||||
|
||||
public PersistentKeyRing(RouterContext ctx) {
|
||||
super();
|
||||
_ctx = ctx;
|
||||
addFromProperties();
|
||||
}
|
||||
|
||||
public SessionKey put(Hash h, SessionKey sk) {
|
||||
SessionKey old = super.put(h, sk);
|
||||
if (!sk.equals(old)) {
|
||||
_ctx.router().setConfigSetting(PROP_PFX + h.toBase64().replace("=", "$"),
|
||||
sk.toBase64());
|
||||
_ctx.router().saveConfig();
|
||||
}
|
||||
return old;
|
||||
}
|
||||
|
||||
public SessionKey remove(Hash h) {
|
||||
_ctx.router().removeConfigSetting(PROP_PFX + h.toBase64().replace("=", "$"));
|
||||
_ctx.router().saveConfig();
|
||||
return super.remove(h);
|
||||
}
|
||||
|
||||
private void addFromProperties() {
|
||||
for (Iterator iter = _ctx.getPropertyNames().iterator(); iter.hasNext(); ) {
|
||||
String prop = (String) iter.next();
|
||||
if (!prop.startsWith(PROP_PFX))
|
||||
continue;
|
||||
String key = _ctx.getProperty(prop);
|
||||
if (key == null || key.length() != 44)
|
||||
continue;
|
||||
String hb = prop.substring(PROP_PFX.length());
|
||||
hb.replace("$", "=");
|
||||
Hash dest = new Hash();
|
||||
SessionKey sk = new SessionKey();
|
||||
try {
|
||||
dest.fromBase64(hb);
|
||||
sk.fromBase64(key);
|
||||
super.put(dest, sk);
|
||||
} catch (DataFormatException dfe) { continue; }
|
||||
}
|
||||
}
|
||||
|
||||
public void renderStatusHTML(Writer out) throws IOException {
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
buf.append("\n<table border=\"1\"><tr><th align=\"left\">Destination Hash<th align=\"left\">Name or Dest.<th align=\"left\">Session Key</tr>");
|
||||
for (Entry<Hash, SessionKey> e : entrySet()) {
|
||||
buf.append("\n<tr><td>");
|
||||
Hash h = e.getKey();
|
||||
buf.append(h.toBase64().substring(0, 6)).append("...");
|
||||
buf.append("<td>");
|
||||
LeaseSet ls = _ctx.netDb().lookupLeaseSetLocally(h);
|
||||
if (ls != null) {
|
||||
Destination dest = ls.getDestination();
|
||||
if (_ctx.clientManager().isLocal(dest)) {
|
||||
TunnelPoolSettings in = _ctx.tunnelManager().getInboundSettings(h);
|
||||
if (in != null && in.getDestinationNickname() != null)
|
||||
buf.append(in.getDestinationNickname());
|
||||
else
|
||||
buf.append(dest.toBase64().substring(0, 6)).append("...");
|
||||
} else {
|
||||
String host = _ctx.namingService().reverseLookup(dest);
|
||||
if (host != null)
|
||||
buf.append(host);
|
||||
else
|
||||
buf.append(dest.toBase64().substring(0, 6)).append("...");
|
||||
}
|
||||
}
|
||||
buf.append("<td>");
|
||||
SessionKey sk = e.getValue();
|
||||
buf.append(sk.toBase64());
|
||||
}
|
||||
buf.append("\n</table>\n");
|
||||
out.write(buf.toString());
|
||||
out.flush();
|
||||
}
|
||||
}
|
@ -26,6 +26,7 @@ import net.i2p.router.transport.VMCommSystem;
|
||||
import net.i2p.router.tunnel.TunnelDispatcher;
|
||||
import net.i2p.router.tunnel.pool.TunnelPoolManager;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.KeyRing;
|
||||
|
||||
/**
|
||||
* Build off the core I2P context to provide a root for a router instance to
|
||||
@ -366,4 +367,21 @@ public class RouterContext extends I2PAppContext {
|
||||
}
|
||||
}
|
||||
|
||||
/** override to support storage in router.config */
|
||||
@Override
|
||||
public KeyRing keyRing() {
|
||||
if (!_keyRingInitialized)
|
||||
initializeKeyRing();
|
||||
return _keyRing;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeKeyRing() {
|
||||
synchronized (this) {
|
||||
if (_keyRing == null)
|
||||
_keyRing = new PersistentKeyRing(this);
|
||||
_keyRingInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user