diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java index 46159ce01c..561f8bebe5 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java @@ -20,7 +20,7 @@ import org.tanukisoftware.wrapper.WrapperManager; */ public class ConfigServiceHandler extends FormHandler { - private class UpdateWrapperManagerTask implements Runnable { + public static class UpdateWrapperManagerTask implements Runnable { private int _exitCode; public UpdateWrapperManagerTask(int exitCode) { _exitCode = exitCode; diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java new file mode 100644 index 0000000000..dd6cdc9b1e --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java @@ -0,0 +1,83 @@ +package net.i2p.router.web; + +import net.i2p.data.DataHelper; + +/** + * + */ +public class ConfigUpdateHandler extends FormHandler { + private String _newsURL; + private long _refreshFrequency; + private String _updateURL; + private String _updatePolicy; + private boolean _updateThroughProxy; + private String _trustedKeys; + + public static final String PROP_NEWS_URL = "router.newsURL"; + public static final String DEFAULT_NEWS_URL = "http://www.i2p/routerConsoleNews.xml"; + public static final String PROP_REFRESH_FREQUENCY = "router.newsRefreshFrequency"; + public static final String DEFAULT_REFRESH_FREQUENCY = 24*60*60*1000 + ""; + public static final String PROP_UPDATE_URL = "router.updateURL"; + public static final String DEFAULT_UPDATE_URL = "http://dev.i2p.net/i2p/i2pupdate.sud"; + public static final String PROP_UPDATE_POLICY = "router.updatePolicy"; + public static final String DEFAULT_UPDATE_POLICY = "notify"; + public static final String PROP_SHOULD_PROXY = "router.updateThroughProxy"; + public static final String DEFAULT_SHOULD_PROXY = Boolean.FALSE.toString(); + public static final String PROP_PROXY_HOST = "router.updateProxyHost"; + public static final String DEFAULT_PROXY_HOST = "localhost"; + public static final String PROP_PROXY_PORT = "router.updateProxyPort"; + public static final String DEFAULT_PROXY_PORT = "4444"; + + protected void processForm() { + if ( (_newsURL != null) && (_newsURL.length() > 0) ) { + String oldURL = _context.router().getConfigSetting(PROP_NEWS_URL); + if ( (oldURL == null) || (!_newsURL.equals(oldURL)) ) { + _context.router().setConfigSetting(PROP_NEWS_URL, _newsURL); + addFormNotice("Updating news URL to " + _newsURL); + } + } + if ( (_updateURL != null) && (_updateURL.length() > 0) ) { + String oldURL = _context.router().getConfigSetting(PROP_UPDATE_URL); + if ( (oldURL == null) || (!_updateURL.equals(oldURL)) ) { + _context.router().setConfigSetting(PROP_UPDATE_URL, _updateURL); + addFormNotice("Updating update URL to " + _updateURL); + } + } + + if (_updateThroughProxy) { + _context.router().setConfigSetting(PROP_SHOULD_PROXY, Boolean.TRUE.toString()); + } else { + _context.router().setConfigSetting(PROP_SHOULD_PROXY, Boolean.FALSE.toString()); + } + + String oldFreqStr = _context.router().getConfigSetting(PROP_REFRESH_FREQUENCY); + long oldFreq = -1; + if (oldFreqStr != null) + try { oldFreq = Long.parseLong(oldFreqStr); } catch (NumberFormatException nfe) {} + if (_refreshFrequency != oldFreq) { + _context.router().setConfigSetting(PROP_REFRESH_FREQUENCY, ""+_refreshFrequency); + addFormNotice("Updating refresh frequency to " + DataHelper.formatDuration(_refreshFrequency)); + } + + if ( (_updatePolicy != null) && (_updatePolicy.length() > 0) ) { + String oldPolicy = _context.router().getConfigSetting(PROP_UPDATE_POLICY); + if ( (oldPolicy == null) || (!_updatePolicy.equals(oldPolicy)) ) { + _context.router().setConfigSetting(PROP_UPDATE_POLICY, _updatePolicy); + addFormNotice("Updating update policy to " + _updatePolicy); + } + } + + // should save the keys... + + _context.router().saveConfig(); + } + + public void setNewsURL(String url) { _newsURL = url; } + public void setRefreshFrequency(String freq) { + try { _refreshFrequency = Long.parseLong(freq); } catch (NumberFormatException nfe) {} + } + public void setUpdateURL(String url) { _updateURL = url; } + public void setUpdatePolicy(String policy) { _updatePolicy = policy; } + public void setTrustedKeys(String keys) { _trustedKeys = keys; } + public void setUpdateThroughProxy(String foo) { _updateThroughProxy = true; } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHelper.java new file mode 100644 index 0000000000..1f35886372 --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHelper.java @@ -0,0 +1,76 @@ +package net.i2p.router.web; + +import java.util.List; +import net.i2p.crypto.TrustedUpdate; +import net.i2p.router.RouterContext; + +public class ConfigUpdateHelper { + private RouterContext _context; + /** + * Configure this bean to query a particular router context + * + * @param contextId begging few characters of the routerHash, or null to pick + * the first one we come across. + */ + public void setContextId(String contextId) { + try { + _context = ContextHelper.getContext(contextId); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + public ConfigUpdateHelper() {} + + public boolean updateAvailable() { + return true; + } + + public String getNewsURL() { + String url = _context.getProperty(ConfigUpdateHandler.PROP_NEWS_URL); + if (url != null) + return url; + else + return ConfigUpdateHandler.DEFAULT_NEWS_URL; + } + public String getUpdateURL() { + String url = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_URL); + if (url != null) + return url; + else + return ConfigUpdateHandler.DEFAULT_UPDATE_URL; + } + + public String getUpdateThroughProxy() { + String proxy = _context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY); + if (Boolean.valueOf(proxy).booleanValue()) + return ""; + else + + return ""; + } + + public String getRefreshFrequencySelectBox() { + return ""; + } + public String getUpdatePolicySelectBox() { + return ""; + } + public String getTrustedKeys() { + StringBuffer buf = new StringBuffer(1024); + TrustedUpdate up = new TrustedUpdate(_context); + List keys = up.getTrustedKeys(); + for (int i = 0; i < keys.size(); i++) + buf.append((String)keys.get(i)).append('\n'); + return buf.toString(); + } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java index 395c6c5060..dc878f555f 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java @@ -96,7 +96,7 @@ public class SummaryHelper { public boolean allowReseed() { return (_context.netDb().getKnownRouters() < 10); } - + /** * Retrieve amount of used memory. * @@ -467,4 +467,6 @@ public class SummaryHelper { return _context.throttle().getTunnelLag() + "ms"; } + + public boolean updateAvailable() { return true; } } \ No newline at end of file diff --git a/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java new file mode 100644 index 0000000000..c2a1535ce7 --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java @@ -0,0 +1,157 @@ +package net.i2p.router.web; + +import java.io.File; +import java.text.DecimalFormat; +import net.i2p.crypto.TrustedUpdate; +import net.i2p.router.Router; +import net.i2p.router.RouterContext; +import net.i2p.util.I2PThread; +import net.i2p.util.EepGet; +import net.i2p.util.Log; + +/** + * Handle the request to update the router by firing off an EepGet call and + * displaying its status to anyone who asks. After the download completes, + * it is verified with the TrustedUpdate, and if it is authentic, the router + * is restarted. + * + */ +public class UpdateHandler { + private static UpdateRunner _updateRunner; + private RouterContext _context; + private Log _log; + private DecimalFormat _pct = new DecimalFormat("00.0%"); + + private static final String SIGNED_UPDATE_FILE = "i2pupdate.sud"; + + /** + * Configure this bean to query a particular router context + * + * @param contextId begging few characters of the routerHash, or null to pick + * the first one we come across. + */ + public void setContextId(String contextId) { + try { + _context = ContextHelper.getContext(contextId); + _log = _context.logManager().getLog(UpdateHandler.class); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + + public void setUpdateNonce(String nonce) { + if (nonce == null) return; + if (nonce.equals(System.getProperty("net.i2p.router.web.UpdateHandler.nonce")) || + nonce.equals(System.getProperty("net.i2p.router.web.UpdateHandler.noncePrev"))) { + synchronized (UpdateHandler.class) { + if (_updateRunner == null) + _updateRunner = new UpdateRunner(); + if (_updateRunner.isRunning()) { + return; + } else { + System.setProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "true"); + I2PThread update = new I2PThread(_updateRunner, "Update"); + update.start(); + } + } + } + } + + public String getStatus() { + return _updateRunner.getStatus(); + } + + public class UpdateRunner implements Runnable, EepGet.StatusListener { + private boolean _isRunning; + private String _status; + private long _startedOn; + private long _written; + public UpdateRunner() { + _isRunning = false; + _status = "Updating
"; + } + public boolean isRunning() { return _isRunning; } + public String getStatus() { return _status; } + public void run() { + _isRunning = true; + update(); + System.setProperty("net.i2p.router.web.ReseedHandler.updateInProgress", "false"); + _isRunning = false; + } + private void update() { + _startedOn = -1; + _status = "Updating
"; + String updateURL = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_URL, ConfigUpdateHandler.DEFAULT_UPDATE_URL); + boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue(); + String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST); + String port = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_PORT, ConfigUpdateHandler.DEFAULT_PROXY_PORT); + int proxyPort = -1; + try { + proxyPort = Integer.parseInt(port); + } catch (NumberFormatException nfe) { + System.setProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false"); + return; + } + try { + EepGet get = null; + if (shouldProxy) + get = new EepGet(_context, proxyHost, proxyPort, 10, SIGNED_UPDATE_FILE, updateURL); + else + get = new EepGet(_context, 10, SIGNED_UPDATE_FILE, updateURL); + get.addStatusListener(UpdateRunner.this); + _startedOn = _context.clock().now(); + get.fetch(); + } catch (Throwable t) { + _context.logManager().getLog(UpdateHandler.class).error("Error updating", t); + System.setProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false"); + } + } + + public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Attempt failed on " + url, cause); + _written = 0; + // ignored + } + public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) { + _written += currentWrite; + StringBuffer buf = new StringBuffer(64); + buf.append("Updating "); + double pct = ((double)alreadyTransferred + (double)_written) / ((double)alreadyTransferred + (double)bytesRemaining); + synchronized (_pct) { + buf.append(_pct.format(pct)); + } + buf.append(":
\n").append(_written+alreadyTransferred); + buf.append(" transferred
"); + _status = buf.toString(); + } + public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile) { + _status = "Update downloaded
"; + TrustedUpdate up = new TrustedUpdate(_context); + boolean ok = up.migrateVerified(SIGNED_UPDATE_FILE, "i2pupdate.zip"); + File f = new File(SIGNED_UPDATE_FILE); + f.delete(); + if (ok) { + _log.log(Log.CRIT, "Update was VERIFIED, restarting to install it"); + _status = "Update verified
Restarting
"; + restart(); + } else { + _log.log(Log.CRIT, "Update was INVALID - have you changed your keys?"); + System.setProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false"); + } + } + public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) { + _log.log(Log.CRIT, "Update did not download completely (" + bytesTransferred + " with " + + bytesRemaining + " after " + currentAttempt + " tries)"); + + _status = "Transfer failed
"; + System.setProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false"); + } + } + + private void restart() { + _context.router().addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_GRACEFUL_RESTART)); + _context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART); + } +} diff --git a/apps/routerconsole/jsp/confignav.jsp b/apps/routerconsole/jsp/confignav.jsp index 21dbf7aac9..0139bbfff9 100644 --- a/apps/routerconsole/jsp/confignav.jsp +++ b/apps/routerconsole/jsp/confignav.jsp @@ -2,9 +2,11 @@ %>Network | <% } else { %>Network | <% } if (request.getRequestURI().indexOf("configservice.jsp") != -1) { %>Service | <% } else { %>Service | <% } + if (request.getRequestURI().indexOf("configupdate.jsp") != -1) { + %>Update | <% } else { %>Update | <% } if (request.getRequestURI().indexOf("configtunnels.jsp") != -1) { %>Tunnels | <% } else { %>Tunnels | <% } if (request.getRequestURI().indexOf("configlogging.jsp") != -1) { %>Logging | <% } else { %>Logging | <% } if (request.getRequestURI().indexOf("configadvanced.jsp") != -1) { - %>Advanced | <% } else { %>Advanced | <% } %> + %>Advanced<% } else { %>Advanced<% } %> diff --git a/apps/routerconsole/jsp/configupdate.jsp b/apps/routerconsole/jsp/configupdate.jsp new file mode 100644 index 0000000000..15f8360793 --- /dev/null +++ b/apps/routerconsole/jsp/configupdate.jsp @@ -0,0 +1,49 @@ +<%@page contentType="text/html"%> +<%@page pageEncoding="UTF-8"%> + + + +I2P Router Console - config update + + + +<%@include file="nav.jsp" %> +<%@include file="summary.jsp" %> + +
+ <%@include file="confignav.jsp" %> + + + + " /> + + + + + " /> + +
+ <% String prev = System.getProperty("net.i2p.router.web.ConfigUpdateHandler.nonce"); + if (prev != null) System.setProperty("net.i2p.router.web.ConfigUpdateHandler.noncePrev", prev); + System.setProperty("net.i2p.router.web.ConfigUpdateHandler.nonce", new java.util.Random().nextLong()+""); %> + " /> + + News URL: + ">
+ Refresh frequency: +
+ Update URL: + ">
+ Update policy: +
+ Update anonymously? +
+ + Trusted keys: + + + +
+ + + diff --git a/apps/routerconsole/jsp/summary.jsp b/apps/routerconsole/jsp/summary.jsp index e6bb510658..20dbdc3f7e 100644 --- a/apps/routerconsole/jsp/summary.jsp +++ b/apps/routerconsole/jsp/summary.jsp @@ -4,6 +4,9 @@ + + +" />
General
@@ -11,8 +14,24 @@ Version:
Uptime:
Now:
- Memory:
-
+ Memory:
<% + if (helper.updateAvailable()) { + if ("true".equals(System.getProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false"))) { + out.print(update.getStatus()); + } else { + long nonce = new java.util.Random().nextLong(); + String prev = System.getProperty("net.i2p.router.web.UpdateHandler.nonce"); + if (prev != null) System.setProperty("net.i2p.router.web.UpdateHandler.noncePrev", prev); + System.setProperty("net.i2p.router.web.UpdateHandler.nonce", nonce+""); + String uri = request.getRequestURI(); + if (uri.indexOf('?') > 0) + uri = uri + "&updateNonce=" + nonce; + else + uri = uri + "?updateNonce=" + nonce; + out.print(" Update"); + } + } + %>
Peers
Active: /
diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java index c36f45eca1..c25adcc02c 100644 --- a/core/java/src/net/i2p/I2PAppContext.java +++ b/core/java/src/net/i2p/I2PAppContext.java @@ -91,7 +91,6 @@ public class I2PAppContext { public static I2PAppContext getGlobalContext() { synchronized (I2PAppContext.class) { if (_globalAppContext == null) { - System.err.println("*** Building a seperate global context!"); _globalAppContext = new I2PAppContext(false, null); } } diff --git a/core/java/src/net/i2p/crypto/DSAEngine.java b/core/java/src/net/i2p/crypto/DSAEngine.java index 56ea80c45a..3190b7651f 100644 --- a/core/java/src/net/i2p/crypto/DSAEngine.java +++ b/core/java/src/net/i2p/crypto/DSAEngine.java @@ -29,10 +29,13 @@ package net.i2p.crypto; * POSSIBILITY OF SUCH DAMAGE. */ +import java.io.InputStream; +import java.io.IOException; import java.math.BigInteger; import java.util.Arrays; import net.i2p.I2PAppContext; +import net.i2p.data.DataHelper; import net.i2p.data.Hash; import net.i2p.data.Signature; import net.i2p.data.SigningPrivateKey; @@ -55,6 +58,12 @@ public class DSAEngine { return verifySignature(signature, signedData, 0, signedData.length, verifyingKey); } public boolean verifySignature(Signature signature, byte signedData[], int offset, int size, SigningPublicKey verifyingKey) { + return verifySignature(signature, calculateHash(signedData, offset, size), verifyingKey); + } + public boolean verifySignature(Signature signature, InputStream in, SigningPublicKey verifyingKey) { + return verifySignature(signature, calculateHash(in), verifyingKey); + } + public boolean verifySignature(Signature signature, Hash hash, SigningPublicKey verifyingKey) { long start = _context.clock().now(); try { @@ -72,7 +81,7 @@ public class DSAEngine { BigInteger r = new NativeBigInteger(1, rbytes); BigInteger y = new NativeBigInteger(1, verifyingKey.getData()); BigInteger w = s.modInverse(CryptoConstants.dsaq); - byte data[] = calculateHash(signedData, offset, size).getData(); + byte data[] = hash.getData(); NativeBigInteger bi = new NativeBigInteger(1, data); BigInteger u1 = bi.multiply(w).mod(CryptoConstants.dsaq); BigInteger u2 = r.multiply(w).mod(CryptoConstants.dsaq); @@ -99,6 +108,18 @@ public class DSAEngine { } public Signature sign(byte data[], int offset, int length, SigningPrivateKey signingKey) { if ((signingKey == null) || (data == null) || (data.length <= 0)) return null; + Hash h = calculateHash(data, offset, length); + return sign(h, signingKey); + } + + public Signature sign(InputStream in, SigningPrivateKey signingKey) { + if ((signingKey == null) || (in == null) ) return null; + Hash h = calculateHash(in); + return sign(h, signingKey); + } + + public Signature sign(Hash hash, SigningPrivateKey signingKey) { + if ((signingKey == null) || (hash == null)) return null; long start = _context.clock().now(); Signature sig = new Signature(); @@ -110,11 +131,8 @@ public class DSAEngine { BigInteger r = CryptoConstants.dsag.modPow(k, CryptoConstants.dsap).mod(CryptoConstants.dsaq); BigInteger kinv = k.modInverse(CryptoConstants.dsaq); - Hash h = calculateHash(data, offset, length); - if (h == null) return null; - - BigInteger M = new NativeBigInteger(1, h.getData()); + BigInteger M = new NativeBigInteger(1, hash.getData()); BigInteger x = new NativeBigInteger(1, signingKey.getData()); BigInteger s = (kinv.multiply(M.add(x.multiply(r)))).mod(CryptoConstants.dsaq); @@ -157,141 +175,27 @@ public class DSAEngine { return sig; } - - private int[] H0 = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0}; - - private Hash calculateHash(byte[] source, int offset, int len) { - long length = len * 8; - int k = 448 - (int) ((length + 1) % 512); - if (k < 0) { - k += 512; - } - int padbytes = k / 8; - int wordlength = len / 4 + padbytes / 4 + 3; - int[] M0 = new int[wordlength]; - int wordcount = 0; - int x = 0; - for (x = 0; x < (len / 4) * 4; x += 4) { - M0[wordcount] = source[offset + x] << 24 >>> 24 << 24; - M0[wordcount] |= source[offset + x + 1] << 24 >>> 24 << 16; - M0[wordcount] |= source[offset + x + 2] << 24 >>> 24 << 8; - M0[wordcount] |= source[offset + x + 3] << 24 >>> 24 << 0; - wordcount++; - } - - switch (len - (wordcount + 1) * 4 + 4) { - case 0: - M0[wordcount] |= 0x80000000; - break; - case 1: - M0[wordcount] = source[offset + x] << 24 >>> 24 << 24; - M0[wordcount] |= 0x00800000; - break; - case 2: - M0[wordcount] = source[offset + x] << 24 >>> 24 << 24; - M0[wordcount] |= source[offset + x + 1] << 24 >>> 24 << 16; - M0[wordcount] |= 0x00008000; - break; - case 3: - M0[wordcount] = source[offset + x] << 24 >>> 24 << 24; - M0[wordcount] |= source[offset + x + 1] << 24 >>> 24 << 16; - M0[wordcount] |= source[offset + x + 2] << 24 >>> 24 << 8; - M0[wordcount] |= 0x00000080; - break; - } - M0[wordlength - 2] = (int) (length >>> 32); - M0[wordlength - 1] = (int) (length); - int[] H = new int[5]; - for (x = 0; x < 5; x++) { - H[x] = H0[x]; - } - int blocks = M0.length / 16; - - int[] W = new int[80]; - for (int bl = 0; bl < blocks; bl++) { - int a = H[0]; - int b = H[1]; - int c = H[2]; - int d = H[3]; - int e = H[4]; - - Arrays.fill(W, 0); - - for (x = 0; x < 80; x++) { - if (x < 16) { - W[x] = M0[bl * 16 + x]; - } else { - W[x] = ROTL(1, W[x - 3] ^ W[x - 8] ^ W[x - 14] ^ W[x - 16]); - } + + public Hash calculateHash(InputStream in) { + SHA1 digest = new SHA1(); + byte buf[] = new byte[64]; + int read = 0; + try { + while ( (read = in.read(buf)) != -1) { + digest.engineUpdate(buf, 0, read); } - - for (x = 0; x < 80; x++) { - int T = add(ROTL(5, a), add(f(x, b, c, d), add(e, add(k(x), W[x])))); - e = d; - d = c; - c = ROTL(30, b); - b = a; - a = T; - } - - H[0] = add(a, H[0]); - H[1] = add(b, H[1]); - H[2] = add(c, H[2]); - H[3] = add(d, H[3]); - H[4] = add(e, H[4]); + } catch (IOException ioe) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Unable to hash the stream", ioe); + return null; } - - byte[] hashbytes = new byte[20]; - for (x = 0; x < 5; x++) { - hashbytes[x * 4] = (byte) (H[x] << 0 >>> 24); - hashbytes[x * 4 + 1] = (byte) (H[x] << 8 >>> 24); - hashbytes[x * 4 + 2] = (byte) (H[x] << 16 >>> 24); - hashbytes[x * 4 + 3] = (byte) (H[x] << 24 >>> 24); - } - Hash hash = new Hash(); - hash.setData(hashbytes); - return hash; + return new Hash(digest.engineDigest()); } - private int k(int t) { - if (t > -1 && t < 20) { - return 0x5a827999; - } else if (t > 19 && t < 40) { - return 0x6ed9eba1; - } else if (t > 39 && t < 60) { - return 0x8f1bbcdc; - } else if (t > 59 && t < 80) { return 0xca62c1d6; } - return 0x00000000; - } - - private int f(int t, int x, int y, int z) { - if (t > -1 && t < 20) { - return Ch(x, y, z); - } else if (t > 19 && t < 40) { - return Parity(x, y, z); - } else if (t > 39 && t < 60) { - return Maj(x, y, z); - } else if (t > 59 && t < 80) { return Parity(x, y, z); } - return 0x00000000; - } - - private int Ch(int x, int y, int z) { - return (x & y) ^ (~x & z); - } - - private int Parity(int x, int y, int z) { - return x ^ y ^ z; - } - - private int Maj(int x, int y, int z) { - return (x & y) ^ (x & z) ^ (y & z); - } - - private int ROTL(int n, int x) { - return (x << n) | (x >>> 32 - n); - } - - private int add(int x, int y) { - return x + y; + public static Hash calculateHash(byte[] source, int offset, int len) { + SHA1 h = new SHA1(); + h.engineUpdate(source, offset, len); + byte digested[] = h.digest(); + return new Hash(digested); } } \ No newline at end of file diff --git a/core/java/src/net/i2p/crypto/SHA1.java b/core/java/src/net/i2p/crypto/SHA1.java new file mode 100644 index 0000000000..ed9729eb66 --- /dev/null +++ b/core/java/src/net/i2p/crypto/SHA1.java @@ -0,0 +1,697 @@ +package net.i2p.crypto; +/* @(#)SHA1.java 1.11 2004-04-26 + * This file was freely contributed to the LimeWire project and is covered + * by its existing GPL licence, but it may be used individually as a public + * domain implementation of a published algorithm (see below for references). + * It was also freely contributed to the Bitzi public domain sources. + * @author Philippe Verdy + */ + +/* Sun may wish to change the following package name, if integrating this + * class in the Sun JCE Security Provider for Java 1.5 (code-named Tiger). + * + * You can include it in your own Security Provider by inserting + * this property in your Provider derived class: + * put("MessageDigest.SHA-1", "com.bitzi.util.SHA1"); + */ +//package com.bitzi.util; +import java.security.*; +//--+---+1--+---+--2+---+---+3--+---+--4+---+---+5--+---+--6+---+---+7--+---+-- +//34567890123456789012345678901234567890123456789012345678901234567890123456789 + +/** + *

The FIPS PUB 180-2 standard specifies four secure hash algorithms (SHA-1, + * SHA-256, SHA-384 and SHA-512) for computing a condensed representation of + * electronic data (message). When a message of any length < 2^^64 bits (for + * SHA-1 and SHA-256) or < 2^^128 bits (for SHA-384 and SHA-512) is input to + * an algorithm, the result is an output called a message digest. The message + * digests range in length from 160 to 512 bits, depending on the algorithm. + * Secure hash algorithms are typically used with other cryptographic + * algorithms, such as digital signature algorithms and keyed-hash message + * authentication codes, or in the generation of random numbers (bits).

+ * + *

The four hash algorithms specified in this "SHS" standard are called + * secure because, for a given algorithm, it is computationally infeasible + * 1) to find a message that corresponds to a given message digest, or 2) + * to find two different messages that produce the same message digest. Any + * change to a message will, with a very high probability, result in a + * different message digest. This will result in a verification failure when + * the secure hash algorithm is used with a digital signature algorithm or a + * keyed-hash message authentication algorithm.

+ * + *

A "SHS change notice" adds a SHA-224 algorithm for interoperability, + * which, like SHA-1 and SHA-256, operates on 512-bit blocks and 32-bit words, + * but truncates the final digest and uses distinct initialization values.

+ * + *

References:

+ *
    + *
  1. NIST FIPS PUB 180-2, "Secure Hash Signature Standard (SHS) with + * change notice", National Institute of Standards and Technology (NIST), + * 2002 August 1, and U.S. Department of Commerce, August 26.
    + * + * http://csrc.ncsl.nist.gov/CryptoToolkit/Hash.html + *
  2. NIST FIPS PUB 180-1, "Secure Hash Standard", + * U.S. Department of Commerce, May 1993.
    + * + * http://www.itl.nist.gov/div897/pubs/fip180-1.htm
  3. + *
  4. Bruce Schneier, "Section 18.7 Secure Hash Algorithm (SHA)", + * Applied Cryptography, 2nd edition,
    + * John Wiley & Sons, 1996
  5. + *
+ */ +public final class SHA1 extends MessageDigest implements Cloneable { + + /** + * This implementation returns a fixed-size digest. + */ + private static final int HASH_LENGTH = 20; // bytes == 160 bits + + /** + * Private context for incomplete blocks and padding bytes. + * INVARIANT: padding must be in 0..63. + * When the padding reaches 64, a new block is computed, and + * the 56 last bytes are kept in the padding history. + */ + private byte[] pad; + private int padding; + + /** + * Private contextual byte count, sent in the next block, + * after the ending padding block. + */ + private long bytes; + + /** + * Private context that contains the current digest key. + */ + private int hA, hB, hC, hD, hE; + + /** + * Creates a SHA1 object with default initial state. + */ + public SHA1() { + super("SHA-1"); + pad = new byte[64]; + init(); + } + + /** + * Clones this object. + */ + public Object clone() throws CloneNotSupportedException { + SHA1 that = (SHA1)super.clone(); + that.pad = (byte[])this.pad.clone(); + return that; + } + + /** + * Returns the digest length in bytes. + * + * Can be used to allocate your own output buffer when + * computing multiple digests. + * + * Overrides the protected abstract method of + * java.security.MessageDigestSpi. + * @return the digest length in bytes. + */ + public int engineGetDigestLength() { + return HASH_LENGTH; + } + + /** + * Reset athen initialize the digest context. + * + * Overrides the protected abstract method of + * java.security.MessageDigestSpi. + */ + protected void engineReset() { + int i = 60; + do { + pad[i ] = (byte)0x00; + pad[i + 1] = (byte)0x00; + pad[i + 2] = (byte)0x00; + pad[i + 3] = (byte)0x00; + } while ((i -= 4) >= 0); + padding = 0; + bytes = 0; + init(); + } + + /** + * Initialize the digest context. + */ + protected void init() { + hA = 0x67452301; + hB = 0xefcdab89; + hC = 0x98badcfe; + hD = 0x10325476; + hE = 0xc3d2e1f0; + } + + /** + * Updates the digest using the specified byte. + * Requires internal buffering, and may be slow. + * + * Overrides the protected abstract method of + * java.security.MessageDigestSpi. + * @param input the byte to use for the update. + */ + public void engineUpdate(byte input) { + bytes++; + if (padding < 63) { + pad[padding++] = input; + return; + } + pad[63] = input; + computeBlock(pad, 0); + padding = 0; + } + + /** + * Updates the digest using the specified array of bytes, + * starting at the specified offset. + * + * Input length can be any size. May require internal buffering, + * if input blocks are not multiple of 64 bytes. + * + * Overrides the protected abstract method of + * java.security.MessageDigestSpi. + * @param input the array of bytes to use for the update. + * @param offset the offset to start from in the array of bytes. + * @param length the number of bytes to use, starting at offset. + */ + public void engineUpdate(byte[] input, int offset, int len) { + if (offset >= 0 && len >= 0 && offset + len <= input.length) { + bytes += len; + /* Terminate the previous block. */ + int padlen = 64 - padding; + if (padding > 0 && len >= padlen) { + System.arraycopy(input, offset, pad, padding, padlen); + computeBlock(pad, 0); + padding = 0; + offset += padlen; + len -= padlen; + } + /* Loop on large sets of complete blocks. */ + while (len >= 512) { + computeBlock(input, offset); + computeBlock(input, offset + 64); + computeBlock(input, offset + 128); + computeBlock(input, offset + 192); + computeBlock(input, offset + 256); + computeBlock(input, offset + 320); + computeBlock(input, offset + 384); + computeBlock(input, offset + 448); + offset += 512; + len -= 512; + } + /* Loop on remaining complete blocks. */ + while (len >= 64) { + computeBlock(input, offset); + offset += 64; + len -= 64; + } + /* remaining bytes kept for next block. */ + if (len > 0) { + System.arraycopy(input, offset, pad, padding, len); + padding += len; + } + return; + } + throw new ArrayIndexOutOfBoundsException(offset); + } + + /** + * Completes the hash computation by performing final operations + * such as padding. Computes the final hash and returns the final + * value as a byte[20] array. Once engineDigest has been called, + * the engine will be automatically reset as specified in the + * JavaSecurity MessageDigest specification. + * + * For faster operations with multiple digests, allocate your own + * array and use engineDigest(byte[], int offset, int len). + * + * Overrides the protected abstract method of + * java.security.MessageDigestSpi. + * @return the length of the digest stored in the output buffer. + */ + public byte[] engineDigest() { + try { + final byte hashvalue[] = new byte[HASH_LENGTH]; + engineDigest(hashvalue, 0, HASH_LENGTH); + return hashvalue; + } catch (DigestException e) { + return null; + } + } + + /** + * Completes the hash computation by performing final operations + * such as padding. Once engineDigest has been called, the engine + * will be automatically reset (see engineReset). + * + * Overrides the protected abstract method of + * java.security.MessageDigestSpi. + * @param hashvalue the output buffer in which to store the digest. + * @param offset offset to start from in the output buffer + * @param len number of bytes within buf allotted for the digest. + * Both this default implementation and the SUN provider + * do not return partial digests. The presence of this + * parameter is solely for consistency in our API's. + * If the value of this parameter is less than the + * actual digest length, the method will throw a + * DigestException. This parameter is ignored if its + * value is greater than or equal to the actual digest + * length. + * @return the length of the digest stored in the output buffer. + */ + public int engineDigest(byte[] hashvalue, int offset, final int len) + throws DigestException { + if (len >= HASH_LENGTH) { + if (hashvalue.length - offset >= HASH_LENGTH) { + /* Flush the trailing bytes, adding padding bytes into last + * blocks. */ + int i; + /* Add padding null bytes but replace the last 8 padding bytes + * by the little-endian 64-bit digested message bit-length. */ + pad[i = padding] = (byte)0x80; /* required 1st padding byte */ + /* Check if 8 bytes available in pad to store the total + * message size */ + switch (i) { /* INVARIANT: i must be in [0..63] */ + case 52: pad[53] = (byte)0x00; /* no break; falls thru */ + case 53: pad[54] = (byte)0x00; /* no break; falls thru */ + case 54: pad[55] = (byte)0x00; /* no break; falls thru */ + case 55: break; + case 56: pad[57] = (byte)0x00; /* no break; falls thru */ + case 57: pad[58] = (byte)0x00; /* no break; falls thru */ + case 58: pad[59] = (byte)0x00; /* no break; falls thru */ + case 59: pad[60] = (byte)0x00; /* no break; falls thru */ + case 60: pad[61] = (byte)0x00; /* no break; falls thru */ + case 61: pad[62] = (byte)0x00; /* no break; falls thru */ + case 62: pad[63] = (byte)0x00; /* no break; falls thru */ + case 63: + computeBlock(pad, 0); + /* Clear the 56 first bytes of pad[]. */ + i = 52; + do { + pad[i ] = (byte)0x00; + pad[i + 1] = (byte)0x00; + pad[i + 2] = (byte)0x00; + pad[i + 3] = (byte)0x00; + } while ((i -= 4) >= 0); + break; + default: + /* Clear the rest of 56 first bytes of pad[]. */ + switch (i & 3) { + case 3: i++; + break; + case 2: pad[(i += 2) - 1] = (byte)0x00; + break; + case 1: pad[(i += 3) - 2] = (byte)0x00; + pad[ i - 1] = (byte)0x00; + break; + case 0: pad[(i += 4) - 3] = (byte)0x00; + pad[ i - 2] = (byte)0x00; + pad[ i - 1] = (byte)0x00; + } + do { + pad[i ] = (byte)0x00; + pad[i + 1] = (byte)0x00; + pad[i + 2] = (byte)0x00; + pad[i + 3] = (byte)0x00; + } while ((i += 4) < 56); + } + /* Convert the message size from bytes to big-endian bits. */ + pad[56] = (byte)((i = (int)(bytes >>> 29)) >> 24); + pad[57] = (byte)(i >>> 16); + pad[58] = (byte)(i >>> 8); + pad[59] = (byte)i; + pad[60] = (byte)((i = (int)bytes << 3) >> 24); + pad[61] = (byte)(i >>> 16); + pad[62] = (byte)(i >>> 8); + pad[63] = (byte)i; + computeBlock(pad, 0); + /* Return the computed digest in big-endian byte order. */ + hashvalue[offset ] = (byte)((i = hA) >>> 24); + hashvalue[offset + 1] = (byte)(i >>> 16); + hashvalue[offset + 2] = (byte)(i >>> 8); + hashvalue[offset + 3] = (byte)i; + hashvalue[offset + 4] = (byte)((i = hB) >>> 24); + hashvalue[offset += 5] = (byte)(i >>> 16); + hashvalue[offset + 1] = (byte)(i >>> 8); + hashvalue[offset + 2] = (byte)i; + hashvalue[offset + 3] = (byte)((i = hC) >>> 24); + hashvalue[offset + 4] = (byte)(i >>> 16); + hashvalue[offset += 5] = (byte)(i >>> 8); + hashvalue[offset + 1] = (byte)i; + hashvalue[offset + 2] = (byte)((i = hD) >>> 24); + hashvalue[offset + 3] = (byte)(i >>> 16); + hashvalue[offset + 4] = (byte)(i >>> 8); + hashvalue[offset += 5] = (byte)i; + hashvalue[offset + 1] = (byte)((i = hE) >>> 24); + hashvalue[offset + 2] = (byte)(i >>> 16); + hashvalue[offset + 3] = (byte)(i >>> 8); + hashvalue[offset + 4] = (byte)i; + engineReset(); /* clear the evidence */ + return HASH_LENGTH; + } + throw new DigestException( + "insufficient space in output buffer to store the digest"); + } + throw new DigestException("partial digests not returned"); + } + + /** + * Updates the digest using the specified array of bytes, + * starting at the specified offset, but an implied length + * of exactly 64 bytes. + * + * Requires no internal buffering, but assumes a fixed input size, + * in which the required padding bytes may have been added. + * + * @param input the array of bytes to use for the update. + * @param offset the offset to start from in the array of bytes. + */ + private void computeBlock(final byte[] input, int offset) { + /* Local temporary work variables for intermediate digests. */ + int a, b, c, d, e; + /* Cache the input block into the local working set of 32-bit + * values, in big-endian byte order. Be careful when + * widening bytes or integers due to sign extension! */ + int i00, i01, i02, i03, i04, i05, i06, i07, + i08, i09, i10, i11, i12, i13, i14, i15; + /* Use hash schedule function Ch (rounds 0..19): + * Ch(x,y,z) = (x & y) ^ (~x & z) = (x & (y ^ z)) ^ z, + * and K00 = .... = K19 = 0x5a827999. */ + /* First pass, on big endian input (rounds 0..15). */ + e = hE + + (((a = hA) << 5) | (a >>> 27)) + 0x5a827999 // K00 + + (((b = hB) & ((c = hC) ^ (d = hD))) ^ d) // Ch(b,c,d) + + (i00 = input[offset ] << 24 + | (input[offset + 1] & 0xff) << 16 + | (input[offset + 2] & 0xff) << 8 + | (input[offset + 3] & 0xff)); // W00 + d += ((e << 5) | (e >>> 27)) + 0x5a827999 // K01 + + ((a & ((b = (b << 30) | (b >>> 2)) ^ c)) ^ c) // Ch(a,b,c) + + (i01 = input[offset + 4] << 24 + | (input[offset += 5] & 0xff) << 16 + | (input[offset + 1] & 0xff) << 8 + | (input[offset + 2] & 0xff)); // W01 + c += ((d << 5) | (d >>> 27)) + 0x5a827999 // K02 + + ((e & ((a = (a << 30) | (a >>> 2)) ^ b)) ^ b) // Ch(e,a,b) + + (i02 = input[offset + 3] << 24 + | (input[offset + 4] & 0xff) << 16 + | (input[offset += 5] & 0xff) << 8 + | (input[offset + 1] & 0xff)); // W02 + b += ((c << 5) | (c >>> 27)) + 0x5a827999 // K03 + + ((d & ((e = (e << 30) | (e >>> 2)) ^ a)) ^ a) // Ch(d,e,a) + + (i03 = input[offset + 2] << 24 + | (input[offset + 3] & 0xff) << 16 + | (input[offset + 4] & 0xff) << 8 + | (input[offset += 5] & 0xff)); // W03 + a += ((b << 5) | (b >>> 27)) + 0x5a827999 // K04 + + ((c & ((d = (d << 30) | (d >>> 2)) ^ e)) ^ e) // Ch(c,d,e) + + (i04 = input[offset + 1] << 24 + | (input[offset + 2] & 0xff) << 16 + | (input[offset + 3] & 0xff) << 8 + | (input[offset + 4] & 0xff)); // W04 + e += ((a << 5) | (a >>> 27)) + 0x5a827999 // K05 + + ((b & ((c = (c << 30) | (c >>> 2)) ^ d)) ^ d) // Ch(b,c,d) + + (i05 = input[offset += 5] << 24 + | (input[offset + 1] & 0xff) << 16 + | (input[offset + 2] & 0xff) << 8 + | (input[offset + 3] & 0xff)); // W05 + d += ((e << 5) | (e >>> 27)) + 0x5a827999 // K06 + + ((a & ((b = (b << 30) | (b >>> 2)) ^ c)) ^ c) // Ch(a,b,c) + + (i06 = input[offset + 4] << 24 + | (input[offset += 5] & 0xff) << 16 + | (input[offset + 1] & 0xff) << 8 + | (input[offset + 2] & 0xff)); // W06 + c += ((d << 5) | (d >>> 27)) + 0x5a827999 // K07 + + ((e & ((a = (a << 30) | (a >>> 2)) ^ b)) ^ b) // Ch(e,a,b) + + (i07 = input[offset + 3] << 24 + | (input[offset + 4] & 0xff) << 16 + | (input[offset += 5] & 0xff) << 8 + | (input[offset + 1] & 0xff)); // W07 + b += ((c << 5) | (c >>> 27)) + 0x5a827999 // K08 + + ((d & ((e = (e << 30) | (e >>> 2)) ^ a)) ^ a) // Ch(d,e,a) + + (i08 = input[offset + 2] << 24 + | (input[offset + 3] & 0xff) << 16 + | (input[offset + 4] & 0xff) << 8 + | (input[offset += 5] & 0xff)); // W08 + a += ((b << 5) | (b >>> 27)) + 0x5a827999 // K09 + + ((c & ((d = (d << 30) | (d >>> 2)) ^ e)) ^ e) // Ch(c,d,e) + + (i09 = input[offset + 1] << 24 + | (input[offset + 2] & 0xff) << 16 + | (input[offset + 3] & 0xff) << 8 + | (input[offset + 4] & 0xff)); // W09 + e += ((a << 5) | (a >>> 27)) + 0x5a827999 // K10 + + ((b & ((c = (c << 30) | (c >>> 2)) ^ d)) ^ d) // Ch(b,c,d) + + (i10 = input[offset += 5] << 24 + | (input[offset + 1] & 0xff) << 16 + | (input[offset + 2] & 0xff) << 8 + | (input[offset + 3] & 0xff)); // W10 + d += ((e << 5) | (e >>> 27)) + 0x5a827999 // K11 + + ((a & ((b = (b << 30) | (b >>> 2)) ^ c)) ^ c) // Ch(a,b,c) + + (i11 = input[offset + 4] << 24 + | (input[offset += 5] & 0xff) << 16 + | (input[offset + 1] & 0xff) << 8 + | (input[offset + 2] & 0xff)); // W11 + c += ((d << 5) | (d >>> 27)) + 0x5a827999 // K12 + + ((e & ((a = (a << 30) | (a >>> 2)) ^ b)) ^ b) // Ch(e,a,b) + + (i12 = input[offset + 3] << 24 + | (input[offset + 4] & 0xff) << 16 + | (input[offset += 5] & 0xff) << 8 + | (input[offset + 1] & 0xff)); // W12 + b += ((c << 5) | (c >>> 27)) + 0x5a827999 // K13 + + ((d & ((e = (e << 30) | (e >>> 2)) ^ a)) ^ a) // Ch(d,e,a) + + (i13 = input[offset + 2] << 24 + | (input[offset + 3] & 0xff) << 16 + | (input[offset + 4] & 0xff) << 8 + | (input[offset += 5] & 0xff)); // W13 + a += ((b << 5) | (b >>> 27)) + 0x5a827999 // K14 + + ((c & ((d = (d << 30) | (d >>> 2)) ^ e)) ^ e) // Ch(c,d,e) + + (i14 = input[offset + 1] << 24 + | (input[offset + 2] & 0xff) << 16 + | (input[offset + 3] & 0xff) << 8 + | (input[offset + 4] & 0xff)); // W14 + e += ((a << 5) | (a >>> 27)) + 0x5a827999 // K15 + + ((b & ((c = (c << 30) | (c >>> 2)) ^ d)) ^ d) // Ch(b,c,d) + + (i15 = input[offset += 5] << 24 + | (input[offset + 1] & 0xff) << 16 + | (input[offset + 2] & 0xff) << 8 + | (input[offset + 3] & 0xff)); // W15 + /* Second pass, on scheduled input (rounds 16..31). */ + d += ((e << 5) | (e >>> 27)) + 0x5a827999 // K16 + + ((a & ((b = (b << 30) | (b >>> 2)) ^ c)) ^ c) // Ch(a,b,c) + + (i00 = ((i00 ^= i02 ^ i08 ^ i13) << 1) | (i00 >>> 31)); // W16 + c += ((d << 5) | (d >>> 27)) + 0x5a827999 // K17 + + ((e & ((a = (a << 30) | (a >>> 2)) ^ b)) ^ b) // Ch(e,a,b) + + (i01 = ((i01 ^= i03 ^ i09 ^ i14) << 1) | (i01 >>> 31)); // W17 + b += ((c << 5) | (c >>> 27)) + 0x5a827999 // K18 + + ((d & ((e = (e << 30) | (e >>> 2)) ^ a)) ^ a) // Ch(d,e,a) + + (i02 = ((i02 ^= i04 ^ i10 ^ i15) << 1) | (i02 >>> 31)); // W18 + a += ((b << 5) | (b >>> 27)) + 0x5a827999 // K19 + + ((c & ((d = (d << 30) | (d >>> 2)) ^ e)) ^ e) // Ch(c,d,e) + + (i03 = ((i03 ^= i05 ^ i11 ^ i00) << 1) | (i03 >>> 31)); // W19 + /* Use hash schedule function Parity (rounds 20..39): + * Parity(x,y,z) = x ^ y ^ z, + * and K20 = .... = K39 = 0x6ed9eba1. */ + e += ((a << 5) | (a >>> 27)) + 0x6ed9eba1 // K20 + + (b ^ (c = (c << 30) | (c >>> 2)) ^ d) // Parity(b,c,d) + + (i04 = ((i04 ^= i06 ^ i12 ^ i01) << 1) | (i04 >>> 31)); // W20 + d += ((e << 5) | (e >>> 27)) + 0x6ed9eba1 // K21 + + (a ^ (b = (b << 30) | (b >>> 2)) ^ c) // Parity(a,b,c) + + (i05 = ((i05 ^= i07 ^ i13 ^ i02) << 1) | (i05 >>> 31)); // W21 + c += ((d << 5) | (d >>> 27)) + 0x6ed9eba1 // K22 + + (e ^ (a = (a << 30) | (a >>> 2)) ^ b) // Parity(e,a,b) + + (i06 = ((i06 ^= i08 ^ i14 ^ i03) << 1) | (i06 >>> 31)); // W22 + b += ((c << 5) | (c >>> 27)) + 0x6ed9eba1 // K23 + + (d ^ (e = (e << 30) | (e >>> 2)) ^ a) // Parity(d,e,a) + + (i07 = ((i07 ^= i09 ^ i15 ^ i04) << 1) | (i07 >>> 31)); // W23 + a += ((b << 5) | (b >>> 27)) + 0x6ed9eba1 // K24 + + (c ^ (d = (d << 30) | (d >>> 2)) ^ e) // Parity(c,d,e) + + (i08 = ((i08 ^= i10 ^ i00 ^ i05) << 1) | (i08 >>> 31)); // W24 + e += ((a << 5) | (a >>> 27)) + 0x6ed9eba1 // K25 + + (b ^ (c = (c << 30) | (c >>> 2)) ^ d) // Parity(b,c,d) + + (i09 = ((i09 ^= i11 ^ i01 ^ i06) << 1) | (i09 >>> 31)); // W25 + d += ((e << 5) | (e >>> 27)) + 0x6ed9eba1 // K26 + + (a ^ (b = (b << 30) | (b >>> 2)) ^ c) // Parity(a,b,c) + + (i10 = ((i10 ^= i12 ^ i02 ^ i07) << 1) | (i10 >>> 31)); // W26 + c += ((d << 5) | (d >>> 27)) + 0x6ed9eba1 // K27 + + (e ^ (a = (a << 30) | (a >>> 2)) ^ b) // Parity(e,a,b) + + (i11 = ((i11 ^= i13 ^ i03 ^ i08) << 1) | (i11 >>> 31)); // W27 + b += ((c << 5) | (c >>> 27)) + 0x6ed9eba1 // K28 + + (d ^ (e = (e << 30) | (e >>> 2)) ^ a) // Parity(d,e,a) + + (i12 = ((i12 ^= i14 ^ i04 ^ i09) << 1) | (i12 >>> 31)); // W28 + a += ((b << 5) | (b >>> 27)) + 0x6ed9eba1 // K29 + + (c ^ (d = (d << 30) | (d >>> 2)) ^ e) // Parity(c,d,e) + + (i13 = ((i13 ^= i15 ^ i05 ^ i10) << 1) | (i13 >>> 31)); // W29 + e += ((a << 5) | (a >>> 27)) + 0x6ed9eba1 // K30 + + (b ^ (c = (c << 30) | (c >>> 2)) ^ d) // Parity(b,c,d) + + (i14 = ((i14 ^= i00 ^ i06 ^ i11) << 1) | (i14 >>> 31)); // W30 + d += ((e << 5) | (e >>> 27)) + 0x6ed9eba1 // K31 + + (a ^ (b = (b << 30) | (b >>> 2)) ^ c) // Parity(a,b,c) + + (i15 = ((i15 ^= i01 ^ i07 ^ i12) << 1) | (i15 >>> 31)); // W31 + /* Third pass, on scheduled input (rounds 32..47). */ + c += ((d << 5) | (d >>> 27)) + 0x6ed9eba1 // K32 + + (e ^ (a = (a << 30) | (a >>> 2)) ^ b) // Parity(e,a,b) + + (i00 = ((i00 ^= i02 ^ i08 ^ i13) << 1) | (i00 >>> 31)); // W32 + b += ((c << 5) | (c >>> 27)) + 0x6ed9eba1 // K33 + + (d ^ (e = (e << 30) | (e >>> 2)) ^ a) // Parity(d,e,a) + + (i01 = ((i01 ^= i03 ^ i09 ^ i14) << 1) | (i01 >>> 31)); // W33 + a += ((b << 5) | (b >>> 27)) + 0x6ed9eba1 // K34 + + (c ^ (d = (d << 30) | (d >>> 2)) ^ e) // Parity(c,d,e) + + (i02 = ((i02 ^= i04 ^ i10 ^ i15) << 1) | (i02 >>> 31)); // W34 + e += ((a << 5) | (a >>> 27)) + 0x6ed9eba1 // K35 + + (b ^ (c = (c << 30) | (c >>> 2)) ^ d) // Parity(b,c,d) + + (i03 = ((i03 ^= i05 ^ i11 ^ i00) << 1) | (i03 >>> 31)); // W35 + d += ((e << 5) | (e >>> 27)) + 0x6ed9eba1 // K36 + + (a ^ (b = (b << 30) | (b >>> 2)) ^ c) // Parity(a,b,c) + + (i04 = ((i04 ^= i06 ^ i12 ^ i01) << 1) | (i04 >>> 31)); // W36 + c += ((d << 5) | (d >>> 27)) + 0x6ed9eba1 // K37 + + (e ^ (a = (a << 30) | (a >>> 2)) ^ b) // Parity(e,a,b) + + (i05 = ((i05 ^= i07 ^ i13 ^ i02) << 1) | (i05 >>> 31)); // W37 + b += ((c << 5) | (c >>> 27)) + 0x6ed9eba1 // K38 + + (d ^ (e = (e << 30) | (e >>> 2)) ^ a) // Parity(d,e,a) + + (i06 = ((i06 ^= i08 ^ i14 ^ i03) << 1) | (i06 >>> 31)); // W38 + a += ((b << 5) | (b >>> 27)) + 0x6ed9eba1 // K39 + + (c ^ (d = (d << 30) | (d >>> 2)) ^ e) // Parity(c,d,e) + + (i07 = ((i07 ^= i09 ^ i15 ^ i04) << 1) | (i07 >>> 31)); // W39 + /* Use hash schedule function Maj (rounds 40..59): + * Maj(x,y,z) = (x&y) ^ (x&z) ^ (y&z) = (x & y) | ((x | y) & z), + * and K40 = .... = K59 = 0x8f1bbcdc. */ + e += ((a << 5) | (a >>> 27)) + 0x8f1bbcdc // K40 + + ((b & (c = (c << 30) | (c >>> 2))) | ((b | c) & d)) // Maj(b,c,d) + + (i08 = ((i08 ^= i10 ^ i00 ^ i05) << 1) | (i08 >>> 31)); // W40 + d += ((e << 5) | (e >>> 27)) + 0x8f1bbcdc // K41 + + ((a & (b = (b << 30) | (b >>> 2))) | ((a | b) & c)) // Maj(a,b,c) + + (i09 = ((i09 ^= i11 ^ i01 ^ i06) << 1) | (i09 >>> 31)); // W41 + c += ((d << 5) | (d >>> 27)) + 0x8f1bbcdc // K42 + + ((e & (a = (a << 30) | (a >>> 2))) | ((e | a) & b)) // Maj(e,a,b) + + (i10 = ((i10 ^= i12 ^ i02 ^ i07) << 1) | (i10 >>> 31)); // W42 + b += ((c << 5) | (c >>> 27)) + 0x8f1bbcdc // K43 + + ((d & (e = (e << 30) | (e >>> 2))) | ((d | e) & a)) // Maj(d,e,a) + + (i11 = ((i11 ^= i13 ^ i03 ^ i08) << 1) | (i11 >>> 31)); // W43 + a += ((b << 5) | (b >>> 27)) + 0x8f1bbcdc // K44 + + ((c & (d = (d << 30) | (d >>> 2))) | ((c | d) & e)) // Maj(c,d,e) + + (i12 = ((i12 ^= i14 ^ i04 ^ i09) << 1) | (i12 >>> 31)); // W44 + e += ((a << 5) | (a >>> 27)) + 0x8f1bbcdc // K45 + + ((b & (c = (c << 30) | (c >>> 2))) | ((b | c) & d)) // Maj(b,c,d) + + (i13 = ((i13 ^= i15 ^ i05 ^ i10) << 1) | (i13 >>> 31)); // W45 + d += ((e << 5) | (e >>> 27)) + 0x8f1bbcdc // K46 + + ((a & (b = (b << 30) | (b >>> 2))) | ((a | b) & c)) // Maj(a,b,c) + + (i14 = ((i14 ^= i00 ^ i06 ^ i11) << 1) | (i14 >>> 31)); // W46 + c += ((d << 5) | (d >>> 27)) + 0x8f1bbcdc // K47 + + ((e & (a = (a << 30) | (a >>> 2))) | ((e | a) & b)) // Maj(e,a,b) + + (i15 = ((i15 ^= i01 ^ i07 ^ i12) << 1) | (i15 >>> 31)); // W47 + /* Fourth pass, on scheduled input (rounds 48..63). */ + b += ((c << 5) | (c >>> 27)) + 0x8f1bbcdc // K48 + + ((d & (e = (e << 30) | (e >>> 2))) | ((d | e) & a)) // Maj(d,e,a) + + (i00 = ((i00 ^= i02 ^ i08 ^ i13) << 1) | (i00 >>> 31)); // W48 + a += ((b << 5) | (b >>> 27)) + 0x8f1bbcdc // K49 + + ((c & (d = (d << 30) | (d >>> 2))) | ((c | d) & e)) // Maj(c,d,e) + + (i01 = ((i01 ^= i03 ^ i09 ^ i14) << 1) | (i01 >>> 31)); // W49 + e += ((a << 5) | (a >>> 27)) + 0x8f1bbcdc // K50 + + ((b & (c = (c << 30) | (c >>> 2))) | ((b | c) & d)) // Maj(b,c,d) + + (i02 = ((i02 ^= i04 ^ i10 ^ i15) << 1) | (i02 >>> 31)); // W50 + d += ((e << 5) | (e >>> 27)) + 0x8f1bbcdc // K51 + + ((a & (b = (b << 30) | (b >>> 2))) | ((a | b) & c)) // Maj(a,b,c) + + (i03 = ((i03 ^= i05 ^ i11 ^ i00) << 1) | (i03 >>> 31)); // W51 + c += ((d << 5) | (d >>> 27)) + 0x8f1bbcdc // K52 + + ((e & (a = (a << 30) | (a >>> 2))) | ((e | a) & b)) // Maj(e,a,b) + + (i04 = ((i04 ^= i06 ^ i12 ^ i01) << 1) | (i04 >>> 31)); // W52 + b += ((c << 5) | (c >>> 27)) + 0x8f1bbcdc // K53 + + ((d & (e = (e << 30) | (e >>> 2))) | ((d | e) & a)) // Maj(d,e,a) + + (i05 = ((i05 ^= i07 ^ i13 ^ i02) << 1) | (i05 >>> 31)); // W53 + a += ((b << 5) | (b >>> 27)) + 0x8f1bbcdc // K54 + + ((c & (d = (d << 30) | (d >>> 2))) | ((c | d) & e)) // Maj(c,d,e) + + (i06 = ((i06 ^= i08 ^ i14 ^ i03) << 1) | (i06 >>> 31)); // W54 + e += ((a << 5) | (a >>> 27)) + 0x8f1bbcdc // K55 + + ((b & (c = (c << 30) | (c >>> 2))) | ((b | c) & d)) // Maj(b,c,d) + + (i07 = ((i07 ^= i09 ^ i15 ^ i04) << 1) | (i07 >>> 31)); // W55 + d += ((e << 5) | (e >>> 27)) + 0x8f1bbcdc // K56 + + ((a & (b = (b << 30) | (b >>> 2))) | ((a | b) & c)) // Maj(a,b,c) + + (i08 = ((i08 ^= i10 ^ i00 ^ i05) << 1) | (i08 >>> 31)); // W56 + c += ((d << 5) | (d >>> 27)) + 0x8f1bbcdc // K57 + + ((e & (a = (a << 30) | (a >>> 2))) | ((e | a) & b)) // Maj(e,a,b) + + (i09 = ((i09 ^= i11 ^ i01 ^ i06) << 1) | (i09 >>> 31)); // W57 + b += ((c << 5) | (c >>> 27)) + 0x8f1bbcdc // K58 + + ((d & (e = (e << 30) | (e >>> 2))) | ((d | e) & a)) // Maj(d,e,a) + + (i10 = ((i10 ^= i12 ^ i02 ^ i07) << 1) | (i10 >>> 31)); // W58 + a += ((b << 5) | (b >>> 27)) + 0x8f1bbcdc // K59 + + ((c & (d = (d << 30) | (d >>> 2))) | ((c | d) & e)) // Maj(c,d,e) + + (i11 = ((i11 ^= i13 ^ i03 ^ i08) << 1) | (i11 >>> 31)); // W59 + /* Use hash schedule function Parity (rounds 60..79): + * Parity(x,y,z) = x ^ y ^ z, + * and K60 = .... = K79 = 0xca62c1d6. */ + e += ((a << 5) | (a >>> 27)) + 0xca62c1d6 // K60 + + (b ^ (c = (c << 30) | (c >>> 2)) ^ d) // Parity(b,c,d) + + (i12 = ((i12 ^= i14 ^ i04 ^ i09) << 1) | (i12 >>> 31)); // W60 + d += ((e << 5) | (e >>> 27)) + 0xca62c1d6 // K61 + + (a ^ (b = (b << 30) | (b >>> 2)) ^ c) // Parity(a,b,c) + + (i13 = ((i13 ^= i15 ^ i05 ^ i10) << 1) | (i13 >>> 31)); // W61 + c += ((d << 5) | (d >>> 27)) + 0xca62c1d6 // K62 + + (e ^ (a = (a << 30) | (a >>> 2)) ^ b) // Parity(e,a,b) + + (i14 = ((i14 ^= i00 ^ i06 ^ i11) << 1) | (i14 >>> 31)); // W62 + b += ((c << 5) | (c >>> 27)) + 0xca62c1d6 // K63 + + (d ^ (e = (e << 30) | (e >>> 2)) ^ a) // Parity(d,e,a) + + (i15 = ((i15 ^= i01 ^ i07 ^ i12) << 1) | (i15 >>> 31)); // W63 + /* Fifth pass, on scheduled input (rounds 64..79). */ + a += ((b << 5) | (b >>> 27)) + 0xca62c1d6 // K64 + + (c ^ (d = (d << 30) | (d >>> 2)) ^ e) // Parity(c,d,e) + + (i00 = ((i00 ^= i02 ^ i08 ^ i13) << 1) | (i00 >>> 31)); // W64 + e += ((a << 5) | (a >>> 27)) + 0xca62c1d6 // K65 + + (b ^ (c = (c << 30) | (c >>> 2)) ^ d) // Parity(b,c,d) + + (i01 = ((i01 ^= i03 ^ i09 ^ i14) << 1) | (i01 >>> 31)); // W65 + d += ((e << 5) | (e >>> 27)) + 0xca62c1d6 // K66 + + (a ^ (b = (b << 30) | (b >>> 2)) ^ c) // Parity(a,b,c) + + (i02 = ((i02 ^= i04 ^ i10 ^ i15) << 1) | (i02 >>> 31)); // W66 + c += ((d << 5) | (d >>> 27)) + 0xca62c1d6 // K67 + + (e ^ (a = (a << 30) | (a >>> 2)) ^ b) // Parity(e,a,b) + + (i03 = ((i03 ^= i05 ^ i11 ^ i00) << 1) | (i03 >>> 31)); // W67 + b += ((c << 5) | (c >>> 27)) + 0xca62c1d6 // K68 + + (d ^ (e = (e << 30) | (e >>> 2)) ^ a) // Parity(d,e,a) + + (i04 = ((i04 ^= i06 ^ i12 ^ i01) << 1) | (i04 >>> 31)); // W68 + a += ((b << 5) | (b >>> 27)) + 0xca62c1d6 // K69 + + (c ^ (d = (d << 30) | (d >>> 2)) ^ e) // Parity(c,d,e) + + (i05 = ((i05 ^= i07 ^ i13 ^ i02) << 1) | (i05 >>> 31)); // W69 + e += ((a << 5) | (a >>> 27)) + 0xca62c1d6 // K70 + + (b ^ (c = (c << 30) | (c >>> 2)) ^ d) // Parity(b,c,d) + + (i06 = ((i06 ^= i08 ^ i14 ^ i03) << 1) | (i06 >>> 31)); // W70 + d += ((e << 5) | (e >>> 27)) + 0xca62c1d6 // K71 + + (a ^ (b = (b << 30) | (b >>> 2)) ^ c) // Parity(a,b,c) + + (i07 = ((i07 ^= i09 ^ i15 ^ i04) << 1) | (i07 >>> 31)); // W71 + c += ((d << 5) | (d >>> 27)) + 0xca62c1d6 // K72 + + (e ^ (a = (a << 30) | (a >>> 2)) ^ b) // Parity(e,a,b) + + (i08 = ((i08 ^= i10 ^ i00 ^ i05) << 1) | (i08 >>> 31)); // W72 + b += ((c << 5) | (c >>> 27)) + 0xca62c1d6 // K73 + + (d ^ (e = (e << 30) | (e >>> 2)) ^ a) // Parity(d,e,a) + + (i09 = ((i09 ^= i11 ^ i01 ^ i06) << 1) | (i09 >>> 31)); // W73 + a += ((b << 5) | (b >>> 27)) + 0xca62c1d6 // K74 + + (c ^ (d = (d << 30) | (d >>> 2)) ^ e) // Parity(c,d,e) + + (i10 = ((i10 ^= i12 ^ i02 ^ i07) << 1) | (i10 >>> 31)); // W74 + e += ((a << 5) | (a >>> 27)) + 0xca62c1d6 // K75 + + (b ^ (c = (c << 30) | (c >>> 2)) ^ d) // Parity(b,c,d) + + (i11 = ((i11 ^= i13 ^ i03 ^ i08) << 1) | (i11 >>> 31)); // W75 + d += ((e << 5) | (e >>> 27)) + 0xca62c1d6 // K76 + + (a ^ (b = (b << 30) | (b >>> 2)) ^ c) // Parity(a,b,c) + + (i12 = ((i12 ^= i14 ^ i04 ^ i09) << 1) | (i12 >>> 31)); // W76 + c += ((d << 5) | (d >>> 27)) + 0xca62c1d6 // K77 + + (e ^ (a = (a << 30) | (a >>> 2)) ^ b) // Parity(e,a,b) + + (i13 = ((i13 ^= i15 ^ i05 ^ i10) << 1) | (i13 >>> 31)); // W77 + /* Terminate the last two rounds of fifth pass, + * feeding the final digest on the fly. */ + hB += + b += ((c << 5) | (c >>> 27)) + 0xca62c1d6 // K78 + + (d ^ (e = (e << 30) | (e >>> 2)) ^ a) // Parity(d,e,a) + + (i14 = ((i14 ^= i00 ^ i06 ^ i11) << 1) | (i14 >>> 31)); // W78 + hA += + a += ((b << 5) | (b >>> 27)) + 0xca62c1d6 // K79 + + (c ^ (d = (d << 30) | (d >>> 2)) ^ e) // Parity(c,d,e) + + (i15 = ((i15 ^= i01 ^ i07 ^ i12) << 1) | (i15 >>> 31)); // W79 + hE += e; + hD += d; + hC += /* c= */ (c << 30) | (c >>> 2); + } +} diff --git a/core/java/src/net/i2p/crypto/SHA1Test.java b/core/java/src/net/i2p/crypto/SHA1Test.java new file mode 100644 index 0000000000..69ad3be144 --- /dev/null +++ b/core/java/src/net/i2p/crypto/SHA1Test.java @@ -0,0 +1,191 @@ +package net.i2p.crypto; +/* @(#)SHA1Test.java 1.10 2004-04-24 + * This file was freely contributed to the LimeWire project and is covered + * by its existing GPL licence, but it may be used individually as a public + * domain implementation of a published algorithm (see below for references). + * It was also freely contributed to the Bitzi public domain sources. + * @author Philippe Verdy + */ + +/* Sun may wish to change the following package name, if integrating this + * class in the Sun JCE Security Provider for Java 1.5 (code-named Tiger). + */ +//package com.bitzi.util; + +import java.security.*; + +public class SHA1Test { + + private static final SHA1 hash = new SHA1(); + + public static void main(String args[]) { +// http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf + System.out.println("****************************************"); + System.out.println("* Basic FIPS PUB 180-1 test vectors... *"); + System.out.println("****************************************"); + tst(1, 1, + "abc", + "A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D"); + tst(1, 2, + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "84983E44 1C3BD26e BAAE4AA1 F95129E5 E54670F1"); + tst(1, 3, /* one million bytes */ + 1000000, "a", + "34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F"); + System.out.println(); + +// http://csrc.ncsl.nist.gov/cryptval/shs/SHAVS.pdf + System.out.println("********************************************************"); + System.out.println("* SHSV Examples of the selected short messages test... *"); + System.out.println("********************************************************"); + tst(2, 2, new byte[] {/* 8 bits, i.e. 1 byte */ + (byte)0x5e}, + "5e6f80a3 4a9798ca fc6a5db9 6cc57ba4 c4db59c2"); + tst(2, 4, new byte[] {/* 128 bits, i.e. 16 bytes */ + (byte)0x9a,(byte)0x7d,(byte)0xfd,(byte)0xf1,(byte)0xec,(byte)0xea,(byte)0xd0,(byte)0x6e, + (byte)0xd6,(byte)0x46,(byte)0xaa,(byte)0x55,(byte)0xfe,(byte)0x75,(byte)0x71,(byte)0x46}, + "82abff66 05dbe1c1 7def12a3 94fa22a8 2b544a35"); + System.out.println(); + + System.out.println("*******************************************************"); + System.out.println("* SHSV Examples of the selected long messages test... *"); + System.out.println("*******************************************************"); + tst(3, 2, new byte[] {/* 1304 bits, i.e. 163 bytes */ + (byte)0xf7,(byte)0x8f,(byte)0x92,(byte)0x14,(byte)0x1b,(byte)0xcd,(byte)0x17,(byte)0x0a, + (byte)0xe8,(byte)0x9b,(byte)0x4f,(byte)0xba,(byte)0x15,(byte)0xa1,(byte)0xd5,(byte)0x9f, + (byte)0x3f,(byte)0xd8,(byte)0x4d,(byte)0x22,(byte)0x3c,(byte)0x92,(byte)0x51,(byte)0xbd, + (byte)0xac,(byte)0xbb,(byte)0xae,(byte)0x61,(byte)0xd0,(byte)0x5e,(byte)0xd1,(byte)0x15, + (byte)0xa0,(byte)0x6a,(byte)0x7c,(byte)0xe1,(byte)0x17,(byte)0xb7,(byte)0xbe,(byte)0xea, + (byte)0xd2,(byte)0x44,(byte)0x21,(byte)0xde,(byte)0xd9,(byte)0xc3,(byte)0x25,(byte)0x92, + (byte)0xbd,(byte)0x57,(byte)0xed,(byte)0xea,(byte)0xe3,(byte)0x9c,(byte)0x39,(byte)0xfa, + (byte)0x1f,(byte)0xe8,(byte)0x94,(byte)0x6a,(byte)0x84,(byte)0xd0,(byte)0xcf,(byte)0x1f, + (byte)0x7b,(byte)0xee,(byte)0xad,(byte)0x17,(byte)0x13,(byte)0xe2,(byte)0xe0,(byte)0x95, + (byte)0x98,(byte)0x97,(byte)0x34,(byte)0x7f,(byte)0x67,(byte)0xc8,(byte)0x0b,(byte)0x04, + (byte)0x00,(byte)0xc2,(byte)0x09,(byte)0x81,(byte)0x5d,(byte)0x6b,(byte)0x10,(byte)0xa6, + (byte)0x83,(byte)0x83,(byte)0x6f,(byte)0xd5,(byte)0x56,(byte)0x2a,(byte)0x56,(byte)0xca, + (byte)0xb1,(byte)0xa2,(byte)0x8e,(byte)0x81,(byte)0xb6,(byte)0x57,(byte)0x66,(byte)0x54, + (byte)0x63,(byte)0x1c,(byte)0xf1,(byte)0x65,(byte)0x66,(byte)0xb8,(byte)0x6e,(byte)0x3b, + (byte)0x33,(byte)0xa1,(byte)0x08,(byte)0xb0,(byte)0x53,(byte)0x07,(byte)0xc0,(byte)0x0a, + (byte)0xff,(byte)0x14,(byte)0xa7,(byte)0x68,(byte)0xed,(byte)0x73,(byte)0x50,(byte)0x60, + (byte)0x6a,(byte)0x0f,(byte)0x85,(byte)0xe6,(byte)0xa9,(byte)0x1d,(byte)0x39,(byte)0x6f, + (byte)0x5b,(byte)0x5c,(byte)0xbe,(byte)0x57,(byte)0x7f,(byte)0x9b,(byte)0x38,(byte)0x80, + (byte)0x7c,(byte)0x7d,(byte)0x52,(byte)0x3d,(byte)0x6d,(byte)0x79,(byte)0x2f,(byte)0x6e, + (byte)0xbc,(byte)0x24,(byte)0xa4,(byte)0xec,(byte)0xf2,(byte)0xb3,(byte)0xa4,(byte)0x27, + (byte)0xcd,(byte)0xbb,(byte)0xfb}, + "cb0082c8 f197d260 991ba6a4 60e76e20 2bad27b3"); + System.out.println(); + +// See also http://csrc.ncsl.nist.gov/cryptval/shs/sha1-vectors.zip + + { + final int RETRIES = 10; + final int ITERATIONS = 2000; + final int BLOCKSIZE = 65536; + byte[] input = new byte[BLOCKSIZE]; + for (int i = BLOCKSIZE; --i >= 0; ) + input[i] = (byte)i; + long best = 0; + for (int i = 0; i < 1000; i++) // training for stable measure + System.currentTimeMillis(); + + for (int retry = 0; retry < RETRIES; retry++) { + long t0 = System.currentTimeMillis(); + for (int i = ITERATIONS; --i >= 0; ); + long t1 = System.currentTimeMillis(); + for (int i = ITERATIONS; --i >= 0; ) + hash.engineUpdate(input, 0, BLOCKSIZE); + long t2 = System.currentTimeMillis(); + long time = (t2 - t1) - (t1 - t0); + if (retry == 0 || time < best) + best = time; + } + hash.engineReset(); + double rate = 1000.0 * ITERATIONS * BLOCKSIZE / best; + System.out.println("Our rate = " + + (float)(rate * 8) + " bits/s = " + + (float)(rate / (1024 * 1024)) + " Megabytes/s"); + // Java 1.5 beta-b32c, on Athlon XP 1800+: + // with java -client: 48.21 Megabytes/s. + // with java -server: 68.23 Megabytes/s. + + try { + MessageDigest md = MessageDigest.getInstance("SHA"); + for (int retry = 0; retry < RETRIES; retry++) { + long t0 = System.currentTimeMillis(); + for (int i = ITERATIONS; --i >= 0; ); + long t1 = System.currentTimeMillis(); + for (int i = ITERATIONS; --i >= 0; ) + md.update(input, 0, BLOCKSIZE); + long t2 = System.currentTimeMillis(); + long time = (t2 - t1) - (t1 - t0); + if (retry == 0 || time < best) + best = time; + } + md.reset(); + rate = 1000.0 * ITERATIONS * BLOCKSIZE / best; + System.out.println("JCE rate = " + + (float)(rate * 8) + " bits/s = " + + (float)(rate / (1024 * 1024)) + " Megabytes/s"); + } catch (NoSuchAlgorithmException nsae) { + System.out.println("No SHA algorithm in local JCE Security Providers"); + } + // Java 1.5 beta-b32c, on Athlon XP 1800+: + // with java -client: 23.20 Megabytes/s. + // with java -server: 45.72 Megabytes/s. + } + } + + private static final boolean tst(final int set, final int vector, + final String source, + final String expect) { + byte[] input = new byte[source.length()]; + for (int i = 0; i < input.length; i++) + input[i] = (byte)source.charAt(i); + return tst(set, vector, input, expect); + } + + private static final boolean tst(final int set, final int vector, + final byte[] input, + final String expect) { + System.out.print("Set " + set + ", vector# " + vector + ": "); + hash.engineUpdate(input, 0, input.length); + return tstResult(expect); + } + + private static final boolean tst(final int set, final int vector, + final int times, final String source, + final String expect) { + byte[] input = new byte[source.length()]; + for (int i = 0; i < input.length; i++) + input[i] = (byte)source.charAt(i); + System.out.print("Set " + set + ", vector# " + vector + ": "); + for (int i = 0; i < times; i++) + hash.engineUpdate(input, 0, input.length); + return tstResult(expect); + } + + private static final boolean tstResult(String expect) { + final String result = toHex(hash.engineDigest()); + expect = expect.toUpperCase(); + if (!expect.equals(result)) { + System.out.println("**************** WRONG ***************"); + System.out.println(" expect: " + expect); + System.out.println(" result: " + result); + return false; + } + System.out.println("OK"); + return true; + } + + private static final String toHex(final byte[] bytes) { + StringBuffer buf = new StringBuffer(bytes.length * 2); + for (int i = 0; i < bytes.length; i++) { + if ((i & 3) == 0 && i != 0) + buf.append(' '); + buf.append(HEX.charAt((bytes[i] >> 4) & 0xF)) + .append(HEX.charAt( bytes[i] & 0xF)); + } + return buf.toString(); + } + private static final String HEX = "0123456789ABCDEF"; +} diff --git a/core/java/src/net/i2p/crypto/TrustedUpdate.java b/core/java/src/net/i2p/crypto/TrustedUpdate.java index 55120c7b7a..c801628a9c 100644 --- a/core/java/src/net/i2p/crypto/TrustedUpdate.java +++ b/core/java/src/net/i2p/crypto/TrustedUpdate.java @@ -6,8 +6,12 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.StringTokenizer; import net.i2p.I2PAppContext; +import net.i2p.data.DataFormatException; +import net.i2p.data.DataHelper; import net.i2p.data.Signature; import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPublicKey; @@ -19,21 +23,122 @@ import net.i2p.util.Log; * @author smeghead */ public class TrustedUpdate { + /** + * default trusted key, generated by jrandom. This can be authenticated + * via gpg without modification (gpg --verify TrustedUpdate.java) + * + */ +/* +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA1 - private static byte[] I2P_PUBLICKEY = { 'p', 'k' }; +*/ + private static final String DEFAULT_TRUSTED_KEY = + "W4kJbnv9KSVwbnapV7SaNW2kMIZKs~hwL0ro9pZXFo1xTwqz45nykCp1H" + + "M7sAKYDZay5z1HvYYOl9CNVz00xF03KPU9RUCVxhDZ1YXhZIskPKjUPUs" + + "CIpE~Z1C~N9KSEV6~2stDlBNH10VZ4T0X1TrcXwb3IBXliWo2y2GAx~Ow="; +/* +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.2.4 (GNU/Linux) + +iD8DBQFCQXoJGnFL2th344YRAtsIAKCUy/sOIsxahUnT2hKLXFL9lXsAmACfUHa5 +CPah6TDXYJCWmR0n3oPtrvo= +=mD0t +-----END PGP SIGNATURE----- +*/ + + private ArrayList _trustedKeys; private I2PAppContext _context; private Log _log; + private static final int VERSION_BYTES = 16; + private static final int HEADER_BYTES = VERSION_BYTES + Signature.SIGNATURE_BYTES; + + public static final String PROP_TRUSTED_KEYS = "router.trustedUpdateKeys"; + public TrustedUpdate() { - _context = I2PAppContext.getGlobalContext(); + this(I2PAppContext.getGlobalContext()); + } + public TrustedUpdate(I2PAppContext ctx) { + _context = ctx; _log = _context.logManager().getLog(TrustedUpdate.class); + _trustedKeys = new ArrayList(1); + String keys = ctx.getProperty(PROP_TRUSTED_KEYS); + if ( (keys != null) && (keys.length() > 0) ) { + StringTokenizer tok = new StringTokenizer(keys, ", "); + while (tok.hasMoreTokens()) + _trustedKeys.add(tok.nextToken()); + } else { + _trustedKeys.add(DEFAULT_TRUSTED_KEY); + } + } + + public ArrayList getTrustedKeys() { return _trustedKeys; } + + public static void main(String[] args) { + if (args.length <= 0) { + usage(); + } else if ("keygen".equals(args[0])) { + genKeysCLI(args[1], args[2]); + } else if ("sign".equals(args[0])) { + signCLI(args[1], args[2], args[3], args[4]); + } else if ("verify".equals(args[0])) { + verifyCLI(args[1]); + } else { + usage(); + } + } + + private static final void usage() { + System.err.println("Usage: TrustedUpdate keygen publicKeyFile privateKeyFile"); + System.err.println(" TrustedUpdate sign origFile signedFile privateKeyFile version"); + System.err.println(" TrustedUpdate verify signedFile"); + } + + private static final void genKeysCLI(String publicKeyFile, String privateKeyFile) { + FileOutputStream out = null; + try { + I2PAppContext ctx = I2PAppContext.getGlobalContext(); + Object keys[] = ctx.keyGenerator().generateSigningKeypair(); + SigningPublicKey pub = (SigningPublicKey)keys[0]; + SigningPrivateKey priv = (SigningPrivateKey)keys[1]; + + out = new FileOutputStream(publicKeyFile); + pub.writeBytes(out); + out.close(); + + out = new FileOutputStream(privateKeyFile); + priv.writeBytes(out); + out.close(); + out = null; + System.out.println("Private keys writen to " + privateKeyFile + " and public to " + publicKeyFile); + System.out.println("Public: " + pub.toBase64()); + } catch (Exception e) { + e.printStackTrace(); + System.err.println("Error writing out the keys"); + } finally { + if (out != null) try { out.close(); } catch (IOException ioe) {} + } } - public static void main(String[] args) { - // If no context is defined, don't define one. - // Expose verify(inputFile, publicKeyFile) via cli param - } + private static final void signCLI(String origFile, String outFile, String privKeyFile, String version) { + TrustedUpdate up = new TrustedUpdate(); + Signature sig = up.sign(origFile, outFile, privKeyFile, version); + if (sig != null) + System.out.println("Signed and written to " + outFile); + else + System.out.println("Error signing"); + } + + private static final void verifyCLI(String signedFile) { + TrustedUpdate up = new TrustedUpdate(); + boolean ok = up.verify(signedFile); + if (ok) + System.out.println("Signature VALID"); + else + System.out.println("Signature INVALID"); + } /** * Reads the version string from a signed I2P update file. @@ -44,14 +149,25 @@ public class TrustedUpdate { * string is present. */ public String getUpdateVersion(String inputFile) { - String updateVersion = null; - byte[] data = readFileBytes(inputFile, 0, 16); - try { - updateVersion = new String(data, "UTF-8"); - } catch (UnsupportedEncodingException e) { + FileInputStream in = null; + try { + in = new FileInputStream(inputFile); + byte data[] = new byte[VERSION_BYTES]; + int read = DataHelper.read(in, data); + if (read != VERSION_BYTES) + return null; + for (int i = 0; i < VERSION_BYTES; i++) + if (data[i] == 0x00) + return new String(data, 0, i, "UTF-8"); + return new String(data, "UTF-8"); + } catch (UnsupportedEncodingException uee) { // If this ever gets called, you need a new JVM. - } - return updateVersion; + throw new RuntimeException("wtf, your JVM doesnt support utf-8? " + uee.getMessage()); + } catch (IOException ioe) { + return ""; + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + } } /** @@ -69,37 +185,80 @@ public class TrustedUpdate { * string is longer than 16 characters it will be * truncated. * - * @return An instance of {@link net.i2p.data.Signature}. + * @return An instance of {@link net.i2p.data.Signature}, or null if there was an error */ public Signature sign(String inputFile, String outputFile, String privateKeyFile, String updateVersion) { + SigningPrivateKey key = new SigningPrivateKey(); + FileInputStream in = null; + try { + in = new FileInputStream(privateKeyFile); + key.readBytes(in); + } catch (IOException ioe) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Unable to load the signing key", ioe); + return null; + } catch (DataFormatException dfe) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Unable to load the signing key", dfe); + return null; + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + } + + return sign(inputFile, outputFile, key, updateVersion); + } + + public Signature sign(String inputFile, String outputFile, SigningPrivateKey privKey, String updateVersion) { byte[] headerUpdateVersion = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - byte[] updateVersionBytes = {}; - if (updateVersion.length() > 16) - updateVersion = updateVersion.substring(0, 16); + byte[] updateVersionBytes = null; + if (updateVersion.length() > VERSION_BYTES) + updateVersion = updateVersion.substring(0, VERSION_BYTES); try { updateVersionBytes = updateVersion.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { // If this ever gets called, you need a new JVM. + throw new RuntimeException("wtf, your JVM doesnt support utf-8? " + e.getMessage()); } - for (int i = 0; i < updateVersionBytes.length; i++) - headerUpdateVersion[i] = updateVersionBytes[i]; - byte[] data = readFileBytes(inputFile, 0, (int) new File(inputFile).length()); - Signature signature = DSAEngine.getInstance().sign(data, new SigningPrivateKey(readFileBytes(privateKeyFile, 0, (int) new File(privateKeyFile).length()-1))); - FileOutputStream fileOutputStream = null; - try { + System.arraycopy(updateVersionBytes, 0, headerUpdateVersion, 0, updateVersionBytes.length); + + Signature signature = null; + FileInputStream in = null; + try { + in = new FileInputStream(inputFile); + signature = _context.dsa().sign(in, privKey); + } catch (Exception e) { + if (_log.shouldLog(Log.ERROR)) + _log.error("Error signing", e); + return null; + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + in = null; + } + FileOutputStream fileOutputStream = null; + try { fileOutputStream = new FileOutputStream(outputFile); fileOutputStream.write(headerUpdateVersion); fileOutputStream.write(signature.getData()); - fileOutputStream.write(data); + + in = new FileInputStream(inputFile); + byte buf[] = new byte[1024]; + int read = 0; + while ( (read = in.read(buf)) != -1) + fileOutputStream.write(buf, 0, read); fileOutputStream.close(); + fileOutputStream = null; } catch (IOException ioe) { if (_log.shouldLog(Log.WARN)) _log.log(Log.WARN, "Error writing signed I2P update file " + outputFile, ioe); - } + return null; + } finally { + if (fileOutputStream != null) try { fileOutputStream.close(); } catch (IOException ioe) {} + if (in != null) try { in.close(); } catch (IOException ioe) {} + } return signature; } @@ -112,10 +271,50 @@ public class TrustedUpdate { * @return true if the file has a valid signature. */ public boolean verify(String inputFile) { - DSAEngine.getInstance().verifySignature(new Signature(readFileBytes(inputFile, 16, 55)), - readFileBytes(inputFile, 56, (int) new File(inputFile).length()-57), - new SigningPublicKey(I2P_PUBLICKEY)); - return false; + for (int i = 0; i < _trustedKeys.size(); i++) { + SigningPublicKey key = new SigningPublicKey(); + try { + key.fromBase64((String)_trustedKeys.get(i)); + boolean ok = verify(inputFile, key); + if (ok) return true; + } catch (DataFormatException dfe) { + _log.log(Log.CRIT, "Trusted key " + i + " is not valid"); + } + } + if (_log.shouldLog(Log.WARN)) + _log.warn("None of the keys match"); + return false; + } + + /** + * Verifies the DSA signature of a signed I2P update. + * + * @param inputFile The signed update file to check. + * @param key public key to verify against + * + * @return true if the file has a valid signature. + */ + public boolean verify(String inputFile, SigningPublicKey key) { + FileInputStream in = null; + try { + in = new FileInputStream(inputFile); + byte version[] = new byte[VERSION_BYTES]; + Signature sig = new Signature(); + if (VERSION_BYTES != DataHelper.read(in, version)) + throw new IOException("Not enough data for the version bytes"); + sig.readBytes(in); + return _context.dsa().verifySignature(sig, in, key); + } catch (IOException ioe) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Error reading " + inputFile + " to verify", ioe); + return false; + } catch (DataFormatException dfe) { + if (_log.shouldLog(Log.ERROR)) + _log.error("Error reading the signature", dfe); + return false; + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + } } /** @@ -127,28 +326,55 @@ public class TrustedUpdate { * @return true if the file has a valid signature. */ public boolean verify(String inputFile, String publicKeyFile) { - DSAEngine.getInstance().verifySignature(new Signature(readFileBytes(inputFile, 16, 55)), - readFileBytes(inputFile, 56, (int) new File(inputFile).length()-57), - new SigningPublicKey(readFileBytes(publicKeyFile, 0, (int) new File(publicKeyFile).length()-1))); - return false; - } - - private byte[] readFileBytes(String inputFile, int offset, int length) { - byte[] bytes = new byte[length]; - FileInputStream fileInputStream = null; - - try { - fileInputStream = new FileInputStream(inputFile); - fileInputStream.read(bytes, offset, length); - fileInputStream.close(); - } catch (FileNotFoundException fnfe) { - if (_log.shouldLog(Log.WARN)) - _log.log(Log.WARN, "File " + inputFile + " not found", fnfe); - } catch (IOException ioe) { - if (_log.shouldLog(Log.WARN)) - _log.log(Log.WARN, "Error reading file " + inputFile, ioe); - } - - return bytes; + SigningPublicKey pub = new SigningPublicKey(); + FileInputStream in = null; + try { + in = new FileInputStream(inputFile); + pub.readBytes(in); + } catch (IOException ioe) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Unable to load the signature", ioe); + return false; + } catch (DataFormatException dfe) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Unable to load the signature", dfe); + return false; + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + } + + return verify(inputFile, pub); } + + /** + * Verify the signature on the signed inputFile, and if it is valid, migrate + * the raw data out of it and into the outputFile + * + * @return true if the signature was valid and the data moved, false otherwise. + */ + public boolean migrateVerified(String inputFile, String outputFile) { + boolean ok = verify(inputFile); + if (!ok) return false; + FileOutputStream out = null; + FileInputStream in = null; + try { + out = new FileOutputStream(outputFile); + in = new FileInputStream(inputFile); + long skipped = 0; + while (skipped < HEADER_BYTES) { + skipped += in.skip(HEADER_BYTES - skipped); + } + + byte buf[] = new byte[1024]; + int read = 0; + while ( (read = in.read(buf)) != -1) + out.write(buf, 0, read); + } catch (IOException ioe) { + return false; + } finally { + if (out != null) try { out.close(); } catch (IOException ioe) {} + if (in != null) try { in.close(); } catch (IOException ioe) {} + } + return true; + } } diff --git a/core/java/src/net/i2p/util/EepGet.java b/core/java/src/net/i2p/util/EepGet.java new file mode 100644 index 0000000000..49351c0126 --- /dev/null +++ b/core/java/src/net/i2p/util/EepGet.java @@ -0,0 +1,513 @@ +package net.i2p.util; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.Socket; +import java.net.URL; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.StringTokenizer; +import java.util.Properties; + +import net.i2p.I2PAppContext; +import net.i2p.data.DataHelper; + +/** + * EepGet [-p localhost:4444] + * [-n #retries] + * [-o outputFile] + * [-m markSize lineLen] + * url + */ +public class EepGet { + private I2PAppContext _context; + private Log _log; + private boolean _shouldProxy; + private String _proxyHost; + private int _proxyPort; + private int _numRetries; + private String _outputFile; + private String _url; + private List _listeners; + + private boolean _keepFetching; + private Socket _proxy; + private OutputStream _proxyOut; + private InputStream _proxyIn; + private OutputStream _out; + private long _alreadyTransferred; + private long _bytesTransferred; + private long _bytesRemaining; + private int _currentAttempt; + private String _etag; + + public EepGet(I2PAppContext ctx, String proxyHost, int proxyPort, int numRetries, String outputFile, String url) { + this(ctx, true, proxyHost, proxyPort, numRetries, outputFile, url); + } + public EepGet(I2PAppContext ctx, int numRetries, String outputFile, String url) { + this(ctx, false, null, -1, numRetries, outputFile, url); + } + public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, String outputFile, String url) { + _context = ctx; + _log = ctx.logManager().getLog(EepGet.class); + _shouldProxy = shouldProxy; + _proxyHost = proxyHost; + _proxyPort = proxyPort; + _numRetries = numRetries; + _outputFile = outputFile; + _url = url; + _alreadyTransferred = 0; + _bytesTransferred = 0; + _bytesRemaining = -1; + _currentAttempt = 0; + _listeners = new ArrayList(1); + } + + /** + * EepGet [-p localhost:4444] [-n #retries] [-o outputFile] [-m markSize lineLen] url + * + */ + public static void main(String args[]) { + String proxyHost = "localhost"; + int proxyPort = 4444; + int numRetries = 5; + int markSize = 1024; + int lineLen = 40; + String saveAs = null; + String url = null; + try { + for (int i = 0; i < args.length; i++) { + if (args[i].equals("-p")) { + proxyHost = args[i+1].substring(0, args[i+1].indexOf(':')); + String port = args[i+1].substring(args[i+1].indexOf(':')+1); + proxyPort = Integer.parseInt(port); + i++; + } else if (args[i].equals("-n")) { + numRetries = Integer.parseInt(args[i+1]); + i++; + } else if (args[i].equals("-o")) { + saveAs = args[i+1]; + i++; + } else if (args[i].equals("-m")) { + markSize = Integer.parseInt(args[i+1]); + lineLen = Integer.parseInt(args[i+2]); + i += 2; + } else { + url = args[i]; + } + } + } catch (Exception e) { + e.printStackTrace(); + usage(); + return; + } + + if (url == null) { + usage(); + return; + } + if (saveAs == null) + saveAs = suggestName(url); + + EepGet get = new EepGet(I2PAppContext.getGlobalContext(), proxyHost, proxyPort, numRetries, saveAs, url); + get.addStatusListener(get.new CLIStatusListener(markSize, lineLen)); + get.fetch(); + } + + public static String suggestName(String url) { + String name = null; + if (url.lastIndexOf('/') >= 0) + name = sanitize(url.substring(url.lastIndexOf('/')+1)); + if (name != null) + return name; + else + return sanitize(url); + } + + private static final String _safeChars = "abcdefghijklmnopqrstuvwxyz" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + "01234567890.,_=@#:"; + private static String sanitize(String name) { + name = name.replace('/', '_'); + StringBuffer buf = new StringBuffer(name); + for (int i = 0; i < name.length(); i++) + if (_safeChars.indexOf(buf.charAt(i)) == -1) + buf.setCharAt(i, '_'); + return buf.toString(); + } + + private static void usage() { + System.err.println("EepGet [-p localhost:4444] [-n #retries] [-o outputFile] [-m markSize lineLen] url"); + } + + public static interface StatusListener { + public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url); + public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile); + public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause); + public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt); + } + private class CLIStatusListener implements StatusListener { + private int _markSize; + private int _lineSize; + private long _startedOn; + private long _written; + private long _lastComplete; + private DecimalFormat _pct = new DecimalFormat("00.0%"); + private DecimalFormat _kbps = new DecimalFormat("###,000.00"); + public CLIStatusListener() { + this(1024, 40); + } + public CLIStatusListener(int markSize, int lineSize) { + _markSize = markSize; + _lineSize = lineSize; + _written = 0; + _lastComplete = _context.clock().now(); + _startedOn = _lastComplete; + } + public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) { + for (int i = 0; i < currentWrite; i++) { + _written++; + if ( (_markSize > 0) && (_written % _markSize == 0) ) { + System.out.print("#"); + + if ( (_lineSize > 0) && (_written % ((long)_markSize*(long)_lineSize) == 0l) ) { + long now = _context.clock().now(); + long timeToSend = now - _lastComplete; + if (timeToSend > 0) { + StringBuffer buf = new StringBuffer(50); + buf.append(" "); + double pct = ((double)alreadyTransferred + (double)_written) / ((double)alreadyTransferred + (double)bytesRemaining); + synchronized (_pct) { + buf.append(_pct.format(pct)); + } + buf.append(": "); + buf.append(_written+alreadyTransferred); + buf.append(" @ "); + double lineKBytes = ((double)_markSize * (double)_lineSize)/1024.0d; + double kbps = lineKBytes/((double)timeToSend/1000.0d); + synchronized (_kbps) { + buf.append(_kbps.format(kbps)); + } + buf.append("KBps"); + + buf.append(" / "); + long lifetime = _context.clock().now() - _startedOn; + double lifetimeKBps = (1000.0d*(double)(_written+alreadyTransferred)/((double)lifetime*1024.0d)); + synchronized (_kbps) { + buf.append(_kbps.format(lifetimeKBps)); + } + buf.append("KBps"); + System.out.println(buf.toString()); + } + _lastComplete = now; + } + } + } + } + public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile) { + System.out.println(); + System.out.println("== " + new Date()); + System.out.println("== Transfer of " + url + " completed with " + (alreadyTransferred+bytesTransferred) + + " and " + (bytesRemaining - bytesTransferred) + " remaining"); + System.out.println("== Output saved to " + outputFile); + long timeToSend = _context.clock().now() - _startedOn; + System.out.println("== Transfer time: " + DataHelper.formatDuration(timeToSend)); + StringBuffer buf = new StringBuffer(50); + buf.append("== Transfer rate: "); + double kbps = (1000.0d*(double)(_written)/((double)timeToSend*1024.0d)); + synchronized (_kbps) { + buf.append(_kbps.format(kbps)); + } + buf.append("KBps"); + System.out.println(buf.toString()); + } + public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) { + System.out.println(); + System.out.println("** " + new Date()); + System.out.println("** Attempt " + currentAttempt + " of " + url + " failed"); + System.out.println("** Transfered " + bytesTransferred + + " with " + (bytesRemaining < 0 ? "unknown" : ""+bytesRemaining) + " remaining"); + System.out.println("** " + cause.getMessage()); + _written = 0; + } + public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) { + System.out.println("== " + new Date()); + System.out.println("== Transfer of " + url + " failed after " + currentAttempt + " attempts"); + System.out.println("== Transfer size: " + bytesTransferred + " with " + + (bytesRemaining < 0 ? "unknown" : ""+bytesRemaining) + " remaining"); + long timeToSend = _context.clock().now() - _startedOn; + System.out.println("== Transfer time: " + DataHelper.formatDuration(timeToSend)); + double kbps = (timeToSend > 0 ? (1000.0d*(double)(bytesTransferred)/((double)timeToSend*1024.0d)) : 0); + StringBuffer buf = new StringBuffer(50); + buf.append("== Transfer rate: "); + synchronized (_kbps) { + buf.append(_kbps.format(kbps)); + } + buf.append("KBps"); + System.out.println(buf.toString()); + } + } + + public void addStatusListener(StatusListener lsnr) { + synchronized (_listeners) { _listeners.add(lsnr); } + } + + public void stopFetching() { _keepFetching = false; } + public void fetch() { + _keepFetching = true; + + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Fetching (proxied? " + _shouldProxy + ") url=" + _url); + while (_keepFetching) { + try { + sendRequest(); + doFetch(); + return; + } catch (IOException ioe) { + for (int i = 0; i < _listeners.size(); i++) + ((StatusListener)_listeners.get(i)).attemptFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt, _numRetries, ioe); + } finally { + if (_out != null) { + try { + _out.close(); + } catch (IOException cioe) {} + _out = null; + } + if (_proxy != null) { + try { + _proxy.close(); + _proxy = null; + } catch (IOException ioe) {} + } + } + + _currentAttempt++; + if (_currentAttempt > _numRetries) + break; + try { Thread.sleep(5*1000); } catch (InterruptedException ie) {} + } + + for (int i = 0; i < _listeners.size(); i++) + ((StatusListener)_listeners.get(i)).transferFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt); + } + + /** return true if the URL was completely retrieved */ + private void doFetch() throws IOException { + readHeaders(); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Headers read completely, reading " + _bytesRemaining); + + byte buf[] = new byte[1024]; + while (_keepFetching) { + int read = _proxyIn.read(buf); + if (read == -1) + break; + _out.write(buf, 0, read); + _bytesTransferred += read; + if (read > 0) + for (int i = 0; i < _listeners.size(); i++) + ((StatusListener)_listeners.get(i)).bytesTransferred(_alreadyTransferred, read, _bytesTransferred, _bytesRemaining, _url); + } + + if (_out != null) + _out.close(); + _out = null; + + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Done transferring " + _bytesTransferred); + + if (_bytesRemaining == _bytesTransferred) { + for (int i = 0; i < _listeners.size(); i++) + ((StatusListener)_listeners.get(i)).transferComplete(_alreadyTransferred, _bytesTransferred, _bytesRemaining, _url, _outputFile); + } else { + throw new IOException("Disconnection on attempt " + _currentAttempt + " after " + _bytesTransferred); + } + } + + private void readHeaders() throws IOException { + String key = null; + StringBuffer buf = new StringBuffer(32); + + boolean read = DataHelper.readLine(_proxyIn, buf); + if (!read) throw new IOException("Unable to read the first line"); + int responseCode = handleStatus(buf.toString()); + + boolean rcOk = false; + switch (responseCode) { + case 200: // full + _out = new FileOutputStream(_outputFile, false); + rcOk = true; + break; + case 206: // partial + _out = new FileOutputStream(_outputFile, true); + rcOk = true; + break; + case 416: // completed (or range out of reach) + _bytesRemaining = 0; + _keepFetching = false; + return; + default: + rcOk = false; + } + + byte lookahead[] = new byte[3]; + while (true) { + int cur = _proxyIn.read(); + switch (cur) { + case -1: + throw new IOException("Headers ended too soon"); + case ':': + if (key == null) { + key = buf.toString(); + buf.setLength(0); + increment(lookahead, cur); + break; + } else { + buf.append((char)cur); + increment(lookahead, cur); + break; + } + case '\n': + case '\r': + if (key != null) + handle(key, buf.toString()); + + buf.setLength(0); + key = null; + increment(lookahead, cur); + if (isEndOfHeaders(lookahead)) { + if (!rcOk) + throw new IOException("Invalid HTTP response code: " + responseCode); + return; + } + break; + default: + buf.append((char)cur); + increment(lookahead, cur); + } + + if (buf.length() > 1024) + throw new IOException("Header line too long: " + buf.toString()); + } + } + + /** + * parse the first status line and grab the response code. + * e.g. "HTTP/1.1 206 OK" vs "HTTP/1.1 200 OK" vs + * "HTTP/1.1 404 NOT FOUND", etc. + * + * @return HTTP response code (200, 206, other) + */ + private int handleStatus(String line) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Status line: [" + line + "]"); + StringTokenizer tok = new StringTokenizer(line, " "); + if (!tok.hasMoreTokens()) { + System.err.println("ERR: status "+ line); + return -1; + } + String protocol = tok.nextToken(); // ignored + if (!tok.hasMoreTokens()) { + System.err.println("ERR: status "+ line); + return -1; + } + String rc = tok.nextToken(); + try { + return Integer.parseInt(rc); + } catch (NumberFormatException nfe) { + nfe.printStackTrace(); + return -1; + } + } + + private void handle(String key, String val) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Header line: [" + key + "] = [" + val + "]"); + if (key.equalsIgnoreCase("Content-length")) { + try { + _bytesRemaining = Long.parseLong(val.trim()); + } catch (NumberFormatException nfe) { + nfe.printStackTrace(); + } + } else if (key.equalsIgnoreCase("ETag")) { + _etag = val.trim(); + } else { + // ignore the rest + } + } + + private void increment(byte[] lookahead, int cur) { + lookahead[0] = lookahead[1]; + lookahead[1] = lookahead[2]; + lookahead[2] = (byte)cur; + } + private boolean isEndOfHeaders(byte lookahead[]) { + byte first = lookahead[0]; + byte second = lookahead[1]; + byte third = lookahead[2]; + return (isNL(second) && isNL(third)) || // \n\n + (isNL(first) && isNL(third)); // \n\r\n + } + + /** we ignore any potential \r, since we trim it on write anyway */ + private static final byte NL = '\n'; + private boolean isNL(byte b) { return (b == NL); } + + private void sendRequest() throws IOException { + File outFile = new File(_outputFile); + if (outFile.exists()) + _alreadyTransferred = outFile.length(); + + String req = getRequest(); + + if (_shouldProxy) { + _proxy = new Socket(_proxyHost, _proxyPort); + } else { + try { + URL url = new URL(_url); + String host = url.getHost(); + int port = url.getPort(); + if (port == -1) + port = 80; + _proxy = new Socket(host, port); + } catch (MalformedURLException mue) { + throw new IOException("Request URL is invalid"); + } + } + _proxyIn = _proxy.getInputStream(); + _proxyOut = _proxy.getOutputStream(); + + _proxyOut.write(req.toString().getBytes()); + _proxyOut.flush(); + + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Request flushed"); + } + + private String getRequest() { + StringBuffer buf = new StringBuffer(512); + buf.append("GET ").append(_url).append(" HTTP/1.1\n"); + try { + URL url = new URL(_url); + buf.append("Host: ").append(url.getHost()).append("\n"); + } catch (MalformedURLException mue) { + mue.printStackTrace(); + } + if (_alreadyTransferred > 0) { + buf.append("Range: bytes="); + buf.append(_alreadyTransferred); + buf.append("-\n"); + } + buf.append("Connection: close\n\n"); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Request: [" + buf.toString() + "]"); + return buf.toString(); + } +} diff --git a/core/java/src/net/i2p/util/LogManager.java b/core/java/src/net/i2p/util/LogManager.java index dfcab56dcf..4b690afa4c 100644 --- a/core/java/src/net/i2p/util/LogManager.java +++ b/core/java/src/net/i2p/util/LogManager.java @@ -245,7 +245,7 @@ public class LogManager { if (!_alreadyNoticedMissingConfig) { if (_log.shouldLog(Log.WARN)) _log.warn("Log file " + _location + " does not exist"); - System.err.println("Log file " + _location + " does not exist"); + //System.err.println("Log file " + _location + " does not exist"); _alreadyNoticedMissingConfig = true; } parseConfig(new Properties()); @@ -644,7 +644,7 @@ public class LogManager { } public void shutdown() { - _log.log(Log.CRIT, "Shutting down logger"); + _log.log(Log.WARN, "Shutting down logger"); _writer.flushRecords(); } diff --git a/core/java/test/net/i2p/crypto/DSABench.java b/core/java/test/net/i2p/crypto/DSABench.java index 5d29e1d073..cbc18fd685 100644 --- a/core/java/test/net/i2p/crypto/DSABench.java +++ b/core/java/test/net/i2p/crypto/DSABench.java @@ -29,6 +29,7 @@ package net.i2p.crypto; * POSSIBILITY OF SUCH DAMAGE. */ +import java.io.ByteArrayInputStream; import net.i2p.data.Signature; import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPublicKey; @@ -57,9 +58,13 @@ public class DSABench { long endkeys = System.currentTimeMillis(); long startsign = System.currentTimeMillis(); Signature s = DSAEngine.getInstance().sign(message, privkey); + Signature s1 = DSAEngine.getInstance().sign(new ByteArrayInputStream(message), privkey); long endsignstartverify = System.currentTimeMillis(); boolean v = DSAEngine.getInstance().verifySignature(s, message, pubkey); - long endverify = System.currentTimeMillis(); + boolean v1 = DSAEngine.getInstance().verifySignature(s1, new ByteArrayInputStream(message), pubkey); + boolean v2 = DSAEngine.getInstance().verifySignature(s1, message, pubkey); + boolean v3 = DSAEngine.getInstance().verifySignature(s, new ByteArrayInputStream(message), pubkey); + long endverify = System.currentTimeMillis(); System.out.print("."); keygentime += endkeys - startkeys; signtime += endsignstartverify - startsign; @@ -67,6 +72,8 @@ public class DSABench { if (!v) { throw new RuntimeException("Holy crap, did not verify"); } + if (!(v1 && v2 && v3)) + throw new RuntimeException("Stream did not verify"); if ( (minKey == 0) && (minS == 0) && (minV == 0) ) { minKey = endkeys - startkeys; maxKey = endkeys - startkeys; diff --git a/history.txt b/history.txt index 0c23f1f9e0..e98ea9b7cc 100644 --- a/history.txt +++ b/history.txt @@ -1,4 +1,27 @@ -$Id: history.txt,v 1.173 2005/03/18 17:34:54 jrandom Exp $ +$Id: history.txt,v 1.174 2005/03/21 20:38:21 jrandom Exp $ + +2005-03-23 jrandom + * New /configupdate.jsp page for controlling the update / notification + process, as well as various minor related updates. Note that not all + options are exposed yet, and the update detection code isn't in place + in this commit - it currently says there is always an update available. + * New EepGet component for reliable downloading, with a CLI exposed in + java -cp lib/i2p.jar net.i2p.util.EepGet url + * Added a default signing key to the TrustedUpdate component to be used + for verifying updates. This signing key can be authenticated via + gpg --verify i2p/core/java/src/net/i2p/crypto/TrustedUpdate.java + * New public domain SHA1 implementation for the DSA code so that we can + handle signing streams of arbitrary size without excess memory usage + (thanks P.Verdy!) + * Added some helpers to the TrustedUpdate to work off streams and to offer + a minimal CLI: + TrustedUpdate keygen pubKeyFile privKeyFile + TrustedUpdate sign origFile signedFile privKeyFile + TrustedUpdate verify signedFile + +2005-03-22 smeghead + * New TrustedUpdate component for signing/verifying files with a DSA + signature. 2005-03-21 jrandom * Fixed the tunnel fragmentation handler to deal with multiple fragments diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 599681fe92..a5ec0048db 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.167 $ $Date: 2005/03/18 17:34:52 $"; + public final static String ID = "$Revision: 1.168 $ $Date: 2005/03/21 20:38:21 $"; public final static String VERSION = "0.5.0.3"; - public final static long BUILD = 1; + public final static long BUILD = 2; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION); System.out.println("Router ID: " + RouterVersion.ID);