* Updates:

- Add the router version to the zip file comment in the updater
    - Add a class to extract the zip file comment
    - Require the sud version header to match the zip file comment
      to prevent spoofing of the version number, since the version
      number in the header is not covered by the sud signature.
This commit is contained in:
zzz
2011-06-15 13:30:24 +00:00
parent 89191f4014
commit c655d23815
3 changed files with 226 additions and 42 deletions

View File

@ -316,31 +316,11 @@
<ant dir="apps/susimail/" target="poupdate" />
<ant dir="apps/desktopgui" target="poupdate" />
</target>
<target name="javadoc">
<target name="javadoc" depends="getReleaseNumber, getBuildNumber" >
<ant dir="apps/jetty" target="ensureJettylib" />
<mkdir dir="./build" />
<mkdir dir="./build/javadoc" />
<!-- get release and build version numbers -->
<exec executable="grep" outputproperty="versionLine" >
<arg value="public final static String VERSION" />
<arg value="core/java/src/net/i2p/CoreVersion.java" />
</exec>
<exec executable="cut" inputstring="${versionLine}" outputproperty="release.number" >
<arg value="-f2" />
<arg value="-d&quot;" />
</exec>
<exec executable="grep" outputproperty="buildLine" >
<arg value="public final static long BUILD" />
<arg value="router/java/src/net/i2p/router/RouterVersion.java" />
</exec>
<exec executable="cut" inputstring="${buildLine}" outputproperty="build.temp" >
<arg value="-f2" />
<arg value="-d=" />
</exec>
<exec executable="cut" inputstring="${build.temp}" outputproperty="build.number" >
<arg value="-f1" />
<arg value="-d;" />
</exec>
<javadoc access="package"
destdir="./build/javadoc"
packagenames="*"
@ -395,6 +375,34 @@
<echo message="Warning, javadoc embeds timestamps in the output, run with 'TZ=UTC ant javadoc' if you plan to distribute" />
</target>
<target name="getReleaseNumber" >
<exec executable="grep" outputproperty="versionLine" failonerror="true" >
<arg value="public final static String VERSION" />
<arg value="core/java/src/net/i2p/CoreVersion.java" />
</exec>
<exec executable="cut" inputstring="${versionLine}" outputproperty="release.number" failonerror="true" >
<arg value="-f2" />
<arg value="-d&quot;" />
</exec>
<echo message="Release number is ${release.number}" />
</target>
<target name="getBuildNumber" >
<exec executable="grep" outputproperty="buildLine" >
<arg value="public final static long BUILD" />
<arg value="router/java/src/net/i2p/router/RouterVersion.java" />
</exec>
<exec executable="cut" inputstring="${buildLine}" outputproperty="build.temp" failonerror="true" >
<arg value="-f2" />
<arg value="-d=" />
</exec>
<exec executable="cut" inputstring="${build.temp}" outputproperty="build.number" failonerror="true" >
<arg value="-f1" />
<arg value="-d;" />
</exec>
<echo message="Build number is ${build.number}" />
</target>
<target name="clean" depends="pkgclean" >
<delete dir="./build" />
<delete file="i2pinstall.exe" failonerror="false" quiet="true" />
@ -704,15 +712,22 @@
<target name="updaterWithJettyFixesAndGeoIP" depends="prepjupdatefixes, prepgeoupdate, preplicenses, zipit" />
<target name="updaterSmall" depends="prepupdateSmall, zipit" />
<target name="updaterRouter" depends="prepupdateRouter, zipit" />
<target name="zipit">
<zip destfile="i2pupdate.zip" basedir="pkg-temp" whenempty="fail" />
<target name="zipit" depends="getReleaseNumber" >
<!--
As of release 0.8.8, the router will enforce a zipfile comment equal to the
version number in the sud/su2 header, since the version in the header is NOT
covered by the signature.
-->
<zip destfile="i2pupdate.zip" basedir="pkg-temp" whenempty="fail" comment="${release.number}" />
<!-- just a test, makes almost no difference
<tar destfile="i2pupdate.tgz" basedir="pkg-temp" compression="gzip" />
<tar destfile="i2pupdate.tbz" basedir="pkg-temp" compression="bzip2" />
-->
</target>
<target name="zipit200">
<zip destfile="i2pupdate200.zip" basedir="pkg-temp" whenempty="fail" />
<target name="zipit200" depends="getReleaseNumber" >
<zip destfile="i2pupdate200.zip" basedir="pkg-temp" whenempty="fail" comment="${release.number}" />
</target>
<target name="pack200">
@ -979,9 +994,9 @@
<!-- this is the same dependency as pkg, but with updater200 in the middle,
since preppkg puts too much stuff in pkg-temp -->
<!--
<target name="release" depends="distclean, updater, updater200, preppkg, installer" >
<target name="release" depends="distclean, updater, updater200, preppkg, installer, getReleaseNumber" >
-->
<target name="release" depends="distclean, updaterWithJettyFixesAndJbigi , updater200WithJettyFixes, preppkg, installer" >
<target name="release" depends="distclean, updaterWithJettyFixesAndJbigi , updater200WithJettyFixes, preppkg, installer, getReleaseNumber" >
<echo message="================================================================" />
<echo message="Did you update these files?" />
<exec executable="ls" failonerror="true">
@ -997,15 +1012,7 @@
<arg value="st" />
</exec>
<echo message="If there are any modified files above, stop now!" />
<!-- get release version number -->
<exec executable="grep" outputproperty="versionLine" failonerror="true" >
<arg value="public final static String VERSION" />
<arg value="core/java/src/net/i2p/CoreVersion.java" />
</exec>
<exec executable="cut" inputstring="${versionLine}" outputproperty="release.number" failonerror="true" >
<arg value="-f2" />
<arg value="-d&quot;" />
</exec>
<echo message="New version number is ${release.number}" />
<copy file="i2pupdate.zip" tofile="i2pupdate_${release.number}.zip" />
<copy file="i2pinstall.exe" tofile="i2pinstall_${release.number}.exe" />
@ -1036,6 +1043,13 @@
<arg value="verifysig" />
<arg value="i2pupdate.sud" />
</java>
<java classname="net.i2p.crypto.TrustedUpdate" fork="true" failonerror="true">
<classpath>
<pathelement location="build/i2p.jar" />
</classpath>
<arg value="verifyversion" />
<arg value="i2pupdate.sud" />
</java>
<java classname="net.i2p.crypto.TrustedUpdate" fork="true" failonerror="true">
<classpath>
<pathelement location="build/i2p.jar" />
@ -1062,6 +1076,13 @@
<arg value="verifysig" />
<arg value="i2pupdate.su2" />
</java>
<java classname="net.i2p.crypto.TrustedUpdate" fork="true" failonerror="true">
<classpath>
<pathelement location="build/i2p.jar" />
</classpath>
<arg value="verifyversion" />
<arg value="i2pupdate.su2" />
</java>
<java classname="net.i2p.crypto.TrustedUpdate" fork="true" failonerror="true">
<classpath>
<pathelement location="build/i2p.jar" />

View File

@ -22,6 +22,7 @@ import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey;
import net.i2p.util.Log;
import net.i2p.util.VersionComparator;
import net.i2p.util.ZipFileComment;
/**
* <p>Handles DSA signing and verification of update files.
@ -35,6 +36,7 @@ import net.i2p.util.VersionComparator;
* 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>
* java net.i2p.crypto.TrustedUpdate verifyversion <i>signedFile</i>
* </pre>
*
* @author jrandom and smeghead
@ -242,6 +244,8 @@ JXQAnA28vDmMMMH/WPbC5ixmJeGGNUiR
ok = verifySigCLI(args[1]);
} else if ("verifyupdate".equals(args[0])) {
ok = verifyUpdateCLI(args[1]);
} else if ("verifyversion".equals(args[0])) {
ok = verifyVersionCLI(args[1]);
} else {
showUsageCLI();
}
@ -301,11 +305,12 @@ JXQAnA28vDmMMMH/WPbC5ixmJeGGNUiR
}
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");
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");
System.err.println(" TrustedUpdate verifyversion signedFile");
}
/** @return success */
@ -349,9 +354,29 @@ JXQAnA28vDmMMMH/WPbC5ixmJeGGNUiR
System.out.println("File version is newer than current version.");
else
System.out.println("File version is older than or equal to current version.");
return isUpdate;
}
/**
* @return true if there's no version mismatch
* @since 0.8.8
*/
private static final boolean verifyVersionCLI(String signedFile) {
TrustedUpdate tu = new TrustedUpdate();
File file = new File(signedFile);
// ignore result, just used to read in version
tu.isUpdatedVersion("0", file);
boolean isMatch = tu.verifyVersionMatch(file);
if (isMatch)
System.out.println("Version verified");
else
System.out.println("Version mismatch, header version does not match zip comment version");
return isMatch;
}
/**
* Fetches the trusted keys for the current instance.
*
@ -490,6 +515,13 @@ JXQAnA28vDmMMMH/WPbC5ixmJeGGNUiR
* file's version is newer than the given current version, migrates the data
* out of <code>signedFile</code> and into <code>outputFile</code>.
*
* As of 0.8.8, the embedded file must be a zip file with
* a standard zip header and a UTF-8 zip file comment
* matching the version in the sud header. This prevents spoofing the version,
* since the sud signature does NOT cover the version in the header.
* (We do this for sud/su2 files but not plugin xpi2p files -
* don't use this method for plugin files)
*
* @param currentVersion The current version to check against.
* @param signedFile A signed update file.
* @param outputFile The file to write the verified data to.
@ -507,12 +539,32 @@ JXQAnA28vDmMMMH/WPbC5ixmJeGGNUiR
return "Downloaded version is not greater than current version";
}
if (!verifyVersionMatch(signedFile))
return "Update file invalid - signed version mismatch";
if (!verify(signedFile))
return "Unknown signing key or corrupt file";
return migrateFile(signedFile, outputFile);
}
/**
* Verify the version in the sud header matches the version in the zip comment
* (and that the embedded file is a zip file at all)
* isUpdatedVersion() must be called first to set _newVersion.
*
* @return true if matches
*
* @since 0.8.8
*/
private boolean verifyVersionMatch(File signedFile) {
try {
String zipComment = ZipFileComment.getComment(signedFile, VERSION_BYTES, HEADER_BYTES);
return zipComment.equals(_newVersion);
} catch (IOException ioe) {}
return false;
}
/**
* Extract the file. Skips and ignores the signature and version. No verification.
*

View File

@ -0,0 +1,111 @@
package net.i2p.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.IOException;
import java.util.zip.ZipException;
import net.i2p.data.DataHelper;
/**
* Not available in ZipFile until Java 7. Refs:
* https://secure.wikimedia.org/wikipedia/en/wiki/ZIP_%28file_format%29
* http://download.oracle.com/javase/1.5.0/docs/api/java/util/zip/ZipFile.html
* http://bugs.sun.com/view_bug.do?bug_id=6646605
*
* Code modified from:
* http://www.flattermann.net/2009/01/read-a-zip-file-comment-with-java/
* Beerware.
*
* since 0.8.8
*/
public abstract class ZipFileComment {
private static final int BLOCK_LEN = 22;
private static final byte[] magicStart = {0x50, 0x4b, 0x03, 0x04};
private static final int HEADER_LEN = magicStart.length;
private static final byte[] magicDirEnd = {0x50, 0x4b, 0x05, 0x06};
private static final int MAGIC_LEN = magicDirEnd.length;
/**
* @param max The max length of the comment in bytes.
* If the actual comment is longer, it will not be found and
* this method will throw an IOE
*
* @return empty string if no comment, or the comment.
* The string is decoded with UTF-8
*
* @throws IOE if no valid end-of-central-directory record found
*/
public static String getComment(File file, int max) throws IOException {
return getComment(file, max, 0);
}
/**
* @param max The max length of the comment in bytes.
* If the actual comment is longer, it will not be found and
* this method will throw an IOE
* @param skip Number of bytes to skip in the file before looking for the
* zip header. Use 56 for sud/su2 files.
*
* @return empty string if no comment, or the comment.
* The string is decoded with UTF-8
*
* @throws IOE if no valid end-of-central-directory record found
*/
public static String getComment(File file, int max, int skip) throws IOException {
if (!file.exists())
throw new FileNotFoundException("File not found: " + file);
long len = file.length();
if (len < BLOCK_LEN + HEADER_LEN + skip)
throw new ZipException("File too short: " + file);
if (len > Integer.MAX_VALUE)
throw new ZipException("File too long: " + file);
int fileLen = (int) len;
byte[] buffer = new byte[Math.min(fileLen - skip, max + BLOCK_LEN)];
InputStream in = null;
try {
in = new FileInputStream(file);
if (skip > 0)
in.skip(skip);
byte[] hdr = new byte[HEADER_LEN];
DataHelper.read(in, hdr);
if (!DataHelper.eq(hdr, magicStart))
throw new ZipException("Not a zip file: " + file);
in.skip(fileLen - (skip + HEADER_LEN + buffer.length));
DataHelper.read(in, buffer);
return getComment(buffer);
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
}
/**
* go backwards from the end
*/
private static String getComment(byte[] buffer) throws IOException {
for (int i = buffer.length - (1 + BLOCK_LEN - MAGIC_LEN); i >= 0; i--) {
if (DataHelper.eq(buffer, i, magicDirEnd, 0, MAGIC_LEN)) {
int commentLen = (buffer[i + BLOCK_LEN - 2] & 0xff) +
((buffer[i + BLOCK_LEN - 1] & 0xff) * 256);
return new String(buffer, i + BLOCK_LEN, commentLen, "UTF-8");
}
}
throw new ZipException("No comment block found");
}
public static void main(String args[]) throws IOException {
if (args.length != 1) {
System.err.println("Usage: ZipFileComment file");
return;
}
int skip = 0;
String file = args[0];
if (file.endsWith(".sud") || file.endsWith(".su2"))
skip = 56;
String c = getComment(new File(file), 256, skip);
System.out.println("comment is: \"" + c + '\"');
}
}