From d48991f71f9b919b5c8e6d959faaeae9236f2202 Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 9 Jul 2014 13:52:26 +0000 Subject: [PATCH 01/17] Crypto: Move TransientSessionKeyManager from core to router. I2PAppContext will return the dummy SessionKeyManager which is sufficient for non-tag uses (e.g. Bote). Client use of end-to-end encryption using SessionTags was disabled in release 0.6, 2005-07-27. --- core/java/src/net/i2p/I2PAppContext.java | 12 +++++++----- .../src/net/i2p/router/RouterContext.java | 19 ++++++++++++++++++- .../router/client/ClientConnectionRunner.java | 2 +- .../crypto/TransientSessionKeyManager.java | 4 +++- .../src/net/i2p/router/crypto/package.html | 7 +++++++ 5 files changed, 36 insertions(+), 8 deletions(-) rename {core/java/src/net/i2p => router/java/src/net/i2p/router}/crypto/TransientSessionKeyManager.java (99%) create mode 100644 router/java/src/net/i2p/router/crypto/package.html diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java index 69e110632e..8357a485ac 100644 --- a/core/java/src/net/i2p/I2PAppContext.java +++ b/core/java/src/net/i2p/I2PAppContext.java @@ -22,7 +22,6 @@ import net.i2p.crypto.HMACGenerator; import net.i2p.crypto.KeyGenerator; import net.i2p.crypto.SHA256Generator; import net.i2p.crypto.SessionKeyManager; -import net.i2p.crypto.TransientSessionKeyManager; import net.i2p.data.Base64; import net.i2p.data.RoutingKeyGenerator; import net.i2p.internal.InternalClientManager; @@ -76,7 +75,7 @@ public class I2PAppContext { protected final I2PProperties _overrideProps; private StatManager _statManager; - private SessionKeyManager _sessionKeyManager; + protected SessionKeyManager _sessionKeyManager; private NamingService _namingService; private ElGamalEngine _elGamalEngine; private ElGamalAESEngine _elGamalAESEngine; @@ -96,7 +95,7 @@ public class I2PAppContext { private SimpleTimer2 _simpleTimer2; private final PortMapper _portMapper; private volatile boolean _statManagerInitialized; - private volatile boolean _sessionKeyManagerInitialized; + protected volatile boolean _sessionKeyManagerInitialized; private volatile boolean _namingServiceInitialized; private volatile boolean _elGamalEngineInitialized; private volatile boolean _elGamalAESEngineInitialized; @@ -599,6 +598,9 @@ public class I2PAppContext { * For client crypto within the router, * use RouterContext.clientManager.getClientSessionKeyManager(dest) * + * As of 0.9.15, this returns a dummy SessionKeyManager in I2PAppContext. + * The dummy SKM does NOT handle session tags. + * Overridden in RouterContext to return the full TransientSessionKeyManager. */ public SessionKeyManager sessionKeyManager() { if (!_sessionKeyManagerInitialized) @@ -606,11 +608,11 @@ public class I2PAppContext { return _sessionKeyManager; } - private void initializeSessionKeyManager() { + protected void initializeSessionKeyManager() { synchronized (_lock3) { if (_sessionKeyManager == null) //_sessionKeyManager = new PersistentSessionKeyManager(this); - _sessionKeyManager = new TransientSessionKeyManager(this); + _sessionKeyManager = new SessionKeyManager(this); _sessionKeyManagerInitialized = true; } } diff --git a/router/java/src/net/i2p/router/RouterContext.java b/router/java/src/net/i2p/router/RouterContext.java index 0afd98a7b5..1f8a38af68 100644 --- a/router/java/src/net/i2p/router/RouterContext.java +++ b/router/java/src/net/i2p/router/RouterContext.java @@ -13,6 +13,7 @@ import net.i2p.data.Hash; import net.i2p.data.RouterInfo; import net.i2p.internal.InternalClientManager; import net.i2p.router.client.ClientManagerFacadeImpl; +import net.i2p.router.crypto.TransientSessionKeyManager; import net.i2p.router.dummy.*; import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade; import net.i2p.router.peermanager.PeerManagerFacadeImpl; @@ -67,7 +68,7 @@ public class RouterContext extends I2PAppContext { private final Set _finalShutdownTasks; // split up big lock on this to avoid deadlocks private volatile boolean _initialized; - private final Object _lock1 = new Object(), _lock2 = new Object(); + private final Object _lock1 = new Object(), _lock2 = new Object(), _lock3 = new Object(); private static final List _contexts = new CopyOnWriteArrayList(); @@ -565,4 +566,20 @@ public class RouterContext extends I2PAppContext { public RouterAppManager routerAppManager() { return _appManager; } + + /** + * As of 0.9.15, this returns a dummy SessionKeyManager in I2PAppContext. + * Overridden in RouterContext to return the full TransientSessionKeyManager. + * + * @since 0.9.15 + */ + @Override + protected void initializeSessionKeyManager() { + synchronized (_lock3) { + if (_sessionKeyManager == null) + //_sessionKeyManager = new PersistentSessionKeyManager(this); + _sessionKeyManager = new TransientSessionKeyManager(this); + _sessionKeyManagerInitialized = true; + } + } } diff --git a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java index 0fcceb23e2..9befe3c0f9 100644 --- a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java +++ b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java @@ -25,7 +25,6 @@ import java.util.concurrent.atomic.AtomicInteger; import net.i2p.client.I2PClient; import net.i2p.crypto.SessionKeyManager; -import net.i2p.crypto.TransientSessionKeyManager; import net.i2p.data.Destination; import net.i2p.data.Hash; import net.i2p.data.LeaseSet; @@ -43,6 +42,7 @@ import net.i2p.data.i2cp.SessionId; import net.i2p.router.Job; import net.i2p.router.JobImpl; import net.i2p.router.RouterContext; +import net.i2p.router.crypto.TransientSessionKeyManager; import net.i2p.util.ConcurrentHashSet; import net.i2p.util.I2PThread; import net.i2p.util.Log; diff --git a/core/java/src/net/i2p/crypto/TransientSessionKeyManager.java b/router/java/src/net/i2p/router/crypto/TransientSessionKeyManager.java similarity index 99% rename from core/java/src/net/i2p/crypto/TransientSessionKeyManager.java rename to router/java/src/net/i2p/router/crypto/TransientSessionKeyManager.java index 7134bcb417..14c5c0510a 100644 --- a/core/java/src/net/i2p/crypto/TransientSessionKeyManager.java +++ b/router/java/src/net/i2p/router/crypto/TransientSessionKeyManager.java @@ -1,4 +1,4 @@ -package net.i2p.crypto; +package net.i2p.router.crypto; /* * free (adj.): unencumbered; not under the control of others @@ -25,6 +25,8 @@ import java.util.TreeSet; import java.util.concurrent.atomic.AtomicInteger; import net.i2p.I2PAppContext; +import net.i2p.crypto.SessionKeyManager; +import net.i2p.crypto.TagSetHandle; import net.i2p.data.DataHelper; import net.i2p.data.PublicKey; import net.i2p.data.SessionKey; diff --git a/router/java/src/net/i2p/router/crypto/package.html b/router/java/src/net/i2p/router/crypto/package.html new file mode 100644 index 0000000000..a1e75c6717 --- /dev/null +++ b/router/java/src/net/i2p/router/crypto/package.html @@ -0,0 +1,7 @@ + + +

+Classes formerly in net.i2p.crypto but moved here as they are only used by the router. +

+ + From 328d7d00080ba06421a4398108764129b5e5e3f4 Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 22 Jul 2014 14:52:08 +0000 Subject: [PATCH 02/17] SAM: - Don't spawn a thread for each transmitted datagram - Set protocol field for raw and signed datagrams - Enforce a 60s timeout for HELLO - Use naming service cache to reduce Destination object churn - Get Log object from the log manager - Log spelling fixes --- apps/sam/java/src/net/i2p/sam/SAMBridge.java | 1 + .../src/net/i2p/sam/SAMDatagramSession.java | 5 ++- apps/sam/java/src/net/i2p/sam/SAMHandler.java | 3 +- .../src/net/i2p/sam/SAMHandlerFactory.java | 17 ++++--- .../src/net/i2p/sam/SAMMessageSession.java | 19 ++++---- .../java/src/net/i2p/sam/SAMRawSession.java | 5 ++- .../src/net/i2p/sam/SAMStreamSession.java | 5 ++- apps/sam/java/src/net/i2p/sam/SAMUtils.java | 44 ++++++++----------- .../java/src/net/i2p/sam/SAMv1Handler.java | 4 +- .../java/src/net/i2p/sam/SAMv3Handler.java | 17 ++++++- 10 files changed, 72 insertions(+), 48 deletions(-) diff --git a/apps/sam/java/src/net/i2p/sam/SAMBridge.java b/apps/sam/java/src/net/i2p/sam/SAMBridge.java index 1894c97198..4679f68b68 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMBridge.java +++ b/apps/sam/java/src/net/i2p/sam/SAMBridge.java @@ -161,6 +161,7 @@ public class SAMBridge implements Runnable, ClientApp { * * @param name name of the destination * @return null if the name does not exist, or if it is improperly formatted + * @deprecated unused */ public Destination getDestination(String name) { synchronized (nameToPrivKeys) { diff --git a/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java b/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java index b9367e24db..32611065da 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java @@ -12,6 +12,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.Properties; +import net.i2p.client.I2PSession; import net.i2p.client.I2PSessionException; import net.i2p.client.datagram.I2PDatagramDissector; import net.i2p.client.datagram.I2PDatagramMaker; @@ -88,7 +89,9 @@ class SAMDatagramSession extends SAMMessageSession { synchronized (dgramMaker) { dgram = dgramMaker.makeI2PDatagram(data); } - return sendBytesThroughMessageSession(dest, dgram); + // TODO pass ports through + return sendBytesThroughMessageSession(dest, dgram, I2PSession.PROTO_DATAGRAM, + I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED); } protected void messageReceived(byte[] msg) { diff --git a/apps/sam/java/src/net/i2p/sam/SAMHandler.java b/apps/sam/java/src/net/i2p/sam/SAMHandler.java index be4d99dab3..30213ca6d7 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMHandler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMHandler.java @@ -13,6 +13,7 @@ import java.nio.channels.SocketChannel; import java.nio.ByteBuffer; import java.util.Properties; +import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; import net.i2p.util.I2PAppThread; import net.i2p.util.Log; @@ -54,7 +55,7 @@ abstract class SAMHandler implements Runnable { */ protected SAMHandler(SocketChannel s, int verMajor, int verMinor, Properties i2cpProps) throws IOException { - _log = new Log(getClass()); + _log = I2PAppContext.getGlobalContext().logManager().getLog(getClass()); socket = s; this.verMajor = verMajor; diff --git a/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java b/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java index 4da5792387..d605d57f90 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java +++ b/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java @@ -9,12 +9,13 @@ package net.i2p.sam; */ import java.io.IOException; -import java.io.UnsupportedEncodingException; +import java.net.Socket; +import java.net.SocketTimeoutException; import java.nio.channels.SocketChannel; -import java.nio.ByteBuffer; import java.util.Properties; import java.util.StringTokenizer; +import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; import net.i2p.util.Log; import net.i2p.util.VersionComparator; @@ -26,6 +27,8 @@ class SAMHandlerFactory { private static final String VERSION = "3.1"; + private static final int HELLO_TIMEOUT = 60*1000; + /** * Return the right SAM handler depending on the protocol version * required by the client. @@ -36,17 +39,21 @@ class SAMHandlerFactory { * @return A SAM protocol handler, or null if the client closed before the handshake */ public static SAMHandler createSAMHandler(SocketChannel s, Properties i2cpProps) throws SAMException { - String line; StringTokenizer tok; - Log log = new Log(SAMHandlerFactory.class); + Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMHandlerFactory.class); try { - line = DataHelper.readLine(s.socket().getInputStream()); + Socket sock = s.socket(); + sock.setSoTimeout(HELLO_TIMEOUT); + String line = DataHelper.readLine(sock.getInputStream()); + sock.setSoTimeout(0); if (line == null) { log.debug("Connection closed by client"); return null; } tok = new StringTokenizer(line.trim(), " "); + } catch (SocketTimeoutException e) { + throw new SAMException("Timeout waiting for HELLO VERSION", e); } catch (IOException e) { throw new SAMException("Error reading from socket", e); } catch (Exception e) { diff --git a/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java b/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java index 452bbe0fb9..e15c6668d2 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java @@ -13,6 +13,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.Properties; +import net.i2p.I2PAppContext; import net.i2p.client.I2PClient; import net.i2p.client.I2PClientFactory; import net.i2p.client.I2PSession; @@ -33,10 +34,8 @@ import net.i2p.util.Log; abstract class SAMMessageSession { protected final Log _log; - - private I2PSession session = null; - - private SAMMessageSessionHandler handler = null; + private I2PSession session; + private SAMMessageSessionHandler handler; /** * Initialize a new SAM message-based session. @@ -48,7 +47,7 @@ abstract class SAMMessageSession { * @throws I2PSessionException */ protected SAMMessageSession(String dest, Properties props) throws IOException, DataFormatException, I2PSessionException { - _log = new Log(getClass()); + _log = I2PAppContext.getGlobalContext().logManager().getLog(getClass()); ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(dest)); initSAMMessageSession(bais, props); } @@ -103,11 +102,15 @@ abstract class SAMMessageSession { * * @param dest Destination * @param data Bytes to be sent + * @param proto I2CP protocol + * @param fromPort I2CP from port + * @param toPort I2CP to port * * @return True if the data was sent, false otherwise - * @throws DataFormatException + * @throws DataFormatException on unknown / bad dest */ - protected boolean sendBytesThroughMessageSession(String dest, byte[] data) throws DataFormatException { + protected boolean sendBytesThroughMessageSession(String dest, byte[] data, + int proto, int fromPort, int toPort) throws DataFormatException { Destination d = SAMUtils.getDest(dest); if (_log.shouldLog(Log.DEBUG)) { @@ -115,7 +118,7 @@ abstract class SAMMessageSession { } try { - return session.sendMessage(d, data); + return session.sendMessage(d, data, proto, fromPort, toPort); } catch (I2PSessionException e) { _log.error("I2PSessionException while sending data", e); return false; diff --git a/apps/sam/java/src/net/i2p/sam/SAMRawSession.java b/apps/sam/java/src/net/i2p/sam/SAMRawSession.java index 08e98c2589..01cbde75a7 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMRawSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMRawSession.java @@ -12,6 +12,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.Properties; +import net.i2p.client.I2PSession; import net.i2p.client.I2PSessionException; import net.i2p.data.DataFormatException; import net.i2p.util.Log; @@ -73,7 +74,9 @@ class SAMRawSession extends SAMMessageSession { public boolean sendBytes(String dest, byte[] data) throws DataFormatException { if (data.length > RAW_SIZE_MAX) throw new DataFormatException("Data size limit exceeded (" + data.length + ")"); - return sendBytesThroughMessageSession(dest, data); + // TODO pass ports through + return sendBytesThroughMessageSession(dest, data, I2PSession.PROTO_DATAGRAM_RAW, + I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED); } protected void messageReceived(byte[] msg) { diff --git a/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java b/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java index db5d39d49a..6235d92a4c 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java @@ -25,6 +25,7 @@ import java.util.Properties; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import net.i2p.I2PAppContext; import net.i2p.I2PException; import net.i2p.client.I2PClient; import net.i2p.client.streaming.I2PServerSocket; @@ -107,9 +108,9 @@ class SAMStreamSession { public SAMStreamSession(InputStream destStream, String dir, Properties props, SAMStreamReceiver recv) throws IOException, DataFormatException, SAMException { this.recv = recv; - _log = new Log(getClass()); + _log = I2PAppContext.getGlobalContext().logManager().getLog(getClass()); if (_log.shouldLog(Log.DEBUG)) - _log.debug("SAM STREAM session instantiated"); + _log.debug("SAM STREAM session instantiated"); Properties allprops = (Properties) System.getProperties().clone(); allprops.putAll(props); diff --git a/apps/sam/java/src/net/i2p/sam/SAMUtils.java b/apps/sam/java/src/net/i2p/sam/SAMUtils.java index 15ecb1143f..7adaadf7b5 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMUtils.java +++ b/apps/sam/java/src/net/i2p/sam/SAMUtils.java @@ -123,26 +123,12 @@ class SAMUtils { * Resolved the specified hostname. * * @param name Hostname to be resolved - * @param pubKey A stream to write the Destination public key (may be null) * * @return the Destination for the specified hostname, or null if not found */ - public static Destination lookupHost(String name, OutputStream pubKey) { + private static Destination lookupHost(String name) { NamingService ns = I2PAppContext.getGlobalContext().namingService(); Destination dest = ns.lookup(name); - - if ((pubKey != null) && (dest != null)) { - try { - dest.writeBytes(pubKey); - } catch (IOException e) { - e.printStackTrace(); - return null; - } catch (DataFormatException e) { - e.printStackTrace(); - return null; - } - } - return dest; } @@ -151,20 +137,26 @@ class SAMUtils { * * @param s Hostname or key to be resolved * - * @return the Destination for the specified hostname, or null if not found + * @return the Destination for the specified hostname, non-null + * @throws DataFormatException on bad Base 64 or name not found */ public static Destination getDest(String s) throws DataFormatException { - Destination d = new Destination() ; - try { - d.fromBase64(s); - } catch (DataFormatException e) { - d = lookupHost(s, null); - if ( d==null ) { - throw e ; - } - } - return d ; + // NamingService caches b64 so just use it for everything + // TODO: Add a static local cache here so SAM doesn't flush the + // NamingService cache + Destination d = lookupHost(s); + if (d == null) { + String msg; + if (s.length() >= 516) + msg = "Bad Base64 dest: "; + else if (s.length() == 60 && s.endsWith(".b32.i2p")) + msg = "Lease set not found: "; + else + msg = "Host name not found: "; + throw new DataFormatException(msg + s); + } + return d; } /** diff --git a/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java index 2ffba654f5..47a2c29e55 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java @@ -246,7 +246,7 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece destKeystream = bridge.getKeystream(dest); if (destKeystream == null) { if (_log.shouldLog(Log.DEBUG)) - _log.debug("Custom destination specified [" + dest + "] but it isnt know, creating a new one"); + _log.debug("Custom destination specified [" + dest + "] but it isn't known, creating a new one"); ByteArrayOutputStream baos = new ByteArrayOutputStream(640); SAMUtils.genRandomKey(baos, null); destKeystream = Base64.encode(baos.toByteArray()); @@ -284,7 +284,7 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece if (!dir.equals("CREATE") && !dir.equals("RECEIVE") && !dir.equals("BOTH")) { if (_log.shouldLog(Log.DEBUG)) - _log.debug("Unknow DIRECTION parameter value: [" + dir + "]"); + _log.debug("Unknown DIRECTION parameter value: [" + dir + "]"); return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unknown DIRECTION parameter\"\n"); } props.remove("DIRECTION"); diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java index 043baba68d..062e5f9fb4 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java @@ -25,6 +25,7 @@ import java.util.Properties; import java.util.HashMap; import java.util.StringTokenizer; +import net.i2p.I2PAppContext; import net.i2p.I2PException; import net.i2p.client.I2PClient; import net.i2p.client.I2PSessionException; @@ -159,7 +160,13 @@ class SAMv3Handler extends SAMv1Handler ByteBuffer outBuf = ByteBuffer.wrap(new byte[inBuf.remaining()]); outBuf.put(inBuf); outBuf.flip(); - new I2PAppThread(new MessageDispatcher(outBuf.array()), "MessageDispatcher").start(); + // A new thread for every message is wildly inefficient... + //new I2PAppThread(new MessageDispatcher(outBuf.array()), "MessageDispatcher").start(); + // inline + // Even though we could be sending messages through multiple sessions, + // that isn't a common use case, and blocking should be rare. + // Inside router context, I2CP drops on overflow. + (new MessageDispatcher(outBuf.array())).run(); } } } @@ -194,9 +201,15 @@ class SAMv3Handler extends SAMv1Handler SessionRecord rec = sSessionsHash.get(nick); if (rec!=null) { rec.getHandler().session.sendBytes(dest,data); + } else { + Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMv3Handler.class); + if (log.shouldLog(Log.WARN)) + log.warn("Dropping datagram, no session for " + nick); } } catch (Exception e) { - // FIXME log? throw? + Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMv3Handler.class); + if (log.shouldLog(Log.WARN)) + log.warn("Error handling datagram", e); } } } From 89764c12e767fecd66dd1c55a266b62c6c66212c Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 23 Jul 2014 13:12:32 +0000 Subject: [PATCH 03/17] bob finals, synch --- apps/BOB/src/net/i2p/BOB/BOB.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/BOB/src/net/i2p/BOB/BOB.java b/apps/BOB/src/net/i2p/BOB/BOB.java index 8277fd2592..6dbbe3712c 100644 --- a/apps/BOB/src/net/i2p/BOB/BOB.java +++ b/apps/BOB/src/net/i2p/BOB/BOB.java @@ -119,15 +119,16 @@ public class BOB implements Runnable, ClientApp { public final static String PROP_BOB_HOST = "BOB.host"; public final static String PROP_CFG_VER = "BOB.CFG.VER"; + /** unused when started via the ClientApp interface */ private static BOB _bob; - private NamedDB database; - private Properties props = new Properties(); - private AtomicBoolean spin = new AtomicBoolean(true); + private final NamedDB database; + private final Properties props = new Properties(); + private final AtomicBoolean spin = new AtomicBoolean(true); private static final String P_RUNNING = "RUNNING"; private static final String P_STARTING = "STARTING"; private static final String P_STOPPING = "STOPPING"; - private AtomicBoolean lock = new AtomicBoolean(false); + private final AtomicBoolean lock = new AtomicBoolean(false); // no longer used. // private static int maxConnections = 0; @@ -143,8 +144,9 @@ public class BOB implements Runnable, ClientApp { * Stop BOB gracefully * @deprecated unused */ - public static void stop() { - _bob.shutdown(null); + public synchronized static void stop() { + if (_bob != null) + _bob.shutdown(null); } /** @@ -189,7 +191,7 @@ public class BOB implements Runnable, ClientApp { * * @param args */ - public static void main(String[] args) { + public synchronized static void main(String[] args) { try { _bob = new BOB(I2PAppContext.getGlobalContext(), null, args); _bob.startup(); From 086381d958a4c431739de5ff96c154375fa3270f Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 6 Aug 2014 16:32:10 +0000 Subject: [PATCH 04/17] SU3File: Add support for XML and NEWS types --- core/java/src/net/i2p/crypto/SU3File.java | 79 +++++++++++++++++------ 1 file changed, 61 insertions(+), 18 deletions(-) diff --git a/core/java/src/net/i2p/crypto/SU3File.java b/core/java/src/net/i2p/crypto/SU3File.java index 1794b0ac47..a6fb62e5e8 100644 --- a/core/java/src/net/i2p/crypto/SU3File.java +++ b/core/java/src/net/i2p/crypto/SU3File.java @@ -49,6 +49,7 @@ public class SU3File { private int _versionLength; private String _signer; private int _signerLength; + private int _fileType = -1; private ContentType _contentType; private long _contentLength; private PublicKey _signerPubkey; @@ -60,18 +61,21 @@ public class SU3File { private static final int MIN_VERSION_BYTES = 16; private static final int VERSION_OFFSET = 40; // Signature.SIGNATURE_BYTES; avoid early ctx init - private static final int TYPE_ZIP = 0; + public static final int TYPE_ZIP = 0; + public static final int TYPE_XML = 1; public static final int CONTENT_UNKNOWN = 0; public static final int CONTENT_ROUTER = 1; public static final int CONTENT_PLUGIN = 2; public static final int CONTENT_RESEED = 3; + public static final int CONTENT_NEWS = 4; private enum ContentType { UNKNOWN(CONTENT_UNKNOWN, "unknown"), ROUTER(CONTENT_ROUTER, "router"), PLUGIN(CONTENT_PLUGIN, "plugin"), - RESEED(CONTENT_RESEED, "reseed") + RESEED(CONTENT_RESEED, "reseed"), + NEWS(CONTENT_NEWS, "news") ; private final int code; @@ -153,6 +157,15 @@ public class SU3File { return _contentType != null ? _contentType.getCode() : -1; } + /** + * @return -1 if unknown + * @since 0.9.15 + */ + public int getFileType() throws IOException { + verifyHeader(); + return _fileType; + } + /** * Throws IOE if verify vails. */ @@ -204,9 +217,9 @@ public class SU3File { if (_contentLength <= 0) throw new IOException("bad content length"); skip(in, 1); - foo = in.read(); - if (foo != TYPE_ZIP) - throw new IOException("bad type"); + _fileType = in.read(); + if (_fileType != TYPE_ZIP && _fileType != TYPE_XML) + throw new IOException("bad file type"); skip(in, 1); int cType = in.read(); _contentType = BY_CODE.get(Integer.valueOf(cType)); @@ -352,11 +365,12 @@ public class SU3File { * Throws on all errors. * * @param content the input file, probably in zip format - * @param contentType 0-255, 0 for zip + * @param fileType 0-255, 0 for zip + * @param contentType 0-255 * @param version 1-255 bytes when converted to UTF-8 * @param signer ID of the public key, 1-255 bytes when converted to UTF-8 */ - public void write(File content, int contentType, String version, + public void write(File content, int fileType, int contentType, String version, String signer, PrivateKey privkey, SigType sigType) throws IOException { InputStream in = null; DigestOutputStream out = null; @@ -386,7 +400,9 @@ public class SU3File { throw new IllegalArgumentException("No content"); DataHelper.writeLong(out, 8, contentLength); out.write((byte) 0); - out.write((byte) TYPE_ZIP); + if (fileType < 0 || fileType > 255) + throw new IllegalArgumentException("bad content type"); + out.write((byte) fileType); out.write((byte) 0); if (contentType < 0 || contentType > 255) throw new IllegalArgumentException("bad content type"); @@ -443,8 +459,9 @@ public class SU3File { // defaults String stype = null; String ctype = null; + String ftype = null; boolean error = false; - Getopt g = new Getopt("SU3File", args, "t:c:"); + Getopt g = new Getopt("SU3File", args, "t:c:f:"); int c; while ((c = g.getopt()) != -1) { switch (c) { @@ -456,6 +473,10 @@ public class SU3File { ctype = g.getOptarg(); break; + case 'f': + ftype = g.getOptarg(); + break; + case '?': case ':': default: @@ -476,7 +497,7 @@ public class SU3File { Properties props = new Properties(); props.setProperty("prng.bufferSize", "16384"); new I2PAppContext(props); - ok = signCLI(stype, ctype, a.get(0), a.get(1), a.get(2), a.get(3), a.get(4), ""); + ok = signCLI(stype, ctype, ftype, a.get(0), a.get(1), a.get(2), a.get(3), a.get(4), ""); } else if ("bulksign".equals(cmd)) { Properties props = new Properties(); props.setProperty("prng.bufferSize", "16384"); @@ -502,11 +523,11 @@ public class SU3File { private static final void showUsageCLI() { System.err.println("Usage: SU3File keygen [-t type|code] publicKeyFile keystore.ks you@mail.i2p"); - System.err.println(" SU3File sign [-c type|code] [-t type|code] inputFile.zip signedFile.su3 keystore.ks version you@mail.i2p"); - System.err.println(" SU3File bulksign [-c type|code] [-t type|code] directory keystore.ks version you@mail.i2p"); + System.err.println(" SU3File sign [-t type|code] [-c type|code] [-f type|code] inputFile.zip signedFile.su3 keystore.ks version you@mail.i2p"); + System.err.println(" SU3File bulksign [-t type|code] [-c type|code] directory keystore.ks version you@mail.i2p"); System.err.println(" SU3File showversion signedFile.su3"); System.err.println(" SU3File verifysig signedFile.su3"); - System.err.println(" SU3File extract signedFile.su3 outFile.zip"); + System.err.println(" SU3File extract signedFile.su3 outFile"); System.err.println(dumpTypes()); } @@ -529,6 +550,9 @@ public class SU3File { buf.append(" DEFAULT"); buf.append('\n'); } + buf.append("Available file types (-f):\n"); + buf.append(" ZIP\t(code: 0) DEFAULT\n"); + buf.append(" XML\t(code: 1)\n"); return buf.toString(); } @@ -574,6 +598,7 @@ public class SU3File { } /** + * Zip only * @return success * @since 0.9.9 */ @@ -608,7 +633,7 @@ public class SU3File { if (!inputFile.endsWith(".zip")) continue; String signedFile = inputFile.substring(0, inputFile.length() - 4) + ".su3"; - boolean rv = signCLI(stype, ctype, inputFile, signedFile, privateKeyFile, version, signerName, keypw); + boolean rv = signCLI(stype, ctype, null, inputFile, signedFile, privateKeyFile, version, signerName, keypw); if (!rv) return false; success++; @@ -622,7 +647,7 @@ public class SU3File { * @return success * @since 0.9.9 */ - private static final boolean signCLI(String stype, String ctype, String inputFile, String signedFile, + private static final boolean signCLI(String stype, String ctype, String ftype, String inputFile, String signedFile, String privateKeyFile, String version, String signerName, String keypw) { SigType type = stype == null ? SigType.getByCode(Integer.valueOf(DEFAULT_SIG_CODE)) : SigType.parseSigType(stype); if (type == null) { @@ -634,14 +659,32 @@ public class SU3File { System.out.println("Content type " + ctype + " is not supported"); return false; } - return signCLI(type, ct, inputFile, signedFile, privateKeyFile, version, signerName, keypw); + int ft = TYPE_ZIP; + if (ftype != null) { + if (ftype.equalsIgnoreCase("ZIP")) { + ft = TYPE_ZIP; + } else if (ftype.equalsIgnoreCase("XML")) { + ft = TYPE_XML; + } else { + try { + ft = Integer.parseInt(ftype); + } catch (NumberFormatException nfe) { + ft = -1; + } + if (ft != TYPE_ZIP && ft != TYPE_XML) { + System.out.println("File type " + ftype + " is not supported"); + return false; + } + } + } + return signCLI(type, ct, ft, inputFile, signedFile, privateKeyFile, version, signerName, keypw); } /** * @return success * @since 0.9.9 */ - private static final boolean signCLI(SigType type, ContentType ctype, String inputFile, String signedFile, + private static final boolean signCLI(SigType type, ContentType ctype, int ftype, String inputFile, String signedFile, String privateKeyFile, String version, String signerName, String keypw) { try { while (keypw.length() < 6) { @@ -657,7 +700,7 @@ public class SU3File { return false; } SU3File file = new SU3File(signedFile); - file.write(new File(inputFile), ctype.getCode(), version, signerName, pk, type); + file.write(new File(inputFile), ftype, ctype.getCode(), version, signerName, pk, type); System.out.println("Input file '" + inputFile + "' signed and written to '" + signedFile + "'"); return true; } catch (GeneralSecurityException gse) { From d1a2e24f0e053215d56a379223a2cb5ae5defa20 Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 6 Aug 2014 16:35:08 +0000 Subject: [PATCH 05/17] SSU: Speed up introductions by responding to HolePunch (ticket #1333) --- .../transport/udp/EstablishmentManager.java | 26 +++++++++++++++++++ .../transport/udp/OutboundEstablishState.java | 22 +++++++++++++++- .../i2p/router/transport/udp/UDPReceiver.java | 11 +++++--- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java index 41b0eeddd6..267c4fc2a5 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -967,6 +967,32 @@ class EstablishmentManager { notifyActivity(); } + /** + * Called from UDPReceiver. + * Accelerate response to RelayResponse if we haven't sent it yet. + * + * @since 0.9.15 + */ + void receiveHolePunch(InetAddress from, int fromPort) { + RemoteHostId id = new RemoteHostId(from.getAddress(), fromPort); + OutboundEstablishState state = _outboundStates.get(id); + if (state != null) { + boolean sendNow = state.receiveHolePunch(); + if (sendNow) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Hole punch from " + state + ", sending SessionRequest now"); + notifyActivity(); + } else { + if (_log.shouldLog(Log.WARN)) + _log.warn("Hole punch from " + state + ", already sent SessionRequest"); + } + } else { + // HolePunch received before RelayResponse, and we didn't know the IP/port, or it changed + if (_log.shouldLog(Log.WARN)) + _log.warn("No state found for hole punch from " + from + " port " + fromPort); + } + } + /** * Are IP and port valid? This is only for checking the relay response. * Reject all IPv6, for now, even if we are configured for it. diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java index 39adc9b24b..ba4375dbdb 100644 --- a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java +++ b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java @@ -99,6 +99,8 @@ class OutboundEstablishState { /** max delay including backoff */ private static final long MAX_DELAY = 15*1000; + private static final long WAIT_FOR_HOLE_PUNCH_DELAY = 500; + /** * @param claimedAddress an IP/port based RemoteHostId, or null if unknown * @param remoteHostId non-null, == claimedAddress if direct, or a hash-based one if indirect @@ -556,7 +558,7 @@ class OutboundEstablishState { public synchronized void introduced(byte bobIP[], int bobPort) { if (_currentState != OutboundState.OB_STATE_PENDING_INTRO) return; // we've already successfully been introduced, so don't overwrite old settings - _nextSend = _context.clock().now() + 500; // wait briefly for the hole punching + _nextSend = _context.clock().now() + WAIT_FOR_HOLE_PUNCH_DELAY; // wait briefly for the hole punching _currentState = OutboundState.OB_STATE_INTRODUCED; if (_claimedAddress != null && bobPort == _bobPort && DataHelper.eq(bobIP, _bobIP)) { // he's who he said he was @@ -570,6 +572,24 @@ class OutboundEstablishState { if (_log.shouldLog(Log.INFO)) _log.info("Introduced to " + _remoteHostId + ", now lets get on with establishing"); } + + /** + * Accelerate response to RelayResponse if we haven't sent it yet. + * + * @return true if we should send the SessionRequest now + * @since 0.9.15 + */ + synchronized boolean receiveHolePunch() { + if (_currentState != OutboundState.OB_STATE_INTRODUCED) + return false; + if (_requestSentCount > 0) + return false; + long now = _context.clock().now(); + if (_log.shouldLog(Log.WARN)) + _log.warn(toString() + " accelerating SessionRequest by " + (_nextSend - now) + " ms"); + _nextSend = now; + return true; + } /** how long have we been trying to establish this session? */ public long getLifetime() { return _context.clock().now() - _establishBegin; } diff --git a/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java b/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java index be5b97dcf8..65a7a81cbf 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java @@ -1,6 +1,7 @@ package net.i2p.router.transport.udp; import java.io.IOException; +import java.net.DatagramPacket; import java.net.DatagramSocket; import java.util.Arrays; @@ -220,11 +221,12 @@ class UDPReceiver { // _socketChanged = false; //} UDPPacket packet = UDPPacket.acquire(_context, true); + DatagramPacket dpacket = packet.getPacket(); // Android ICS bug // http://code.google.com/p/android/issues/detail?id=24748 if (_isAndroid) - packet.getPacket().setLength(UDPPacket.MAX_PACKET_SIZE); + dpacket.setLength(UDPPacket.MAX_PACKET_SIZE); // block before we read... //if (_log.shouldLog(Log.DEBUG)) @@ -236,9 +238,9 @@ class UDPReceiver { //if (_log.shouldLog(Log.INFO)) // _log.info("Before blocking socket.receive on " + System.identityHashCode(packet)); //synchronized (Runner.this) { - _socket.receive(packet.getPacket()); + _socket.receive(dpacket); //} - int size = packet.getPacket().getLength(); + int size = dpacket.getLength(); if (_log.shouldLog(Log.INFO)) _log.info("After blocking socket.receive: packet is " + size + " bytes on " + System.identityHashCode(packet)); packet.resetBegin(); @@ -266,7 +268,8 @@ class UDPReceiver { _context.statManager().addRateData("udp.receiveHolePunch", 1); // nat hole punch packets are 0 bytes if (_log.shouldLog(Log.INFO)) - _log.info("Received a 0 byte udp packet from " + packet.getPacket().getAddress() + ":" + packet.getPacket().getPort()); + _log.info("Received a 0 byte udp packet from " + dpacket.getAddress() + ":" + dpacket.getPort()); + _transport.getEstablisher().receiveHolePunch(dpacket.getAddress(), dpacket.getPort()); packet.release(); } } catch (IOException ioe) { From 9655e79d26c934292b116e27db126850e82de3b2 Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 6 Aug 2014 18:13:54 +0000 Subject: [PATCH 06/17] UPnP: Disable external entities in XML parser --- .../cybergarage/xml/parser/JaxpParser.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/router/java/src/org/cybergarage/xml/parser/JaxpParser.java b/router/java/src/org/cybergarage/xml/parser/JaxpParser.java index 139f59f487..1d6e97f6a8 100644 --- a/router/java/src/org/cybergarage/xml/parser/JaxpParser.java +++ b/router/java/src/org/cybergarage/xml/parser/JaxpParser.java @@ -21,18 +21,21 @@ package org.cybergarage.xml.parser; +import java.io.ByteArrayInputStream; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import org.cybergarage.xml.Node; import org.cybergarage.xml.Parser; import org.cybergarage.xml.ParserException; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; +import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; @@ -116,8 +119,25 @@ public class JaxpParser extends Parser org.cybergarage.xml.Node root = null; try { + // https://www.owasp.org/index.php/XML_External_Entity_%28XXE%29_Processing DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setValidating(false); + factory.setNamespaceAware(true); + factory.setExpandEntityReferences(false); + try { + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + } catch (ParserConfigurationException pce) {} + try { + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + } catch (ParserConfigurationException pce) {} + try { + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + } catch (ParserConfigurationException pce) {} + try { + factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + } catch (ParserConfigurationException pce) {} DocumentBuilder builder = factory.newDocumentBuilder(); + builder.setEntityResolver(new BlankingResolver()); InputSource inSrc = new InputSource(new NullFilterInputStream(inStream)); Document doc = builder.parse(inSrc); @@ -163,4 +183,16 @@ public class JaxpParser extends Parser return rv; } } + + /** + * I2P - + * http://stackoverflow.com/questions/5883542/disable-xml-validation-based-on-external-dtd-xsd + */ + private static class BlankingResolver implements EntityResolver { + private static final byte[] DUMMY = new byte[0]; + + public InputSource resolveEntity(String arg0, String arg1) { + return new InputSource(new ByteArrayInputStream(DUMMY)); + } + } } From 2878a6487e20094eba0e2ec362c818f9e4e85a6b Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 7 Aug 2014 17:05:25 +0000 Subject: [PATCH 07/17] KeysAndCert: Change hashcode to prevent possible collisions caused by apps with zeroed pubkey --- core/java/src/net/i2p/data/KeysAndCert.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/java/src/net/i2p/data/KeysAndCert.java b/core/java/src/net/i2p/data/KeysAndCert.java index 6936c0f3e4..b0a8a845b7 100644 --- a/core/java/src/net/i2p/data/KeysAndCert.java +++ b/core/java/src/net/i2p/data/KeysAndCert.java @@ -130,17 +130,19 @@ public class KeysAndCert extends DataStructureImpl { && DataHelper.eq(_certificate, ident._certificate); } - /** the public key has enough randomness in it to use it by itself for speed */ + /** the signing key has enough randomness in it to use it by itself for speed */ @Override public int hashCode() { - if (_publicKey == null) + // don't use public key, some app devs thinking of using + // an all-zeros or leading-zeros public key for destinations + if (_signingKey == null) return 0; - return _publicKey.hashCode(); + return _signingKey.hashCode(); } @Override public String toString() { - StringBuilder buf = new StringBuilder(64); + StringBuilder buf = new StringBuilder(256); buf.append('[').append(getClass().getSimpleName()).append(": "); buf.append("\n\tHash: ").append(getHash().toBase64()); buf.append("\n\tCertificate: ").append(_certificate); From 79fe799aeb2b172edad2f1bcfd1b2ad4b6ebe18b Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 7 Aug 2014 18:45:04 +0000 Subject: [PATCH 08/17] Plugins: Stub out SU3 support --- .../i2p/router/update/PluginUpdateRunner.java | 53 ++++++++++++++++++- core/java/src/net/i2p/crypto/SU3File.java | 13 ++--- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateRunner.java b/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateRunner.java index d532a9c5c6..65df785db2 100644 --- a/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateRunner.java +++ b/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateRunner.java @@ -1,6 +1,7 @@ package net.i2p.router.update; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.lang.IllegalArgumentException; import java.net.URI; @@ -9,6 +10,7 @@ import java.util.Map; import java.util.Properties; import net.i2p.CoreVersion; +import net.i2p.crypto.SU3File; import net.i2p.crypto.TrustedUpdate; import net.i2p.data.DataHelper; import net.i2p.router.RouterContext; @@ -118,7 +120,6 @@ class PluginUpdateRunner extends UpdateRunner { @Override public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) { - boolean update = false; updateStatus("" + _("Plugin downloaded") + ""); File f = new File(_updateFile); File appDir = new SecureDirectory(_context.getConfigDir(), PLUGIN_DIR); @@ -127,7 +128,43 @@ class PluginUpdateRunner extends UpdateRunner { statusDone("" + _("Cannot create plugin directory {0}", appDir.getAbsolutePath()) + ""); return; } + boolean isSU3; + try { + isSU3 = isSU3File(f); + } catch (IOException ioe) { + f.delete(); + statusDone("" + ioe + ""); + return; + } + if (isSU3) + processSU3(f, appDir, url); + else + processSUD(f, appDir, url); + } + /** + * @since 0.9.15 + * @return if SU3 + */ + private static boolean isSU3File(File f) throws IOException { + FileInputStream fis = null; + try { + fis = new FileInputStream(f); + for (int i = 0; i < SU3File.MAGIC.length(); i++) { + if (fis.read() != SU3File.MAGIC.charAt(i)) + return false; + } + return true; + } finally { + if (fis != null) try { fis.close(); } catch (IOException ioe) {} + } + } + + /** + * @since 0.9.15 + * @return success + */ + private void processSUD(File f, File appDir, String url) { TrustedUpdate up = new TrustedUpdate(_context); File to = new File(_context.getTempDir(), "tmp" + _context.random().nextInt() + ZIP); // extract to a zip file whether the sig is good or not, so we can get the properties file @@ -222,7 +259,21 @@ class PluginUpdateRunner extends UpdateRunner { String sudVersion = TrustedUpdate.getVersionString(f); f.delete(); + processFinal(to, appDir, tempDir, url, props, sudVersion, pubkey, signer); + } + /** + * @since 0.9.15 + */ + private void processSU3(File f, File appDir, String url) { + // TODO + } + + /** + * @since 0.9.15 + */ + private void processFinal(File to, File appDir, File tempDir, String url, Properties props, String sudVersion, String pubkey, String signer) { + boolean update = false; String appName = props.getProperty("name"); String version = props.getProperty("version"); if (appName == null || version == null || appName.length() <= 0 || version.length() <= 0 || diff --git a/core/java/src/net/i2p/crypto/SU3File.java b/core/java/src/net/i2p/crypto/SU3File.java index a6fb62e5e8..30e172d4db 100644 --- a/core/java/src/net/i2p/crypto/SU3File.java +++ b/core/java/src/net/i2p/crypto/SU3File.java @@ -56,7 +56,8 @@ public class SU3File { private boolean _headerVerified; private SigType _sigType; - private static final byte[] MAGIC = DataHelper.getUTF8("I2Psu3"); + public static final String MAGIC = "I2Psu3"; + private static final byte[] MAGIC_BYTES = DataHelper.getASCII(MAGIC); private static final int FILE_VERSION = 0; private static final int MIN_VERSION_BYTES = 16; private static final int VERSION_OFFSET = 40; // Signature.SIGNATURE_BYTES; avoid early ctx init @@ -189,9 +190,9 @@ public class SU3File { * Throws if verify vails. */ private void verifyHeader(InputStream in) throws IOException, DataFormatException { - byte[] magic = new byte[MAGIC.length]; + byte[] magic = new byte[MAGIC_BYTES.length]; DataHelper.read(in, magic); - if (!DataHelper.eq(magic, MAGIC)) + if (!DataHelper.eq(magic, MAGIC_BYTES)) throw new IOException("Not an su3 file"); skip(in, 1); int foo = in.read(); @@ -300,9 +301,9 @@ public class SU3File { // read 10 bytes to get the sig type in.mark(10); // following is a dup of that in verifyHeader() - byte[] magic = new byte[MAGIC.length]; + byte[] magic = new byte[MAGIC_BYTES.length]; DataHelper.read(in, magic); - if (!DataHelper.eq(magic, MAGIC)) + if (!DataHelper.eq(magic, MAGIC_BYTES)) throw new IOException("Not an su3 file"); skip(in, 1); int foo = in.read(); @@ -379,7 +380,7 @@ public class SU3File { in = new BufferedInputStream(new FileInputStream(content)); MessageDigest md = sigType.getDigestInstance(); out = new DigestOutputStream(new BufferedOutputStream(new FileOutputStream(_file)), md); - out.write(MAGIC); + out.write(MAGIC_BYTES); out.write((byte) 0); out.write((byte) FILE_VERSION); DataHelper.writeLong(out, 2, sigType.getCode()); From 35bb8c5348ead682534072278bd8ce7540bf459c Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 7 Aug 2014 19:06:41 +0000 Subject: [PATCH 09/17] Plugins: partial SU3 support --- .../i2p/router/update/PluginUpdateRunner.java | 94 ++++++++++++++----- 1 file changed, 70 insertions(+), 24 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateRunner.java b/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateRunner.java index 65df785db2..0025436a54 100644 --- a/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateRunner.java +++ b/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateRunner.java @@ -175,27 +175,9 @@ class PluginUpdateRunner extends UpdateRunner { to.delete(); return; } - File tempDir = new File(_context.getTempDir(), "tmp" + _context.random().nextInt() + "-unzip"); - if (!FileUtil.extractZip(to, tempDir, Log.ERROR)) { - f.delete(); - to.delete(); - FileUtil.rmdir(tempDir, false); - statusDone("" + _("Plugin from {0} is corrupt", url) + ""); + Properties props = getPluginConfig(f, to, url); + if (props == null) return; - } - File installProps = new File(tempDir, "plugin.config"); - Properties props = new OrderedProperties(); - try { - DataHelper.loadProps(props, installProps); - } catch (IOException ioe) { - f.delete(); - to.delete(); - FileUtil.rmdir(tempDir, false); - statusDone("" + _("Plugin from {0} does not contain the required configuration file", url) + ""); - return; - } - // we don't need this anymore, we will unzip again - FileUtil.rmdir(tempDir, false); // ok, now we check sigs and deal with a bad sig String pubkey = props.getProperty("key"); @@ -259,20 +241,85 @@ class PluginUpdateRunner extends UpdateRunner { String sudVersion = TrustedUpdate.getVersionString(f); f.delete(); - processFinal(to, appDir, tempDir, url, props, sudVersion, pubkey, signer); + processFinal(to, appDir, url, props, sudVersion, pubkey, signer); } /** * @since 0.9.15 */ private void processSU3(File f, File appDir, String url) { - // TODO + SU3File su3 = new SU3File(_context, f); + File to = new File(_context.getTempDir(), "tmp" + _context.random().nextInt() + ZIP); + String sudVersion; + String signingKeyName; + try { + su3.verifyAndMigrate(to); + sudVersion = su3.getVersionString(); + signingKeyName = su3.getSignerString(); + } catch (IOException ioe) { + statusDone("" + ioe + ' ' + _("from {0}", url) + " "); + f.delete(); + to.delete(); + return; + } + Properties props = getPluginConfig(f, to, url); + if (props == null) + return; + String pubkey = props.getProperty("key"); + String signer = props.getProperty("signer"); + if (pubkey == null || signer == null || pubkey.length() != 172 || signer.length() <= 0) { + f.delete(); + to.delete(); + statusDone("" + _("Plugin from {0} contains an invalid key", url) + ""); + return; + } + if (!signer.equals(signingKeyName)) { + f.delete(); + to.delete(); + if (signingKeyName == null) + _log.error("Failed to verify plugin signature, corrupt plugin or bad signature, signed by: " + signer); + else + // shouldn't happen + _log.error("Plugin signer \"" + signer + "\" does not match new signer in plugin.config file \"" + signingKeyName + "\""); + statusDone("" + _("Plugin signature verification of {0} failed", url) + ""); + return; + } + processFinal(to, appDir, url, props, sudVersion, pubkey, signer); + } + + /** + * @since 0.9.15 + * @return null on error + */ + private Properties getPluginConfig(File f, File to, String url) { + File tempDir = new File(_context.getTempDir(), "tmp" + _context.random().nextInt() + "-unzip"); + if (!FileUtil.extractZip(to, tempDir, Log.ERROR)) { + f.delete(); + to.delete(); + FileUtil.rmdir(tempDir, false); + statusDone("" + _("Plugin from {0} is corrupt", url) + ""); + return null; + } + File installProps = new File(tempDir, "plugin.config"); + Properties props = new OrderedProperties(); + try { + DataHelper.loadProps(props, installProps); + } catch (IOException ioe) { + f.delete(); + to.delete(); + statusDone("" + _("Plugin from {0} does not contain the required configuration file", url) + ""); + return null; + } finally { + // we don't need this anymore, we will unzip again + FileUtil.rmdir(tempDir, false); + } + return props; } /** * @since 0.9.15 */ - private void processFinal(File to, File appDir, File tempDir, String url, Properties props, String sudVersion, String pubkey, String signer) { + private void processFinal(File to, File appDir, String url, Properties props, String sudVersion, String pubkey, String signer) { boolean update = false; String appName = props.getProperty("name"); String version = props.getProperty("version"); @@ -324,7 +371,6 @@ class PluginUpdateRunner extends UpdateRunner { DataHelper.loadProps(oldProps, oldPropFile); } catch (IOException ioe) { to.delete(); - FileUtil.rmdir(tempDir, false); statusDone("" + _("Installed plugin does not contain the required configuration file", url) + ""); return; } From 506419964b930792e8b155e286ad9786ba9de6e3 Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 7 Aug 2014 19:27:53 +0000 Subject: [PATCH 10/17] Plugins: SU3 support in form, type checking, don't require DSA key in SU3 --- .../net/i2p/router/update/PluginUpdateHandler.java | 4 +++- .../net/i2p/router/update/PluginUpdateRunner.java | 12 ++++++++---- .../src/net/i2p/router/web/ConfigClientsHandler.java | 4 +++- .../src/net/i2p/router/web/ConfigClientsHelper.java | 4 +++- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateHandler.java index 64304bd3bc..f327f739da 100644 --- a/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateHandler.java @@ -43,7 +43,9 @@ class PluginUpdateHandler implements Checker, Updater { Properties props = PluginStarter.pluginProperties(_context, appName); String oldVersion = props.getProperty("version"); - String xpi2pURL = props.getProperty("updateURL"); + String xpi2pURL = props.getProperty("updateURL.su3"); + if (xpi2pURL == null) + xpi2pURL = props.getProperty("updateURL"); List updateSources = null; if (xpi2pURL != null) { try { diff --git a/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateRunner.java b/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateRunner.java index 0025436a54..e42df7877e 100644 --- a/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateRunner.java +++ b/apps/routerconsole/java/src/net/i2p/router/update/PluginUpdateRunner.java @@ -254,6 +254,10 @@ class PluginUpdateRunner extends UpdateRunner { String signingKeyName; try { su3.verifyAndMigrate(to); + if (su3.getFileType() != SU3File.TYPE_ZIP) + throw new IOException("bad file type"); + if (su3.getContentType() != SU3File.CONTENT_PLUGIN) + throw new IOException("bad content type"); sudVersion = su3.getVersionString(); signingKeyName = su3.getSignerString(); } catch (IOException ioe) { @@ -265,9 +269,8 @@ class PluginUpdateRunner extends UpdateRunner { Properties props = getPluginConfig(f, to, url); if (props == null) return; - String pubkey = props.getProperty("key"); String signer = props.getProperty("signer"); - if (pubkey == null || signer == null || pubkey.length() != 172 || signer.length() <= 0) { + if (signer == null || signer.length() <= 0) { f.delete(); to.delete(); statusDone("" + _("Plugin from {0} contains an invalid key", url) + ""); @@ -284,7 +287,7 @@ class PluginUpdateRunner extends UpdateRunner { statusDone("" + _("Plugin signature verification of {0} failed", url) + ""); return; } - processFinal(to, appDir, url, props, sudVersion, pubkey, signer); + processFinal(to, appDir, url, props, sudVersion, null, signer); } /** @@ -317,6 +320,7 @@ class PluginUpdateRunner extends UpdateRunner { } /** + * @param pubkey null OK for su3 * @since 0.9.15 */ private void processFinal(File to, File appDir, String url, Properties props, String sudVersion, String pubkey, String signer) { @@ -377,7 +381,7 @@ class PluginUpdateRunner extends UpdateRunner { String oldPubkey = oldProps.getProperty("key"); String oldKeyName = oldProps.getProperty("signer"); String oldAppName = oldProps.getProperty("name"); - if ((!pubkey.equals(oldPubkey)) || (!signer.equals(oldKeyName)) || (!appName.equals(oldAppName))) { + if ((pubkey != null && !pubkey.equals(oldPubkey)) || (!signer.equals(oldKeyName)) || (!appName.equals(oldAppName))) { to.delete(); statusDone("" + _("Signature of downloaded plugin does not match installed plugin") + ""); return; diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java index 1c1c28568d..443b9f093e 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java @@ -344,7 +344,9 @@ public class ConfigClientsHandler extends FormHandler { private void updatePlugin(String app) { Properties props = PluginStarter.pluginProperties(_context, app); - String url = props.getProperty("updateURL"); + String url = props.getProperty("updateURL.su3"); + if (url == null) + url = props.getProperty("updateURL"); if (url == null) { addFormError(_("No update URL specified for {0}",app)); return; diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java index c1463000bc..8d33a365ef 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java @@ -232,7 +232,9 @@ public class ConfigClientsHelper extends HelperBase { desc.append("") .append("").append(_("Website")).append(" "); } - String updateURL = stripHTML(appProps, "updateURL"); + String updateURL = stripHTML(appProps, "updateURL.su3"); + if (updateURL == null) + updateURL = stripHTML(appProps, "updateURL"); if (updateURL != null) { desc.append("") .append("").append(_("Update link")).append(" "); From 381f494754d21166b4c0e6ea5af723d180e76ffc Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 7 Aug 2014 20:17:51 +0000 Subject: [PATCH 11/17] SU3File: Fix NPE on EOF reading input --- core/java/src/net/i2p/crypto/SU3File.java | 28 +++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/core/java/src/net/i2p/crypto/SU3File.java b/core/java/src/net/i2p/crypto/SU3File.java index 30e172d4db..455e43d628 100644 --- a/core/java/src/net/i2p/crypto/SU3File.java +++ b/core/java/src/net/i2p/crypto/SU3File.java @@ -619,7 +619,12 @@ public class SU3File { try { while (keypw.length() < 6) { System.out.print("Enter password for key \"" + signerName + "\": "); - keypw = DataHelper.readLine(System.in).trim(); + keypw = DataHelper.readLine(System.in); + if (keypw == null) { + System.out.println("\nEOF reading password"); + return false; + } + keypw = keypw.trim(); if (keypw.length() > 0 && keypw.length() < 6) System.out.println("Key password must be at least 6 characters"); } @@ -690,7 +695,12 @@ public class SU3File { try { while (keypw.length() < 6) { System.out.print("Enter password for key \"" + signerName + "\": "); - keypw = DataHelper.readLine(System.in).trim(); + keypw = DataHelper.readLine(System.in); + if (keypw == null) { + System.out.println("\nEOF reading password"); + return false; + } + keypw = keypw.trim(); if (keypw.length() > 0 && keypw.length() < 6) System.out.println("Key password must be at least 6 characters"); } @@ -784,11 +794,21 @@ public class SU3File { try { while (alias.length() == 0) { System.out.print("Enter key name (example@mail.i2p): "); - alias = DataHelper.readLine(System.in).trim(); + alias = DataHelper.readLine(System.in); + if (alias == null) { + System.out.println("\nEOF reading key name"); + return false; + } + alias = alias.trim(); } while (keypw.length() < 6) { System.out.print("Enter new key password: "); - keypw = DataHelper.readLine(System.in).trim(); + keypw = DataHelper.readLine(System.in); + if (keypw == null) { + System.out.println("\nEOF reading password"); + return false; + } + keypw = keypw.trim(); if (keypw.length() > 0 && keypw.length() < 6) System.out.println("Key password must be at least 6 characters"); } From c9e20c5d23b3e0e88f6f5b41ad13539aedc64f6d Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 7 Aug 2014 21:22:18 +0000 Subject: [PATCH 12/17] log tweak --- core/java/src/net/i2p/crypto/SU3File.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/java/src/net/i2p/crypto/SU3File.java b/core/java/src/net/i2p/crypto/SU3File.java index 455e43d628..8b09338a76 100644 --- a/core/java/src/net/i2p/crypto/SU3File.java +++ b/core/java/src/net/i2p/crypto/SU3File.java @@ -255,7 +255,7 @@ public class SU3File { } if (_signerPubkey == null) - throw new IOException("unknown signer: " + _signer); + throw new IOException("unknown signer: " + _signer + " for content type: " + _contentType.getName()); _headerVerified = true; } @@ -325,7 +325,7 @@ public class SU3File { else skip(in, getContentOffset()); if (_signerPubkey == null) - throw new IOException("unknown signer: " + _signer); + throw new IOException("unknown signer: " + _signer + " for content type: " + _contentType.getName()); if (migrateTo != null) // else verify only out = new FileOutputStream(migrateTo); byte[] buf = new byte[16*1024]; From b1d60122a3e1b16227599668362c5f2ce4455522 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 8 Aug 2014 16:40:48 +0000 Subject: [PATCH 13/17] better msg to client on unsupported sigtype --- .../router/client/ClientMessageEventListener.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java index 6dd0d833bb..cdcc0008cf 100644 --- a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java +++ b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java @@ -11,6 +11,7 @@ package net.i2p.router.client; import java.util.Properties; import net.i2p.CoreVersion; +import net.i2p.crypto.SigType; import net.i2p.data.Hash; import net.i2p.data.Payload; import net.i2p.data.i2cp.BandwidthLimitsMessage; @@ -195,10 +196,16 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi if (_log.shouldLog(Log.DEBUG)) _log.debug("Signature verified correctly on create session message"); } else { - if (_log.shouldLog(Log.ERROR)) - _log.error("Signature verification *FAILED* on a create session message. Hijack attempt?"); // For now, we do NOT send a SessionStatusMessage - see javadoc above - _runner.disconnectClient("Invalid signature on CreateSessionMessage"); + int itype = in.getDestination().getCertificate().getCertificateType(); + SigType stype = SigType.getByCode(itype); + if (stype == null || !stype.isAvailable()) { + _log.error("Client requested unsupported signature type " + itype); + _runner.disconnectClient("Unsupported signature type " + itype); + } else { + _log.error("Signature verification failed on a create session message"); + _runner.disconnectClient("Invalid signature on CreateSessionMessage"); + } return; } From fe4d98f0dfe4cd923a48ff114e88ee572c0e7845 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 8 Aug 2014 16:40:56 +0000 Subject: [PATCH 14/17] javadoc --- core/java/src/net/i2p/data/Base32.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/java/src/net/i2p/data/Base32.java b/core/java/src/net/i2p/data/Base32.java index 75671c31d4..4ca506b883 100644 --- a/core/java/src/net/i2p/data/Base32.java +++ b/core/java/src/net/i2p/data/Base32.java @@ -56,6 +56,7 @@ public class Base32 { }; private final static byte BAD_ENCODING = -9; // Indicates error in encoding + /** Defeats instantiation. */ private Base32() { // nop } @@ -136,6 +137,9 @@ public class Base32 { } /** + * Returns lower case. + * Does not add trailing '='. + * * @param source if null will return "" */ public static String encode(String source) { @@ -143,6 +147,9 @@ public class Base32 { } /** + * Returns lower case. + * Does not add trailing '='. + * * @param source The data to convert non-null */ public static String encode(byte[] source) { @@ -182,6 +189,8 @@ public class Base32 { /** * Decodes data from Base32 notation and * returns it as a string. + * Case-insensitive. + * Does not allow trailing '='. * * @param s the string to decode, if null returns null * @return The data as a string or null on failure @@ -194,6 +203,9 @@ public class Base32 { } /** + * Case-insensitive. + * Does not allow trailing '='. + * * @param s non-null * @return decoded data, null on error */ From 03d8314842520b234bc000a902c0cd2a831e8bbe Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 8 Aug 2014 16:41:27 +0000 Subject: [PATCH 15/17] dir for plugin certs From 0bacbbc553a12b094042d4cd216a77f392905a5f Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 9 Aug 2014 17:55:17 +0000 Subject: [PATCH 16/17] SigType: Add static isAvailable() methods SU3File: - Add -x option to bypass signature verification - Add -k option to use specified private key cert for verification - Don't verify signature in showversion --- core/java/src/net/i2p/crypto/SU3File.java | 143 +++++++++++++++++----- core/java/src/net/i2p/crypto/SigType.java | 23 ++++ 2 files changed, 138 insertions(+), 28 deletions(-) diff --git a/core/java/src/net/i2p/crypto/SU3File.java b/core/java/src/net/i2p/crypto/SU3File.java index 8b09338a76..9c158a48a1 100644 --- a/core/java/src/net/i2p/crypto/SU3File.java +++ b/core/java/src/net/i2p/crypto/SU3File.java @@ -15,6 +15,8 @@ import java.security.GeneralSecurityException; import java.security.MessageDigest; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; @@ -55,6 +57,8 @@ public class SU3File { private PublicKey _signerPubkey; private boolean _headerVerified; private SigType _sigType; + private boolean _verifySignature = true; + private File _certFile; public static final String MAGIC = "I2Psu3"; private static final byte[] MAGIC_BYTES = DataHelper.getASCII(MAGIC); @@ -130,17 +134,44 @@ public class SU3File { _file = file; } + /** + * Should the signature be verified? Default true + * @since 0.9.15 + */ + public void setVerifySignature(boolean shouldVerify) { + _verifySignature = shouldVerify; + } + + /** + * Use this X.509 cert file for verification instead of $I2P/certificates/content_type/foo_at_mail.i2p + * @since 0.9.15 + */ + private void setPublicKeyCertificate(File certFile) { + _certFile = certFile; + } + + /** + * This does not check the signature, but it will fail if the signer is unknown, + * unless setVerifySignature(false) has been called. + */ public String getVersionString() throws IOException { verifyHeader(); return _version; } + /** + * This does not check the signature, but it will fail if the signer is unknown, + * unless setVerifySignature(false) has been called. + */ public String getSignerString() throws IOException { verifyHeader(); return _signer; } /** + * This does not check the signature, but it will fail if the signer is unknown, + * unless setVerifySignature(false) has been called. + * * @return null if unknown * @since 0.9.9 */ @@ -150,6 +181,9 @@ public class SU3File { } /** + * This does not check the signature, but it will fail if the signer is unknown, + * unless setVerifySignature(false) has been called. + * * @return -1 if unknown * @since 0.9.9 */ @@ -159,6 +193,9 @@ public class SU3File { } /** + * This does not check the signature, but it will fail if the signer is unknown, + * unless setVerifySignature(false) has been called. + * * @return -1 if unknown * @since 0.9.15 */ @@ -168,6 +205,9 @@ public class SU3File { } /** + * This does not check the signature, but it will fail if the signer is unknown, + * unless setVerifySignature(false) has been called. + * * Throws IOE if verify vails. */ public void verifyHeader() throws IOException { @@ -245,17 +285,22 @@ public class SU3File { throw new EOFException(); _signer = DataHelper.getUTF8(data); - KeyRing ring = new DirKeyRing(new File(_context.getBaseDir(), "certificates")); - try { - _signerPubkey = ring.getKey(_signer, _contentType.getName(), _sigType); - } catch (GeneralSecurityException gse) { - IOException ioe = new IOException("keystore error"); - ioe.initCause(gse); - throw ioe; + if (_verifySignature) { + if (_certFile != null) { + _signerPubkey = loadKey(_certFile); + } else { + KeyRing ring = new DirKeyRing(new File(_context.getBaseDir(), "certificates")); + try { + _signerPubkey = ring.getKey(_signer, _contentType.getName(), _sigType); + } catch (GeneralSecurityException gse) { + IOException ioe = new IOException("keystore error"); + ioe.initCause(gse); + throw ioe; + } + if (_signerPubkey == null) + throw new IOException("unknown signer: " + _signer + " for content type: " + _contentType.getName()); + } } - - if (_signerPubkey == null) - throw new IOException("unknown signer: " + _signer + " for content type: " + _contentType.getName()); _headerVerified = true; } @@ -324,8 +369,10 @@ public class SU3File { verifyHeader(in); else skip(in, getContentOffset()); - if (_signerPubkey == null) - throw new IOException("unknown signer: " + _signer + " for content type: " + _contentType.getName()); + if (_verifySignature) { + if (_signerPubkey == null) + throw new IOException("unknown signer: " + _signer + " for content type: " + _contentType.getName()); + } if (migrateTo != null) // else verify only out = new FileOutputStream(migrateTo); byte[] buf = new byte[16*1024]; @@ -338,15 +385,19 @@ public class SU3File { out.write(buf, 0, read); tot += read; } - byte[] sha = md.digest(); - din.on(false); - Signature signature = new Signature(_sigType); - signature.readBytes(in); - SimpleDataStructure hash = _sigType.getHashInstance(); - hash.setData(sha); - //System.out.println("hash\n" + HexDump.dump(sha)); - //System.out.println("sig\n" + HexDump.dump(signature.getData())); - rv = _context.dsa().verifySignature(signature, hash, _signerPubkey); + if (_verifySignature) { + byte[] sha = md.digest(); + din.on(false); + Signature signature = new Signature(_sigType); + signature.readBytes(in); + SimpleDataStructure hash = _sigType.getHashInstance(); + hash.setData(sha); + //System.out.println("hash\n" + HexDump.dump(sha)); + //System.out.println("sig\n" + HexDump.dump(signature.getData())); + rv = _context.dsa().verifySignature(signature, hash, _signerPubkey); + } else { + rv = true; + } } catch (DataFormatException dfe) { IOException ioe = new IOException("foo"); ioe.initCause(dfe); @@ -461,8 +512,10 @@ public class SU3File { String stype = null; String ctype = null; String ftype = null; + String kfile = null; boolean error = false; - Getopt g = new Getopt("SU3File", args, "t:c:f:"); + boolean shouldVerify = true; + Getopt g = new Getopt("SU3File", args, "t:c:f:k:x"); int c; while ((c = g.getopt()) != -1) { switch (c) { @@ -478,6 +531,14 @@ public class SU3File { ftype = g.getOptarg(); break; + case 'k': + kfile = g.getOptarg(); + break; + + case 'x': + shouldVerify = false; + break; + case '?': case ':': default: @@ -505,11 +566,11 @@ public class SU3File { new I2PAppContext(props); ok = bulkSignCLI(stype, ctype, a.get(0), a.get(1), a.get(2), a.get(3)); } else if ("verifysig".equals(cmd)) { - ok = verifySigCLI(a.get(0)); + ok = verifySigCLI(a.get(0), kfile); } else if ("keygen".equals(cmd)) { ok = genKeysCLI(stype, a.get(0), a.get(1), a.get(2)); } else if ("extract".equals(cmd)) { - ok = extractCLI(a.get(0), a.get(1)); + ok = extractCLI(a.get(0), a.get(1), shouldVerify); } else { showUsageCLI(); } @@ -527,8 +588,8 @@ public class SU3File { System.err.println(" SU3File sign [-t type|code] [-c type|code] [-f type|code] inputFile.zip signedFile.su3 keystore.ks version you@mail.i2p"); System.err.println(" SU3File bulksign [-t type|code] [-c type|code] directory keystore.ks version you@mail.i2p"); System.err.println(" SU3File showversion signedFile.su3"); - System.err.println(" SU3File verifysig signedFile.su3"); - System.err.println(" SU3File extract signedFile.su3 outFile"); + System.err.println(" SU3File verifysig [-k file.crt] signedFile.su3 ## -k use this pubkey cert for verification"); + System.err.println(" SU3File extract [-x] [-k file.crt] signedFile.su3 outFile ## -x don't check sig"); System.err.println(dumpTypes()); } @@ -579,6 +640,7 @@ public class SU3File { private static final boolean showVersionCLI(String signedFile) { try { SU3File file = new SU3File(signedFile); + file.setVerifySignature(false); String versionString = file.getVersionString(); if (versionString.equals("")) System.out.println("No version string found in file '" + signedFile + "'"); @@ -726,10 +788,12 @@ public class SU3File { } /** @return valid */ - private static final boolean verifySigCLI(String signedFile) { + private static final boolean verifySigCLI(String signedFile, String pkFile) { InputStream in = null; try { SU3File file = new SU3File(signedFile); + if (pkFile != null) + file.setPublicKeyCertificate(new File(pkFile)); boolean isValidSignature = file.verify(); if (isValidSignature) System.out.println("Signature VALID (signed by " + file.getSignerString() + ' ' + file._sigType + ')'); @@ -747,10 +811,11 @@ public class SU3File { * @return success * @since 0.9.9 */ - private static final boolean extractCLI(String signedFile, String outFile) { + private static final boolean extractCLI(String signedFile, String outFile, boolean verifySig) { InputStream in = null; try { SU3File file = new SU3File(signedFile); + file.setVerifySignature(verifySig); File out = new File(outFile); boolean ok = file.verifyAndMigrate(out); if (ok) @@ -836,4 +901,26 @@ public class SU3File { } return true; } + + /** + * For the -k CLI option + * @return non-null, throws IOE on all errors + * @since 0.9.15 + */ + private static PublicKey loadKey(File kd) throws IOException { + InputStream fis = null; + try { + fis = new FileInputStream(kd); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate)cf.generateCertificate(fis); + cert.checkValidity(); + return cert.getPublicKey(); + } catch (GeneralSecurityException gse) { + IOException ioe = new IOException("cert error"); + ioe.initCause(gse); + throw ioe; + } finally { + try { if (fis != null) fis.close(); } catch (IOException foo) {} + } + } } diff --git a/core/java/src/net/i2p/crypto/SigType.java b/core/java/src/net/i2p/crypto/SigType.java index f63eadea84..700f355c66 100644 --- a/core/java/src/net/i2p/crypto/SigType.java +++ b/core/java/src/net/i2p/crypto/SigType.java @@ -178,6 +178,29 @@ public enum SigType { return true; } + /** + * @return true if supported in this JVM + * @since 0.9.15 + */ + public static boolean isAvailable(int code) { + SigType type = getByCode(code); + if (type == null) + return false; + return type.isAvailable(); + } + + /** + * @param stype number or name + * @return true if supported in this JVM + * @since 0.9.15 + */ + public static boolean isAvailable(String stype) { + SigType type = parseSigType(stype); + if (type == null) + return false; + return type.isAvailable(); + } + private static final Map BY_CODE = new HashMap(); static { From 22a7757461a80566e9b1381c42ed31fda0ea6bfd Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 9 Aug 2014 18:08:00 +0000 Subject: [PATCH 17/17] Console: Show share options below 30% (ticket #1329) --- .../java/src/net/i2p/router/web/ConfigNetHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java index d3450eb6f3..efe9f4f819 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java @@ -298,9 +298,9 @@ public class ConfigNetHelper extends HelperBase { StringBuilder buf = new StringBuilder(256); buf.append("