From 41c59d7e1be0c4e326cbce5333541acb9607d3af Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Fri, 4 Nov 2016 19:35:05 -0700 Subject: [PATCH] Upgrade signing algorithm & digest algo. Since we're upping the protocol version, we can do all this too at the same time. So v2 digest will use SHA256, sign using SHA256withRSA and will include the contents of META-INF in the digests. Upgrades++! This also includes the code from pull request #73 to allow a .crt file to be included with the app to allow for signature verification of standalone apps (we already did this for applets via the applet parameters). --- .../threerings/getdown/data/Application.java | 2 +- .../com/threerings/getdown/data/Digest.java | 51 +++++++++++-------- .../getdown/launcher/GetdownApp.java | 24 ++++++++- .../getdown/tools/AppletParamSigner.java | 4 +- .../com/threerings/getdown/tools/Differ.java | 4 +- .../threerings/getdown/tools/Digester.java | 3 +- 6 files changed, 62 insertions(+), 26 deletions(-) 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;