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"%>
+
+
+
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:
+ *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);