From 8d891b99d17889ef2fea5bd51619c934acd70e86 Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 20 Jan 2009 17:12:24 +0000 Subject: [PATCH] * Router: Add a keyring for decrypting leases * Routerconsole: Add configkeyring.jsp --- .../i2p/router/web/ConfigKeyringHandler.java | 55 ++++++++++ .../i2p/router/web/ConfigKeyringHelper.java | 36 ++++++ apps/routerconsole/jsp/configkeyring.jsp | 58 ++++++++++ apps/routerconsole/jsp/confignav.jsp | 2 + core/java/src/net/i2p/I2PAppContext.java | 22 ++++ core/java/src/net/i2p/util/KeyRing.java | 20 ++++ .../src/net/i2p/router/PersistentKeyRing.java | 103 ++++++++++++++++++ .../src/net/i2p/router/RouterContext.java | 18 +++ 8 files changed, 314 insertions(+) create mode 100644 apps/routerconsole/java/src/net/i2p/router/web/ConfigKeyringHandler.java create mode 100644 apps/routerconsole/java/src/net/i2p/router/web/ConfigKeyringHelper.java create mode 100644 apps/routerconsole/jsp/configkeyring.jsp create mode 100644 core/java/src/net/i2p/util/KeyRing.java create mode 100644 router/java/src/net/i2p/router/PersistentKeyRing.java diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigKeyringHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigKeyringHandler.java new file mode 100644 index 000000000..09f0905bf --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigKeyringHandler.java @@ -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; } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigKeyringHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigKeyringHelper.java new file mode 100644 index 000000000..48bc15068 --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigKeyringHelper.java @@ -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()); + } +} diff --git a/apps/routerconsole/jsp/configkeyring.jsp b/apps/routerconsole/jsp/configkeyring.jsp new file mode 100644 index 000000000..7dd8bf178 --- /dev/null +++ b/apps/routerconsole/jsp/configkeyring.jsp @@ -0,0 +1,58 @@ +<%@page contentType="text/html"%> +<%@page pageEncoding="UTF-8"%> + + + +I2P Router Console - config keyring + + + +<%@include file="nav.jsp" %> +<%@include file="summary.jsp" %> + +
+ <%@include file="confignav.jsp" %> + + + + " /> + + + + + + + " /> + +

+

Keyring

+ The router keyring is used to decrypt encrypted leaseSets. + The keyring may contain keys for local or remote encrypted destinations. +

+

+ +
+ +
+ <% 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()+""); %> + " /> +

Manual Keyring Addition

+ Enter keys for encrypted remote destinations here. + Keys for local destinations must be entered on the I2PTunnel page. +

+ +
Dest. name, hash, or full key: + +
Session Key: + +
+
+

+ + +
+ + + diff --git a/apps/routerconsole/jsp/confignav.jsp b/apps/routerconsole/jsp/confignav.jsp index b6a5ce6df..851ab79b5 100644 --- a/apps/routerconsole/jsp/confignav.jsp +++ b/apps/routerconsole/jsp/confignav.jsp @@ -10,6 +10,8 @@ %>Clients | <% } else { %>Clients | <% } if (request.getRequestURI().indexOf("configpeer.jsp") != -1) { %>Peers | <% } else { %>Peers | <% } + if (request.getRequestURI().indexOf("configkeyring.jsp") != -1) { + %>Keyring | <% } else { %>Keyring | <% } if (request.getRequestURI().indexOf("configlogging.jsp") != -1) { %>Logging | <% } else { %>Logging | <% } if (request.getRequestURI().indexOf("configstats.jsp") != -1) { diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java index 3e514ea18..6b3b0fd5b 100644 --- a/core/java/src/net/i2p/I2PAppContext.java +++ b/core/java/src/net/i2p/I2PAppContext.java @@ -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] * diff --git a/core/java/src/net/i2p/util/KeyRing.java b/core/java/src/net/i2p/util/KeyRing.java new file mode 100644 index 000000000..6bbfb38de --- /dev/null +++ b/core/java/src/net/i2p/util/KeyRing.java @@ -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 { + public KeyRing() { + super(0); + } + + public void renderStatusHTML(Writer out) throws IOException {} +} diff --git a/router/java/src/net/i2p/router/PersistentKeyRing.java b/router/java/src/net/i2p/router/PersistentKeyRing.java new file mode 100644 index 000000000..d02275ea2 --- /dev/null +++ b/router/java/src/net/i2p/router/PersistentKeyRing.java @@ -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"); + for (Entry e : entrySet()) { + buf.append("\n
Destination HashName or Dest.Session Key
"); + Hash h = e.getKey(); + buf.append(h.toBase64().substring(0, 6)).append("..."); + buf.append(""); + 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(""); + SessionKey sk = e.getValue(); + buf.append(sk.toBase64()); + } + buf.append("\n
\n"); + out.write(buf.toString()); + out.flush(); + } +} diff --git a/router/java/src/net/i2p/router/RouterContext.java b/router/java/src/net/i2p/router/RouterContext.java index 087cde918..517a5ba35 100644 --- a/router/java/src/net/i2p/router/RouterContext.java +++ b/router/java/src/net/i2p/router/RouterContext.java @@ -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; + } + } + }