diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java b/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java
index 057ad2ef40dcc4c727134dab460735a91ce03ef1..0299156a20a2e445317833357392eb263f1ceba5 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java
@@ -7,6 +7,7 @@ import java.util.List;
 import java.util.StringTokenizer;
 
 import net.i2p.I2PAppContext;
+import net.i2p.crypto.TrustedUpdate;
 import net.i2p.data.DataHelper;
 import net.i2p.router.RouterContext;
 import net.i2p.router.RouterVersion;
@@ -136,7 +137,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
                         String ver = buf.substring(index+VERSION_PREFIX.length(), end);
                         if (_log.shouldLog(Log.DEBUG))
                             _log.debug("Found version: [" + ver + "]");
-                        if (needsUpdate(ver)) {
+                        if (TrustedUpdate.needsUpdate(RouterVersion.VERSION, ver)) {
                             if (_log.shouldLog(Log.DEBUG))
                                 _log.debug("Our version is out of date, update!");
                             break;
@@ -191,54 +192,6 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
         }
     }
     
-    private boolean needsUpdate(String version) {
-        StringTokenizer newTok = new StringTokenizer(sanitize(version), ".");
-        StringTokenizer ourTok = new StringTokenizer(sanitize(RouterVersion.VERSION), ".");
-        
-        while (newTok.hasMoreTokens() && ourTok.hasMoreTokens()) {
-            String newVer = newTok.nextToken();
-            String oldVer = ourTok.nextToken();
-            switch (compare(newVer, oldVer)) {
-                case -1: // newVer is smaller
-                    return false;
-                case 0: // eq
-                    break;
-                case 1: // newVer is larger
-                    return true;
-            }
-        }
-        if (newTok.hasMoreTokens() && !ourTok.hasMoreTokens())
-            return true;
-        return false;
-    }
-    
-    private static final String VALID = "0123456789.";
-    private static final String sanitize(String str) {
-        StringBuffer buf = new StringBuffer(str);
-        for (int i = 0; i < buf.length(); i++) {
-            if (VALID.indexOf(buf.charAt(i)) == -1) {
-                buf.deleteCharAt(i);
-                i--;
-            }
-        }
-        return buf.toString();
-    }
-    
-    private static final int compare(String lhs, String rhs) {
-        try {
-            int left = Integer.parseInt(lhs);
-            int right = Integer.parseInt(rhs);
-            if (left < right) 
-                return -1;
-            else if (left == right)
-                return 0;
-            else
-                return 1;
-        } catch (NumberFormatException nfe) {
-            return 0;
-        }
-    }
-    
     public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
         // ignore
     }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java
index c2fd838f5b2f47b2aa3fff853b354b07fe3fa185..c412d025b31f59a0585bf9cc6b5a483d9114c05f 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java
@@ -2,19 +2,25 @@ 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.router.RouterVersion;
 import net.i2p.util.EepGet;
+import net.i2p.util.I2PThread;
 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.
- *
+ * <p>Handles the request to update the router by firing off an
+ * {@link net.i2p.util.EepGet} call to download the latest signed update file
+ * and displaying the status to anyone who asks.
+ * </p>
+ * <p>After the download completes the signed update file is verified with
+ * {@link net.i2p.crypto.TrustedUpdate}, and if it's authentic the payload
+ * of the signed update file is unpacked and the router is restarted to complete
+ * the update process.
+ * </p>
  */
 public class UpdateHandler {
     private static UpdateRunner _updateRunner;
@@ -140,7 +146,7 @@ public class UpdateHandler {
         public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile) {
             _status = "<b>Update downloaded</b><br />";
             TrustedUpdate up = new TrustedUpdate(_context);
-            boolean ok = up.migrateVerified(SIGNED_UPDATE_FILE, "i2pupdate.zip");
+            boolean ok = up.migrateVerified(RouterVersion.VERSION, SIGNED_UPDATE_FILE, "i2pupdate.zip");
             File f = new File(SIGNED_UPDATE_FILE);
             f.delete();
             if (ok) {
diff --git a/core/java/src/net/i2p/crypto/TrustedUpdate.java b/core/java/src/net/i2p/crypto/TrustedUpdate.java
index c801628a9c149640e279a18585988afc73c58af4..acd4bfc964b97b68ce5ef1bea6b5becd46ffc998 100644
--- a/core/java/src/net/i2p/crypto/TrustedUpdate.java
+++ b/core/java/src/net/i2p/crypto/TrustedUpdate.java
@@ -1,14 +1,15 @@
 package net.i2p.crypto;
 
-import java.io.File;
-import java.io.FileNotFoundException;
+import java.io.ByteArrayInputStream;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.SequenceInputStream;
 import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
 import java.util.StringTokenizer;
 
+import net.i2p.CoreVersion;
 import net.i2p.I2PAppContext;
 import net.i2p.data.DataFormatException;
 import net.i2p.data.DataHelper;
@@ -18,15 +19,28 @@ import net.i2p.data.SigningPublicKey;
 import net.i2p.util.Log;
 
 /**
- * Handles DSA signing and verification of I2P update archives.
+ * <p>Handles DSA signing and verification of update files.
+ * </p>
+ * <p>For convenience this class also makes certain operations available via the
+ * command line. These can be invoked as follows:
+ * </p>
+ * <pre>
+ * java net.i2p.crypto.TrustedUpdate keygen       <i>publicKeyFile privateKeyFile</i>
+ * java net.i2p.crypto.TrustedUpdate showversion  <i>signedFile</i>
+ * java net.i2p.crypto.TrustedUpdate sign         <i>inputFile signedFile privateKeyFile version</i>
+ * java net.i2p.crypto.TrustedUpdate verifysig    <i>signedFile</i>
+ * java net.i2p.crypto.TrustedUpdate verifyupdate <i>signedFile</i>
+ * </pre>
  * 
- * @author smeghead
+ * @author jrandom and smeghead
  */
 public class TrustedUpdate {
+
     /**
-     * default trusted key, generated by jrandom.  This can be authenticated
-     * via gpg without modification (gpg --verify TrustedUpdate.java)
-     *
+     * <p>Default trusted key generated by jrandom@i2p.net. This can be
+     * authenticated via <code>gpg</code> without modification:</p>
+     * <p>
+     * <code>gpg --verify TrustedUpdate.java</code></p>
      */
 /*
 -----BEGIN PGP SIGNED MESSAGE-----
@@ -47,334 +61,575 @@ CPah6TDXYJCWmR0n3oPtrvo=
 -----END PGP SIGNATURE-----
 */
 
-    private ArrayList _trustedKeys;
+    private static final String VALID_VERSION_CHARS = "0123456789.";
+    private static final int    VERSION_BYTES       = 16;
+    private static final int    HEADER_BYTES        = Signature.SIGNATURE_BYTES + VERSION_BYTES;
+    private static final String PROP_TRUSTED_KEYS   = "router.trustedUpdateKeys";
+
+    private static I2PAppContext _context;
 
-    private I2PAppContext _context;
-    private Log           _log;
+    private Log       _log;
+    private ArrayList _trustedKeys;
 
-    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";
-    
+    /**
+     * Constructs a new <code>TrustedUpdate</code> with the default global
+     * context.
+     */
     public TrustedUpdate() {
         this(I2PAppContext.getGlobalContext());
     }
-    public TrustedUpdate(I2PAppContext ctx) {
-        _context = ctx;
+
+    /**
+     * Constructs a new <code>TrustedUpdate</code> with the given
+     * {@link net.i2p.I2PAppContext}.
+     * 
+     * @param context An instance of <code>I2PAppContext</code>.
+     */
+    public TrustedUpdate(I2PAppContext context) {
+        _context = context;
         _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());
+        _trustedKeys = new ArrayList();
+
+        String propertyTrustedKeys = context.getProperty(PROP_TRUSTED_KEYS);
+
+        if ( (propertyTrustedKeys != null) && (propertyTrustedKeys.length() > 0) ) {
+            StringTokenizer propertyTrustedKeysTokens = new StringTokenizer(propertyTrustedKeys, ",");
+
+            while (propertyTrustedKeysTokens.hasMoreTokens())
+                _trustedKeys.add(propertyTrustedKeysTokens.nextToken().trim());
+
         } 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();
+
+    /**
+     * Parses command line arguments when this class is used from the command
+     * line.
+     * 
+     * @param args Command line parameters.
+     */
+    public static void main(String[] args) {
+        try {
+            if ("keygen".equals(args[0])) {
+                genKeysCLI(args[1], args[2]);
+            } else if ("showversion".equals(args[0])) {
+                showVersionCLI(args[1]);
+            } else if ("sign".equals(args[0])) {
+                signCLI(args[1], args[2], args[3], args[4]);
+            } else if ("verifysig".equals(args[0])) {
+                verifySigCLI(args[1]);
+            } else if ("verifyupdate".equals(args[0])) {
+                verifyUpdateCLI(args[1]);
+            } else {
+                showUsageCLI();
+            }
+        } catch (ArrayIndexOutOfBoundsException aioobe) {
+            showUsageCLI();
+        }
+    }
+
+    /**
+     * Checks if the given version is newer than the given current version.
+     * 
+     * @param currentVersion The current version.
+     * @param newVersion     The version to test.
+     * 
+     * @return <code>true</code> if the given version is newer than the current
+     *         version, otherwise <code>false</code>.
+     */
+    public static final boolean needsUpdate(String currentVersion, String newVersion) {
+        StringTokenizer newVersionTokens = new StringTokenizer(sanitize(newVersion), ".");
+        StringTokenizer currentVersionTokens = new StringTokenizer(sanitize(currentVersion), ".");
+
+        while (newVersionTokens.hasMoreTokens() && currentVersionTokens.hasMoreTokens()) {
+            String newNumber = newVersionTokens.nextToken();
+            String currentNumber = currentVersionTokens.nextToken();
+
+            switch (compare(newNumber, currentNumber)) {
+                case -1: // newNumber is smaller
+                    return false;
+                case 0: // eq
+                    break;
+                case 1: // newNumber is larger
+                    return true;
+            }
         }
-	}
 
-    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");
+        if (newVersionTokens.hasMoreTokens() && !currentVersionTokens.hasMoreTokens())
+            return true;
+
+        return false;
     }
-    
+
+    private static final int compare(String lop, String rop) {
+        try {
+            int left = Integer.parseInt(lop);
+            int right = Integer.parseInt(rop);
+
+            if (left < right) 
+                return -1;
+            else if (left == right)
+                return 0;
+            else
+                return 1;
+        } catch (NumberFormatException nfe) {
+            return 0;
+        }
+    }
+
     private static final void genKeysCLI(String publicKeyFile, String privateKeyFile) {
-        FileOutputStream out = null;
+        FileOutputStream fileOutputStream = 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());
+            Object signingKeypair[] = _context.keyGenerator().generateSigningKeypair();
+            SigningPublicKey signingPublicKey = (SigningPublicKey) signingKeypair[0];
+            SigningPrivateKey signingPrivateKey = (SigningPrivateKey) signingKeypair[1];
+
+            fileOutputStream = new FileOutputStream(publicKeyFile);
+            signingPublicKey.writeBytes(fileOutputStream);
+            fileOutputStream.close();
+            fileOutputStream = null;
+
+            fileOutputStream = new FileOutputStream(privateKeyFile);
+            signingPrivateKey.writeBytes(fileOutputStream);
+
+            System.out.println("\r\nPrivate key written to: " + privateKeyFile);
+            System.out.println("Public key written to: " + publicKeyFile);
+            System.out.println("\r\nPublic key: " + signingPublicKey.toBase64() + "\r\n");
         } catch (Exception e) {
+            System.err.println("Error writing keys:");
             e.printStackTrace();
-            System.err.println("Error writing out the keys");
         } finally {
-            if (out != null) try { out.close(); } catch (IOException ioe) {}
+            if (fileOutputStream != null)
+                try {
+                    fileOutputStream.close();
+                } catch (IOException ioe) {
+                }
+        }
+    }
+
+    private static final String sanitize(String versionString) {
+        StringBuffer versionStringBuffer = new StringBuffer(versionString);
+
+        for (int i = 0; i < versionStringBuffer.length(); i++) {
+            if (VALID_VERSION_CHARS.indexOf(versionStringBuffer.charAt(i)) == -1) {
+                versionStringBuffer.deleteCharAt(i);
+                i--;
+            }
         }
+
+        return versionStringBuffer.toString();
     }
 
-    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);
+    private static final void showUsageCLI() {
+        System.err.println("Usage: TrustedUpdate keygen       publicKeyFile privateKeyFile");
+        System.err.println("       TrustedUpdate showversion  signedFile");
+        System.err.println("       TrustedUpdate sign         inputFile signedFile privateKeyFile version");
+        System.err.println("       TrustedUpdate verifysig    signedFile");
+        System.err.println("       TrustedUpdate verifyupdate signedFile");
+    }
+
+    private static final void showVersionCLI(String signedFile) {
+        String versionString = new TrustedUpdate().getVersionString(signedFile);
+
+        if (versionString == "")
+            System.out.println("No version string found in file '" + signedFile + "'");
         else
-            System.out.println("Error signing");
+            System.out.println("Version: " + versionString);
     }
-    
-    private static final void verifyCLI(String signedFile) {
-        TrustedUpdate up = new TrustedUpdate();
-        boolean ok = up.verify(signedFile);
-        if (ok)
+
+    private static final void signCLI(String inputFile, String signedFile, String privateKeyFile, String version) {
+        Signature signature = new TrustedUpdate().sign(inputFile, signedFile, privateKeyFile, version);
+
+        if (signature != null)
+            System.out.println("Input file '" + inputFile + "' signed and written to '" + signedFile + "'");
+        else
+            System.out.println("Error signing input file '" + inputFile + "'");
+    }
+
+    private static final void verifySigCLI(String signedFile) {
+        boolean isValidSignature = new TrustedUpdate().verify(signedFile);
+
+        if (isValidSignature)
             System.out.println("Signature VALID");
         else
             System.out.println("Signature INVALID");
     }
 
-	/**
-	 * Reads the version string from a signed I2P update file.
-	 * 
-	 * @param inputFile A signed I2P update file.
-	 * 
-	 * @return The update version string read, or an empty string if no version
-	 *         string is present.
-	 */
-	public String getUpdateVersion(String inputFile) {
-        FileInputStream in = null;
+    private static final void verifyUpdateCLI(String signedFile) {
+        boolean isUpdate = new TrustedUpdate().isUpdatedVersion(CoreVersion.VERSION, signedFile);
+
+        if (isUpdate)
+            System.out.println("File version is newer than current version.");
+        else
+            System.out.println("File version is older than or equal to current version.");
+    }
+
+    /**
+     * Fetches the trusted keys for the current instance.
+     * 
+     * @return An <code>ArrayList</code> containting the trusted keys.
+     */
+    public ArrayList getTrustedKeys() {
+        return _trustedKeys;
+    }
+
+    /**
+     * Reads the version string from a signed update file.
+     * 
+     * @param signedFile A signed update file.
+     * 
+     * @return The version string read, or an empty string if no version string
+     *         is present.
+     */
+    public String getVersionString(String signedFile) {
+        FileInputStream fileInputStream = null;
+
         try {
-            in = new FileInputStream(inputFile);
-            byte data[] = new byte[VERSION_BYTES];
-            int read = DataHelper.read(in, data);
-            if (read != VERSION_BYTES)
-                return null;
+            fileInputStream = new FileInputStream(signedFile);
+            byte[] data = new byte[VERSION_BYTES];
+            int bytesRead = DataHelper.read(fileInputStream, data, Signature.SIGNATURE_BYTES, VERSION_BYTES);
+
+            if (bytesRead != VERSION_BYTES)
+                return "";
+
             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.
             throw new RuntimeException("wtf, your JVM doesnt support utf-8? " + uee.getMessage());
-		} catch (IOException ioe) {
+        } catch (IOException ioe) {
             return "";
         } finally {
-            if (in != null) try { in.close(); } catch (IOException ioe) {}
+            if (fileInputStream != null)
+                try {
+                    fileInputStream.close();
+                } catch (IOException ioe) {
+                }
         }
-	}
-
-	/**
-	 * Uses the given private key to sign the given input file with DSA. The
-	 * output will be a binary file where the first 16 bytes are the I2P
-	 * update's version string encoded in UTF-8 (padded with trailing
-	 * <code>0h</code> characters if necessary), the next 40 bytes are the
-	 * resulting DSA signature, and the remaining bytes are the input file.
-	 * 
-	 * @param inputFile      The file to be signed.
-	 * @param outputFile     The signed file to write.
-	 * @param privateKeyFile The name of the file containing the private key to
-	 *                       sign <code>inputFile</code> with.
-	 * @param updateVersion  The version number of the I2P update. If this
-	 *                       string is longer than 16 characters it will be
-	 *                       truncated.
-	 * 
-	 * @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;
+    }
+
+    /**
+     * Verifies that the version of the given signed update file is newer than
+     * <code>currentVersion</code>.
+     * 
+     * @param currentVersion The current version to check against.
+     * @param signedFile     The signed update file.
+     * 
+     * @return <code>true</code> if the signed update file's version is newer
+     *         than the current version, otherwise <code>false</code>.
+     */
+    public boolean isUpdatedVersion(String currentVersion, String signedFile) {
+        if (needsUpdate(currentVersion, getVersionString(signedFile)))
+            return true;
+        else
+            return false;
+    }
+
+    /**
+     * Verifies the signature of a signed update file, and if it's valid and the
+     * file's version is newer than the given current version, migrates the data
+     * out of <code>signedFile</code> and into <code>outputFile</code>.
+     * 
+     * @param currentVersion The current version to check against.
+     * @param signedFile     A signed update file.
+     * @param outputFile     The file to write the verified data to.
+     * 
+     * @return <code>true</code> if the signature and version were valid and the
+     *         data was moved, <code>false</code> otherwise.
+     */
+    public boolean migrateVerified(String currentVersion, String signedFile, String outputFile) {
+        if (!isUpdatedVersion(currentVersion, signedFile))
+            return false;
+
+        if (!verify(signedFile))
+            return false;
+
+        FileInputStream fileInputStream = null;
+        FileOutputStream fileOutputStream = null;
+
         try {
-            in = new FileInputStream(privateKeyFile);
-            key.readBytes(in);
+            fileInputStream = new FileInputStream(signedFile);
+            fileOutputStream = new FileOutputStream(outputFile);
+            long skipped = 0;
+
+            while (skipped < HEADER_BYTES)
+                skipped += fileInputStream.skip(HEADER_BYTES - skipped);
+
+            byte[] buffer = new byte[1024];
+            int bytesRead = 0;
+
+            while ( (bytesRead = fileInputStream.read(buffer)) != -1) 
+                fileOutputStream.write(buffer, 0, bytesRead);
+        } catch (IOException ioe) {
+            return false;
+        } finally {
+            if (fileInputStream != null)
+                try {
+                    fileInputStream.close();
+                } catch (IOException ioe) {
+                }
+
+            if (fileOutputStream != null)
+                try {
+                    fileOutputStream.close();
+                } catch (IOException ioe) {
+                }
+        }
+
+        return true;
+    }
+
+    /**
+     * Uses the given private key to sign the given input file along with its
+     * version string using DSA. The output will be a signed update file where
+     * the first 40 bytes are the resulting DSA signature, the next 16 bytes are
+     * the input file's version string encoded in UTF-8 (padded with trailing
+     * <code>0h</code> characters if necessary), and the remaining bytes are the
+     * raw bytes of the input file.
+     * 
+     * @param inputFile      The file to be signed.
+     * @param signedFile     The signed update file to write.
+     * @param privateKeyFile The name of the file containing the private key to
+     *                       sign <code>inputFile</code> with.
+     * @param version        The version string of the input file. If this is
+     *                       longer than 16 characters it will be truncated.
+     * 
+     * @return An instance of {@link net.i2p.data.Signature}, or
+     *         <code>null</code> if there was an error.
+     */
+    public Signature sign(String inputFile, String signedFile, String privateKeyFile, String version) {
+        FileInputStream fileInputStream = null;
+        SigningPrivateKey signingPrivateKey = new SigningPrivateKey();
+
+        try {
+            fileInputStream = new FileInputStream(privateKeyFile);
+            signingPrivateKey.readBytes(fileInputStream);
         } 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) {}
+            if (fileInputStream != null)
+                try {
+                    fileInputStream.close();
+                } catch (IOException ioe) {
+                }
         }
-        
-        return sign(inputFile, outputFile, key, updateVersion);
+
+        return sign(inputFile, signedFile, signingPrivateKey, version);
     }
-    
-	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 = 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.
+
+    /**
+     * Uses the given {@link net.i2p.data.SigningPrivateKey} to sign the given
+     * input file along with its version string using DSA. The output will be a
+     * signed update file where the first 40 bytes are the resulting DSA
+     * signature, the next 16 bytes are the input file's version string encoded
+     * in UTF-8 (padded with trailing <code>0h</code> characters if necessary),
+     * and the remaining bytes are the raw bytes of the input file.
+     * 
+     * @param inputFile         The file to be signed.
+     * @param signedFile        The signed update file to write.
+     * @param signingPrivateKey An instance of <code>SigningPrivateKey</code>
+     *                          to sign <code>inputFile</code> with.
+     * @param version           The version string of the input file. If this is
+     *                          longer than 16 characters it will be truncated.
+     * 
+     * @return An instance of {@link net.i2p.data.Signature}, or
+     *         <code>null</code> if there was an error.
+     */
+    public Signature sign(String inputFile, String signedFile, SigningPrivateKey signingPrivateKey, String version) {
+        byte[] versionHeader = {
+                0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00 };
+        byte[] versionRawBytes = null;
+
+        if (version.length() > VERSION_BYTES)
+            version = version.substring(0, VERSION_BYTES);
+
+        try {
+            versionRawBytes = version.getBytes("UTF-8");
+        } catch (UnsupportedEncodingException e) {
             throw new RuntimeException("wtf, your JVM doesnt support utf-8? " + e.getMessage());
-		}
-        System.arraycopy(updateVersionBytes, 0, headerUpdateVersion, 0, updateVersionBytes.length);
+        }
 
+        System.arraycopy(versionRawBytes, 0, versionHeader, 0, versionRawBytes.length);
+
+        FileInputStream fileInputStream = null;
         Signature signature = null;
-        FileInputStream in = null;
+        SequenceInputStream bytesToSignInputStream = null;
+        ByteArrayInputStream versionHeaderInputStream = null;
+
         try {
-            in = new FileInputStream(inputFile);
-            signature = _context.dsa().sign(in, privKey);
+            fileInputStream = new FileInputStream(inputFile);
+            versionHeaderInputStream = new ByteArrayInputStream(versionHeader);
+            bytesToSignInputStream = new SequenceInputStream(versionHeaderInputStream, fileInputStream);
+            signature = _context.dsa().sign(bytesToSignInputStream, signingPrivateKey);
+
         } 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;
+            if (bytesToSignInputStream != null)
+                try {
+                    bytesToSignInputStream.close();
+                } catch (IOException ioe) {
+                }
+
+            fileInputStream = null;
         }
+
         FileOutputStream fileOutputStream = null;
+
         try {
-			fileOutputStream = new FileOutputStream(outputFile);
-			fileOutputStream.write(headerUpdateVersion);
-			fileOutputStream.write(signature.getData());
-            
-            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);
+            fileOutputStream = new FileOutputStream(signedFile);
+            fileOutputStream.write(signature.getData());
+            fileOutputStream.write(versionHeader);
+            fileInputStream = new FileInputStream(inputFile);
+            byte[] buffer = new byte[1024];
+            int bytesRead = 0;
+            while ( (bytesRead = fileInputStream.read(buffer)) != -1) 
+                fileOutputStream.write(buffer, 0, bytesRead);
+            fileOutputStream.close();
+        } catch (IOException ioe) {
+            if (_log.shouldLog(Log.WARN))
+                _log.log(Log.WARN, "Error writing signed file " + signedFile, ioe);
+
             return null;
-		} finally {
-            if (fileOutputStream != null) try { fileOutputStream.close(); } catch (IOException ioe) {}
-            if (in != null) try { in.close(); } catch (IOException ioe) {}
+        } finally {
+            if (fileInputStream != null)
+                try {
+                    fileInputStream.close();
+                } catch (IOException ioe) {
+                }
+
+            if (fileOutputStream != null)
+                try {
+                    fileOutputStream.close();
+                } catch (IOException ioe) {
+                }
         }
 
-		return signature;
-	}
-
-	/**
-	 * Verifies the DSA signature of a signed I2P update.
-	 * 
-	 * @param inputFile The signed update file to check.
-	 * 
-	 * @return <code>true</code> if the file has a valid signature.
-	 */
-	public boolean verify(String inputFile) {
+        return signature;
+    }
+
+    /**
+     * Verifies the DSA signature of a signed update file.
+     * 
+     * @param signedFile The signed update file to check.
+     * 
+     * @return <code>true</code> if the file has a valid signature, otherwise
+     *         <code>false</code>.
+     */
+    public boolean verify(String signedFile) {
         for (int i = 0; i < _trustedKeys.size(); i++) {
-            SigningPublicKey key = new SigningPublicKey();
+            SigningPublicKey signingPublicKey = new SigningPublicKey();
+
             try {
-                key.fromBase64((String)_trustedKeys.get(i));
-                boolean ok = verify(inputFile, key);
-                if (ok) return true;
+                signingPublicKey.fromBase64((String)_trustedKeys.get(i));
+                boolean isValidSignature = verify(signedFile, signingPublicKey);
+
+                if (isValidSignature)
+                    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 <code>true</code> 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) {}
-        }
-	}
-
-	/**
-	 * Verifies the DSA signature of a signed I2P update.
-	 * 
-	 * @param inputFile     The signed update file to check.
-	 * @param publicKeyFile The public key to use for verification.
-	 * 
-	 * @return <code>true</code> if the file has a valid signature. 
-	 */
-	public boolean verify(String inputFile, String publicKeyFile) {
-        SigningPublicKey pub = new SigningPublicKey();
-        FileInputStream in = null;
+
+    /**
+     * Verifies the DSA signature of a signed update file.
+     * 
+     * @param signedFile    The signed update file to check.
+     * @param publicKeyFile A file containing the public key to use for
+     *                      verification.
+     * 
+     * @return <code>true</code> if the file has a valid signature, otherwise
+     *         <code>false</code>.
+     */
+    public boolean verify(String signedFile, String publicKeyFile) {
+        SigningPublicKey signingPublicKey = new SigningPublicKey();
+        FileInputStream fileInputStream = null;
+
         try {
-            in = new FileInputStream(inputFile);
-            pub.readBytes(in);
+            fileInputStream = new FileInputStream(signedFile);
+            signingPublicKey.readBytes(fileInputStream);
         } 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) {}
+            if (fileInputStream != null)
+                try {
+                    fileInputStream.close();
+                } catch (IOException ioe) {
+                }
         }
-        
-        return verify(inputFile, pub);
-	}
-    
+
+        return verify(signedFile, signingPublicKey);
+    }
+
     /**
-     * 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.
+     * Verifies the DSA signature of a signed update file.
+     * 
+     * @param signedFile       The signed update file to check.
+     * @param signingPublicKey An instance of
+     *                         {@link net.i2p.data.SigningPublicKey} to use for
+     *                         verification.
+     * 
+     * @return <code>true</code> if the file has a valid signature, otherwise
+     *         <code>false</code>.
      */
-    public boolean migrateVerified(String inputFile, String outputFile) {
-        boolean ok = verify(inputFile);
-        if (!ok) return false;
-        FileOutputStream out = null;
-        FileInputStream in = null;
+    public boolean verify(String signedFile, SigningPublicKey signingPublicKey) {
+        FileInputStream fileInputStream = 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);
+            fileInputStream = new FileInputStream(signedFile);
+            Signature signature = new Signature();
+
+            signature.readBytes(fileInputStream);
+
+            return _context.dsa().verifySignature(signature, fileInputStream, signingPublicKey);
         } catch (IOException ioe) {
+            if (_log.shouldLog(Log.WARN))
+                _log.warn("Error reading " + signedFile + " to verify", ioe);
+
+            return false;
+        } catch (DataFormatException dfe) {
+            if (_log.shouldLog(Log.ERROR))
+                _log.error("Error reading the signature", dfe);
+
             return false;
         } finally {
-            if (out != null) try { out.close(); } catch (IOException ioe) {}
-            if (in != null) try { in.close(); } catch (IOException ioe) {}
+            if (fileInputStream != null)
+                try {
+                    fileInputStream.close();
+                } catch (IOException ioe) {
+                }
         }
-        return true;
     }
 }
diff --git a/history.txt b/history.txt
index 91ac6030a66f610f02b8685b755b27e94f30403d..3488ff706991d037fc62a6fd84e7b36df9ad4346 100644
--- a/history.txt
+++ b/history.txt
@@ -1,4 +1,14 @@
-$Id: history.txt,v 1.188 2005/04/05 17:24:32 jrandom Exp $
+$Id: history.txt,v 1.189 2005/04/06 10:43:25 jrandom Exp $
+
+2005-04-08  smeghead
+    * Security improvements to TrustedUpdate: signing and verification of the
+      version string along with the data payload for signed update files
+      (consequently the positions of the DSA signature and version string fields
+      have been swapped in the spec for the update file's header); router will
+      no longer perform a trusted update if the signed update's version is lower
+      than or equal to the currently running router's version.
+    * Added two new CLI commands to TrustedUpdate: showversion, verifyupdate.
+    * Extended TrustedUpdate public API for use by third party applications.
 
 * 2005-04-06  0.5.0.6 released