diff --git a/src/main/java/com/threerings/getdown/data/Application.java b/src/main/java/com/threerings/getdown/data/Application.java index 0c56e15..c6dd522 100644 --- a/src/main/java/com/threerings/getdown/data/Application.java +++ b/src/main/java/com/threerings/getdown/data/Application.java @@ -1509,7 +1509,7 @@ public class Application FileInputStream dataInput = null; try { dataInput = new FileInputStream(target); - Signature sig = Signature.getInstance("SHA1withRSA"); + Signature sig = Signature.getInstance(Digest.SIG_ALGO); sig.initVerify(cert); while ((length = dataInput.read(buffer)) != -1) { sig.update(buffer, 0, length); diff --git a/src/main/java/com/threerings/getdown/data/Digest.java b/src/main/java/com/threerings/getdown/data/Digest.java index 0de8dc3..7eb74d2 100644 --- a/src/main/java/com/threerings/getdown/data/Digest.java +++ b/src/main/java/com/threerings/getdown/data/Digest.java @@ -25,7 +25,7 @@ import com.threerings.getdown.util.ProgressObserver; import static com.threerings.getdown.Log.log; /** - * Manages the digest.txt file and the computing and processing of MD5 digests for an + * Manages the digest.txt file and the computing and processing of digests for an * application. */ public class Digest @@ -33,6 +33,9 @@ public class Digest /** The current version of the digest protocol. */ public static final int VERSION = 2; + /** The current algorithm used to sign digest files. */ + public static final String SIG_ALGO = "SHA256withRSA"; + /** * Returns the name of the digest file for the specified protocol version. */ @@ -48,13 +51,13 @@ public class Digest public static void createDigest (int version, List resources, File output) throws IOException { - MessageDigest md = getMessageDigest(); + MessageDigest md = getMessageDigest(version); StringBuilder data = new StringBuilder(); PrintWriter pout = null; try { pout = new PrintWriter(new OutputStreamWriter(new FileOutputStream(output), "UTF-8")); - // compute and append the MD5 digest of each resource in the list + // compute and append the digest of each resource in the list for (Resource rsrc : resources) { String path = rsrc.getPath(); try { @@ -81,25 +84,33 @@ public class Digest /** * Obtains an appropriate message digest instance for use by the Getdown system. */ - public static MessageDigest getMessageDigest () + public static MessageDigest getMessageDigest (int version) { + String algo = version > 1 ? "SHA-256" : "MD5"; try { - return MessageDigest.getInstance("MD5"); + return MessageDigest.getInstance(algo); } catch (NoSuchAlgorithmException nsae) { - throw new RuntimeException("JVM does not support MD5. Gurp!"); + throw new RuntimeException("JVM does not support " + algo + ". Gurp!"); } } /** - * Creates a digest instance which will parse and validate the digest.txt in the - * supplied application directory. + * Creates a digest instance which will parse and validate the digest in the supplied + * application directory, using the current digest version. + */ + public Digest (File appdir) throws IOException { + this(appdir, VERSION); + } + + /** + * Creates a digest instance which will parse and validate the digest in the supplied + * application directory. * @param version the version of the digest protocol to use. */ - public Digest (File appdir) - throws IOException + public Digest (File appdir, int version) throws IOException { // parse and validate our digest file contents - String filename = digestFile(VERSION); + String filename = digestFile(version); StringBuilder data = new StringBuilder(); File dfile = new File(appdir, filename); for (String[] pair : ConfigUtil.parsePairs(dfile, false)) { @@ -112,11 +123,11 @@ public class Digest } // we've reached the end, validate our contents - MessageDigest md = getMessageDigest(); + MessageDigest md = getMessageDigest(version); byte[] contents = data.toString().getBytes("UTF-8"); - String md5 = StringUtil.hexlate(md.digest(contents)); - if (!md5.equals(_metaDigest)) { - String err = MessageUtil.tcompose("m.invalid_digest_file", _metaDigest, md5); + String hash = StringUtil.hexlate(md.digest(contents)); + if (!hash.equals(_metaDigest)) { + String err = MessageUtil.tcompose("m.invalid_digest_file", _metaDigest, hash); throw new IOException(err); } } @@ -130,7 +141,7 @@ public class Digest } /** - * Computes the MD5 hash of the specified resource and compares it with the value parsed from + * Computes the hash of the specified resource and compares it with the value parsed from * the digest file. Logs a message if the resource fails validation. * * @return true if the resource is valid, false if it failed the digest check or if an I/O @@ -139,13 +150,13 @@ public class Digest public boolean validateResource (Resource resource, ProgressObserver obs) { try { - String cmd5 = resource.computeDigest(VERSION, getMessageDigest(), obs); - String emd5 = _digests.get(resource.getPath()); - if (cmd5.equals(emd5)) { + String chash = resource.computeDigest(VERSION, getMessageDigest(VERSION), obs); + String ehash = _digests.get(resource.getPath()); + if (chash.equals(ehash)) { return true; } log.info("Resource failed digest check", - "rsrc", resource, "computed", cmd5, "expected", emd5); + "rsrc", resource, "computed", chash, "expected", ehash); } catch (Throwable t) { log.info("Resource failed digest check", "rsrc", resource, "error", t); } diff --git a/src/main/java/com/threerings/getdown/launcher/GetdownApp.java b/src/main/java/com/threerings/getdown/launcher/GetdownApp.java index 08087c6..fb68080 100644 --- a/src/main/java/com/threerings/getdown/launcher/GetdownApp.java +++ b/src/main/java/com/threerings/getdown/launcher/GetdownApp.java @@ -11,11 +11,16 @@ import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.BufferedOutputStream; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import javax.swing.JFrame; @@ -26,6 +31,7 @@ import com.samskivert.util.ArrayUtil; import com.samskivert.util.RunAnywhere; import com.samskivert.util.StringUtil; +import com.threerings.getdown.data.Digest; import com.threerings.getdown.data.SysProps; import static com.threerings.getdown.Log.log; @@ -80,6 +86,22 @@ public class GetdownApp System.exit(-1); } + // load X.509 certificate if it exists + File crtFile = new File(appDir, Digest.digestFile(Digest.VERSION) + ".crt"); + List crts = new ArrayList(); + if (crtFile.exists()) { + try { + FileInputStream fis = new FileInputStream(crtFile); + X509Certificate certificate = (X509Certificate) + CertificateFactory.getInstance("X.509").generateCertificate(fis); + fis.close(); + crts.add(certificate); + } catch (Exception e) { + log.warning("Certificate error: " + e.getMessage()); + System.exit(-1); + } + } + // pipe our output into a file in the application directory if (!SysProps.noLogRedir()) { File logFile = new File(appDir, "launcher.log"); @@ -105,7 +127,7 @@ public class GetdownApp log.info("-- Cur dir: " + System.getProperty("user.dir")); log.info("---------------------------------------------"); - Getdown app = new Getdown(appDir, appId, null, null, appArgs) { + Getdown app = new Getdown(appDir, appId, crts, null, appArgs) { @Override protected Container createContainer () { // create our user interface, and display it diff --git a/src/main/java/com/threerings/getdown/tools/AppletParamSigner.java b/src/main/java/com/threerings/getdown/tools/AppletParamSigner.java index ab83a30..78cac26 100644 --- a/src/main/java/com/threerings/getdown/tools/AppletParamSigner.java +++ b/src/main/java/com/threerings/getdown/tools/AppletParamSigner.java @@ -13,6 +13,8 @@ import java.security.Signature; import org.apache.commons.codec.binary.Base64; +import com.threerings.getdown.data.Digest; + /** * Produces a signed hash of the appbase, appname, and image path to ensure that signed copies of * Getdown are not hijacked to run malicious code. @@ -41,7 +43,7 @@ public class AppletParamSigner store.load(new BufferedInputStream(new FileInputStream(keystore)), storepass.toCharArray()); PrivateKey key = (PrivateKey)store.getKey(alias, keypass.toCharArray()); - Signature sig = Signature.getInstance("SHA1withRSA"); + Signature sig = Signature.getInstance(Digest.SIG_ALGO); sig.initSign(key); sig.update(params.getBytes()); String signed = new String(Base64.encodeBase64(sig.sign())); diff --git a/src/main/java/com/threerings/getdown/tools/Differ.java b/src/main/java/com/threerings/getdown/tools/Differ.java index 9a34b72..fbde2a9 100644 --- a/src/main/java/com/threerings/getdown/tools/Differ.java +++ b/src/main/java/com/threerings/getdown/tools/Differ.java @@ -94,7 +94,8 @@ public class Differ ArrayList nrsrcs, boolean verbose) throws IOException { - MessageDigest md = Digest.getMessageDigest(); + int version = Digest.VERSION; + MessageDigest md = Digest.getMessageDigest(version); JarOutputStream jout = null; try { jout = new JarOutputStream( @@ -107,7 +108,6 @@ public class Differ Resource orsrc = (oidx == -1) ? null : orsrcs.remove(oidx); if (orsrc != null) { // first see if they are the same - int version = Digest.VERSION; String odig = orsrc.computeDigest(version, md, null); String ndig = rsrc.computeDigest(version, md, null); if (odig.equals(ndig)) { diff --git a/src/main/java/com/threerings/getdown/tools/Digester.java b/src/main/java/com/threerings/getdown/tools/Digester.java index 661abf5..a3738c3 100644 --- a/src/main/java/com/threerings/getdown/tools/Digester.java +++ b/src/main/java/com/threerings/getdown/tools/Digester.java @@ -110,7 +110,8 @@ public class Digester PrivateKey key = (PrivateKey)store.getKey(storeAlias, storePass.toCharArray()); // sign the digest file - Signature sig = Signature.getInstance("SHA1withRSA"); + String algo = version > 1 ? Digest.SIG_ALGO : "SHA1withRSA"; + Signature sig = Signature.getInstance(algo); dataInput = new FileInputStream(inputFile); byte[] buffer = new byte[8192]; int length;