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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user