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).
This commit is contained in:
Michael Bayne
2016-11-04 19:35:05 -07:00
parent d75d5a7a69
commit 41c59d7e1b
6 changed files with 62 additions and 26 deletions
@@ -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);
@@ -25,7 +25,7 @@ import com.threerings.getdown.util.ProgressObserver;
import static com.threerings.getdown.Log.log;
/**
* Manages the <code>digest.txt</code> file and the computing and processing of MD5 digests for an
* Manages the <code>digest.txt</code> 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<Resource> 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 <code>digest.txt</code> 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);
}
@@ -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<Certificate> crts = new ArrayList<Certificate>();
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
@@ -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()));
@@ -94,7 +94,8 @@ public class Differ
ArrayList<Resource> 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)) {
@@ -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;