diff --git a/etc/getdown.pro b/etc/getdown.pro
index a8fb173..aac5920 100644
--- a/etc/getdown.pro
+++ b/etc/getdown.pro
@@ -8,6 +8,7 @@
-injars dist/getdown.jar(!**/tools/DigesterTask*,!**/tools/Differ*)
-injars lib/jRegistryKey.jar(!META-INF/*)
-injars lib/samskivert.jar(!META-INF/*,!**/velocity/**,!**/xml/**)
+-injars lib/commons-codec.jar(!META-INF/*)
-injars lib/commons-io.jar(!META-INF/*)
-injars lib/snark.jar(!META-INF/*)
diff --git a/lib/LIBS b/lib/LIBS
index ff566c1..e2f70ea 100644
--- a/lib/LIBS
+++ b/lib/LIBS
@@ -1,5 +1,6 @@
samskivert.jar
ant.jar
+commons-codec.jar
commons-io.jar
javaws.jar
snark.jar
diff --git a/src/java/com/threerings/getdown/launcher/GetdownApplet.java b/src/java/com/threerings/getdown/launcher/GetdownApplet.java
index 9c4a454..a8220c8 100644
--- a/src/java/com/threerings/getdown/launcher/GetdownApplet.java
+++ b/src/java/com/threerings/getdown/launcher/GetdownApplet.java
@@ -23,6 +23,9 @@ package com.threerings.getdown.launcher;
import java.awt.Container;
import java.awt.Image;
import java.net.URL;
+import java.security.GeneralSecurityException;
+import java.security.Signature;
+import java.security.cert.Certificate;
import javax.swing.JApplet;
import javax.swing.JPanel;
@@ -32,6 +35,8 @@ import java.io.FileOutputStream;
import java.io.File;
import java.io.PrintStream;
+import org.apache.commons.codec.binary.Base64;
+
import com.samskivert.util.RunAnywhere;
import com.samskivert.util.StringUtil;
@@ -46,6 +51,54 @@ public class GetdownApplet extends JApplet
@Override // documentation inherited
public void init ()
{
+ // First off, verify that we are not being hijacked to execute
+ // malicious code in the name of the signer.
+ String appbase = getParameter("appbase");
+ String appname = getParameter("appname");
+ String imgpath = getParameter("bgimage");
+ if (appbase == null) {
+ appbase = "";
+ }
+ if (appname == null) {
+ appname = "";
+ }
+ if (imgpath == null) {
+ imgpath = "";
+ }
+ String params = appbase + appname + imgpath;
+ String signature = getParameter("signature");
+ if (signature == null) {
+ signature = "";
+ }
+
+ Object[] signers = GetdownApplet.class.getSigners();
+ if (signers.length == 0) {
+ _safe = true;
+ }
+ for (Object signer : signers) {
+ if (!_safe && signer instanceof Certificate) {
+ Certificate cert = (Certificate)signer;
+ try {
+ Signature sig = Signature.getInstance("SHA1withRSA");
+ sig.initVerify(cert);
+ Log.info(params);
+ sig.update(params.getBytes());
+ byte[] rawsig = Base64.decodeBase64(signature.getBytes());
+ if (sig.verify(rawsig)) {
+ _safe = true;
+ }
+ } catch (GeneralSecurityException gse) {
+ // ignore the error - the default is to not launch.
+ }
+ }
+ }
+
+ if (!_safe) {
+ Log.warning("Signed getdown invoked on unsigned application; " +
+ "aborting installation.");
+ return;
+ }
+
// when run from an applet, we install
String root;
if (RunAnywhere.isWindows()) {
@@ -56,7 +109,6 @@ public class GetdownApplet extends JApplet
root = ".getdown";
}
- String appname = getParameter("appname");
String appdir = System.getProperty("user.home") +
File.separator + root + File.separator + appname;
@@ -82,7 +134,6 @@ public class GetdownApplet extends JApplet
// if our getdown.txt file does not exist, auto-create it
File gdfile = new File(appDir, "getdown.txt");
if (!gdfile.exists()) {
- String appbase = getParameter("appbase");
if (StringUtil.isBlank(appbase)) {
Log.warning("Missing 'appbase' cannot auto-create " +
"application directory.");
@@ -96,7 +147,6 @@ public class GetdownApplet extends JApplet
}
// if a background image was specified, grabbit
- String imgpath = getParameter("bgimage");
try {
if (!StringUtil.isBlank(imgpath)) {
_bgimage = getImage(new URL(getDocumentBase(), imgpath));
@@ -156,6 +206,10 @@ public class GetdownApplet extends JApplet
@Override // documentation inherited
public void start ()
{
+ if (!_safe) {
+ return;
+ }
+
try {
_getdown.start();
} catch (Exception e) {
@@ -188,4 +242,10 @@ public class GetdownApplet extends JApplet
protected Getdown _getdown;
protected Image _bgimage;
+
+ /**
+ * Getdown will refuse to initialize if the jar is signed but the
+ * parameters are not validated to prevent malicious code from being run.
+ */
+ protected boolean _safe = false;
}
diff --git a/src/java/com/threerings/getdown/launcher/TorrentDownloader.java b/src/java/com/threerings/getdown/launcher/TorrentDownloader.java
index 25a45e5..dcb6cc4 100644
--- a/src/java/com/threerings/getdown/launcher/TorrentDownloader.java
+++ b/src/java/com/threerings/getdown/launcher/TorrentDownloader.java
@@ -83,14 +83,15 @@ public class TorrentDownloader extends Downloader
Snark snark = _torrentmap.get(rsrc);
SnarkShutdown snarkStopper = _stoppermap.get(rsrc);
snark.collectPieces();
+ // Override the start time, since Snark allocates storage prior to
+ // doing any downloading
+ _start = System.currentTimeMillis();
while (_currentSize != snark.meta.getTotalLength()) {
long now = System.currentTimeMillis();
if ((now - _lastUpdate) >= UPDATE_DELAY) {
_currentSize = snark.coordinator.getDownloaded();
if ((_currentSize < SIZE_THRESHOLD &&
- (now - _start) >= TIME_THRESHOLD) ||
- (_currentSize == 0 &&
- (now - _start) >= TIME_THRESHOLD / 4)) {
+ (now - _start) >= TIME_THRESHOLD)) {
Log.info("Torrenting too slow, falling back to HTTP.");
// The download isn't going as planned, abort;
snarkStopper.run();
diff --git a/src/java/com/threerings/getdown/tools/AppletParamSigner.java b/src/java/com/threerings/getdown/tools/AppletParamSigner.java
new file mode 100644
index 0000000..202fe47
--- /dev/null
+++ b/src/java/com/threerings/getdown/tools/AppletParamSigner.java
@@ -0,0 +1,59 @@
+package com.threerings.getdown.tools;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.Signature;
+
+import org.apache.commons.codec.binary.Base64;
+
+/**
+ * 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.
+ */
+public class AppletParamSigner
+{
+ public static void main (String[] args)
+ {
+ try {
+ if (args.length != 7) {
+ System.err.println(
+ "AppletParamSigner keystore storepass alias keypass " +
+ "appbase appname imgpath"
+ );
+ System.exit(255);
+ }
+ String keystore = args[0];
+ String storepass = args[1];
+ String alias = args[2];
+ String keypass = args[3];
+ String appbase = args[4];
+ String appname = args[5];
+ String imgpath = args[6];
+ String params = appbase + appname + imgpath;
+
+ KeyStore store = KeyStore.getInstance("JKS");
+ store.load(new BufferedInputStream(new FileInputStream(keystore)),
+ storepass.toCharArray());
+ PrivateKey key = (PrivateKey)store.getKey(alias,
+ keypass.toCharArray());
+ Signature sig = Signature.getInstance("SHA1withRSA");
+ sig.initSign(key);
+ sig.update(params.getBytes());
+ String signed = new String(Base64.encodeBase64(sig.sign()));
+ System.out.println(
+ "");
+ System.out.println(
+ "");
+ System.out.println(
+ "");
+ System.out.println(
+ "");
+ } catch (Exception e) {
+ System.err.println("Failed to produce signature.");
+ e.printStackTrace();
+ }
+ }
+
+}