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 057ad2ef4..0299156a2 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 c2fd838f5..c412d025b 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. - * + *
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. + *
+ *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. + *
*/ 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 = "Update downloadedHandles DSA signing and verification of update files. + *
+ *For convenience this class also makes certain operations available via the + * command line. These can be invoked as follows: + *
+ *+ * java net.i2p.crypto.TrustedUpdate keygen publicKeyFile privateKeyFile + * java net.i2p.crypto.TrustedUpdate showversion signedFile + * java net.i2p.crypto.TrustedUpdate sign inputFile signedFile privateKeyFile version + * java net.i2p.crypto.TrustedUpdate verifysig signedFile + * java net.i2p.crypto.TrustedUpdate verifyupdate signedFile + ** - * @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) - * + *
Default trusted key generated by jrandom@i2p.net. This can be
+ * authenticated via gpg
without modification:
+ * gpg --verify TrustedUpdate.java
TrustedUpdate
with the default global
+ * context.
+ */
public TrustedUpdate() {
this(I2PAppContext.getGlobalContext());
}
- public TrustedUpdate(I2PAppContext ctx) {
- _context = ctx;
+
+ /**
+ * Constructs a new TrustedUpdate
with the given
+ * {@link net.i2p.I2PAppContext}.
+ *
+ * @param context An instance of I2PAppContext
.
+ */
+ 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();
- }
- }
- 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;
+ /**
+ * 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 {
- 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) {}
+ 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();
}
}
- 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");
+ /**
+ * Checks if the given version is newer than the given current version.
+ *
+ * @param currentVersion The current version.
+ * @param newVersion The version to test.
+ *
+ * @return true
if the given version is newer than the current
+ * version, otherwise false
.
+ */
+ 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;
+ }
+ }
+
+ if (newVersionTokens.hasMoreTokens() && !currentVersionTokens.hasMoreTokens())
+ return true;
+
+ return false;
}
-
- private static final void verifyCLI(String signedFile) {
- TrustedUpdate up = new TrustedUpdate();
- boolean ok = up.verify(signedFile);
- if (ok)
+
+ 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 fileOutputStream = null;
+
+ try {
+ 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();
+ } finally {
+ 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 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("Version: " + versionString);
+ }
+
+ 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 ArrayList
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) {
+ }
}
- }
+ }
+
+ /**
+ * Verifies that the version of the given signed update file is newer than
+ * currentVersion
.
+ *
+ * @param currentVersion The current version to check against.
+ * @param signedFile The signed update file.
+ *
+ * @return true
if the signed update file's version is newer
+ * than the current version, otherwise false
.
+ */
+ 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 signedFile
and into outputFile
.
+ *
+ * @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 true
if the signature and version were valid and the
+ * data was moved, false
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;
- /**
- * 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
- * 0h
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 inputFile
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;
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
+ * 0h
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 inputFile
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
+ * null
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);
- }
-
- 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.
- throw new RuntimeException("wtf, your JVM doesnt support utf-8? " + e.getMessage());
- }
- System.arraycopy(updateVersionBytes, 0, headerUpdateVersion, 0, updateVersionBytes.length);
- Signature signature = null;
- FileInputStream in = null;
+ return sign(inputFile, signedFile, signingPrivateKey, version);
+ }
+
+ /**
+ * 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 0h
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 SigningPrivateKey
+ * to sign inputFile
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
+ * null
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 {
- in = new FileInputStream(inputFile);
- signature = _context.dsa().sign(in, privKey);
+ versionRawBytes = version.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("wtf, your JVM doesnt support utf-8? " + e.getMessage());
+ }
+
+ System.arraycopy(versionRawBytes, 0, versionHeader, 0, versionRawBytes.length);
+
+ FileInputStream fileInputStream = null;
+ Signature signature = null;
+ SequenceInputStream bytesToSignInputStream = null;
+ ByteArrayInputStream versionHeaderInputStream = null;
+
+ try {
+ 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;
- }
+ return signature;
+ }
- /**
- * Verifies the DSA signature of a signed I2P update.
- *
- * @param inputFile The signed update file to check.
- *
- * @return true
if the file has a valid signature.
- */
- public boolean verify(String inputFile) {
+ /**
+ * Verifies the DSA signature of a signed update file.
+ *
+ * @param signedFile The signed update file to check.
+ *
+ * @return true
if the file has a valid signature, otherwise
+ * false
.
+ */
+ 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 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) {}
- }
- }
- /**
- * 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 true
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 true
if the file has a valid signature, otherwise
+ * false
.
+ */
+ 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 true
if the file has a valid signature, otherwise
+ * false
.
*/
- 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 91ac6030a..3488ff706 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