diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java index 7a80c181f..fe92e94b6 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java @@ -26,6 +26,7 @@ import net.i2p.util.Log; /** * Supports the following: + *
* (where protocol is generally HTTP/1.1 but is ignored) * (where host is one of: * example.i2p @@ -39,16 +40,19 @@ import net.i2p.util.Log; * CONNECT host protocol * CONNECT host:port * CONNECT host:port protocol (this is the standard) + ** * Additional lines after the CONNECT line but before the blank line are ignored and stripped. * The CONNECT line is removed for .i2p accesses * but passed along for outproxy accesses. * * Ref: + *
* INTERNET-DRAFT Ari Luotonen * Expires: September 26, 1997 Netscape Communications Corporation ** * @author zzz a stripped-down I2PTunnelHTTPClient */ diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java index 6be20ba8b..c2a014020 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java @@ -127,6 +127,8 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr // this does not increment totalSent i2pout.write(initialI2PData); // do NOT flush here, it will block and then onTimeout.run() won't happen on fail. + // But if we don't flush, then we have to wait for the connectDelay timer to fire + // in i2p socket? To be researched and/or fixed. //i2pout.flush(); } } diff --git a/apps/streaming/java/src/net/i2p/client/streaming/I2PSocketManagerFull.java b/apps/streaming/java/src/net/i2p/client/streaming/I2PSocketManagerFull.java index b904243b1..c26a27fe2 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/I2PSocketManagerFull.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/I2PSocketManagerFull.java @@ -176,7 +176,11 @@ public class I2PSocketManagerFull implements I2PSocketManager { } /** - * Create a new connected socket (block until the socket is created) + * Create a new connected socket. Blocks until the socket is created, + * unless the connectDelay option (i2p.streaming.connectDelay) is + * set and greater than zero. If so this will return immediately, + * and the client may quickly write initial data to the socket and + * this data will be bundled in the SYN packet. * * @param peer Destination to connect to * @param options I2P socket options to be used for connecting @@ -199,6 +203,7 @@ public class I2PSocketManagerFull implements I2PSocketManager { if (_log.shouldLog(Log.INFO)) _log.info("Connecting to " + peer.calculateHash().toBase64().substring(0,6) + " with options: " + opts); + // the following blocks unless connect delay > 0 Connection con = _connectionManager.connect(peer, opts); if (con == null) throw new TooManyStreamsException("Too many streams (max " + _maxStreams + ")"); @@ -212,7 +217,11 @@ public class I2PSocketManagerFull implements I2PSocketManager { } /** - * Create a new connected socket (block until the socket is created) + * Create a new connected socket. Blocks until the socket is created, + * unless the connectDelay option (i2p.streaming.connectDelay) is + * set and greater than zero in the default options. If so this will return immediately, + * and the client may quickly write initial data to the socket and + * this data will be bundled in the SYN packet. * * @param peer Destination to connect to * diff --git a/core/java/src/net/i2p/crypto/CryptixAESEngine.java b/core/java/src/net/i2p/crypto/CryptixAESEngine.java index fb3393dcd..acd2eb702 100644 --- a/core/java/src/net/i2p/crypto/CryptixAESEngine.java +++ b/core/java/src/net/i2p/crypto/CryptixAESEngine.java @@ -41,11 +41,13 @@ public class CryptixAESEngine extends AESEngine { _cache = new CryptixAESKeyCache(); } + /** @param length must be a multiple of 16 */ @Override public void encrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) { encrypt(payload, payloadIndex, out, outIndex, sessionKey, iv, 0, length); } + /** @param length must be a multiple of 16 */ @Override public void encrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int ivOffset, int length) { if ( (payload == null) || (out == null) || (sessionKey == null) || (iv == null) ) @@ -142,7 +144,7 @@ public class CryptixAESEngine extends AESEngine { CryptixRijndael_Algorithm.blockEncrypt(payload, out, inIndex, outIndex, sessionKey.getPreparedKey(), 16); } - /** decrypt the data with the session key provided + /** decrypt exactly 16 bytes of data with the session key provided * @param payload encrypted data * @param sessionKey private session key */ diff --git a/core/java/src/net/i2p/crypto/CryptoConstants.java b/core/java/src/net/i2p/crypto/CryptoConstants.java index 4728de8de..228a92e84 100644 --- a/core/java/src/net/i2p/crypto/CryptoConstants.java +++ b/core/java/src/net/i2p/crypto/CryptoConstants.java @@ -35,21 +35,21 @@ import net.i2p.util.NativeBigInteger; /** * Prime for ElGamal from http://tools.ietf.org/html/rfc3526 - * Primes for DSA: unknown. + * Primes for DSA: Generated by TheCrypto http://article.gmane.org/gmane.comp.security.invisiblenet.iip.devel/343 */ public class CryptoConstants { public static final BigInteger dsap = new NativeBigInteger( "9c05b2aa960d9b97b8931963c9cc9e8c3026e9b8ed92fad0a69cc886d5bf8015fcadae31" - + "a0ad18fab3f01b00a358de237655c4964afaa2b337e96ad316b9fb1cc564b5aec5b69a9f" - + "f6c3e4548707fef8503d91dd8602e867e6d35d2235c1869ce2479c3b9d5401de04e0727f" - + "b33d6511285d4cf29538d9e3b6051f5b22cc1c93", + + "a0ad18fab3f01b00a358de237655c4964afaa2b337e96ad316b9fb1cc564b5aec5b69a9f" + + "f6c3e4548707fef8503d91dd8602e867e6d35d2235c1869ce2479c3b9d5401de04e0727f" + + "b33d6511285d4cf29538d9e3b6051f5b22cc1c93", 16); public static final BigInteger dsaq = new NativeBigInteger("a5dfc28fef4ca1e286744cd8eed9d29d684046b7", 16); public static final BigInteger dsag = new NativeBigInteger( "c1f4d27d40093b429e962d7223824e0bbc47e7c832a39236fc683af84889581075ff9082" - + "ed32353d4374d7301cda1d23c431f4698599dda02451824ff369752593647cc3ddc197de" - + "985e43d136cdcfc6bd5409cd2f450821142a5e6f8eb1c3ab5d0484b8129fcf17bce4f7f3" - + "3321c3cb3dbb14a905e7b2b3e93be4708cbcc82", + + "ed32353d4374d7301cda1d23c431f4698599dda02451824ff369752593647cc3ddc197de" + + "985e43d136cdcfc6bd5409cd2f450821142a5e6f8eb1c3ab5d0484b8129fcf17bce4f7f3" + + "3321c3cb3dbb14a905e7b2b3e93be4708cbcc82", 16); public static final BigInteger elgp = new NativeBigInteger("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" diff --git a/core/java/src/net/i2p/crypto/ElGamalAESEngine.java b/core/java/src/net/i2p/crypto/ElGamalAESEngine.java index 820a57c67..f4f276de2 100644 --- a/core/java/src/net/i2p/crypto/ElGamalAESEngine.java +++ b/core/java/src/net/i2p/crypto/ElGamalAESEngine.java @@ -43,19 +43,19 @@ public class ElGamalAESEngine { _context.statManager().createFrequencyStat("crypto.elGamalAES.encryptNewSession", "how frequently we encrypt to a new ElGamal/AES+SessionTag session?", - "Encryption", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l}); + "Encryption", new long[] { 60*60*1000l}); _context.statManager().createFrequencyStat("crypto.elGamalAES.encryptExistingSession", "how frequently we encrypt to an existing ElGamal/AES+SessionTag session?", - "Encryption", new long[] { 60 * 1000l, 60 * 60 * 1000l, 24 * 60 * 60 * 1000l}); + "Encryption", new long[] { 60*60*1000l}); _context.statManager().createFrequencyStat("crypto.elGamalAES.decryptNewSession", "how frequently we decrypt with a new ElGamal/AES+SessionTag session?", - "Encryption", new long[] { 60 * 1000l, 60 * 60 * 1000l, 24 * 60 * 60 * 1000l}); + "Encryption", new long[] { 60*60*1000l}); _context.statManager().createFrequencyStat("crypto.elGamalAES.decryptExistingSession", "how frequently we decrypt with an existing ElGamal/AES+SessionTag session?", - "Encryption", new long[] { 60 * 1000l, 60 * 60 * 1000l, 24 * 60 * 60 * 1000l}); + "Encryption", new long[] { 60*60*1000l}); _context.statManager().createFrequencyStat("crypto.elGamalAES.decryptFailed", - "how frequently we fail to decrypt with ElGamal/AES+SessionTag?", "Encryption", - new long[] { 60 * 60 * 1000l, 24 * 60 * 60 * 1000l}); + "how frequently we fail to decrypt with ElGamal/AES+SessionTag?", + "Encryption", new long[] { 60*60*1000l}); } /** @@ -73,6 +73,7 @@ public class ElGamalAESEngine { * This works according to the * ElGamal+AES algorithm in the data structure spec. * + * @return decrypted data or null on failure */ public byte[] decrypt(byte data[], PrivateKey targetPrivateKey, SessionKeyManager keyManager) throws DataFormatException { if (data == null) { @@ -148,9 +149,12 @@ public class ElGamalAESEngine { /** * scenario 1: * Begin with 222 bytes, ElG encrypted, containing: + *March 26, 1997 * Tunneling SSL Through a WWW Proxy + *
* - 32 byte SessionKey * - 32 byte pre-IV for the AES * - 158 bytes of random padding + *+ * After encryption, the ElG section is 514 bytes long. * Then encrypt with AES using that session key and the first 16 bytes of the SHA256 of the pre-IV, using * the decryptAESBlock method & structure. * @@ -213,6 +217,7 @@ public class ElGamalAESEngine { * scenario 2: * The data begins with 32 byte session tag, which also serves as the preIV. * Then decrypt with AES using that session key and the first 16 bytes of the SHA256 of the pre-IV: + *
* - 2 byte integer specifying the # of session tags * - that many 32 byte session tags * - 4 byte integer specifying data.length @@ -220,11 +225,13 @@ public class ElGamalAESEngine { * - 1 byte flag that, if == 1, is followed by a new SessionKey * - data * - random bytes, padding the total size to greater than paddedSize with a mod 16 = 0 + ** * If anything doesn't match up in decryption, it falls back to decryptNewSession * * @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 + * @return decrypted data or null on failure * */ byte[] decryptExistingSession(byte data[], SessionKey key, PrivateKey targetPrivateKey, Set foundTags, @@ -264,6 +271,7 @@ public class ElGamalAESEngine { /** * Decrypt the AES data with the session key and IV. The result should be: + *
* - 2 byte integer specifying the # of session tags * - that many 32 byte session tags * - 4 byte integer specifying data.length @@ -271,6 +279,7 @@ public class ElGamalAESEngine { * - 1 byte flag that, if == 1, is followed by a new SessionKey * - data * - random bytes, padding the total size to greater than paddedSize with a mod 16 = 0 + ** * If anything doesn't match up in decryption, return null. Otherwise, return * the decrypted data and update the session as necessary. If the sentTag is not null, @@ -278,6 +287,7 @@ 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 + * @return decrypted data or null on failure */ byte[] decryptAESBlock(byte encrypted[], SessionKey key, byte iv[], byte sentTag[], Set foundTags, SessionKey foundKey) throws DataFormatException { @@ -299,10 +309,10 @@ public class ElGamalAESEngine { //ByteArrayInputStream bais = new ByteArrayInputStream(decrypted); int cur = 0; long numTags = DataHelper.fromLong(decrypted, cur, 2); + if ((numTags < 0) || (numTags > 200)) throw new Exception("Invalid number of session tags"); if (numTags > 0) tags = new ArrayList((int)numTags); cur += 2; //_log.debug("# tags: " + numTags); - if ((numTags < 0) || (numTags > 200)) throw new Exception("Invalid number of session tags"); if (numTags * SessionTag.BYTE_LENGTH > decrypted.length - 2) { throw new Exception("# tags: " + numTags + " is too many for " + (decrypted.length - 2)); } @@ -363,6 +373,8 @@ public class ElGamalAESEngine { * @param newKey key to be delivered to the target, with which the tagsForDelivery should be associated, or null * @param paddedSize minimum size in bytes of the body after padding it (if less than the * body's real size, no bytes are appended but the body is not truncated) + * + * Unused externally, only called by below (i.e. newKey is always null) */ public byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, SessionTag currentTag, SessionKey newKey, long paddedSize) { @@ -384,6 +396,7 @@ public class ElGamalAESEngine { /** * Encrypt the data to the target using the given key and deliver the specified tags * No new session key + * This is the one called from GarlicMessageBuilder and is the primary entry point. */ public byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, SessionTag currentTag, long paddedSize) { @@ -394,6 +407,8 @@ public class ElGamalAESEngine { * Encrypt the data to the target using the given key and deliver the specified tags * No new session key * No current tag (encrypt as new session) + * + * @deprecated unused */ public byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, long paddedSize) { return encrypt(data, target, key, tagsForDelivery, null, null, paddedSize); @@ -403,6 +418,8 @@ public class ElGamalAESEngine { * Encrypt the data to the target using the given key delivering no tags * No new session key * No current tag (encrypt as new session) + * + * @deprecated unused */ public byte[] encrypt(byte data[], PublicKey target, SessionKey key, long paddedSize) { return encrypt(data, target, key, null, null, null, paddedSize); @@ -411,10 +428,14 @@ public class ElGamalAESEngine { /** * scenario 1: * Begin with 222 bytes, ElG encrypted, containing: + *
* - 32 byte SessionKey * - 32 byte pre-IV for the AES * - 158 bytes of random padding - * Then encrypt with AES using that session key and the first 16 bytes of the SHA256 of the pre-IV: + *+ * After encryption, the ElG section is 514 bytes long. + * Then encrypt the following with AES using that session key and the first 16 bytes of the SHA256 of the pre-IV: + *
* - 2 byte integer specifying the # of session tags * - that many 32 byte session tags * - 4 byte integer specifying data.length @@ -422,6 +443,7 @@ public class ElGamalAESEngine { * - 1 byte flag that, if == 1, is followed by a new SessionKey * - data * - random bytes, padding the total size to greater than paddedSize with a mod 16 = 0 + ** */ byte[] encryptNewSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, @@ -440,10 +462,12 @@ public class ElGamalAESEngine { //_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)) + if (_log.shouldLog(Log.INFO)) { + long after = _context.clock().now(); _log.info("elgEngine.encrypt of the session key took " + (after - before) + "ms"); + } if (elgEncr.length < 514) { + // ??? ElGamalEngine.encrypt() always returns 514 bytes byte elg[] = new byte[514]; int diff = elg.length - elgEncr.length; //if (_log.shouldLog(Log.DEBUG)) _log.debug("Difference in size: " + diff); @@ -474,6 +498,7 @@ public class ElGamalAESEngine { * scenario 2: * Begin with 32 byte session tag, which also serves as the preIV. * Then encrypt with AES using that session key and the first 16 bytes of the SHA256 of the pre-IV: + *
* - 2 byte integer specifying the # of session tags * - that many 32 byte session tags * - 4 byte integer specifying data.length @@ -481,6 +506,7 @@ public class ElGamalAESEngine { * - 1 byte flag that, if == 1, is followed by a new SessionKey * - data * - random bytes, padding the total size to greater than paddedSize with a mod 16 = 0 + ** */ byte[] encryptExistingSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, @@ -506,6 +532,7 @@ public class ElGamalAESEngine { * For both scenarios, this method encrypts the AES area using the given key, iv * and making sure the resulting data is at least as long as the paddedSize and * also mod 16 bytes. The contents of the encrypted data is: + *
* - 2 byte integer specifying the # of session tags * - that many 32 byte session tags * - 4 byte integer specifying data.length @@ -513,6 +540,7 @@ public class ElGamalAESEngine { * - 1 byte flag that, if == 1, is followed by a new SessionKey * - data * - random bytes, padding the total size to greater than paddedSize with a mod 16 = 0 + ** */ final byte[] encryptAESBlock(byte data[], SessionKey key, byte[] iv, Set tagsForDelivery, SessionKey newKey, diff --git a/core/java/src/net/i2p/crypto/ElGamalEngine.java b/core/java/src/net/i2p/crypto/ElGamalEngine.java index c5e9cd963..ae3b0eb3a 100644 --- a/core/java/src/net/i2p/crypto/ElGamalEngine.java +++ b/core/java/src/net/i2p/crypto/ElGamalEngine.java @@ -85,9 +85,17 @@ public class ElGamalEngine { } /** encrypt the data to the public key - * @return encrypted data, will be about twice as big as the cleartext + * @return encrypted data, will be exactly 514 bytes long + * Contains the two-part encrypted data starting at bytes 0 and 257. + * If the encrypted parts are smaller than 257 bytes, they will be + * padded with leading zeros. + * The parts appear to always be 256 bytes or less, in other words, + * bytes 0 and 257 are always zero. * @param publicKey public key encrypt to * @param data data to encrypt, must be 222 bytes or less + * As the encrypted data may contain a substantial number of zeros if the + * cleartext is smaller than 222 bytes, it is recommended that the caller pad + * the cleartext to 222 bytes with random data. */ public byte[] encrypt(byte data[], PublicKey publicKey) { if ((data == null) || (data.length >= 223)) @@ -97,6 +105,7 @@ public class ElGamalEngine { long start = _context.clock().now(); byte d2[] = new byte[1+Hash.HASH_LENGTH+data.length]; + // FIXME this isn't a random nonzero byte! d2[0] = (byte)0xFF; Hash hash = _context.sha().calculateHash(data); System.arraycopy(hash.getData(), 0, d2, 1, Hash.HASH_LENGTH); @@ -156,11 +165,15 @@ public class ElGamalEngine { } /** Decrypt the data - * @param encrypted encrypted data, must be 514 bytes or less + * @param encrypted encrypted data, must be exactly 514 bytes + * Contains the two-part encrypted data starting at bytes 0 and 257. + * If the encrypted parts are smaller than 257 bytes, they must be + * padded with leading zeros. * @param privateKey private key to decrypt with - * @return unencrypted data + * @return unencrypted data or null on failure */ public byte[] decrypt(byte encrypted[], PrivateKey privateKey) { + // actually it must be exactly 514 bytes or the arraycopy below will AIOOBE if ((encrypted == null) || (encrypted.length > 514)) throw new IllegalArgumentException("Data to decrypt must be <= 514 bytes at the moment"); long start = _context.clock().now(); diff --git a/core/java/src/net/i2p/data/Base32.java b/core/java/src/net/i2p/data/Base32.java index adb7e9196..3071e0bc0 100644 --- a/core/java/src/net/i2p/data/Base32.java +++ b/core/java/src/net/i2p/data/Base32.java @@ -23,12 +23,14 @@ import net.i2p.util.Log; * No whitespace allowed. * * Decode accepts upper or lower case. + * @author zzz + * @since 0.7 */ public class Base32 { private final static Log _log = new Log(Base32.class); - /** The 64 valid Base32 values. */ + /** The 32 valid Base32 values. */ private final static char[] ALPHABET = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', @@ -132,10 +134,16 @@ public class Base32 { System.out.println("or : Base32 decode"); } + /** + * @param source if null will return "" + */ public static String encode(String source) { return (source != null ? encode(source.getBytes()) : ""); } + /** + * @param source The data to convert non-null + */ public static String encode(byte[] source) { StringBuilder buf = new StringBuilder((source.length + 7) * 8 / 5); encodeBytes(source, buf); @@ -147,7 +155,7 @@ public class Base32 { /** * Encodes a byte array into Base32 notation. * - * @param source The data to convert + * @param source The data to convert non-null */ private static void encodeBytes(byte[] source, StringBuilder out) { int usedbits = 0; @@ -174,7 +182,7 @@ public class Base32 { * Decodes data from Base32 notation and * returns it as a string. * - * @param s the string to decode + * @param s the string to decode, if null returns null * @return The data as a string or null on failure */ public static String decodeToString(String s) { @@ -184,6 +192,10 @@ public class Base32 { return new String(b); } + /** + * @param s non-null + * @return decoded data, null on error + */ public static byte[] decode(String s) { return decode(s.getBytes()); } @@ -194,8 +206,8 @@ public class Base32 { * Decodes Base32 content in byte array format and returns * the decoded byte array. * - * @param source The Base32 encoded data - * @return decoded data + * @param source The Base32 encoded data non-null + * @return decoded data, null on error */ private static byte[] decode(byte[] source) { int len58; diff --git a/core/java/src/net/i2p/data/Base64.java b/core/java/src/net/i2p/data/Base64.java index 2b9fe85e6..b5e03b03c 100644 --- a/core/java/src/net/i2p/data/Base64.java +++ b/core/java/src/net/i2p/data/Base64.java @@ -42,23 +42,46 @@ public class Base64 { private final static Log _log = new Log(Base64.class); - /** added by aum */ + /** + * @param source if null will return "" + */ public static String encode(String source) { return (source != null ? encode(source.getBytes()) : ""); } + + /** + * @param source if null will return "" + */ public static String encode(byte[] source) { return (source != null ? encode(source, 0, source.length) : ""); } + + /** + * @param source if null will return "" + */ public static String encode(byte[] source, int off, int len) { return (source != null ? encode(source, off, len, false) : ""); } + + /** + * @param source if null will return "" + * @param useStandardAlphabet Warning, must be false for I2P compatibility + */ public static String encode(byte[] source, boolean useStandardAlphabet) { return (source != null ? encode(source, 0, source.length, useStandardAlphabet) : ""); } + + /** + * @param source if null will return "" + * @param useStandardAlphabet Warning, must be false for I2P compatibility + */ public static String encode(byte[] source, int off, int len, boolean useStandardAlphabet) { return (source != null ? safeEncode(source, off, len, useStandardAlphabet) : ""); } + /** + * @param s Base 64 encoded string using the I2P alphabet A-Z, a-z, 0-9, -, ~ + */ public static byte[] decode(String s) { return safeDecode(s, false); } @@ -84,6 +107,8 @@ public class Base64 { (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '+', (byte) '/'}; + + /** The 64 valid Base64 values for I2P. */ private final static byte[] ALPHABET_ALT = { (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', @@ -241,7 +266,7 @@ public class Base64 { * @return four byte array in Base64 notation. * @since 1.3 */ -/***** unused +/***** unused (standard alphabet) private static byte[] encode3to4(byte[] threeBytes) { return encode3to4(threeBytes, 3); } // end encodeToBytes @@ -260,11 +285,13 @@ public class Base64 { * @return four byte array in Base64 notation. * @since 1.3 */ +/***** unused (standard alphabet) private static byte[] encode3to4(byte[] threeBytes, int numSigBytes) { byte[] dest = new byte[4]; encode3to4(threeBytes, 0, numSigBytes, dest, 0); return dest; } +******/ /** * Encodes up to three bytes of the array source @@ -287,6 +314,7 @@ public class Base64 { * @return the destination array * @since 1.3 */ +/***** unused (standard alphabet) private static byte[] encode3to4(byte[] source, int srcOffset, int numSigBytes, byte[] destination, int destOffset) { // 1 2 3 // 01234567890123456789012345678901 Bit position @@ -329,7 +357,11 @@ public class Base64 { return destination; } // end switch } // end encode3to4 +******/ + /** + * @param alpha alphabet + */ private static void encode3to4(byte[] source, int srcOffset, int numSigBytes, StringBuilder buf, byte alpha[]) { // 1 2 3 // 01234567890123456789012345678901 Bit position @@ -628,7 +660,7 @@ public class Base64 { * Decodes data from Base64 notation. * * @param s the string to decode - * @return the decoded data + * @return the decoded data, null on error * @since 1.4 */ private static byte[] standardDecode(String s) { @@ -647,6 +679,7 @@ public class Base64 { * @param s the strind to decode * @return The data as a string * @since 1.4 + * @throws NPE on error? */ public static String decodeToString(String s) { return new String(decode(s)); @@ -659,7 +692,7 @@ public class Base64 { * @param source The Base64 encoded data * @param off The offset of where to begin decoding * @param len The length of characters to decode - * @return decoded data + * @return decoded data, null on error * @since 1.3 */ private static byte[] decode(byte[] source, int off, int len) { diff --git a/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java b/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java index 7bd8352b6..7ff81a2ad 100644 --- a/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java +++ b/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java @@ -23,7 +23,7 @@ import net.i2p.data.SessionKey; * byte 184: flags * bytes 185-188: request time (in hours since the epoch) * bytes 189-192: next message ID - * bytes 193-222: uninterpreted / random padding + * bytes 193-221: uninterpreted / random padding * * */ @@ -226,7 +226,7 @@ public class BuildRequestRecord { * byte 184: flags * bytes 185-188: request time (in hours since the epoch) * bytes 189-192: next message ID - * bytes 193-222: uninterpreted / random padding + * bytes 193-221: uninterpreted / random padding */ DataHelper.toLong(buf, OFF_RECV_TUNNEL, 4, receiveTunnelId); System.arraycopy(peer.getData(), 0, buf, OFF_OUR_IDENT, Hash.HASH_LENGTH); diff --git a/router/java/src/net/i2p/router/message/GarlicMessageBuilder.java b/router/java/src/net/i2p/router/message/GarlicMessageBuilder.java index a9bea9c2f..99c282a0c 100644 --- a/router/java/src/net/i2p/router/message/GarlicMessageBuilder.java +++ b/router/java/src/net/i2p/router/message/GarlicMessageBuilder.java @@ -35,6 +35,7 @@ public class GarlicMessageBuilder { /** * This was 100 since 0.6.1.10 (50 before that). It's important because: + *
* - Tags are 32 bytes. So it previously added 3200 bytes to an initial message. * - Too many tags adds a huge overhead to short-duration connections * (like http, datagrams, etc.) @@ -43,14 +44,17 @@ public class GarlicMessageBuilder { * - This reduces the effective maximum datagram size because the client * doesn't know when tags will be bundled, so the tag size must be * subtracted from the maximum I2NP size or transport limit. + ** * Issues with too small a value: + *
* - When tags are sent, a reply leaseset (~1KB) is always bundled. * Maybe don't need to bundle more than every minute or so * rather than every time? * - Does the number of tags (and the threshold of 20) limit the effective * streaming lib window size? Should the threshold and the number of * sent tags be variable based on the message rate? + ** * We have to be very careful if we implement an adaptive scheme, * since the key manager is per-router, not per-local-dest. @@ -218,6 +222,7 @@ public class GarlicMessageBuilder { byte cloveSet[] = buildCloveSet(ctx, config); + // TODO - 128 is the minimum padded size - should it be more? less? random? byte encData[] = ctx.elGamalAESEngine().encrypt(cloveSet, target, encryptKey, wrappedTags, encryptTag, 128); msg.setData(encData); msg.setMessageExpiration(config.getExpiration()); diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/MessageWrapper.java b/router/java/src/net/i2p/router/networkdb/kademlia/MessageWrapper.java index 82c7f121f..1f521455e 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/MessageWrapper.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/MessageWrapper.java @@ -20,7 +20,7 @@ import net.i2p.router.message.PayloadGarlicConfig; import net.i2p.util.Log; /** - * Method an class for garlic encrypting outbound netdb traffic, + * Method and class for garlic encrypting outbound netdb traffic, * including management of the ElGamal/AES tags * * @since 0.7.10 diff --git a/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java b/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java index 747c923ec..dce7d0de7 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java +++ b/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java @@ -29,18 +29,45 @@ import net.i2p.router.Router; import net.i2p.router.RouterContext; import net.i2p.util.Log; -/* +/** + * Handle the 4-phase establishment, which is as follows: + * + *
+ * * Alice contacts Bob * ========================================================= + * + * Message 1 (Session Request): * X+(H(X) xor Bob.identHash)-----------------------------> + * + * Message 2 (Session Created): * <----------------------------------------Y+E(H(X+Y)+tsB, sk, Y[239:255]) - * E(#+Alice.identity+tsA+padding+S(X+Y+Bob.identHash+tsA+tsB+padding), sk, hX_xor_Bob.identHash[16:31])---> + * + * Message 3 (Session Confirm A): + * E(sz+Alice.identity+tsA+padding+S(X+Y+Bob.identHash+tsA+tsB), sk, hX_xor_Bob.identHash[16:31])---> + * + * Message 4 (Session Confirm B): * <----------------------E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev) * + * Key: + * + * X, Y: 256 byte DH keys + * H(): 32 byte SHA256 Hash + * E(data, session key, IV): AES256 Encrypt + * S(): 40 byte DSA Signature + * tsA, tsB: timestamps (4 bytes, seconds since epoch) + * sk: 32 byte Session key + * sz: 2 byte size of Alice identity to follow + * + *+ * + * * Alternately, when Bob receives a connection, it could be a * check connection (perhaps prompted by Bob asking for someone * to verify his listener). check connections are formatted per - * {@link #isCheckInfo()} + * isCheckInfo() + * NOTE: Check info is unused. + * */ public class EstablishState { private RouterContext _context; @@ -57,7 +84,9 @@ public class EstablishState { // alice receives (and bob sends) private byte _Y[]; private transient byte _e_hXY_tsB[]; + /** Bob's Timestamp in seconds */ private transient long _tsB; + /** Alice's Timestamp in seconds */ private transient long _tsA; private transient byte _e_bobSig[]; @@ -98,9 +127,6 @@ public class EstablishState { _log = ctx.logManager().getLog(getClass()); _transport = transport; _con = con; - _verified = false; - _corrupt = false; - _confirmWritten = false; _dh = new DHSessionKeyBuilder(); if (_con.isInbound()) { _X = new byte[256]; @@ -116,10 +142,7 @@ public class EstablishState { _prevEncrypted = new byte[16]; _curEncrypted = new byte[16]; - _curEncryptedOffset = 0; _curDecrypted = new byte[16]; - - _received = 0; } /** @@ -150,7 +173,10 @@ public class EstablishState { public boolean getFailedBySkew() { return _failedBySkew; } - /** we are Bob, so receive these bytes as part of an inbound connection */ + /** + * we are Bob, so receive these bytes as part of an inbound connection + * This method receives messages 1 and 3, and sends messages 2 and 4. + */ private void receiveInbound(ByteBuffer src) { if (_log.shouldLog(Log.DEBUG)) _log.debug(prefix()+"Receiving inbound: prev received=" + _received + " src.remaining=" + src.remaining()); @@ -311,7 +337,10 @@ public class EstablishState { } } - /** we are Alice, so receive these bytes as part of an outbound connection */ + /** + * We are Alice, so receive these bytes as part of an outbound connection. + * This method receives messages 2 and 4, and sends message 3. + */ private void receiveOutbound(ByteBuffer src) { if (_log.shouldLog(Log.DEBUG)) _log.debug(prefix()+"Receive outbound " + src + " received=" + _received); @@ -498,8 +527,10 @@ public class EstablishState { public boolean isComplete() { return _verified; } /** - * we are establishing an outbound connection, so prepare ourselves by + * We are Alice. + * We are establishing an outbound connection, so prepare ourselves by * queueing up the write of the first part of the handshake + * This method sends message #1 to Bob. */ public void prepareOutbound() { if (_received <= 0) { @@ -516,7 +547,9 @@ public class EstablishState { } /** - * make sure the signatures are correct, and if they are, update the + * We are Bob. Verify message #3 from Alice, then send message #4 to Alice. + * + * Make sure the signatures are correct, and if they are, update the * NIOConnection with the session key / peer ident / clock skew / iv. * The NIOConnection itself is responsible for registering with the * transport @@ -623,6 +656,9 @@ public class EstablishState { } } + /** + * We are Bob. Send message #4 to Alice. + */ private void sendInboundConfirm(RouterIdentity alice, long tsA) { // send Alice E(S(X+Y+Alice.identHash+tsA+tsB), sk, prev) byte toSign[] = new byte[256+256+32+4+4]; diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java index c0d9e22f3..3ebbdd343 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java @@ -607,6 +607,7 @@ public class PacketBuilder { // pad here if we want. maybe randomized? // pad up so we're on the encryption boundary + // TODO: why not random data? if ( (off % 16) != 0) off += 16 - (off % 16); packet.getPacket().setLength(off);