Modified Getdown to use a signature on the digest.txt file instead of a
signature on the applet parameters, to verify that the signed applet version is not being misused.
This commit is contained in:
@@ -36,6 +36,7 @@ import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -43,6 +44,9 @@ import java.io.InputStreamReader;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Signature;
|
||||
import java.security.cert.Certificate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -58,6 +62,7 @@ import com.samskivert.util.ArrayIntSet;
|
||||
import com.samskivert.util.RunAnywhere;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import com.threerings.getdown.Log;
|
||||
@@ -82,6 +87,9 @@ public class Application
|
||||
* application (minus this prefix). */
|
||||
public static final String PROP_PASSTHROUGH_PREFIX = "app.";
|
||||
|
||||
/** Suffix used for control file signatures. */
|
||||
public static final String SIGNATURE_SUFFIX = ".sig";
|
||||
|
||||
/** Used to communicate information about the UI displayed when updating the application. */
|
||||
public static class UpdateInterface
|
||||
{
|
||||
@@ -135,6 +143,16 @@ public class Application
|
||||
public void updateStatus (String message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an application instance with no signers.
|
||||
*
|
||||
* @see #Application(File, String, Object[])
|
||||
*/
|
||||
public Application (File appdir, String appid)
|
||||
{
|
||||
this(appdir, appid, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an application instance which records the location of the <code>getdown.txt</code>
|
||||
* configuration file from the supplied application directory.
|
||||
@@ -143,11 +161,13 @@ public class Application
|
||||
* be launched. That application will use <code>appid.class</code> and
|
||||
* <code>appid.apparg</code> to configure itself but all other parameters will be the same as
|
||||
* the primary application.
|
||||
* @param signers an array of possible signers of this application. Used to verify the digest.
|
||||
*/
|
||||
public Application (File appdir, String appid)
|
||||
public Application (File appdir, String appid, Object[] signers)
|
||||
{
|
||||
_appdir = appdir;
|
||||
_appid = appid;
|
||||
_signers = signers;
|
||||
_config = getLocalPath(CONFIG_FILE);
|
||||
}
|
||||
|
||||
@@ -650,7 +670,7 @@ public class Application
|
||||
// now re-download our control files; we download the digest first so that if it fails, our
|
||||
// config file will still reference the old version and re-running the updater will start
|
||||
// the whole process over again
|
||||
downloadControlFile(Digest.DIGEST_FILE);
|
||||
downloadControlFile(Digest.DIGEST_FILE, true);
|
||||
downloadControlFile(CONFIG_FILE);
|
||||
}
|
||||
|
||||
@@ -835,7 +855,7 @@ public class Application
|
||||
String olddig = (_digest == null) ? "" : _digest.getMetaDigest();
|
||||
try {
|
||||
status.updateStatus("m.checking");
|
||||
downloadControlFile(Digest.DIGEST_FILE);
|
||||
downloadControlFile(Digest.DIGEST_FILE, true);
|
||||
_digest = new Digest(_appdir);
|
||||
if (!olddig.equals(_digest.getMetaDigest())) {
|
||||
Log.info("Unversioned digest changed. Revalidating...");
|
||||
@@ -853,7 +873,7 @@ public class Application
|
||||
// exceptions to propagate up to the caller as there is nothing else we can do
|
||||
if (_digest == null) {
|
||||
status.updateStatus("m.updating_metadata");
|
||||
downloadControlFile(Digest.DIGEST_FILE);
|
||||
downloadControlFile(Digest.DIGEST_FILE, true);
|
||||
_digest = new Digest(_appdir);
|
||||
}
|
||||
|
||||
@@ -864,7 +884,7 @@ public class Application
|
||||
// attempt to redownload both of our metadata files; again we pass errors up to our
|
||||
// caller because there's nothing we can do to automatically recover
|
||||
downloadControlFile(CONFIG_FILE);
|
||||
downloadControlFile(Digest.DIGEST_FILE);
|
||||
downloadControlFile(Digest.DIGEST_FILE, true);
|
||||
_digest = new Digest(_appdir);
|
||||
// revalidate everything if we end up downloading new metadata
|
||||
clearValidationMarkers();
|
||||
@@ -985,13 +1005,114 @@ public class Application
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a new copy of the specified control file and, if the download is successful, moves
|
||||
* it over the old file on the filesystem.
|
||||
* Downloads a new copy of the specified control file and does not check any signature.
|
||||
*/
|
||||
protected void downloadControlFile (String path)
|
||||
throws IOException
|
||||
{
|
||||
downloadControlFile(path, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a new copy of the specified control file, optionally validating its signature.
|
||||
* If the download is successful, moves it over the old file on the filesystem.
|
||||
*
|
||||
* <p> We implement simple signing of the digest.txt file for use with the Getdown applet, but
|
||||
* this should never be used as-is with a non-applet getdown installation, as the signing
|
||||
* format has no provisions for declaring arbitrary signing key IDs, signature algorithm, et al
|
||||
* -- it is entirely reliant on the ability to upgrade the Getdown applet, and its signature
|
||||
* validation implementation, at-will (ie, via an Applet).
|
||||
*
|
||||
* <p> TODO: Switch to PKCS #7 or CMS.
|
||||
*/
|
||||
protected void downloadControlFile (String path, boolean validateSignature)
|
||||
throws IOException
|
||||
{
|
||||
File target = downloadFile(path);
|
||||
|
||||
if (validateSignature) {
|
||||
if (_signers == null) {
|
||||
Log.info("No signers, not verifying file [path=" + path + "].");
|
||||
|
||||
} else {
|
||||
File signatureFile = downloadFile(path + SIGNATURE_SUFFIX);
|
||||
byte[] signature = null;
|
||||
try {
|
||||
signature = IOUtils.toByteArray(new FileReader(signatureFile), "utf8");
|
||||
} finally {
|
||||
// delete the file regardless
|
||||
signatureFile.delete();
|
||||
}
|
||||
|
||||
FileInputStream dataInput = new FileInputStream(target);
|
||||
byte[] buffer = new byte[8192];
|
||||
int length, validated = 0;
|
||||
for (Object signer : _signers) {
|
||||
if (!(signer instanceof Certificate)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Certificate cert = (Certificate)signer;
|
||||
try {
|
||||
Signature sig = Signature.getInstance("SHA1withRSA");
|
||||
sig.initVerify(cert);
|
||||
while ((length = dataInput.read(buffer)) != -1) {
|
||||
sig.update(buffer, 0, length);
|
||||
}
|
||||
|
||||
if (!sig.verify(Base64.decodeBase64(signature))) {
|
||||
Log.info("Signature does not match [cert=" + cert.getPublicKey() + "]");
|
||||
continue;
|
||||
} else {
|
||||
Log.info("Signature matches [cert=" + cert.getPublicKey() + "]");
|
||||
validated++;
|
||||
}
|
||||
|
||||
} catch (GeneralSecurityException gse) {
|
||||
// no problem!
|
||||
}
|
||||
}
|
||||
|
||||
// if we couldn't find a key that validates our digest, we are the hosed!
|
||||
if (validated == 0) {
|
||||
// delete the temporary digest file as we know it is invalid
|
||||
target.delete();
|
||||
throw new IOException("m.corrupt_digest_signature_error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Windows is a wonderful operating system, it won't let you rename a file overtop of
|
||||
// another one; thus to avoid running the risk of getting royally fucked, we have to do
|
||||
// this complicated backup bullshit; this way if the shit hits the fan before we get the
|
||||
// new copy into place, we should be able to read from the backup copy; yay!
|
||||
File original = getLocalPath(path);
|
||||
if (RunAnywhere.isWindows() && original.exists()) {
|
||||
File backup = getLocalPath(path + "_old");
|
||||
if (backup.exists() && !backup.delete()) {
|
||||
Log.warning("Failed to delete " + backup + ".");
|
||||
}
|
||||
if (!original.renameTo(backup)) {
|
||||
Log.warning("Failed to move " + original + " to backup. We will likely fail " +
|
||||
"to replace it with " + target + ".");
|
||||
}
|
||||
}
|
||||
|
||||
// now attempt to replace the current file with the new one
|
||||
if (!target.renameTo(original)) {
|
||||
throw new IOException("Failed to rename(" + target + ", " + original + ")");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a path to a temporary file, returning a {@link File} instance with the path
|
||||
* contents.
|
||||
*/
|
||||
protected File downloadFile (String path)
|
||||
throws IOException
|
||||
{
|
||||
File target = getLocalPath(path + "_new");
|
||||
|
||||
URL targetURL = null;
|
||||
try {
|
||||
targetURL = getRemoteURL(path);
|
||||
@@ -1015,26 +1136,7 @@ public class Application
|
||||
StreamUtil.close(fout);
|
||||
}
|
||||
|
||||
// Windows is a wonderful operating system, it won't let you rename a file overtop of
|
||||
// another one; thus to avoid running the risk of getting royally fucked, we have to do
|
||||
// this complicated backup bullshit; this way if the shit hits the fan before we get the
|
||||
// new copy into place, we should be able to read from the backup copy; yay!
|
||||
File original = getLocalPath(path);
|
||||
if (RunAnywhere.isWindows() && original.exists()) {
|
||||
File backup = getLocalPath(path + "_old");
|
||||
if (backup.exists() && !backup.delete()) {
|
||||
Log.warning("Failed to delete " + backup + ".");
|
||||
}
|
||||
if (!original.renameTo(backup)) {
|
||||
Log.warning("Failed to move " + original + " to backup. We will likely fail " +
|
||||
"to replace it with " + target + ".");
|
||||
}
|
||||
}
|
||||
|
||||
// now attempt to replace the current file with the new one
|
||||
if (!target.renameTo(original)) {
|
||||
throw new IOException("Failed to rename(" + target + ", " + original + ")");
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
/** Helper function for creating {@link Resource} instances. */
|
||||
@@ -1131,5 +1233,7 @@ public class Application
|
||||
protected ArrayList<String> _jvmargs = new ArrayList<String>();
|
||||
protected ArrayList<String> _appargs = new ArrayList<String>();
|
||||
|
||||
protected Object[] _signers;
|
||||
|
||||
protected static final String[] SA_PROTO = new String[0];
|
||||
}
|
||||
|
||||
@@ -80,6 +80,11 @@ public abstract class Getdown extends Thread
|
||||
}
|
||||
|
||||
public Getdown (File appDir, String appId)
|
||||
{
|
||||
this(appDir, appId, null);
|
||||
}
|
||||
|
||||
public Getdown (File appDir, String appId, Object[] signers)
|
||||
{
|
||||
super("Getdown");
|
||||
try {
|
||||
@@ -96,7 +101,7 @@ public abstract class Getdown extends Thread
|
||||
updateStatus(errmsg);
|
||||
_dead = true;
|
||||
}
|
||||
_app = new Application(appDir, appId);
|
||||
_app = new Application(appDir, appId, signers);
|
||||
_startup = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,9 +23,6 @@ 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;
|
||||
@@ -38,8 +35,6 @@ import java.io.PrintStream;
|
||||
import netscape.javascript.JSObject;
|
||||
import netscape.javascript.JSException;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
||||
import com.samskivert.util.RunAnywhere;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
@@ -103,7 +98,10 @@ public class GetdownApplet extends JApplet
|
||||
}
|
||||
|
||||
try {
|
||||
_getdown = new Getdown(appdir, null) {
|
||||
// XXX getSigners() returns all certificates used to sign this applet which may allow
|
||||
// a third party to insert a trusted certificate. This should be replaced with
|
||||
// statically included trusted keys.
|
||||
_getdown = new Getdown(appdir, null, GetdownApplet.class.getSigners()) {
|
||||
protected Container createContainer () {
|
||||
getContentPane().removeAll();
|
||||
return getContentPane();
|
||||
@@ -191,42 +189,6 @@ public class GetdownApplet extends JApplet
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Object[] signers = GetdownApplet.class.getSigners();
|
||||
if (signers.length == 0) {
|
||||
Log.info("No signers, not verifying param signature.");
|
||||
|
||||
} else {
|
||||
String signature = getParameter("signature");
|
||||
if (signature == null) {
|
||||
signature = "";
|
||||
}
|
||||
|
||||
Log.info("Verifying signature '" + signature + "'.");
|
||||
String params = appbase + appname + imgpath;
|
||||
int validated = 0;
|
||||
for (Object signer : signers) {
|
||||
if (signer instanceof Certificate) {
|
||||
Certificate cert = (Certificate)signer;
|
||||
try {
|
||||
Signature sig = Signature.getInstance("SHA1withRSA");
|
||||
sig.initVerify(cert);
|
||||
sig.update(params.getBytes());
|
||||
if (!sig.verify(Base64.decodeBase64(signature.getBytes()))) {
|
||||
Log.info("Signature does not match '" + cert.getPublicKey() + "'.");
|
||||
}
|
||||
validated++;
|
||||
} catch (GeneralSecurityException gse) {
|
||||
// no problem!
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we couldn't find a key that validates our parameters, we are the hosed!
|
||||
if (validated == 0) {
|
||||
throw new Exception("m.corrupt_param_signature_error");
|
||||
}
|
||||
}
|
||||
|
||||
// pass through properties parameters
|
||||
String properties = getParameter("properties");
|
||||
if (properties != null) {
|
||||
|
||||
@@ -89,7 +89,7 @@ m.insufficient_permissions_error = You did not accept this application's \
|
||||
security dialog is shown, click the button to accept the digital signature \
|
||||
and grant this application the privileges it needs to run.
|
||||
|
||||
m.corrupt_param_signature_error = We couldn't verify the application's digital \
|
||||
m.corrupt_digest_signature_error = We couldn't verify the application's digital \
|
||||
signature.\nPlease check that you are launching the application from\nthe \
|
||||
correct website.
|
||||
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Getdown - application installer, patcher and launcher
|
||||
// Copyright (C) 2004-2006 Three Rings Design, Inc.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU General Public License as published by the Free
|
||||
// Software Foundation; either version 2 of the License, or (at your option)
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
// more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// this program; if not, write to the: Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
package com.threerings.getdown.tools;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.Signature;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.Task;
|
||||
|
||||
/**
|
||||
* Computes and fills in signed applet parameters. The input file should contain the following
|
||||
* tokens: <code>@APPBASE@, @APPNAME@, @IMGPATH@, @SIGNATURE@</code> which will be filled in based
|
||||
* on supplied and computed values.
|
||||
*/
|
||||
public class AppletParamTask extends Task
|
||||
{
|
||||
public void setAppbase (String appbase)
|
||||
{
|
||||
_appbase = appbase;
|
||||
}
|
||||
|
||||
public void setAppname (String appname)
|
||||
{
|
||||
_appname = appname;
|
||||
}
|
||||
|
||||
public void setImgpath (String imgpath)
|
||||
{
|
||||
_imgpath = imgpath;
|
||||
}
|
||||
|
||||
public void setKeystore (File keystore)
|
||||
{
|
||||
_keystore = keystore;
|
||||
}
|
||||
|
||||
public void setStorepass (String storepass)
|
||||
{
|
||||
_storepass = storepass;
|
||||
}
|
||||
|
||||
public void setAlias (String alias)
|
||||
{
|
||||
_alias = alias;
|
||||
}
|
||||
|
||||
public void setKeypass (String keypass)
|
||||
{
|
||||
_keypass = keypass;
|
||||
}
|
||||
|
||||
public void setFile (File file)
|
||||
{
|
||||
_input = file;
|
||||
}
|
||||
|
||||
public void setTofile (File tofile)
|
||||
{
|
||||
_output = tofile;
|
||||
}
|
||||
|
||||
public void execute () throws BuildException
|
||||
{
|
||||
String params = _appbase + _appname + _imgpath;
|
||||
|
||||
try {
|
||||
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 signature = new String(Base64.encodeBase64(sig.sign()));
|
||||
|
||||
BufferedReader bin = new BufferedReader(new FileReader(_input));
|
||||
PrintWriter pout = new PrintWriter(new FileWriter(_output));
|
||||
String line;
|
||||
while ((line = bin.readLine()) != null) {
|
||||
line = line.replaceAll("@APPNAME@", _appname);
|
||||
line = line.replaceAll("@APPBASE@", _appbase);
|
||||
line = line.replaceAll("@IMGPATH@", _imgpath);
|
||||
line = line.replaceAll("@SIGNATURE@", signature);
|
||||
pout.println(line);
|
||||
}
|
||||
|
||||
bin.close();
|
||||
pout.close();
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new BuildException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected String _appbase, _appname, _imgpath;
|
||||
protected File _keystore, _input, _output;
|
||||
protected String _storepass = "", _alias, _keypass = "";
|
||||
}
|
||||
@@ -21,9 +21,17 @@
|
||||
package com.threerings.getdown.tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.Signature;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.Task;
|
||||
|
||||
@@ -45,6 +53,30 @@ public class DigesterTask extends Task
|
||||
_appdir = appdir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the digest signing keystore.
|
||||
*/
|
||||
public void setKeystore (File path)
|
||||
{
|
||||
_storepath = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the keystore decryption key.
|
||||
*/
|
||||
public void setStorepass (String password)
|
||||
{
|
||||
_storepass = password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the private key alias.
|
||||
*/
|
||||
public void setAlias (String alias)
|
||||
{
|
||||
_storealias = alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the actual work of the task.
|
||||
*/
|
||||
@@ -52,16 +84,27 @@ public class DigesterTask extends Task
|
||||
{
|
||||
// make sure appdir is set
|
||||
if (_appdir == null) {
|
||||
String errmsg = "Must specify the path to the application " +
|
||||
"directory via the 'appdir' attribute.";
|
||||
throw new BuildException(errmsg);
|
||||
throw new BuildException("Must specify the path to the application directory " +
|
||||
"via the 'appdir' attribute.");
|
||||
}
|
||||
|
||||
// make sure _storepass and _keyalias are set, if _storepath is set
|
||||
if (_storepath != null) {
|
||||
if (_storepass == null || _storealias == null) {
|
||||
throw new BuildException(
|
||||
"Must specify both a keystore password and a private key alias.");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
createDigest(_appdir);
|
||||
if (_storepath != null) {
|
||||
signDigest(_appdir, _storepath, _storepass, _storealias);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new BuildException("Error creating digest: " +
|
||||
ioe.getMessage(), ioe);
|
||||
throw new BuildException("Error creating digest: " + ioe.getMessage(), ioe);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
throw new BuildException("Error creating signature: " + gse.getMessage(), gse);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +133,47 @@ public class DigesterTask extends Task
|
||||
Digest.createDigest(rsrcs, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a digest file in the specified application directory.
|
||||
*/
|
||||
public static void signDigest (File appdir, File storePath, String storePass, String storeAlias)
|
||||
throws IOException, GeneralSecurityException
|
||||
{
|
||||
File inputFile = new File(appdir, Digest.DIGEST_FILE);
|
||||
File signatureFile = new File(appdir, Digest.DIGEST_FILE + Application.SIGNATURE_SUFFIX);
|
||||
|
||||
// initialize the keystore
|
||||
KeyStore store = KeyStore.getInstance("JKS");
|
||||
FileInputStream storeInput = new FileInputStream(storePath);
|
||||
store.load(storeInput, storePass.toCharArray());
|
||||
PrivateKey key = (PrivateKey)store.getKey(storeAlias, storePass.toCharArray());
|
||||
|
||||
// sign the digest file
|
||||
Signature sig = Signature.getInstance("SHA1withRSA");
|
||||
FileInputStream dataInput = new FileInputStream(inputFile);
|
||||
byte[] buffer = new byte[8192];
|
||||
int length;
|
||||
|
||||
sig.initSign(key);
|
||||
while ((length = dataInput.read(buffer)) != -1) {
|
||||
sig.update(buffer, 0, length);
|
||||
}
|
||||
|
||||
// Write out the signature
|
||||
FileOutputStream signatureOutput = new FileOutputStream(signatureFile);
|
||||
String signed = new String(Base64.encodeBase64(sig.sign()));
|
||||
signatureOutput.write(signed.getBytes("utf8"));
|
||||
}
|
||||
|
||||
/** The application directory in which we're creating a digest file. */
|
||||
protected File _appdir;
|
||||
|
||||
/** The path to the keystore we'll use to sign the digest file, if any. */
|
||||
protected File _storepath;
|
||||
|
||||
/** The decryption key for the keystore. */
|
||||
protected String _storepass;
|
||||
|
||||
/** The private key alias. */
|
||||
protected String _storealias;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user