From c14e52ceb54e8d9afd4ac329fbb9088bbc395a56 Mon Sep 17 00:00:00 2001 From: jrandom Date: Wed, 27 Sep 2006 06:00:33 +0000 Subject: [PATCH] 2006-09-27 jrandom * added HMAC-SHA256 * properly use CRLF with EepPost * suppress jbigi/jcpuid messages if jbigi.dontLog/jcpuid.dontLog is set * PBE session key generation (with 1000 rounds of SHA256) * misc SDK helper functions --- .../freenet/support/CPUInformation/CPUID.java | 2 +- core/java/src/net/i2p/I2PAppContext.java | 16 ++++++ .../src/net/i2p/crypto/HMAC256Generator.java | 51 +++++++++++++++++++ .../src/net/i2p/crypto/HMACGenerator.java | 4 +- .../java/src/net/i2p/crypto/KeyGenerator.java | 19 ++++++- core/java/src/net/i2p/data/DataHelper.java | 16 +++++- core/java/src/net/i2p/data/PrivateKey.java | 1 + core/java/src/net/i2p/data/PublicKey.java | 5 ++ core/java/src/net/i2p/util/EepPost.java | 47 +++++++++-------- .../src/net/i2p/util/NativeBigInteger.java | 2 +- history.txt | 9 +++- .../src/net/i2p/router/RouterVersion.java | 4 +- 12 files changed, 144 insertions(+), 32 deletions(-) create mode 100644 core/java/src/net/i2p/crypto/HMAC256Generator.java diff --git a/core/java/src/freenet/support/CPUInformation/CPUID.java b/core/java/src/freenet/support/CPUInformation/CPUID.java index 59a7f2cc4e..da50de64b1 100644 --- a/core/java/src/freenet/support/CPUInformation/CPUID.java +++ b/core/java/src/freenet/support/CPUInformation/CPUID.java @@ -32,7 +32,7 @@ public class CPUID { * initialization? this would otherwise use the Log component, but this makes * it easier for other systems to reuse this class */ - private static final boolean _doLog = true; + private static final boolean _doLog = System.getProperty("jcpuid.dontLog") == null; //.matches() is a java 1.4+ addition, using a simplified version for 1.3+ //private static final boolean isX86 = System.getProperty("os.arch").toLowerCase().matches("i?[x0-9]86(_64)?"); diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java index 9370a683cc..aca9ebdc1a 100644 --- a/core/java/src/net/i2p/I2PAppContext.java +++ b/core/java/src/net/i2p/I2PAppContext.java @@ -14,6 +14,7 @@ import net.i2p.crypto.DummyElGamalEngine; import net.i2p.crypto.DummyPooledRandomSource; import net.i2p.crypto.ElGamalAESEngine; import net.i2p.crypto.ElGamalEngine; +import net.i2p.crypto.HMAC256Generator; import net.i2p.crypto.HMACGenerator; import net.i2p.crypto.KeyGenerator; import net.i2p.crypto.PersistentSessionKeyManager; @@ -67,6 +68,7 @@ public class I2PAppContext { private AESEngine _AESEngine; private LogManager _logManager; private HMACGenerator _hmac; + private HMAC256Generator _hmac256; private SHA256Generator _sha; protected Clock _clock; // overridden in RouterContext private DSAEngine _dsa; @@ -82,6 +84,7 @@ public class I2PAppContext { private volatile boolean _AESEngineInitialized; private volatile boolean _logManagerInitialized; private volatile boolean _hmacInitialized; + private volatile boolean _hmac256Initialized; private volatile boolean _shaInitialized; protected volatile boolean _clockInitialized; // used in RouterContext private volatile boolean _dsaInitialized; @@ -353,6 +356,19 @@ public class I2PAppContext { _hmacInitialized = true; } } + + public HMAC256Generator hmac256() { + if (!_hmac256Initialized) initializeHMAC256(); + return _hmac256; + } + private void initializeHMAC256() { + synchronized (this) { + if (_hmac256 == null) { + _hmac256 = new HMAC256Generator(this); + } + _hmac256Initialized = true; + } + } /** * Our SHA256 instance (see the hmac discussion for why its context specific) diff --git a/core/java/src/net/i2p/crypto/HMAC256Generator.java b/core/java/src/net/i2p/crypto/HMAC256Generator.java new file mode 100644 index 0000000000..2d106298b5 --- /dev/null +++ b/core/java/src/net/i2p/crypto/HMAC256Generator.java @@ -0,0 +1,51 @@ +package net.i2p.crypto; + +import gnu.crypto.hash.Sha256Standalone; +import net.i2p.I2PAppContext; +import net.i2p.data.Base64; +import net.i2p.data.Hash; +import net.i2p.data.SessionKey; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.macs.HMac; + +/** + * Calculate the HMAC-SHA256 of a key+message. All the good stuff occurs + * in {@link org.bouncycastle.crypto.macs.HMac} and + * {@link org.bouncycastle.crypto.digests.MD5Digest}. + * + */ +public class HMAC256Generator extends HMACGenerator { + public HMAC256Generator(I2PAppContext context) { super(context); } + + protected HMac acquire() { + synchronized (_available) { + if (_available.size() > 0) + return (HMac)_available.remove(0); + } + // 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" + return new HMac(new Sha256ForMAC()); + } + + private class Sha256ForMAC extends Sha256Standalone implements Digest { + public String getAlgorithmName() { return "sha256 for hmac"; } + public int getDigestSize() { return 32; } + public int doFinal(byte[] out, int outOff) { + byte rv[] = digest(); + System.arraycopy(rv, 0, out, outOff, rv.length); + reset(); + return rv.length; + } + + } + + public static void main(String args[]) { + I2PAppContext ctx = I2PAppContext.getGlobalContext(); + byte data[] = new byte[64]; + ctx.random().nextBytes(data); + SessionKey key = ctx.keyGenerator().generateSessionKey(); + Hash mac = ctx.hmac256().calculate(key, data); + System.out.println(Base64.encode(mac.getData())); + } +} \ No newline at end of file diff --git a/core/java/src/net/i2p/crypto/HMACGenerator.java b/core/java/src/net/i2p/crypto/HMACGenerator.java index b369bb324a..fa853df9b3 100644 --- a/core/java/src/net/i2p/crypto/HMACGenerator.java +++ b/core/java/src/net/i2p/crypto/HMACGenerator.java @@ -20,7 +20,7 @@ import org.bouncycastle.crypto.macs.HMac; public class HMACGenerator { private I2PAppContext _context; /** set of available HMAC instances for calculate */ - private List _available; + protected List _available; /** set of available byte[] buffers for verify */ private List _availableTmp; @@ -85,7 +85,7 @@ public class HMACGenerator { return eq; } - private HMac acquire() { + protected HMac acquire() { synchronized (_available) { if (_available.size() > 0) return (HMac)_available.remove(0); diff --git a/core/java/src/net/i2p/crypto/KeyGenerator.java b/core/java/src/net/i2p/crypto/KeyGenerator.java index 8e3af0a514..a221f9ef13 100644 --- a/core/java/src/net/i2p/crypto/KeyGenerator.java +++ b/core/java/src/net/i2p/crypto/KeyGenerator.java @@ -9,10 +9,13 @@ package net.i2p.crypto; * */ +import gnu.crypto.hash.Sha256Standalone; import java.math.BigInteger; import net.i2p.I2PAppContext; +import net.i2p.data.Base64; import net.i2p.data.DataHelper; +import net.i2p.data.Hash; import net.i2p.data.PrivateKey; import net.i2p.data.PublicKey; import net.i2p.data.SessionKey; @@ -53,6 +56,18 @@ public class KeyGenerator { return key; } + private static final int PBE_ROUNDS = 1000; + /** PBE the passphrase with the salt */ + public SessionKey generateSessionKey(byte salt[], byte passphrase[]) { + byte salted[] = new byte[16+passphrase.length]; + System.arraycopy(salt, 0, salted, 0, Math.min(salt.length, 16)); + System.arraycopy(passphrase, 0, salted, 16, passphrase.length); + byte h[] = _context.sha().calculateHash(salted).getData(); + for (int i = 1; i < PBE_ROUNDS; i++) + _context.sha().calculateHash(h, 0, Hash.HASH_LENGTH, h, 0); + return new SessionKey(h); + } + /** standard exponent size */ private static final int PUBKEY_EXPONENT_SIZE_FULL = 2048; /** @@ -95,7 +110,7 @@ public class KeyGenerator { * @return the corresponding PublicKey object */ public static PublicKey getPublicKey(PrivateKey priv) { - BigInteger a = new NativeBigInteger(priv.toByteArray()); + BigInteger a = new NativeBigInteger(1, priv.toByteArray()); BigInteger aalpha = CryptoConstants.elgg.modPow(a, CryptoConstants.elgp); PublicKey pub = new PublicKey(); byte [] pubBytes = aalpha.toByteArray(); @@ -132,7 +147,7 @@ public class KeyGenerator { * @return a SigningPublicKey object */ public static SigningPublicKey getSigningPublicKey(SigningPrivateKey priv) { - BigInteger x = new NativeBigInteger(priv.toByteArray()); + BigInteger x = new NativeBigInteger(1, priv.toByteArray()); BigInteger y = CryptoConstants.dsag.modPow(x, CryptoConstants.dsap); SigningPublicKey pub = new SigningPublicKey(); byte [] pubBytes = padBuffer(y.toByteArray(), SigningPublicKey.KEYSIZE_BYTES); diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java index d3b7cbb06e..6a67774754 100644 --- a/core/java/src/net/i2p/data/DataHelper.java +++ b/core/java/src/net/i2p/data/DataHelper.java @@ -9,6 +9,7 @@ package net.i2p.data; * */ +import gnu.crypto.hash.Sha256Standalone; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; @@ -770,9 +771,11 @@ public class DataHelper { * Read a newline delimited line from the stream, returning the line (without * the newline), or null if EOF reached before the newline was found */ - public static String readLine(InputStream in) throws IOException { + public static String readLine(InputStream in) throws IOException { return readLine(in, (Sha256Standalone)null); } + /** update the hash along the way */ + public static String readLine(InputStream in, Sha256Standalone hash) throws IOException { StringBuffer buf = new StringBuffer(128); - boolean ok = readLine(in, buf); + boolean ok = readLine(in, buf, hash); if (ok) return buf.toString(); else @@ -785,8 +788,13 @@ public class DataHelper { * newline was found */ public static boolean readLine(InputStream in, StringBuffer buf) throws IOException { + return readLine(in, buf, null); + } + /** update the hash along the way */ + public static boolean readLine(InputStream in, StringBuffer buf, Sha256Standalone hash) throws IOException { int c = -1; while ( (c = in.read()) != -1) { + if (hash != null) hash.update((byte)c); if (c == '\n') break; buf.append((char)c); @@ -797,6 +805,10 @@ public class DataHelper { return true; } + public static void write(OutputStream out, byte data[], Sha256Standalone hash) throws IOException { + hash.update(data); + out.write(data); + } public static List sortStructures(Collection dataStructures) { if (dataStructures == null) return new ArrayList(); diff --git a/core/java/src/net/i2p/data/PrivateKey.java b/core/java/src/net/i2p/data/PrivateKey.java index d076adaf64..6b4f923f8f 100644 --- a/core/java/src/net/i2p/data/PrivateKey.java +++ b/core/java/src/net/i2p/data/PrivateKey.java @@ -32,6 +32,7 @@ public class PrivateKey extends DataStructureImpl { public PrivateKey() { setData(null); } + public PrivateKey(byte data[]) { setData(data); } /** constructs from base64 * @param base64Data a string of base64 data (the output of .toBase64() called diff --git a/core/java/src/net/i2p/data/PublicKey.java b/core/java/src/net/i2p/data/PublicKey.java index aaf5a65ae3..2f271ac5f1 100644 --- a/core/java/src/net/i2p/data/PublicKey.java +++ b/core/java/src/net/i2p/data/PublicKey.java @@ -31,6 +31,11 @@ public class PublicKey extends DataStructureImpl { public PublicKey() { setData(null); } + public PublicKey(byte data[]) { + if ( (data == null) || (data.length != KEYSIZE_BYTES) ) + throw new IllegalArgumentException("Data must be specified, and the correct size"); + setData(data); + } /** constructs from base64 * @param base64Data a string of base64 data (the output of .toBase64() called diff --git a/core/java/src/net/i2p/util/EepPost.java b/core/java/src/net/i2p/util/EepPost.java index b3db1762c9..3b45280528 100644 --- a/core/java/src/net/i2p/util/EepPost.java +++ b/core/java/src/net/i2p/util/EepPost.java @@ -13,6 +13,7 @@ import net.i2p.util.Log; public class EepPost { private I2PAppContext _context; private Log _log; + private static final String CRLF = "\r\n"; public EepPost() { this(I2PAppContext.getGlobalContext()); @@ -65,6 +66,7 @@ public class EepPost { _onCompletion = onCompletion; } public void run() { + if (_log.shouldLog(Log.DEBUG)) _log.debug("Running the post task"); Socket s = null; try { URL u = new URL(_url); @@ -81,17 +83,20 @@ public class EepPost { _proxyPort = p; } + if (_log.shouldLog(Log.DEBUG)) _log.debug("Connecting to the server/proxy..."); s = new Socket(_proxyHost, _proxyPort); + if (_log.shouldLog(Log.DEBUG)) _log.debug("Connected"); OutputStream out = s.getOutputStream(); String sep = getSeparator(); long length = calcContentLength(sep, _fields); + if (_log.shouldLog(Log.DEBUG)) _log.debug("Separator: " + sep + " content length: " + length); String header = getHeader(isProxy, path, h, p, sep, length); if (_log.shouldLog(Log.DEBUG)) _log.debug("Header: \n" + header); out.write(header.getBytes()); out.flush(); if (false) { - out.write(("--" + sep + "\ncontent-disposition: form-data; name=\"field1\"\n\nStuff goes here\n--" + sep + "--\n").getBytes()); + out.write(("--" + sep + CRLF + "content-disposition: form-data; name=\"field1\"" + CRLF + CRLF + "Stuff goes here" + CRLF + "--" + sep + "--" + CRLF).getBytes()); } else { sendFields(out, sep, _fields); } @@ -121,18 +126,18 @@ public class EepPost { Object val = fields.get(key); if (val instanceof File) { File f = (File)val; - len += ("--" + sep + "\nContent-Disposition: form-data; name=\"" + key + "\"; filename=\"" + f.getName() + "\"\n").length(); + len += ("--" + sep + CRLF + "Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + f.getName() + "\"" + CRLF).length(); //len += ("Content-length: " + f.length() + "\n").length(); - len += ("Content-Type: application/octet-stream\n\n").length(); + len += ("Content-Type: application/octet-stream" + CRLF + CRLF).length(); len += f.length(); - len += 1; // nl + len += CRLF.length(); // nl } else { - len += ("--" + sep + "\nContent-Disposition: form-data; name=\"" + key + "\"\n\n").length(); + len += ("--" + sep + CRLF + "Content-Disposition: form-data; name=\"" + key + "\"" + CRLF + CRLF).length(); len += val.toString().length(); - len += 1; // nl + len += CRLF.length(); // nl } } - len += 2 + sep.length() + 2; + len += 2 + sep.length() + 2 + CRLF.length(); //2 + sep.length() + 2; //len += 2; return len; } @@ -145,29 +150,29 @@ public class EepPost { else sendField(out, separator, field, val.toString()); } - out.write(("--" + separator + "--\n").getBytes()); + out.write(("--" + separator + "--" + CRLF).getBytes()); } private void sendFile(OutputStream out, String separator, String field, File file) throws IOException { long len = file.length(); - out.write(("--" + separator + "\n").getBytes()); - out.write(("Content-Disposition: form-data; name=\"" + field + "\"; filename=\"" + file.getName() + "\"\n").getBytes()); + out.write(("--" + separator + CRLF).getBytes()); + out.write(("Content-Disposition: form-data; name=\"" + field + "\"; filename=\"" + file.getName() + "\"" + CRLF).getBytes()); //out.write(("Content-length: " + len + "\n").getBytes()); - out.write(("Content-Type: application/octet-stream\n\n").getBytes()); + out.write(("Content-Type: application/octet-stream" + CRLF + CRLF).getBytes()); FileInputStream in = new FileInputStream(file); byte buf[] = new byte[1024]; int read = -1; while ( (read = in.read(buf)) != -1) out.write(buf, 0, read); - out.write("\n".getBytes()); + out.write(CRLF.getBytes()); in.close(); } private void sendField(OutputStream out, String separator, String field, String val) throws IOException { - out.write(("--" + separator + "\n").getBytes()); - out.write(("Content-Disposition: form-data; name=\"" + field + "\"\n\n").getBytes()); + out.write(("--" + separator + CRLF).getBytes()); + out.write(("Content-Disposition: form-data; name=\"" + field + "\"" + CRLF + CRLF).getBytes()); out.write(val.getBytes()); - out.write("\n".getBytes()); + out.write(CRLF.getBytes()); } private String getHeader(boolean isProxy, String path, String host, int port, String separator, long length) { @@ -179,16 +184,16 @@ public class EepPost { buf.append(":").append(port); } buf.append(path); - buf.append(" HTTP/1.1\n"); + buf.append(" HTTP/1.1" + CRLF); buf.append("Host: ").append(host); if (port != 80) buf.append(":").append(port); - buf.append("\n"); - buf.append("Connection: close\n"); - buf.append("Content-length: ").append(length).append("\n"); + buf.append(CRLF); + buf.append("Connection: close" + CRLF); + buf.append("Content-length: ").append(length).append(CRLF); buf.append("Content-type: multipart/form-data, boundary=").append(separator); - buf.append("\n"); - buf.append("\n"); + buf.append(CRLF); + buf.append(CRLF); return buf.toString(); } diff --git a/core/java/src/net/i2p/util/NativeBigInteger.java b/core/java/src/net/i2p/util/NativeBigInteger.java index 589abb7333..8803b684b6 100644 --- a/core/java/src/net/i2p/util/NativeBigInteger.java +++ b/core/java/src/net/i2p/util/NativeBigInteger.java @@ -91,7 +91,7 @@ public class NativeBigInteger extends BigInteger { * initialization? this would otherwise use the Log component, but this makes * it easier for other systems to reuse this class */ - private static final boolean _doLog = true; + private static final boolean _doLog = System.getProperty("jbigi.dontLog") == null; private final static String JBIGI_OPTIMIZATION_K6 = "k6"; private final static String JBIGI_OPTIMIZATION_K6_2 = "k62"; diff --git a/history.txt b/history.txt index 9029ccda38..6f3c6153dd 100644 --- a/history.txt +++ b/history.txt @@ -1,4 +1,11 @@ -$Id: history.txt,v 1.527 2006-09-26 23:31:34 complication Exp $ +$Id: history.txt,v 1.528 2006-09-26 23:44:13 complication Exp $ + +2006-09-27 jrandom + * added HMAC-SHA256 + * properly use CRLF with EepPost + * suppress jbigi/jcpuid messages if jbigi.dontLog/jcpuid.dontLog is set + * PBE session key generation (with 1000 rounds of SHA256) + * misc SDK helper functions 2006-09-26 Complication * Take back another inadverent logging change in NTCPConnection diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 6859313069..ffe8aac99b 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.462 $ $Date: 2006-09-25 22:11:39 $"; + public final static String ID = "$Revision: 1.463 $ $Date: 2006-09-26 23:02:15 $"; public final static String VERSION = "0.6.1.25"; - public final static long BUILD = 9; + public final static long BUILD = 10; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID);