diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java index 076ee99fab457ab68b32abe0d0381a871ac99853..6a206924b4c9d462c741f49c3b46ccdd77f18a2b 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java @@ -26,6 +26,7 @@ public class I2PSocketManagerFactory { public static final String PROP_MANAGER = "i2p.streaming.manager"; public static final String DEFAULT_MANAGER = "net.i2p.client.streaming.I2PSocketManagerImpl"; + //public static final String DEFAULT_MANAGER = "net.i2p.client.streaming.I2PSocketManagerFull"; /** * Create a socket manager using a brand new destination connected to the @@ -83,9 +84,11 @@ public class I2PSocketManagerFactory { if (true) { // for the old streaming lib opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED); + //opts.setProperty("tunnels.depthInbound", "0"); } else { // for new streaming lib: opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_BEST_EFFORT); + //opts.setProperty("tunnels.depthInbound", "0"); } opts.setProperty(I2PClient.PROP_TCP_HOST, i2cpHost); diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/StreamSinkClient.java b/apps/ministreaming/java/src/net/i2p/client/streaming/StreamSinkClient.java index 4ef7f2cfc51905e1e4c31cc1234206d7f9845754..0a8d29cae9c01a5254dc5f5066e517464118ead8 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/StreamSinkClient.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/StreamSinkClient.java @@ -8,6 +8,7 @@ import java.io.OutputStream; import java.net.ConnectException; import java.net.NoRouteToHostException; +import java.util.Properties; import java.util.Random; import net.i2p.I2PAppContext; @@ -26,6 +27,8 @@ public class StreamSinkClient { private int _sendSize; private int _writeDelay; private String _peerDestFile; + private String _i2cpHost; + private int _i2cpPort; /** @@ -35,6 +38,11 @@ public class StreamSinkClient { * @param serverDestFile file containing the StreamSinkServer's binary Destination */ public StreamSinkClient(int sendSize, int writeDelayMs, String serverDestFile) { + this(null, -1, sendSize, writeDelayMs, serverDestFile); + } + public StreamSinkClient(String i2cpHost, int i2cpPort, int sendSize, int writeDelayMs, String serverDestFile) { + _i2cpHost = i2cpHost; + _i2cpPort = i2cpPort; _sendSize = sendSize; _writeDelay = writeDelayMs; _peerDestFile = serverDestFile; @@ -46,7 +54,11 @@ public class StreamSinkClient { * */ public void runClient() { - I2PSocketManager mgr = I2PSocketManagerFactory.createManager(); + I2PSocketManager mgr = null; + if (_i2cpHost != null) + mgr = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, new Properties()); + else + mgr = I2PSocketManagerFactory.createManager(); Destination peer = null; FileInputStream fis = null; try { @@ -81,9 +93,9 @@ public class StreamSinkClient { try { Thread.sleep(_writeDelay); } catch (InterruptedException ie) {} } } + sock.close(); long afterSending = System.currentTimeMillis(); System.out.println("Sent " + _sendSize + "KB in " + (afterSending-beforeSending) + "ms"); - sock.close(); } catch (InterruptedIOException iie) { _log.error("Timeout connecting to the peer", iie); return; @@ -103,7 +115,7 @@ public class StreamSinkClient { } /** - * Fire up the client. <code>Usage: StreamSinkClient sendSizeKB writeDelayMs serverDestFile</code> <br /> + * Fire up the client. <code>Usage: StreamSinkClient [i2cpHost i2cpPort] sendSizeKB writeDelayMs serverDestFile</code> <br /> * <ul> * <li><b>sendSizeKB</b>: how many KB to send</li> * <li><b>writeDelayMs</b>: how long to wait between each .write (0 for no delay)</li> @@ -111,25 +123,40 @@ public class StreamSinkClient { * </ul> */ public static void main(String args[]) { - if (args.length != 3) { - System.out.println("Usage: StreamSinkClient sendSizeKB writeDelayMs serverDestFile"); - } else { - int sendSizeKB = -1; - int writeDelayMs = -1; - try { - sendSizeKB = Integer.parseInt(args[0]); - } catch (NumberFormatException nfe) { - System.err.println("Send size invalid [" + args[0] + "]"); - return; - } - try { - writeDelayMs = Integer.parseInt(args[1]); - } catch (NumberFormatException nfe) { - System.err.println("Write delay ms invalid [" + args[1] + "]"); - return; - } - StreamSinkClient client = new StreamSinkClient(sendSizeKB, writeDelayMs, args[2]); - client.runClient(); + StreamSinkClient client = null; + int sendSizeKB = -1; + int writeDelayMs = -1; + + switch (args.length) { + case 3: + try { + sendSizeKB = Integer.parseInt(args[0]); + } catch (NumberFormatException nfe) { + System.err.println("Send size invalid [" + args[0] + "]"); + return; + } + try { + writeDelayMs = Integer.parseInt(args[1]); + } catch (NumberFormatException nfe) { + System.err.println("Write delay ms invalid [" + args[1] + "]"); + return; + } + client = new StreamSinkClient(sendSizeKB, writeDelayMs, args[2]); + break; + case 5: + try { + int port = Integer.parseInt(args[1]); + sendSizeKB = Integer.parseInt(args[2]); + writeDelayMs = Integer.parseInt(args[3]); + client = new StreamSinkClient(args[0], port, sendSizeKB, writeDelayMs, args[4]); + } catch (NumberFormatException nfe) { + System.err.println("arg error"); + } + break; + default: + System.out.println("Usage: StreamSinkClient [i2cpHost i2cpPort] sendSizeKB writeDelayMs serverDestFile"); } + if (client != null) + client.runClient(); } } diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/StreamSinkSend.java b/apps/ministreaming/java/src/net/i2p/client/streaming/StreamSinkSend.java index e7398e5b64dcd3434f7d232ee84a1e59f925c454..6b43fb0c4fef4e1838dd2d3aa354e0534acf6413 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/StreamSinkSend.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/StreamSinkSend.java @@ -85,9 +85,9 @@ public class StreamSinkSend { } } fis.close(); + sock.close(); long afterSending = System.currentTimeMillis(); System.out.println("Sent " + (size / 1024) + "KB in " + (afterSending-beforeSending) + "ms"); - sock.close(); } catch (InterruptedIOException iie) { _log.error("Timeout connecting to the peer", iie); return; diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/StreamSinkServer.java b/apps/ministreaming/java/src/net/i2p/client/streaming/StreamSinkServer.java index 8567f29765a74ae8f7bfcc3fb1e305b76f683886..f9aae66e161b46b220521dd396c4d27c1146f69c 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/StreamSinkServer.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/StreamSinkServer.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.ConnectException; +import java.util.Properties; import net.i2p.I2PAppContext; import net.i2p.I2PException; @@ -23,6 +24,8 @@ public class StreamSinkServer { private Log _log; private String _sinkDir; private String _destFile; + private String _i2cpHost; + private int _i2cpPort; /** * Create but do not start the streaming server. @@ -31,8 +34,13 @@ public class StreamSinkServer { * @param ourDestFile filename to write our binary destination to */ public StreamSinkServer(String sinkDir, String ourDestFile) { + this(sinkDir, ourDestFile, null, -1); + } + public StreamSinkServer(String sinkDir, String ourDestFile, String i2cpHost, int i2cpPort) { _sinkDir = sinkDir; _destFile = ourDestFile; + _i2cpHost = i2cpHost; + _i2cpPort = i2cpPort; _log = I2PAppContext.getGlobalContext().logManager().getLog(StreamSinkServer.class); } @@ -42,7 +50,11 @@ public class StreamSinkServer { * */ public void runServer() { - I2PSocketManager mgr = I2PSocketManagerFactory.createManager(); + I2PSocketManager mgr = null; + if (_i2cpHost != null) + mgr = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, new Properties()); + else + mgr = I2PSocketManagerFactory.createManager(); Destination dest = mgr.getSession().getMyDestination(); System.out.println("Listening for connections on: " + dest.calculateHash().toBase64()); FileOutputStream fos = null; @@ -95,6 +107,7 @@ public class StreamSinkServer { sink.mkdirs(); File cur = File.createTempFile("clientSink", ".dat", sink); _fos = new FileOutputStream(cur); + System.out.println("Writing to " + cur.getAbsolutePath()); } catch (IOException ioe) { _log.error("Error creating sink", ioe); _fos = null; @@ -121,18 +134,30 @@ public class StreamSinkServer { } /** - * Fire up the streaming server. <code>Usage: StreamSinkServer sinkDir ourDestFile</code><br /> + * Fire up the streaming server. <code>Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile</code><br /> * <ul> * <li><b>sinkDir</b>: Directory to store received files in</li> * <li><b>ourDestFile</b>: filename to write our binary destination to</li> * </ul> */ public static void main(String args[]) { - if (args.length != 2) { - System.out.println("Usage: StreamSinkServer sinkDir ourDestFile"); - } else { - StreamSinkServer server = new StreamSinkServer(args[0], args[1]); - server.runServer(); + StreamSinkServer server = null; + switch (args.length) { + case 2: + server = new StreamSinkServer(args[0], args[1]); + break; + case 4: + try { + int port = Integer.parseInt(args[1]); + server = new StreamSinkServer(args[2], args[3], args[0], port); + } catch (NumberFormatException nfe) { + System.out.println("Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile"); + } + break; + default: + System.out.println("Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile"); } + if (server != null) + server.runServer(); } } diff --git a/core/java/src/net/i2p/client/I2CPMessageProducer.java b/core/java/src/net/i2p/client/I2CPMessageProducer.java index f074e6e7e3187e8c3a60c4db0bb114bbf342ae36..c9b16177c89017ce7f61f436552ba2cfa51f0c1d 100644 --- a/core/java/src/net/i2p/client/I2CPMessageProducer.java +++ b/core/java/src/net/i2p/client/I2CPMessageProducer.java @@ -109,8 +109,8 @@ class I2CPMessageProducer { // generateNewTags would only generate tags if necessary data.setEncryptedData(encr); - _log.debug("Encrypting the payload to public key " + dest.getPublicKey().toBase64() + "\nPayload: " - + data.calculateHash()); + //_log.debug("Encrypting the payload to public key " + dest.getPublicKey().toBase64() + "\nPayload: " + // + data.calculateHash()); return data; } diff --git a/core/java/src/net/i2p/client/I2PSessionImpl2.java b/core/java/src/net/i2p/client/I2PSessionImpl2.java index 7d9ed1e707f6c41f58f71fce62139dece9694c3c..0e5724616bdd84f2e880ed7d17889708c174287f 100644 --- a/core/java/src/net/i2p/client/I2PSessionImpl2.java +++ b/core/java/src/net/i2p/client/I2PSessionImpl2.java @@ -114,17 +114,21 @@ class I2PSessionImpl2 extends I2PSessionImpl { Set sentTags = null; int oldTags = _context.sessionKeyManager().getAvailableTags(dest.getPublicKey(), key); long availTimeLeft = _context.sessionKeyManager().getAvailableTimeLeft(dest.getPublicKey(), key); - if (oldTags < 10) { - sentTags = createNewTags(50); - //_log.error("** sendBestEffort only had " + oldTags + " adding 50"); - } else if (availTimeLeft < 30 * 1000) { - // if we have > 10 tags, but they expire in under 30 seconds, we want more - sentTags = createNewTags(50); - if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Tags are almost expired, adding 50 new ones"); - //_log.error("** sendBestEffort available time left " + availTimeLeft); - } else { - //_log.error("sendBestEffort old tags: " + oldTags + " available time left: " + availTimeLeft); + + if ( (tagsSent == null) || (tagsSent.size() <= 0) ) { + if (oldTags < 10) { + sentTags = createNewTags(50); + //_log.error("** sendBestEffort only had " + oldTags + " adding 50"); + } else if (availTimeLeft < 30 * 1000) { + // if we have > 10 tags, but they expire in under 30 seconds, we want more + sentTags = createNewTags(50); + if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Tags are almost expired, adding 50 new ones"); + //_log.error("** sendBestEffort available time left " + availTimeLeft); + } else { + //_log.error("sendBestEffort old tags: " + oldTags + " available time left: " + availTimeLeft); + } } + SessionKey newKey = null; if (false) // rekey newKey = _context.keyGenerator().generateSessionKey(); diff --git a/core/java/src/net/i2p/client/MessageStatusMessageHandler.java b/core/java/src/net/i2p/client/MessageStatusMessageHandler.java index e16bccc56456c5e2138b04eed6de8033a2c1ec99..e32f14e7245e7f10cc6cc58f79973836ff2c29e8 100644 --- a/core/java/src/net/i2p/client/MessageStatusMessageHandler.java +++ b/core/java/src/net/i2p/client/MessageStatusMessageHandler.java @@ -13,13 +13,13 @@ import net.i2p.I2PAppContext; import net.i2p.data.i2cp.I2CPMessage; import net.i2p.data.i2cp.MessageStatusMessage; import net.i2p.data.i2cp.ReceiveMessageBeginMessage; +import net.i2p.util.Log; /** * Handle I2CP MessageStatusMessages from the router. This currently only takes * into account status of available, automatically prefetching them as soon as * possible * - * @author jrandom */ class MessageStatusMessageHandler extends HandlerImpl { public MessageStatusMessageHandler(I2PAppContext context) { @@ -28,41 +28,44 @@ class MessageStatusMessageHandler extends HandlerImpl { public void handleMessage(I2CPMessage message, I2PSessionImpl session) { boolean skipStatus = true; - if (I2PClient.PROP_RELIABILITY_GUARANTEED.equals(session.getOptions() - .getProperty(I2PClient.PROP_RELIABILITY, - I2PClient.PROP_RELIABILITY_BEST_EFFORT))) + if (I2PClient.PROP_RELIABILITY_GUARANTEED.equals(session.getOptions().getProperty(I2PClient.PROP_RELIABILITY, + I2PClient.PROP_RELIABILITY_BEST_EFFORT))) skipStatus = false; - _log.debug("Handle message " + message); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Handle message " + message); MessageStatusMessage msg = (MessageStatusMessage) message; switch (msg.getStatus()) { - case MessageStatusMessage.STATUS_AVAILABLE: - ReceiveMessageBeginMessage m = new ReceiveMessageBeginMessage(); - m.setMessageId(msg.getMessageId()); - m.setSessionId(msg.getSessionId()); - try { - session.sendMessage(m); - } catch (I2PSessionException ise) { - _log.error("Error asking for the message", ise); - } - return; - case MessageStatusMessage.STATUS_SEND_ACCEPTED: - session.receiveStatus(msg.getMessageId().getMessageId(), msg.getNonce(), msg.getStatus()); - // noop - return; - case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_SUCCESS: - case MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS: - _log.info("Message delivery succeeded for message " + msg.getMessageId()); - //if (!skipStatus) - session.receiveStatus(msg.getMessageId().getMessageId(), msg.getNonce(), msg.getStatus()); - return; - case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_FAILURE: - case MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE: - _log.info("Message delivery FAILED for message " + msg.getMessageId()); - //if (!skipStatus) - session.receiveStatus(msg.getMessageId().getMessageId(), msg.getNonce(), msg.getStatus()); - return; - default: - _log.error("Invalid message delivery status received: " + msg.getStatus()); + case MessageStatusMessage.STATUS_AVAILABLE: + ReceiveMessageBeginMessage m = new ReceiveMessageBeginMessage(); + m.setMessageId(msg.getMessageId()); + m.setSessionId(msg.getSessionId()); + try { + session.sendMessage(m); + } catch (I2PSessionException ise) { + _log.error("Error asking for the message", ise); + } + return; + case MessageStatusMessage.STATUS_SEND_ACCEPTED: + session.receiveStatus(msg.getMessageId().getMessageId(), msg.getNonce(), msg.getStatus()); + // noop + return; + case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_SUCCESS: + case MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS: + if (_log.shouldLog(Log.INFO)) + _log.info("Message delivery succeeded for message " + msg.getMessageId()); + //if (!skipStatus) + session.receiveStatus(msg.getMessageId().getMessageId(), msg.getNonce(), msg.getStatus()); + return; + case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_FAILURE: + case MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE: + if (_log.shouldLog(Log.INFO)) + _log.info("Message delivery FAILED for message " + msg.getMessageId()); + //if (!skipStatus) + session.receiveStatus(msg.getMessageId().getMessageId(), msg.getNonce(), msg.getStatus()); + return; + default: + if (_log.shouldLog(Log.ERROR)) + _log.error("Invalid message delivery status received: " + msg.getStatus()); } } } \ No newline at end of file diff --git a/core/java/src/net/i2p/crypto/AESEngine.java b/core/java/src/net/i2p/crypto/AESEngine.java index 2d474bffb026310271faa069a98932fbc03853ed..3b6b4e9eea0dca2dbaf45773339198f459844d8d 100644 --- a/core/java/src/net/i2p/crypto/AESEngine.java +++ b/core/java/src/net/i2p/crypto/AESEngine.java @@ -9,8 +9,6 @@ package net.i2p.crypto; * */ -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; import net.i2p.I2PAppContext; @@ -52,25 +50,26 @@ public class AESEngine { public byte[] safeEncrypt(byte payload[], SessionKey sessionKey, byte iv[], int paddedSize) { if ((iv == null) || (payload == null) || (sessionKey == null) || (iv.length != 16)) return null; - ByteArrayOutputStream baos = new ByteArrayOutputStream(paddedSize + 64); + int size = Hash.HASH_LENGTH + + 4 // sizeof(payload) + + payload.length; + int padding = ElGamalAESEngine.getPaddingSize(size, paddedSize); + + byte data[] = new byte[size + padding]; Hash h = _context.sha().calculateHash(iv); - try { - h.writeBytes(baos); - DataHelper.writeLong(baos, 4, payload.length); - baos.write(payload); - byte tv[] = baos.toByteArray(); - baos.write(ElGamalAESEngine.getPadding(_context, tv.length, paddedSize)); - } catch (IOException ioe) { - _log.error("Error writing data", ioe); - return null; - } catch (DataFormatException dfe) { - _log.error("Error writing data", dfe); - return null; - } - byte orig[] = baos.toByteArray(); - byte rv[] = new byte[orig.length]; - encrypt(orig, 0, rv, 0, sessionKey, iv, rv.length); - return rv; + + int cur = 0; + System.arraycopy(h.getData(), 0, data, cur, Hash.HASH_LENGTH); + cur += Hash.HASH_LENGTH; + DataHelper.toLong(data, cur, 4, payload.length); + cur += 4; + System.arraycopy(payload, 0, data, cur, payload.length); + cur += payload.length; + byte paddingData[] = ElGamalAESEngine.getPadding(_context, size, paddedSize); + System.arraycopy(paddingData, 0, data, cur, paddingData.length); + + encrypt(data, 0, data, 0, sessionKey, iv, data.length); + return data; } public byte[] safeDecrypt(byte payload[], SessionKey sessionKey, byte iv[]) { @@ -82,31 +81,29 @@ public class AESEngine { _log.error("Error decrypting the data - payload " + payload.length + " decrypted to null"); return null; } - ByteArrayInputStream bais = new ByteArrayInputStream(decr); - Hash h = _context.sha().calculateHash(iv); - try { - Hash rh = new Hash(); - rh.readBytes(bais); - if (!h.equals(rh)) { + + int cur = 0; + byte h[] = _context.sha().calculateHash(iv).getData(); + for (int i = 0; i < Hash.HASH_LENGTH; i++) { + if (decr[i] != h[i]) { _log.error("Hash does not match [key=" + sessionKey + " / iv =" + DataHelper.toString(iv, iv.length) + "]", new Exception("Hash error")); return null; } - long len = DataHelper.readLong(bais, 4); - byte data[] = new byte[(int) len]; - int read = bais.read(data); - if (read != len) { - _log.error("Not enough to read"); - return null; - } - return data; - } catch (IOException ioe) { - _log.error("Error writing data", ioe); - return null; - } catch (DataFormatException dfe) { - _log.error("Error writing data", dfe); + } + cur += Hash.HASH_LENGTH; + + long len = DataHelper.fromLong(decr, cur, 4); + cur += 4; + + if (cur + len > decr.length) { + _log.error("Not enough to read"); return null; } + + byte data[] = new byte[(int)len]; + System.arraycopy(decr, cur, data, 0, (int)len); + return data; } diff --git a/core/java/src/net/i2p/crypto/AESOutputStream.java b/core/java/src/net/i2p/crypto/AESOutputStream.java index 3b28aac0e4a2af730ba6d308b988ab0eb7b99fb0..3deea7f496d36604c61716003ad1adea63aea1ed 100644 --- a/core/java/src/net/i2p/crypto/AESOutputStream.java +++ b/core/java/src/net/i2p/crypto/AESOutputStream.java @@ -9,7 +9,6 @@ package net.i2p.crypto; * */ -import java.io.ByteArrayOutputStream; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; diff --git a/core/java/src/net/i2p/crypto/CryptixAESEngine.java b/core/java/src/net/i2p/crypto/CryptixAESEngine.java index 8b1454fef649f93d92b2f99b22e4e89e050a353d..7cad00105a16ff1da92a917417ba08adea7d4004 100644 --- a/core/java/src/net/i2p/crypto/CryptixAESEngine.java +++ b/core/java/src/net/i2p/crypto/CryptixAESEngine.java @@ -29,10 +29,12 @@ public class CryptixAESEngine extends AESEngine { private final static CryptixRijndael_Algorithm _algo = new CryptixRijndael_Algorithm(); private final static boolean USE_FAKE_CRYPTO = false; private final static byte FAKE_KEY = 0x2A; + private CryptixAESKeyCache _cache; public CryptixAESEngine(I2PAppContext context) { super(context); _log = context.logManager().getLog(CryptixAESEngine.class); + _cache = new CryptixAESKeyCache(); } public void encrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) { @@ -65,8 +67,13 @@ public class CryptixAESEngine extends AESEngine { public void decrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) { if ((iv== null) || (payload == null) || (payload.length <= 0) || (sessionKey == null) - || (iv.length != 16)) + || (iv.length != 16) ) throw new IllegalArgumentException("bad setup"); + else if (out == null) + throw new IllegalArgumentException("out is null"); + else if (out.length - outIndex < length) + throw new IllegalArgumentException("out is too small (out.length=" + out.length + + " outIndex=" + outIndex + " length=" + length); if (USE_FAKE_CRYPTO) { _log.warn("AES Crypto disabled! Using trivial XOR"); @@ -74,23 +81,26 @@ public class CryptixAESEngine extends AESEngine { return ; } - int numblock = payload.length / 16; - if (payload.length % 16 != 0) numblock++; + int numblock = length / 16; + if (length % 16 != 0) numblock++; - decryptBlock(payload, 0, sessionKey, out, 0); - DataHelper.xor(out, 0, iv, 0, out, 0, 16); + decryptBlock(payload, payloadIndex, sessionKey, out, outIndex); + DataHelper.xor(out, outIndex, iv, 0, out, outIndex, 16); for (int x = 1; x < numblock; x++) { - decryptBlock(payload, x * 16, sessionKey, out, x * 16); - DataHelper.xor(out, x * 16, payload, (x - 1) * 16, out, x * 16, 16); + decryptBlock(payload, payloadIndex + (x * 16), sessionKey, out, outIndex + (x * 16)); + DataHelper.xor(out, outIndex + x * 16, payload, payloadIndex + (x - 1) * 16, out, outIndex + x * 16, 16); } } final void encryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte out[], int outIndex) { + CryptixAESKeyCache.KeyCacheEntry keyData = _cache.acquireKey(); try { - Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16); + Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16, keyData); CryptixRijndael_Algorithm.blockEncrypt(payload, out, inIndex, outIndex, key, 16); } catch (InvalidKeyException ike) { _log.error("Invalid key", ike); + } finally { + _cache.releaseKey(keyData); } } @@ -100,11 +110,20 @@ public class CryptixAESEngine extends AESEngine { * @return unencrypted data */ final void decryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte rv[], int outIndex) { + if ( (payload == null) || (rv == null) ) + throw new IllegalArgumentException("null block args [payload=" + payload + " rv="+rv); + if (payload.length - inIndex > rv.length - outIndex) + throw new IllegalArgumentException("bad block args [payload.len=" + payload.length + + " inIndex=" + inIndex + " rv.len=" + rv.length + + " outIndex="+outIndex); + CryptixAESKeyCache.KeyCacheEntry keyData = _cache.acquireKey(); try { - Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16); + Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16, keyData); CryptixRijndael_Algorithm.blockDecrypt(payload, rv, inIndex, outIndex, key, 16); } catch (InvalidKeyException ike) { _log.error("Invalid key", ike); + } finally { + _cache.releaseKey(keyData); } } @@ -113,6 +132,8 @@ public class CryptixAESEngine extends AESEngine { try { testEDBlock(ctx); testED(ctx); + testFake(ctx); + testNull(ctx); } catch (Exception e) { e.printStackTrace(); } @@ -134,6 +155,42 @@ public class CryptixAESEngine extends AESEngine { else System.out.println("full D(E(orig)) == orig"); } + private static void testFake(I2PAppContext ctx) { + SessionKey key = ctx.keyGenerator().generateSessionKey(); + SessionKey wrongKey = ctx.keyGenerator().generateSessionKey(); + byte iv[] = new byte[16]; + byte orig[] = new byte[128]; + byte encrypted[] = new byte[128]; + byte decrypted[] = new byte[128]; + ctx.random().nextBytes(iv); + ctx.random().nextBytes(orig); + CryptixAESEngine aes = new CryptixAESEngine(ctx); + aes.encrypt(orig, 0, encrypted, 0, key, iv, orig.length); + aes.decrypt(encrypted, 0, decrypted, 0, wrongKey, iv, encrypted.length); + if (DataHelper.eq(decrypted,orig)) + throw new RuntimeException("full D(E(orig)) == orig when we used the wrong key!"); + else + System.out.println("full D(E(orig)) != orig when we used the wrong key"); + } + private static void testNull(I2PAppContext ctx) { + SessionKey key = ctx.keyGenerator().generateSessionKey(); + SessionKey wrongKey = ctx.keyGenerator().generateSessionKey(); + byte iv[] = new byte[16]; + byte orig[] = new byte[128]; + byte encrypted[] = new byte[128]; + byte decrypted[] = new byte[128]; + ctx.random().nextBytes(iv); + ctx.random().nextBytes(orig); + CryptixAESEngine aes = new CryptixAESEngine(ctx); + aes.encrypt(orig, 0, encrypted, 0, key, iv, orig.length); + try { + aes.decrypt(null, 0, null, 0, wrongKey, iv, encrypted.length); + } catch (IllegalArgumentException iae) { + return; + } + + throw new RuntimeException("full D(E(orig)) didn't fail when we used null!"); + } private static void testEDBlock(I2PAppContext ctx) { SessionKey key = ctx.keyGenerator().generateSessionKey(); byte iv[] = new byte[16]; diff --git a/core/java/src/net/i2p/crypto/CryptixAESKeyCache.java b/core/java/src/net/i2p/crypto/CryptixAESKeyCache.java new file mode 100644 index 0000000000000000000000000000000000000000..780360ed19a7352032f539ef60f058dbd48d5122 --- /dev/null +++ b/core/java/src/net/i2p/crypto/CryptixAESKeyCache.java @@ -0,0 +1,70 @@ +package net.i2p.crypto; + +import java.util.ArrayList; +import java.util.List; + +/** + * Cache the objects used in CryptixRijndael_Algorithm.makeKey to reduce + * memory churn. The KeyCacheEntry should be held onto as long as the + * data referenced in it is needed (which often is only one or two lines + * of code) + * + */ +final class CryptixAESKeyCache { + private List _availableKeys; + + private static final int KEYSIZE = 32; // 256bit AES + private static final int BLOCKSIZE = 16; + private static final int ROUNDS = CryptixRijndael_Algorithm.getRounds(KEYSIZE, BLOCKSIZE); + private static final int BC = BLOCKSIZE / 4; + private static final int KC = KEYSIZE / 4; + + public CryptixAESKeyCache() { + _availableKeys = new ArrayList(64); + for (int i = 0; i < 64; i++) { + _availableKeys.add(createNew()); + } + } + + /** + * Get the next available structure, either from the cache or a brand new one + * + */ + public final KeyCacheEntry acquireKey() { + synchronized (_availableKeys) { + if (_availableKeys.size() > 0) + return (KeyCacheEntry)_availableKeys.remove(0); + } + return createNew(); + } + + /** + * Put this structure back onto the available cache for reuse + * + */ + public final void releaseKey(KeyCacheEntry key) { + synchronized (_availableKeys) { + _availableKeys.add(key); + } + } + + private static final KeyCacheEntry createNew() { + KeyCacheEntry e = new KeyCacheEntry(); + e.Ke = new int[ROUNDS + 1][BC]; // encryption round keys + e.Kd = new int[ROUNDS + 1][BC]; // decryption round keys + e.tk = new int[KC]; + e.key = new Object[] { e.Ke, e.Kd }; + return e; + } + + /** + * all the data alloc'ed in a makeKey call + */ + public static final class KeyCacheEntry { + int[][] Ke; + int[][] Kd; + int[] tk; + + Object[] key; + } +} diff --git a/core/java/src/net/i2p/crypto/CryptixRijndael_Algorithm.java b/core/java/src/net/i2p/crypto/CryptixRijndael_Algorithm.java index 3546d29f7f4a5e5d9ce6f20c603624c54e5575b4..58444d0236f9c4f4770a909afeedcbda76b8b3a3 100644 --- a/core/java/src/net/i2p/crypto/CryptixRijndael_Algorithm.java +++ b/core/java/src/net/i2p/crypto/CryptixRijndael_Algorithm.java @@ -453,6 +453,11 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor * @param sessionKey The session key to use for decryption. */ public static final void blockDecrypt(byte[] in, byte[] result, int inOffset, int outOffset, Object sessionKey) { + if (in.length - inOffset > result.length - outOffset) + throw new IllegalArgumentException("result too small: in.len=" + in.length + " in.offset=" + inOffset + + " result.len=" + result.length + " result.offset=" + outOffset); + if (in.length - inOffset <= 15) + throw new IllegalArgumentException("data too small: " + in.length + " inOffset: " + inOffset); if (_RDEBUG) trace(_IN, "blockDecrypt(" + in + ", " + inOffset + ", " + sessionKey + ")"); int[][] Kd = (int[][]) ((Object[]) sessionKey)[1]; // extract decryption round keys int ROUNDS = Kd.length - 1; @@ -534,18 +539,31 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor * @exception InvalidKeyException If the key is invalid. */ public static final/* synchronized */Object makeKey(byte[] k, int blockSize) throws InvalidKeyException { + return makeKey(k, blockSize, null); + } + public static final/* synchronized */Object makeKey(byte[] k, int blockSize, CryptixAESKeyCache.KeyCacheEntry keyData) throws InvalidKeyException { if (_RDEBUG) trace(_IN, "makeKey(" + k + ", " + blockSize + ")"); if (k == null) throw new InvalidKeyException("Empty key"); if (!(k.length == 16 || k.length == 24 || k.length == 32)) throw new InvalidKeyException("Incorrect key length"); int ROUNDS = getRounds(k.length, blockSize); int BC = blockSize / 4; - int[][] Ke = new int[ROUNDS + 1][BC]; // encryption round keys - int[][] Kd = new int[ROUNDS + 1][BC]; // decryption round keys + int[][] Ke = null; // new int[ROUNDS + 1][BC]; // encryption round keys + int[][] Kd = null; // new int[ROUNDS + 1][BC]; // decryption round keys int ROUND_KEY_COUNT = (ROUNDS + 1) * BC; int KC = k.length / 4; - int[] tk = new int[KC]; + int[] tk = null; // new int[KC]; int i, j; + + if (keyData == null) { + Ke = new int[ROUNDS + 1][BC]; + Kd = new int[ROUNDS + 1][BC]; + tk = new int[KC]; + } else { + Ke = keyData.Ke; + Kd = keyData.Kd; + tk = keyData.tk; + } // copy user material bytes into temporary ints for (i = 0, j = 0; i < KC;) @@ -604,7 +622,11 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor } // assemble the encryption (Ke) and decryption (Kd) round keys into // one sessionKey object - Object[] sessionKey = new Object[] { Ke, Kd}; + Object[] sessionKey = null; + if (keyData == null) + sessionKey = new Object[] { Ke, Kd}; + else + sessionKey = keyData.key; if (_RDEBUG) trace(_OUT, "makeKey()"); return sessionKey; } diff --git a/core/java/src/net/i2p/crypto/ElGamalAESEngine.java b/core/java/src/net/i2p/crypto/ElGamalAESEngine.java index 1b6e501ee3d84a2084d4547b857cd58b496b89a9..dc6f19a95dd467199c96abfaaae1c89cb3cfa9fb 100644 --- a/core/java/src/net/i2p/crypto/ElGamalAESEngine.java +++ b/core/java/src/net/i2p/crypto/ElGamalAESEngine.java @@ -10,7 +10,6 @@ package net.i2p.crypto; */ import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; @@ -147,25 +146,19 @@ public class ElGamalAESEngine { System.arraycopy(data, 0, elgEncr, 514 - data.length, data.length); } byte elgDecr[] = _context.elGamalEngine().decrypt(elgEncr, targetPrivateKey); - if (elgDecr == null) return null; - - ByteArrayInputStream bais = new ByteArrayInputStream(elgDecr); - byte preIV[] = null; - - try { - usedKey.readBytes(bais); - preIV = new byte[32]; - int read = bais.read(preIV); - if (read != preIV.length) { - // hmm, this can't really happen... - throw new DataFormatException("Somehow ElGamal broke and 256 bytes is less than 32 bytes..."); } - } catch (IOException ioe) { - if (_log.shouldLog(Log.ERROR)) _log.error("Error decrypting the new session", ioe); + if (elgDecr == null) { + if (_log.shouldLog(Log.WARN)) + _log.warn("decrypt returned null", new Exception("decrypt failed")); return null; } - // ignore the next 192 bytes - byte aesEncr[] = new byte[data.length - 514]; - System.arraycopy(data, 514, aesEncr, 0, aesEncr.length); + + byte preIV[] = null; + + byte key[] = new byte[SessionKey.KEYSIZE_BYTES]; + System.arraycopy(elgDecr, 0, key, 0, SessionKey.KEYSIZE_BYTES); + usedKey.setData(key); + preIV = new byte[32]; + System.arraycopy(elgDecr, SessionKey.KEYSIZE_BYTES, preIV, 0, 32); //_log.debug("Pre IV for decryptNewSession: " + DataHelper.toString(preIV, 32)); //_log.debug("SessionKey for decryptNewSession: " + DataHelper.toString(key.getData(), 32)); @@ -173,7 +166,7 @@ public class ElGamalAESEngine { byte iv[] = new byte[16]; System.arraycopy(ivHash.getData(), 0, iv, 0, 16); - byte aesDecr[] = decryptAESBlock(aesEncr, usedKey, iv, null, foundTags, foundKey); + byte aesDecr[] = decryptAESBlock(data, 514, data.length-514, usedKey, iv, null, foundTags, foundKey); if (_log.shouldLog(Log.DEBUG)) _log.debug("Decrypt with a NEW session successfull: # tags read = " + foundTags.size(), @@ -203,8 +196,6 @@ public class ElGamalAESEngine { SessionKey usedKey, SessionKey foundKey) throws DataFormatException { byte preIV[] = new byte[32]; System.arraycopy(data, 0, preIV, 0, preIV.length); - byte encr[] = new byte[data.length - 32]; - System.arraycopy(data, 32, encr, 0, encr.length); Hash ivHash = _context.sha().calculateHash(preIV); byte iv[] = new byte[16]; System.arraycopy(ivHash.getData(), 0, iv, 0, 16); @@ -213,7 +204,7 @@ public class ElGamalAESEngine { //_log.debug("Pre IV for decryptExistingSession: " + DataHelper.toString(preIV, 32)); //_log.debug("SessionKey for decryptNewSession: " + DataHelper.toString(key.getData(), 32)); - byte decrypted[] = decryptAESBlock(encr, key, iv, preIV, foundTags, foundKey); + byte decrypted[] = decryptAESBlock(data, 32, data.length-32, key, iv, preIV, foundTags, foundKey); if (decrypted == null) { // it begins with a valid session tag, but thats just a coincidence. if (_log.shouldLog(Log.DEBUG)) @@ -244,12 +235,16 @@ public class ElGamalAESEngine { * @param foundTags set which is filled with any sessionTags found during decryption * @param foundKey session key which may be filled with a new sessionKey found during decryption */ - byte[] decryptAESBlock(byte encrypted[], SessionKey key, byte iv[], byte sentTag[], Set foundTags, - SessionKey foundKey) throws DataFormatException { + byte[] decryptAESBlock(byte encrypted[], SessionKey key, byte iv[], + byte sentTag[], Set foundTags, SessionKey foundKey) throws DataFormatException { + return decryptAESBlock(encrypted, 0, encrypted.length, key, iv, sentTag, foundTags, foundKey); + } + byte[] decryptAESBlock(byte encrypted[], int offset, int encryptedLen, SessionKey key, byte iv[], + byte sentTag[], Set foundTags, SessionKey foundKey) throws DataFormatException { //_log.debug("iv for decryption: " + DataHelper.toString(iv, 16)); //_log.debug("decrypting AES block. encr.length = " + (encrypted == null? -1 : encrypted.length) + " sentTag: " + DataHelper.toString(sentTag, 32)); - byte decrypted[] = new byte[encrypted.length]; - _context.aes().decrypt(encrypted, 0, decrypted, 0, key, iv, encrypted.length); + byte decrypted[] = new byte[encryptedLen]; + _context.aes().decrypt(encrypted, offset, decrypted, 0, key, iv, encryptedLen); //Hash h = _context.sha().calculateHash(decrypted); //_log.debug("Hash of entire aes block after decryption: \n" + DataHelper.toString(h.getData(), 32)); try { @@ -271,7 +266,7 @@ public class ElGamalAESEngine { } long len = DataHelper.readLong(bais, 4); //_log.debug("len: " + len); - if ((len < 0) || (len > encrypted.length)) throw new Exception("Invalid size of payload"); + if ((len < 0) || (len > encryptedLen)) throw new Exception("Invalid size of payload"); byte hashval[] = new byte[32]; int read = bais.read(hashval); if (read != hashval.length) throw new Exception("Invalid size of hash"); @@ -371,54 +366,45 @@ public class ElGamalAESEngine { byte[] encryptNewSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, SessionKey newKey, long paddedSize) { //_log.debug("Encrypting to a NEW session"); - try { - ByteArrayOutputStream elgSrc = new ByteArrayOutputStream(64); - key.writeBytes(elgSrc); - byte preIV[] = new byte[32]; - _context.random().nextBytes(preIV); - elgSrc.write(preIV); - byte rnd[] = new byte[158]; - _context.random().nextBytes(rnd); - elgSrc.write(rnd); - elgSrc.flush(); - - //_log.debug("Pre IV for encryptNewSession: " + DataHelper.toString(preIV, 32)); - //_log.debug("SessionKey for encryptNewSession: " + DataHelper.toString(key.getData(), 32)); - long before = _context.clock().now(); - byte elgEncr[] = _context.elGamalEngine().encrypt(elgSrc.toByteArray(), target); - long after = _context.clock().now(); - if (_log.shouldLog(Log.INFO)) - _log.info("elgEngine.encrypt of the session key took " + (after - before) + "ms"); - if (elgEncr.length < 514) { - byte elg[] = new byte[514]; - int diff = elg.length - elgEncr.length; - if (_log.shouldLog(Log.DEBUG)) _log.debug("Difference in size: " + diff); - System.arraycopy(elgEncr, 0, elg, diff, elgEncr.length); - elgEncr = elg; - } - //_log.debug("ElGamal encrypted length: " + elgEncr.length + " elGamal source length: " + elgSrc.toByteArray().length); - - Hash ivHash = _context.sha().calculateHash(preIV); - byte iv[] = new byte[16]; - System.arraycopy(ivHash.getData(), 0, iv, 0, 16); - byte aesEncr[] = encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize); - //_log.debug("AES encrypted length: " + aesEncr.length); - - byte rv[] = new byte[elgEncr.length + aesEncr.length]; - System.arraycopy(elgEncr, 0, rv, 0, elgEncr.length); - System.arraycopy(aesEncr, 0, rv, elgEncr.length, aesEncr.length); - //_log.debug("Return length: " + rv.length); - long finish = _context.clock().now(); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("after the elgEngine.encrypt took a total of " + (finish - after) + "ms"); - return rv; - } catch (IOException ioe) { - _log.error("Error encrypting the new session", ioe); - return null; - } catch (DataFormatException dfe) { - _log.error("Error writing out the bytes for the new session", dfe); - return null; + byte elgSrcData[] = new byte[SessionKey.KEYSIZE_BYTES+32+158]; + System.arraycopy(key.getData(), 0, elgSrcData, 0, SessionKey.KEYSIZE_BYTES); + byte preIV[] = new byte[32]; + _context.random().nextBytes(preIV); + System.arraycopy(preIV, 0, elgSrcData, SessionKey.KEYSIZE_BYTES, 32); + byte rnd[] = new byte[158]; + _context.random().nextBytes(rnd); + System.arraycopy(rnd, 0, elgSrcData, SessionKey.KEYSIZE_BYTES+32, 158); + + //_log.debug("Pre IV for encryptNewSession: " + DataHelper.toString(preIV, 32)); + //_log.debug("SessionKey for encryptNewSession: " + DataHelper.toString(key.getData(), 32)); + long before = _context.clock().now(); + byte elgEncr[] = _context.elGamalEngine().encrypt(elgSrcData, target); + long after = _context.clock().now(); + if (_log.shouldLog(Log.INFO)) + _log.info("elgEngine.encrypt of the session key took " + (after - before) + "ms"); + if (elgEncr.length < 514) { + byte elg[] = new byte[514]; + int diff = elg.length - elgEncr.length; + if (_log.shouldLog(Log.DEBUG)) _log.debug("Difference in size: " + diff); + System.arraycopy(elgEncr, 0, elg, diff, elgEncr.length); + elgEncr = elg; } + //_log.debug("ElGamal encrypted length: " + elgEncr.length + " elGamal source length: " + elgSrc.toByteArray().length); + + Hash ivHash = _context.sha().calculateHash(preIV); + byte iv[] = new byte[16]; + System.arraycopy(ivHash.getData(), 0, iv, 0, 16); + byte aesEncr[] = encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize); + //_log.debug("AES encrypted length: " + aesEncr.length); + + byte rv[] = new byte[elgEncr.length + aesEncr.length]; + System.arraycopy(elgEncr, 0, rv, 0, elgEncr.length); + System.arraycopy(aesEncr, 0, rv, elgEncr.length, aesEncr.length); + //_log.debug("Return length: " + rv.length); + long finish = _context.clock().now(); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("after the elgEngine.encrypt took a total of " + (finish - after) + "ms"); + return rv; } /** @@ -445,11 +431,10 @@ public class ElGamalAESEngine { byte iv[] = new byte[16]; System.arraycopy(ivHash.getData(), 0, iv, 0, 16); - byte aesEncr[] = encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize); - byte rv[] = new byte[rawTag.length + aesEncr.length]; - System.arraycopy(rawTag, 0, rv, 0, rawTag.length); - System.arraycopy(aesEncr, 0, rv, rawTag.length, aesEncr.length); - return rv; + byte aesEncr[] = encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize, SessionTag.BYTE_LENGTH); + // that prepended SessionTag.BYTE_LENGTH bytes at the beginning of the buffer + System.arraycopy(rawTag, 0, aesEncr, 0, rawTag.length); + return aesEncr; } private final static Set EMPTY_SET = new HashSet(); @@ -469,52 +454,64 @@ public class ElGamalAESEngine { */ final byte[] encryptAESBlock(byte data[], SessionKey key, byte[] iv, Set tagsForDelivery, SessionKey newKey, long paddedSize) { + return encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize, 0); + } + final byte[] encryptAESBlock(byte data[], SessionKey key, byte[] iv, Set tagsForDelivery, SessionKey newKey, + long paddedSize, int prefixBytes) { //_log.debug("iv for encryption: " + DataHelper.toString(iv, 16)); //_log.debug("Encrypting AES"); - try { - ByteArrayOutputStream aesSrc = new ByteArrayOutputStream((int) paddedSize); - if (tagsForDelivery == null) tagsForDelivery = EMPTY_SET; - DataHelper.writeLong(aesSrc, 2, tagsForDelivery.size()); - for (Iterator iter = tagsForDelivery.iterator(); iter.hasNext();) { - SessionTag tag = (SessionTag) iter.next(); - aesSrc.write(tag.getData()); - } - //_log.debug("# tags created, registered, and written: " + tags.size()); - DataHelper.writeLong(aesSrc, 4, data.length); - //_log.debug("data length: " + data.length); - Hash hash = _context.sha().calculateHash(data); - hash.writeBytes(aesSrc); - //_log.debug("hash of data: " + DataHelper.toString(hash.getData(), 32)); - if (newKey == null) { - byte flag = 0x00; // don't rekey - aesSrc.write(flag); - //_log.debug("flag written"); - } else { - byte flag = 0x01; // rekey - aesSrc.write(flag); - aesSrc.write(newKey.getData()); - } - aesSrc.write(data); - int len = aesSrc.toByteArray().length; - //_log.debug("raw data written: " + len); - byte padding[] = getPadding(_context, len, paddedSize); - //_log.debug("padding length: " + padding.length); - aesSrc.write(padding); - - byte aesUnencr[] = aesSrc.toByteArray(); - //Hash h = _context.sha().calculateHash(aesUnencr); - //_log.debug("Hash of entire aes block before encryption: (len=" + aesUnencr.length + ")\n" + DataHelper.toString(h.getData(), 32)); - byte aesEncr[] = new byte[aesUnencr.length]; - _context.aes().encrypt(aesUnencr, 0, aesEncr, 0, key, iv, aesEncr.length); - //_log.debug("Encrypted length: " + aesEncr.length); - return aesEncr; - } catch (IOException ioe) { - if (_log.shouldLog(Log.ERROR)) _log.error("Error encrypting AES chunk", ioe); - return null; - } catch (DataFormatException dfe) { - if (_log.shouldLog(Log.ERROR)) _log.error("Error formatting the bytes to write the AES chunk", dfe); - return null; + if (tagsForDelivery == null) tagsForDelivery = EMPTY_SET; + int size = 2 // sizeof(tags) + + tagsForDelivery.size() + + SessionTag.BYTE_LENGTH*tagsForDelivery.size() + + 4 // payload length + + Hash.HASH_LENGTH + + (newKey == null ? 1 : 1 + SessionKey.KEYSIZE_BYTES) + + data.length; + int totalSize = size + getPaddingSize(size, paddedSize); + + byte aesData[] = new byte[totalSize + prefixBytes]; + + int cur = prefixBytes; + DataHelper.toLong(aesData, cur, 2, tagsForDelivery.size()); + cur += 2; + for (Iterator iter = tagsForDelivery.iterator(); iter.hasNext();) { + SessionTag tag = (SessionTag) iter.next(); + System.arraycopy(tag.getData(), 0, aesData, cur, SessionTag.BYTE_LENGTH); + cur += SessionTag.BYTE_LENGTH; + } + //_log.debug("# tags created, registered, and written: " + tags.size()); + DataHelper.toLong(aesData, cur, 4, data.length); + cur += 4; + //_log.debug("data length: " + data.length); + Hash hash = _context.sha().calculateHash(data); + System.arraycopy(hash.getData(), 0, aesData, cur, Hash.HASH_LENGTH); + cur += Hash.HASH_LENGTH; + + //_log.debug("hash of data: " + DataHelper.toString(hash.getData(), 32)); + if (newKey == null) { + aesData[cur++] = 0x00; // don't rekey + //_log.debug("flag written"); + } else { + aesData[cur++] = 0x01; // rekey + System.arraycopy(newKey.getData(), 0, aesData, cur, SessionKey.KEYSIZE_BYTES); + cur += SessionKey.KEYSIZE_BYTES; } + System.arraycopy(data, 0, aesData, cur, data.length); + cur += data.length; + + //_log.debug("raw data written: " + len); + byte padding[] = getPadding(_context, size, paddedSize); + //_log.debug("padding length: " + padding.length); + System.arraycopy(padding, 0, aesData, cur, padding.length); + cur += padding.length; + + //Hash h = _context.sha().calculateHash(aesUnencr); + //_log.debug("Hash of entire aes block before encryption: (len=" + aesUnencr.length + ")\n" + DataHelper.toString(h.getData(), 32)); + _context.aes().encrypt(aesData, prefixBytes, aesData, prefixBytes, key, iv, aesData.length - prefixBytes); + //_log.debug("Encrypted length: " + aesEncr.length); + //return aesEncr; + return aesData; } /** @@ -523,6 +520,12 @@ public class ElGamalAESEngine { * */ final static byte[] getPadding(I2PAppContext context, int curSize, long minPaddedSize) { + int size = getPaddingSize(curSize, minPaddedSize); + byte rv[] = new byte[size]; + context.random().nextBytes(rv); + return rv; + } + final static int getPaddingSize(int curSize, long minPaddedSize) { int diff = 0; if (curSize < minPaddedSize) { diff = (int) minPaddedSize - curSize; @@ -530,9 +533,7 @@ public class ElGamalAESEngine { int numPadding = diff; if (((curSize + diff) % 16) != 0) numPadding += (16 - ((curSize + diff) % 16)); - byte rv[] = new byte[numPadding]; - context.random().nextBytes(rv); - return rv; + return numPadding; } } \ No newline at end of file diff --git a/core/java/src/net/i2p/crypto/ElGamalEngine.java b/core/java/src/net/i2p/crypto/ElGamalEngine.java index b6f0cf97150137e1c4d621f079c5d9aa083563ba..b9c35138833bf8d2c5713964fb5f845e27922d44 100644 --- a/core/java/src/net/i2p/crypto/ElGamalEngine.java +++ b/core/java/src/net/i2p/crypto/ElGamalEngine.java @@ -30,7 +30,6 @@ package net.i2p.crypto; */ import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.math.BigInteger; import net.i2p.I2PAppContext; @@ -98,19 +97,12 @@ public class ElGamalEngine { long start = _context.clock().now(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(256); - try { - baos.write(0xFF); - Hash hash = _context.sha().calculateHash(data); - hash.writeBytes(baos); - baos.write(data); - baos.flush(); - } catch (Exception e) { - if (_log.shouldLog(Log.ERROR)) _log.error("Internal error writing to buffer", e); - return null; - } - - byte d2[] = baos.toByteArray(); + byte d2[] = new byte[1+Hash.HASH_LENGTH+data.length]; + d2[0] = (byte)0xFF; + Hash hash = _context.sha().calculateHash(data); + System.arraycopy(hash.getData(), 0, d2, 1, Hash.HASH_LENGTH); + System.arraycopy(data, 0, d2, 1+Hash.HASH_LENGTH, data.length); + long t0 = _context.clock().now(); BigInteger m = new NativeBigInteger(1, d2); long t1 = _context.clock().now(); diff --git a/core/java/src/net/i2p/data/Payload.java b/core/java/src/net/i2p/data/Payload.java index 4c4bbf58572f7cafb8b4bdce35594260ff3bf02c..ac3db075214160746e352f4a2d61f02e2c8dd5f9 100644 --- a/core/java/src/net/i2p/data/Payload.java +++ b/core/java/src/net/i2p/data/Payload.java @@ -81,6 +81,13 @@ public class Payload extends DataStructureImpl { out.write(_encryptedData); _log.debug("wrote payload: " + _encryptedData.length); } + public int writeBytes(byte target[], int offset) { + if (_encryptedData == null) throw new IllegalStateException("Not yet encrypted. Please set the encrypted data"); + DataHelper.toLong(target, offset, 4, _encryptedData.length); + offset += 4; + System.arraycopy(_encryptedData, 0, target, offset, _encryptedData.length); + return 4 + _encryptedData.length; + } public boolean equals(Object object) { if ((object == null) || !(object instanceof Payload)) return false; @@ -94,6 +101,7 @@ public class Payload extends DataStructureImpl { } public String toString() { + if (true) return "[Payload]"; StringBuffer buf = new StringBuffer(128); buf.append("[Payload: "); if (getUnencryptedData() != null) diff --git a/core/java/src/net/i2p/data/i2cp/SendMessageMessage.java b/core/java/src/net/i2p/data/i2cp/SendMessageMessage.java index d25fa30960580a9995c66f80a8611c93e52bb144..357bff1d9dab704717e81c3096ec7a917106278f 100644 --- a/core/java/src/net/i2p/data/i2cp/SendMessageMessage.java +++ b/core/java/src/net/i2p/data/i2cp/SendMessageMessage.java @@ -12,11 +12,13 @@ package net.i2p.data.i2cp; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.data.Payload; +import net.i2p.data.i2cp.I2CPMessageException; import net.i2p.util.Log; /** @@ -87,20 +89,31 @@ public class SendMessageMessage extends I2CPMessageImpl { } protected byte[] doWriteMessage() throws I2CPMessageException, IOException { + throw new RuntimeException("wtf, dont run me"); + } + + /** + * Write out the full message to the stream, including the 4 byte size and 1 + * byte type header. Override the parent so we can be more mem efficient + * + */ + public void writeMessage(OutputStream out) throws I2CPMessageException, IOException { if ((_sessionId == null) || (_destination == null) || (_payload == null) || (_nonce <= 0)) throw new I2CPMessageException("Unable to write out the message as there is not enough data"); - ByteArrayOutputStream os = new ByteArrayOutputStream(512); + int len = 2 + _destination.size() + _payload.getSize() + 4 + 4; + try { - _sessionId.writeBytes(os); - _destination.writeBytes(os); - _payload.writeBytes(os); - DataHelper.writeLong(os, 4, _nonce); + DataHelper.writeLong(out, 4, len); + DataHelper.writeLong(out, 1, getType()); + _sessionId.writeBytes(out); + _destination.writeBytes(out); + _payload.writeBytes(out); + DataHelper.writeLong(out, 4, _nonce); } catch (DataFormatException dfe) { - throw new I2CPMessageException("Error writing out the message data", dfe); + throw new I2CPMessageException("Error writing the msg", dfe); } - return os.toByteArray(); } - + public int getType() { return MESSAGE_TYPE; } diff --git a/core/java/test/net/i2p/crypto/ElGamalAESEngineTest.java b/core/java/test/net/i2p/crypto/ElGamalAESEngineTest.java index 7bf7c90b84358f906407cc8da881d87413e70c88..0ee419165a8b7aa51b4cd012138317b01ef80463 100644 --- a/core/java/test/net/i2p/crypto/ElGamalAESEngineTest.java +++ b/core/java/test/net/i2p/crypto/ElGamalAESEngineTest.java @@ -123,7 +123,7 @@ class ElGamalAESEngineTest { _log.debug("** Encryption complete. Beginning decryption"); Set foundTags = new HashSet(); SessionKey foundKey = new SessionKey(); - byte decrypted[] = _context.elGamalAESEngine().decryptAESBlock(encrypted, sessionKey, iv, null, foundTags, foundKey); + byte decrypted[] = _context.elGamalAESEngine().decryptAESBlock(encrypted, 0, encrypted.length, sessionKey, iv, null, foundTags, foundKey); if (decrypted == null) throw new Exception("Decryption failed"); String read = new String(decrypted); _log.debug("read: " + read); diff --git a/history.txt b/history.txt index c8bdd19a1b88bde22f55337ab2d4a3313088fba7..ba6aa69c72bd46ad29debd4556fecee6ed09c2a4 100644 --- a/history.txt +++ b/history.txt @@ -1,4 +1,18 @@ -$Id: history.txt,v 1.57 2004/10/27 21:11:52 jrandom Exp $ +$Id: history.txt,v 1.58 2004/10/29 21:40:52 jrandom Exp $ + +2004-10-30 jrandom + * Cache the temporary objects used in the AES encryption/decryption + process so that AES doesn't require any memory allocation to process + data. + * Dramatically reduce memory usage within various crypto implementations + by avoiding unnecessary (though simplifying) buffers. + * If we specify some tags to be sent in an I2CP message explicitly, use + only those, not those plus a new set (otherwise we aren't sure on ACK + which set was delivered) + * Allow configuration for the partial send timeout (how long before + resending a message down a different tunnel in a lease). This can be + updated with the "router.clientPartialSendTimeout" router config prop. + * Logging 2004-10-29 jrandom * Strip the Referer, Via, and From headers completely, rather than diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index da11ca1fda201dfa64c313ab95bb1b21dae18ef1..2b5d0e2ed4ab4bdd69abd7f415780bccc5250f18 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -15,9 +15,9 @@ import net.i2p.CoreVersion; * */ public class RouterVersion { - public final static String ID = "$Revision: 1.63 $ $Date: 2004/10/27 21:11:52 $"; + public final static String ID = "$Revision: 1.64 $ $Date: 2004/10/29 21:40:52 $"; public final static String VERSION = "0.4.1.3"; - public final static long BUILD = 4; + public final static long BUILD = 5; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION); System.out.println("Router ID: " + RouterVersion.ID); diff --git a/router/java/src/net/i2p/router/message/OutboundClientMessageJob.java b/router/java/src/net/i2p/router/message/OutboundClientMessageJob.java index 5253cda0f8fd25341bd99023423e1b061c9757e1..9051f13880c88ef05a5f7a959d990d4a78c99c85 100644 --- a/router/java/src/net/i2p/router/message/OutboundClientMessageJob.java +++ b/router/java/src/net/i2p/router/message/OutboundClientMessageJob.java @@ -36,7 +36,7 @@ import net.i2p.util.Log; /** * Send a client message, taking into consideration the fact that there may be * multiple inbound tunnels that the target provides. This job sends it to one - * of them and if it doesnt get a confirmation within 15 seconds (SEND_TIMEOUT_MS), + * of them and if it doesnt get a confirmation within a few seconds (getSendTimeout()), * it tries the next, continuing on until a confirmation is received, the full * timeout has been reached (60 seconds, or the ms defined in the client's or * router's "clientMessageTimeout" option). @@ -63,7 +63,9 @@ public class OutboundClientMessageJob extends JobImpl { private final static long OVERALL_TIMEOUT_MS_DEFAULT = 60*1000; /** how long for each send do we allow before going on to the next? */ - private final static long SEND_TIMEOUT_MS = 10*1000; + private final static long DEFAULT_SEND_PARTIAL_TIMEOUT = 10*1000; + private static final String PROP_SEND_PARTIAL_TIMEOUT = "router.clientPartialSendTimeout"; + /** priority of messages, that might get honored some day... */ private final static int SEND_PRIORITY = 500; @@ -132,6 +134,15 @@ public class OutboundClientMessageJob extends JobImpl { _shouldBundle = getShouldBundle(); } + private long getSendTimeout() { + String timeout = getContext().getProperty(PROP_SEND_PARTIAL_TIMEOUT, ""+DEFAULT_SEND_PARTIAL_TIMEOUT); + try { + return Long.parseLong(timeout); + } catch (NumberFormatException nfe) { + return DEFAULT_SEND_PARTIAL_TIMEOUT; + } + } + public String getName() { return "Outbound client message"; } public void runJob() { @@ -375,7 +386,7 @@ public class OutboundClientMessageJob extends JobImpl { SendTunnelMessageJob j = new SendTunnelMessageJob(getContext(), msg, outTunnelId, lease.getRouterIdentity().getHash(), lease.getTunnelId(), null, onReply, - onFail, selector, SEND_TIMEOUT_MS, + onFail, selector, getSendTimeout(), SEND_PRIORITY); getContext().jobQueue().addJob(j); } else {