diff --git a/android/README.txt b/android/README.txt index 67b8aa30f33128ab1ccbf38fa1b7b84f937adad3..5f633b966bd5cf90dea914203107b46c998811df 100644 --- a/android/README.txt +++ b/android/README.txt @@ -28,7 +28,7 @@ ant debug ../../android-sdk-linux_86/tools/emulator -avd i2p & #then wait a couple minutes until the emulator is up -#then install the I2P app +#then install the I2P app (ONE TIME ONLY) ant install #then run the debugger @@ -36,3 +36,5 @@ ant install #to rebuild and reinstall to emulator: ant reinstall + +# Now click on the I2P icon on your phone! diff --git a/android/build.properties b/android/build.properties new file mode 100644 index 0000000000000000000000000000000000000000..181724115d6453de5705a4d1fd7f7f682a345281 --- /dev/null +++ b/android/build.properties @@ -0,0 +1 @@ +application-package=net.i2p.router diff --git a/android/build.xml b/android/build.xml index 94356fcf426ca2303b4719007d3357d27cec5d51..6869bce6bd0454592287308aa304505ebcb1f8af 100644 --- a/android/build.xml +++ b/android/build.xml @@ -76,6 +76,9 @@ <mkdir dir="tmp" /> <unjar src="../build/i2p.jar" dest="tmp/" /> <delete file="tmp/net/i2p/util/LogWriter.class" /> + <delete file="tmp/net/i2p/util/SecureDirectory.class" /> + <delete file="tmp/net/i2p/util/SecureFile.class" /> + <delete file="tmp/net/i2p/util/SecureFileOutputStream.class" /> <!-- org.bouncycastle.crypto already in android but we need a little trickery because our HMac is incompatible... and the libs aren't in the SDK to compile against??? --> @@ -237,6 +240,7 @@ <target name="compile" depends="buildrouter, resource-src, aidl"> <javac encoding="ascii" target="1.5" debug="true" extdirs="" destdir="${out-classes}" + includeantruntime="false" bootclasspathref="android.target.classpath"> <src path="${source-folder}" /> <src path="${gen-folder}" /> @@ -280,6 +284,12 @@ <!-- Package the application and sign it with a debug key. This is the default target when building. It is used for debug. --> + <!-- + I2P when this fails 365 days later because the key expired, delete ~/.android/debug.keystore + Then do 'ant uninstall' (since the new key doesn't match the old key) + Then do 'ant install' + See http://developer.android.com/guide/publishing/app-signing.html for more info + --> <target name="debug" depends="dex, package-resources"> <apkbuilder outfolder="${out-folder}" @@ -327,12 +337,12 @@ </exec> </target> - <!-- Uinstall the package from the default emulator --> + <!-- Uninstall the package from the default emulator --> <target name="uninstall"> <echo>Uninstalling ${application-package} from the default emulator...</echo> <exec executable="${adb}" failonerror="true"> <arg value="uninstall" /> - <arg path="${application-package}" /> + <arg value="${application-package}" /> </exec> </target> diff --git a/android/res/raw/router_config b/android/res/raw/router_config index be361459b66359cd48cc1d5563354b6a1e2d72de..ab95944842da7f96ef0bb2b8f5399f5b24c2e216 100644 --- a/android/res/raw/router_config +++ b/android/res/raw/router_config @@ -6,17 +6,27 @@ i2p.dir.pid=/data/data/net.i2p.router/files/tmp prng.buffers=2 router.decayingBloomFilterM=20 stat.full=false -i2np.udp.maxConnections=30 +# # no I2CP +# i2p.dummyClientFacade=true -# for now +# +##### Transport +# +# +# NTCP +# #i2np.ntcp.enable=false +i2np.ntcp.maxConnections=8 # # UDP crashes the JVM, don't know why # i2np.udp.enable=false +i2np.udp.maxConnections=12 +# # no COMM at all!!! #i2p.vmCommSystem=true +# # not on android i2np.upnp.enable=false routerconsole.geoip.enable=false diff --git a/android/src/net/i2p/router/I2PAndroid.java b/android/src/net/i2p/router/I2PAndroid.java index 88d522dd632b00279f28caf8038f1817b715296e..262493ec36d0ee9ebd1ee474df65e9d423761554 100644 --- a/android/src/net/i2p/router/I2PAndroid.java +++ b/android/src/net/i2p/router/I2PAndroid.java @@ -46,6 +46,7 @@ public class I2PAndroid extends Activity { System.err.println("onStart called"); super.onStart(); +// net.i2p.crypto.DSAEngine.main(null); RouterLaunch.main(null); System.err.println("Router.main finished"); } diff --git a/android/src/net/i2p/util/LogWriter.java b/android/src/net/i2p/util/LogWriter.java index 0babfab37be87e8b6212c4833860d953dcb721d5..18ba54c8ed3f7f5a1b1b6d1bb507fa5db8171859 100644 --- a/android/src/net/i2p/util/LogWriter.java +++ b/android/src/net/i2p/util/LogWriter.java @@ -10,6 +10,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.List; +import java.util.Queue; /** * bridge to android logging @@ -56,11 +57,21 @@ class LogWriter implements Runnable { public void flushRecords() { flushRecords(true); } public void flushRecords(boolean shouldWait) { try { - List records = _manager._removeAll(); + // zero copy, drain the manager queue directly + Queue<LogRecord> records = _manager.getQueue(); if (records == null) return; - for (int i = 0; i < records.size(); i++) { - LogRecord rec = (LogRecord) records.get(i); - writeRecord(rec); + if (!records.isEmpty()) { + LogRecord rec; + while ((rec = records.poll()) != null) { + writeRecord(rec); + } + try { + if (_currentOut != null) + _currentOut.flush(); + } catch (IOException ioe) { + //if (++_diskFullMessageCount < MAX_DISKFULL_MESSAGES) + System.err.println("Error writing the router log - disk full? " + ioe); + } } } catch (Throwable t) { t.printStackTrace(); diff --git a/android/src/net/i2p/util/SecureDirectory.java b/android/src/net/i2p/util/SecureDirectory.java new file mode 100644 index 0000000000000000000000000000000000000000..0c34c91c054caf237279f2d3099a758cfca03c67 --- /dev/null +++ b/android/src/net/i2p/util/SecureDirectory.java @@ -0,0 +1,22 @@ +package net.i2p.util; + +import java.io.File; + +/** + * setXXX() not available until API level 9 (Platform Version 2.3) + * @since 0.8.7 + */ +public class SecureDirectory extends File { + + public SecureDirectory(String pathname) { + super(pathname); + } + + public SecureDirectory(String parent, String child) { + super(parent, child); + } + + public SecureDirectory(File parent, String child) { + super(parent, child); + } +} diff --git a/android/src/net/i2p/util/SecureFile.java b/android/src/net/i2p/util/SecureFile.java new file mode 100644 index 0000000000000000000000000000000000000000..e9362ef946873f6f6875eb0677611a90bd38ef52 --- /dev/null +++ b/android/src/net/i2p/util/SecureFile.java @@ -0,0 +1,22 @@ +package net.i2p.util; + +import java.io.File; + +/** + * setXXX() not available until API level 9 (Platform Version 2.3) + * @since 0.8.7 + */ +public class SecureFile extends SecureDirectory { + + public SecureFile(String pathname) { + super(pathname); + } + + public SecureFile(String parent, String child) { + super(parent, child); + } + + public SecureFile(File parent, String child) { + super(parent, child); + } +} diff --git a/android/src/net/i2p/util/SecureFileOutputStream.java b/android/src/net/i2p/util/SecureFileOutputStream.java new file mode 100644 index 0000000000000000000000000000000000000000..e45798cf988741fb430fae2f5cf334d4989354b6 --- /dev/null +++ b/android/src/net/i2p/util/SecureFileOutputStream.java @@ -0,0 +1,53 @@ +package net.i2p.util; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; + +import net.i2p.I2PAppContext; + +/** + * setXXX() not available until API level 9 (Platform Version 2.3) + * @since 0.8.7 + */ +public class SecureFileOutputStream extends FileOutputStream { + + /** + * super() + */ + public SecureFileOutputStream(String file) throws FileNotFoundException { + super(file); + } + + /** + * super() + */ + public SecureFileOutputStream(String file, boolean append) throws FileNotFoundException { + super(file, append); + } + + /** + * super() + */ + public SecureFileOutputStream(File file) throws FileNotFoundException { + super(file); + } + + /** + * super() + */ + public SecureFileOutputStream(File file, boolean append) throws FileNotFoundException { + super(file, append); + } + + /** @return false */ + static boolean canSetPerms() { + return false; + } + + /** + * noop + */ + public static void setPerms(File f) { + } +} diff --git a/apps/i2psnark/java/src/org/klomp/snark/Storage.java b/apps/i2psnark/java/src/org/klomp/snark/Storage.java index 9c8f02cc4b25f477893253717406782fd830ed13..30118e9585b055e778576bbc13802e530ef41761 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Storage.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Storage.java @@ -557,7 +557,8 @@ public class Storage private static final char[] ILLEGAL = new char[] { '<', '>', ':', '"', '/', '\\', '|', '?', '*', 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 }; + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 0x7f }; /** * Removes 'suspicious' characters from the given file name. diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java index b54ca796998e5ba6f55de887e7935af9496326a7..fa352ada6acd6283dc3e1a838c7af5bf567bc1c2 100644 --- a/core/java/src/net/i2p/I2PAppContext.java +++ b/core/java/src/net/i2p/I2PAppContext.java @@ -687,14 +687,14 @@ public class I2PAppContext { } } - /** @deprecated unused */ + /** @deprecated used only by syndie */ public HMAC256Generator hmac256() { if (!_hmac256Initialized) initializeHMAC256(); return _hmac256; } - /** @deprecated unused */ + /** @deprecated used only by syndie */ private void initializeHMAC256() { synchronized (this) { if (_hmac256 == null) { diff --git a/core/java/src/net/i2p/crypto/ElGamalAESEngine.java b/core/java/src/net/i2p/crypto/ElGamalAESEngine.java index df4867574d7f52911025297a414592f2eec845bf..4e6e634ca53dc5aee5f075a1859e31473c7070d5 100644 --- a/core/java/src/net/i2p/crypto/ElGamalAESEngine.java +++ b/core/java/src/net/i2p/crypto/ElGamalAESEngine.java @@ -91,14 +91,12 @@ public class ElGamalAESEngine { SessionTag st = new SessionTag(tag); SessionKey key = keyManager.consumeTag(st); SessionKey foundKey = new SessionKey(); - foundKey.setData(null); SessionKey usedKey = new SessionKey(); Set foundTags = new HashSet(); byte decrypted[] = null; boolean wasExisting = false; if (key != null) { //if (_log.shouldLog(Log.DEBUG)) _log.debug("Key is known for tag " + st); - usedKey.setData(key.getData()); long id = _context.random().nextLong(); if (_log.shouldLog(Log.DEBUG)) _log.debug(id + ": Decrypting existing session encrypted with tag: " + st.toString() + ": key: " + key.toBase64() + ": " + data.length + " bytes: " + Base64.encode(data, 0, 64)); @@ -138,7 +136,7 @@ public class ElGamalAESEngine { if (_log.shouldLog(Log.DEBUG)) _log.debug("Found key: " + foundKey.toBase64() + " tags: " + foundTags + " wasExisting? " + wasExisting); keyManager.tagsReceived(foundKey, foundTags); - } else { + } else if (usedKey.getData() != null) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Used key: " + usedKey.toBase64() + " tags: " + foundTags + " wasExisting? " + wasExisting); keyManager.tagsReceived(usedKey, foundTags); @@ -160,11 +158,12 @@ public class ElGamalAESEngine { * the decryptAESBlock method & structure. * * @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 + * @param foundKey out parameter. Data must be unset when called; may be filled with a new sessionKey found during decryption + * @param usedKey out parameter. Data must be unset when called; usedKey.setData() will be called by this method on success. * * @return null if decryption fails */ - byte[] decryptNewSession(byte data[], PrivateKey targetPrivateKey, Set foundTags, SessionKey usedKey, + private byte[] decryptNewSession(byte data[], PrivateKey targetPrivateKey, Set foundTags, SessionKey usedKey, SessionKey foundKey) throws DataFormatException { if (data == null) { //if (_log.shouldLog(Log.WARN)) _log.warn("Data is null, unable to decrypt new session"); @@ -231,19 +230,19 @@ public class ElGamalAESEngine { * 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 + * @param foundKey out parameter. Data must be unset when called; may be filled with a new sessionKey found during decryption + * @param usedKey out parameter. Data must be unset when called; usedKey.setData() will be called by this method on success. + * * @return decrypted data or null on failure * */ - byte[] decryptExistingSession(byte data[], SessionKey key, PrivateKey targetPrivateKey, Set foundTags, + private byte[] decryptExistingSession(byte data[], SessionKey key, PrivateKey targetPrivateKey, Set foundTags, SessionKey usedKey, SessionKey foundKey) throws DataFormatException { byte preIV[] = new byte[32]; System.arraycopy(data, 0, preIV, 0, preIV.length); Hash ivHash = _context.sha().calculateHash(preIV); byte iv[] = new byte[16]; System.arraycopy(ivHash.getData(), 0, iv, 0, 16); - - usedKey.setData(key.getData()); //_log.debug("Pre IV for decryptExistingSession: " + DataHelper.toString(preIV, 32)); //_log.debug("SessionKey for decryptNewSession: " + DataHelper.toString(key.getData(), 32)); @@ -267,6 +266,7 @@ public class ElGamalAESEngine { //if (_log.shouldLog(Log.DEBUG)) // _log.debug("Decrypt with an EXISTING session tag successfull, # tags read: " + foundTags.size(), // new Exception("Decrypted by")); + usedKey.setData(key.getData()); return decrypted; } @@ -287,14 +287,15 @@ public class ElGamalAESEngine { * consume it, but if it is null, record the keys, etc as part of a new session. * * @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 + * @param foundKey out parameter. Data must be unset when called; 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[], + private 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[], + + private 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)); @@ -448,7 +449,7 @@ public class ElGamalAESEngine { * </pre> * */ - byte[] encryptNewSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, + private byte[] encryptNewSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, SessionKey newKey, long paddedSize) { //_log.debug("Encrypting to a NEW session"); byte elgSrcData[] = new byte[SessionKey.KEYSIZE_BYTES+32+158]; @@ -511,7 +512,7 @@ public class ElGamalAESEngine { * </pre> * */ - byte[] encryptExistingSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, + private byte[] encryptExistingSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, SessionTag currentTag, SessionKey newKey, long paddedSize) { //_log.debug("Encrypting to an EXISTING session"); byte rawTag[] = currentTag.getData(); @@ -543,11 +544,12 @@ public class ElGamalAESEngine { * </pre> * */ - final byte[] encryptAESBlock(byte data[], SessionKey key, byte[] iv, Set tagsForDelivery, SessionKey newKey, + private 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, + + private 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"); @@ -616,6 +618,7 @@ public class ElGamalAESEngine { context.random().nextBytes(rv); return rv; } + final static int getPaddingSize(int curSize, long minPaddedSize) { int diff = 0; if (curSize < minPaddedSize) { diff --git a/core/java/src/net/i2p/crypto/HMAC256Generator.java b/core/java/src/net/i2p/crypto/HMAC256Generator.java index 3fc554639f5c6c6323cf6d303cd95fe3b4002e44..a4f541e34214ee1d4fd18b33d7f084e10d02134a 100644 --- a/core/java/src/net/i2p/crypto/HMAC256Generator.java +++ b/core/java/src/net/i2p/crypto/HMAC256Generator.java @@ -13,9 +13,12 @@ import org.bouncycastle.crypto.macs.I2PHMac; /** * Calculate the HMAC-SHA256 of a key+message. All the good stuff occurs * in {@link org.bouncycastle.crypto.macs.I2PHMac} and - * {@link org.bouncycastle.crypto.digests.MD5Digest}. + * {@link net.i2p.crypto.Sha256Standalone}. * - * deprecated unused + * This should be compatible with javax.crypto.Mac.getInstance("HmacSHA256") + * but that is untested. + * + * deprecated used only by syndie */ public class HMAC256Generator extends HMACGenerator { public HMAC256Generator(I2PAppContext context) { super(context); } diff --git a/core/java/src/net/i2p/crypto/HMACGenerator.java b/core/java/src/net/i2p/crypto/HMACGenerator.java index 237c650550306ef5be52b57c64746d592202d203..3fe36c61e2c09eb89be4073817a3f440fa4e0569 100644 --- a/core/java/src/net/i2p/crypto/HMACGenerator.java +++ b/core/java/src/net/i2p/crypto/HMACGenerator.java @@ -3,41 +3,67 @@ package net.i2p.crypto; import java.util.Arrays; import java.util.concurrent.LinkedBlockingQueue; +// following are for main() tests +//import java.security.InvalidKeyException; +//import java.security.Key; +//import java.security.NoSuchAlgorithmException; +//import javax.crypto.spec.SecretKeySpec; +//import net.i2p.data.Base64; + import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; import net.i2p.data.Hash; import net.i2p.data.SessionKey; +import net.i2p.util.SimpleByteCache; import org.bouncycastle.crypto.digests.MD5Digest; import org.bouncycastle.crypto.Mac; import org.bouncycastle.crypto.macs.I2PHMac; /** - * Calculate the HMAC-MD5 of a key+message. All the good stuff occurs + * Calculate the HMAC-MD5-128 of a key+message. All the good stuff occurs * in {@link org.bouncycastle.crypto.macs.I2PHMac} and * {@link org.bouncycastle.crypto.digests.MD5Digest}. * + * Keys are always 32 bytes. + * This is used only by UDP. + * Use deprecated outside the router, this may move to router.jar. + * + * NOTE THIS IS NOT COMPATIBLE with javax.crypto.Mac.getInstance("HmacMD5") + * as we tell I2PHMac that the digest length is 32 bytes, so it generates + * a different result. + * + * Quote jrandom: + * "The HMAC is hardcoded to use SHA256 digest size + * for backwards compatability. next time we have a backwards + * incompatible change, we should update this." + * + * Does this mean he intended it to be compatible with MD5? + * See also 2005-07-05 status notes. + * */ public class HMACGenerator { - private I2PAppContext _context; /** set of available HMAC instances for calculate */ protected final LinkedBlockingQueue<I2PHMac> _available; - /** set of available byte[] buffers for verify */ - private final LinkedBlockingQueue<byte[]> _availableTmp; + /** + * @param context unused + */ public HMACGenerator(I2PAppContext context) { - _context = context; _available = new LinkedBlockingQueue(32); - _availableTmp = new LinkedBlockingQueue(32); } /** * Calculate the HMAC of the data with the given key + * + * @return the first 16 bytes contain the HMAC, the last 16 bytes are zero + * @deprecated unused */ public Hash calculate(SessionKey key, byte data[]) { if ((key == null) || (key.getData() == null) || (data == null)) throw new NullPointerException("Null arguments for HMAC"); - byte rv[] = new byte[Hash.HASH_LENGTH]; + byte rv[] = acquireTmp(); + Arrays.fill(rv, (byte)0x0); calculate(key, data, 0, data.length, rv, 0); return new Hash(rv); } @@ -52,10 +78,8 @@ public class HMACGenerator { I2PHMac mac = acquire(); mac.init(key.getData()); mac.update(data, offset, length); - //byte rv[] = new byte[Hash.HASH_LENGTH]; mac.doFinal(target, targetOffset); release(mac); - //return new Hash(rv); } /** @@ -77,7 +101,6 @@ public class HMACGenerator { mac.init(key.getData()); mac.update(curData, curOffset, curLength); byte rv[] = acquireTmp(); - //byte rv[] = new byte[Hash.HASH_LENGTH]; mac.doFinal(rv, 0); release(mac); @@ -93,6 +116,7 @@ public class HMACGenerator { // the HMAC is hardcoded to use SHA256 digest size // for backwards compatability. next time we have a backwards // incompatible change, we should update this by removing ", 32" + // SEE NOTES ABOVE return new I2PHMac(new MD5Digest(), 32); } @@ -100,17 +124,74 @@ public class HMACGenerator { _available.offer(mac); } - // temp buffers for verify(..) + /** + * Not really tmp, just from the byte array cache. + * Does NOT zero. + */ private byte[] acquireTmp() { - byte rv[] = _availableTmp.poll(); - if (rv != null) - Arrays.fill(rv, (byte)0x0); - else - rv = new byte[Hash.HASH_LENGTH]; + byte rv[] = SimpleByteCache.acquire(Hash.HASH_LENGTH); return rv; } private void releaseTmp(byte tmp[]) { - _availableTmp.offer(tmp); + SimpleByteCache.release(tmp); + } + + //private static final int RUNS = 100000; + + /** + * Test the BC and the JVM's implementations for speed + */ +/**** All this did was prove that we aren't compatible with standard HmacMD5 + public static void main(String args[]) { + if (args.length != 2) { + System.err.println("Usage: HMACGenerator keySeedString dataString"); + return; + } + + byte[] rand = SHA256Generator.getInstance().calculateHash(args[0].getBytes()).getData(); + byte[] data = args[1].getBytes(); + Key keyObj = new SecretKeySpec(rand, "HmacMD5"); + + byte[] keyBytes = keyObj.getEncoded(); + System.out.println("key bytes (" + keyBytes.length + ") is [" + Base64.encode(keyBytes) + "]"); + SessionKey key = new SessionKey(keyBytes); + System.out.println("session key is [" + key); + System.out.println("key object is [" + keyObj); + + HMACGenerator gen = new HMACGenerator(I2PAppContext.getGlobalContext()); + byte[] result = new byte[16]; + long start = System.currentTimeMillis(); + for (int i = 0; i < RUNS; i++) { + gen.calculate(key, data, 0, data.length, result, 0); + if (i == 0) + System.out.println("MAC [" + Base64.encode(result) + "]"); + } + long time = System.currentTimeMillis() - start; + System.out.println("Time for " + RUNS + " HMAC-MD5 computations:"); + System.out.println("BC time (ms): " + time); + + start = System.currentTimeMillis(); + javax.crypto.Mac mac; + try { + mac = javax.crypto.Mac.getInstance("HmacMD5"); + } catch (NoSuchAlgorithmException e) { + System.err.println("Fatal: " + e); + return; + } + for (int i = 0; i < RUNS; i++) { + try { + mac.init(keyObj); + } catch (InvalidKeyException e) { + System.err.println("Fatal: " + e); + } + byte[] sha = mac.doFinal(data); + if (i == 0) + System.out.println("MAC [" + Base64.encode(sha) + "]"); + } + time = System.currentTimeMillis() - start; + + System.out.println("JVM time (ms): " + time); } +****/ } diff --git a/core/java/src/net/i2p/crypto/KeyGenerator.java b/core/java/src/net/i2p/crypto/KeyGenerator.java index 7853063f32b66ce940da09fc15191d437fcbe322..689516be059a3b9fef5f4ec6f3abaa0bb0bc30ef 100644 --- a/core/java/src/net/i2p/crypto/KeyGenerator.java +++ b/core/java/src/net/i2p/crypto/KeyGenerator.java @@ -20,6 +20,7 @@ import net.i2p.data.SessionKey; import net.i2p.data.Signature; import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPublicKey; +import net.i2p.data.SimpleDataStructure; import net.i2p.util.Clock; import net.i2p.util.Log; import net.i2p.util.NativeBigInteger; @@ -29,18 +30,17 @@ import net.i2p.util.RandomSource; * @author jrandom */ public class KeyGenerator { - private Log _log; - private I2PAppContext _context; + private final Log _log; + private final I2PAppContext _context; public KeyGenerator(I2PAppContext context) { _log = context.logManager().getLog(KeyGenerator.class); _context = context; } + public static KeyGenerator getInstance() { return I2PAppContext.getGlobalContext().keyGenerator(); } - - /** Generate a private 256 bit session key * @return session key @@ -84,11 +84,11 @@ public class KeyGenerator { * index 1 is a PrivateKey * @return pair of keys */ - public Object[] generatePKIKeypair() { + public SimpleDataStructure[] generatePKIKeypair() { BigInteger a = new NativeBigInteger(PUBKEY_EXPONENT_SIZE, _context.random()); BigInteger aalpha = CryptoConstants.elgg.modPow(a, CryptoConstants.elgp); - Object[] keys = new Object[2]; + SimpleDataStructure[] keys = new SimpleDataStructure[2]; keys[0] = new PublicKey(); keys[1] = new PrivateKey(); byte[] k0 = aalpha.toByteArray(); @@ -97,8 +97,8 @@ public class KeyGenerator { // bigInteger.toByteArray returns SIGNED integers, but since they'return positive, // signed two's complement is the same as unsigned - ((PublicKey) keys[0]).setData(padBuffer(k0, PublicKey.KEYSIZE_BYTES)); - ((PrivateKey) keys[1]).setData(padBuffer(k1, PrivateKey.KEYSIZE_BYTES)); + keys[0].setData(padBuffer(k0, PublicKey.KEYSIZE_BYTES)); + keys[1].setData(padBuffer(k1, PrivateKey.KEYSIZE_BYTES)); return keys; } @@ -120,8 +120,8 @@ public class KeyGenerator { * index 1 is a SigningPrivateKey * @return pair of keys */ - public Object[] generateSigningKeypair() { - Object[] keys = new Object[2]; + public SimpleDataStructure[] generateSigningKeypair() { + SimpleDataStructure[] keys = new SimpleDataStructure[2]; BigInteger x = null; // make sure the random key is less than the DSA q @@ -135,8 +135,8 @@ public class KeyGenerator { byte k0[] = padBuffer(y.toByteArray(), SigningPublicKey.KEYSIZE_BYTES); byte k1[] = padBuffer(x.toByteArray(), SigningPrivateKey.KEYSIZE_BYTES); - ((SigningPublicKey) keys[0]).setData(k0); - ((SigningPrivateKey) keys[1]).setData(k1); + keys[0].setData(k0); + keys[1].setData(k1); return keys; } diff --git a/core/java/src/net/i2p/crypto/SHA256Generator.java b/core/java/src/net/i2p/crypto/SHA256Generator.java index a62f2129b87db3c109f0452221e7dadba31a7677..4224d2e2194138b0145244320e91ef70ab1e8667 100644 --- a/core/java/src/net/i2p/crypto/SHA256Generator.java +++ b/core/java/src/net/i2p/crypto/SHA256Generator.java @@ -32,6 +32,9 @@ public final class SHA256Generator { _useGnu = useGnu; } + /** + * @param context unused + */ public SHA256Generator(I2PAppContext context) { _digests = new LinkedBlockingQueue(32); } diff --git a/core/java/src/net/i2p/data/SessionKey.java b/core/java/src/net/i2p/data/SessionKey.java index 7621c2b8a7cdad0258464ddac9d00880dc30fdc0..bbe1e533c55ca71fd4ab8a3e148b1e332be0ad36 100644 --- a/core/java/src/net/i2p/data/SessionKey.java +++ b/core/java/src/net/i2p/data/SessionKey.java @@ -41,7 +41,7 @@ public class SessionKey extends SimpleDataStructure { */ @Override public void setData(byte[] data) { - _data = data; + super.setData(data); _preparedKey = null; } diff --git a/core/java/src/net/i2p/util/ReusableGZIPOutputStream.java b/core/java/src/net/i2p/util/ReusableGZIPOutputStream.java index bbc1c334a41a60c56818a48e35f2302cf9438631..f7665e2fc2b48970c3de68a1e4b80bbb558af32a 100644 --- a/core/java/src/net/i2p/util/ReusableGZIPOutputStream.java +++ b/core/java/src/net/i2p/util/ReusableGZIPOutputStream.java @@ -15,7 +15,9 @@ import net.i2p.data.DataHelper; */ public class ReusableGZIPOutputStream extends ResettableGZIPOutputStream { // Apache Harmony 5.0M13 Deflater doesn't work after reset() - private static final boolean ENABLE_CACHING = !System.getProperty("java.vendor").startsWith("Apache"); + // Neither does Android + private static final boolean ENABLE_CACHING = !(System.getProperty("java.vendor").startsWith("Apache") || + System.getProperty("java.vendor").contains("Android")); private static final LinkedBlockingQueue<ReusableGZIPOutputStream> _available; static { if (ENABLE_CACHING) diff --git a/core/java/src/org/bouncycastle/crypto/macs/I2PHMac.java b/core/java/src/org/bouncycastle/crypto/macs/I2PHMac.java index 125583613589391c50b042b8fc489d5762f9a790..b8d9c073ee818870ee9a6b440827753a24aff4f3 100644 --- a/core/java/src/org/bouncycastle/crypto/macs/I2PHMac.java +++ b/core/java/src/org/bouncycastle/crypto/macs/I2PHMac.java @@ -27,9 +27,10 @@ package org.bouncycastle.crypto.macs; */ //import org.bouncycastle.crypto.CipherParameters; -import java.util.ArrayList; import java.util.Arrays; +import net.i2p.util.SimpleByteCache; + import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.Mac; @@ -65,6 +66,11 @@ implements Mac { this(digest, digest.getDigestSize()); } + + /** + * @param sz override the digest's size + * SEE NOTES in HMACGenerator about why this isn't compatible with standard HmacMD5 + */ public I2PHMac( Digest digest, int sz) { @@ -165,28 +171,14 @@ implements Mac return len; } - /** - * list of buffers - index 0 is the cache for 32 byte arrays, while index 1 is the cache for 16 byte arrays - */ - private static ArrayList _tmpBuf[] = new ArrayList[] { new ArrayList(), new ArrayList() }; private static byte[] acquireTmp(int sz) { - byte rv[] = null; - synchronized (_tmpBuf[sz == 32 ? 0 : 1]) { - if (!_tmpBuf[sz == 32 ? 0 : 1].isEmpty()) - rv = (byte[])_tmpBuf[sz == 32 ? 0 : 1].remove(0); - } - if (rv != null) - Arrays.fill(rv, (byte)0x0); - else - rv = new byte[sz]; + byte[] rv = SimpleByteCache.acquire(sz); + Arrays.fill(rv, (byte)0x0); return rv; } + private static void releaseTmp(byte buf[]) { - if (buf == null) return; - synchronized (_tmpBuf[buf.length == 32 ? 0 : 1]) { - if (_tmpBuf[buf.length == 32 ? 0 : 1].size() < 100) - _tmpBuf[buf.length == 32 ? 0 : 1].add((Object)buf); - } + SimpleByteCache.release(buf); } /** diff --git a/history.txt b/history.txt index 53958417ef6d8a560417a5f7e8e938660a07c9ae..2260f8b26e86f7388124ee35db37ade617f3a49a 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,12 @@ +2011-06-02 zzz + * Android: Build fixes + * Crypto: + - HMAC Javadocs and cleanups + - HMAC Use SimpleByteCache + * ElGamalAESEngine: Fixups required after SessionKey enforcement + * Reseed: Give up on a seed after 90% of fetches fail + * SessionKey: Enforce data size and prevent reuse + 2011-06-02 sponge * Remove txt file in BOB.jar as per zzz's request. diff --git a/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java b/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java index b0a4cfc6d1ddbfdf9b781f74ff3f9493e35d6b97..6ac4b43673796dfa54083b45b13de47a6c885bda 100644 --- a/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java +++ b/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java @@ -150,12 +150,15 @@ public class DatabaseStoreMessage extends I2NPMessageImpl { int len = Hash.HASH_LENGTH + 1 + 4; // key+type+replyToken if (_replyToken > 0) len += 4 + Hash.HASH_LENGTH; // replyTunnel+replyGateway - if (_dbEntry.getType() == DatabaseEntry.KEY_TYPE_LEASESET) { + int type = _dbEntry.getType(); + if (type == DatabaseEntry.KEY_TYPE_LEASESET) { _byteCache = _dbEntry.toByteArray(); - } else if (_dbEntry.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO) { + } else if (type == DatabaseEntry.KEY_TYPE_ROUTERINFO) { byte uncompressed[] = _dbEntry.toByteArray(); _byteCache = DataHelper.compress(uncompressed); len += 2; + } else { + throw new IllegalStateException("Invalid key type " + type); } len += _byteCache.length; return len; @@ -166,7 +169,7 @@ public class DatabaseStoreMessage extends I2NPMessageImpl { if (_dbEntry == null) throw new I2NPMessageException("Missing entry"); int type = _dbEntry.getType(); if (type != DatabaseEntry.KEY_TYPE_LEASESET && type != DatabaseEntry.KEY_TYPE_ROUTERINFO) - throw new I2NPMessageException("Invalid key type"); + throw new I2NPMessageException("Invalid key type " + type); // Use the hash of the DatabaseEntry System.arraycopy(getKey().getData(), 0, out, curIndex, Hash.HASH_LENGTH); diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index f9275bada05932564a0fd36b6bd1946f3aa953a1..1245a6208605d2b497d70e4fec0f511d24c78cb2 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -311,8 +311,11 @@ public class Router { } public RouterInfo getRouterInfo() { return _routerInfo; } + public void setRouterInfo(RouterInfo info) { _routerInfo = info; + if (_log.shouldLog(Log.INFO)) + _log.info("setRouterInfo() : " + info, new Exception("I did it")); if (info != null) _context.jobQueue().addJob(new PersistRouterInfoJob(_context)); } @@ -614,6 +617,10 @@ public class Router { } } // hard and ugly + if (System.getProperty("wrapper.version") != null) + _log.log(Log.CRIT, "Restarting with new router identity"); + else + _log.log(Log.CRIT, "Shutting down because old router identity was invalid - restart I2P"); finalShutdown(EXIT_HARD_RESTART); } diff --git a/router/java/src/net/i2p/router/RouterContext.java b/router/java/src/net/i2p/router/RouterContext.java index 4ac21fce55f35f924779be56bfac6d662d53e22c..c7d54f88d21d60eb1b5541d5ef81b581f39c7226 100644 --- a/router/java/src/net/i2p/router/RouterContext.java +++ b/router/java/src/net/i2p/router/RouterContext.java @@ -121,7 +121,7 @@ public class RouterContext extends I2PAppContext { public void initAll() { if (getBooleanProperty("i2p.dummyClientFacade")) - System.err.println("i2p.dummpClientFacade currently unsupported"); + System.err.println("i2p.dummyClientFacade currently unsupported"); _clientManagerFacade = new ClientManagerFacadeImpl(this); // removed since it doesn't implement InternalClientManager for now //else diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index ea17ad55f3270be34b24fd56bd0b1495da601896..e1f87e077ded8c1235896b49923811522eadbbfe 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 15; + public final static long BUILD = 16; /** for example "-test" */ public final static String EXTRA = ""; diff --git a/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java b/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java index c2275a90d7197cb98bf4ea062749ce8c50fdf15f..8ca29317c3abcca0f504f10649d38f6b9ca3c465 100644 --- a/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java +++ b/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java @@ -65,7 +65,7 @@ public class PublishLocalRouterInfoJob extends JobImpl { try { getContext().netDb().publish(ri); } catch (IllegalArgumentException iae) { - _log.log(Log.CRIT, "Error publishing our identity - corrupt?", iae); + _log.log(Log.CRIT, "Error publishing our identity - corrupt? Restart required", iae); getContext().router().rebuildNewIdentity(); } } catch (DataFormatException dfe) { diff --git a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java index 3e1c0b0753c533f69ba5eff10d29cecb2c53647a..1fb70e36db6c0acc4945b8954e284252b8eda3b7 100644 --- a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java +++ b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java @@ -344,6 +344,9 @@ public class Reseeder { } catch (IOException e) { errors++; } + // Give up on this one after 10 with only 0 or 1 good + if (errors >= 10 && fetched <= 1) + break; } System.err.println("Reseed got " + fetched + " router infos from " + seedURL + " with " + errors + " errors");