summaryrefslogtreecommitdiffstats
path: root/tools/signapk
diff options
context:
space:
mode:
authorDoug Zongker <dougz@android.com>2012-10-15 17:10:13 -0700
committerDoug Zongker <dougz@google.com>2013-01-02 13:31:00 -0800
commitb14c97621bb748d717fd9c91b21cb4d7cf0bd85e (patch)
tree2bc0aa964c403b87b4dcd23cd368bd0219fb1839 /tools/signapk
parent26f47aba2d973bc1cc459a63df7bd0eec799f4ff (diff)
downloadbuild-b14c97621bb748d717fd9c91b21cb4d7cf0bd85e.zip
build-b14c97621bb748d717fd9c91b21cb4d7cf0bd85e.tar.gz
build-b14c97621bb748d717fd9c91b21cb4d7cf0bd85e.tar.bz2
add multiple key support to signapk
Support signing .apks (but not OTA packages) with multiple keys. Bug: 7350459 Change-Id: I794e1da0555e2bb9247a59c756656d4ca7ee04cf
Diffstat (limited to 'tools/signapk')
-rw-r--r--tools/signapk/SignApk.java100
1 files changed, 67 insertions, 33 deletions
diff --git a/tools/signapk/SignApk.java b/tools/signapk/SignApk.java
index 07aefa7..1055704 100644
--- a/tools/signapk/SignApk.java
+++ b/tools/signapk/SignApk.java
@@ -83,6 +83,8 @@ import javax.crypto.spec.PBEKeySpec;
class SignApk {
private static final String CERT_SF_NAME = "META-INF/CERT.SF";
private static final String CERT_RSA_NAME = "META-INF/CERT.RSA";
+ private static final String CERT_SF_MULTI_NAME = "META-INF/CERT%d.SF";
+ private static final String CERT_RSA_MULTI_NAME = "META-INF/CERT%d.RSA";
private static final String OTACERT_NAME = "META-INF/com/android/otacert";
@@ -90,7 +92,8 @@ class SignApk {
// Files matching this pattern are not copied to the output.
private static Pattern stripPattern =
- Pattern.compile("^META-INF/(.*)[.](SF|RSA|DSA)$");
+ Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA)|com/android/otacert))|(" +
+ Pattern.quote(JarFile.MANIFEST_NAME) + ")$");
private static X509Certificate readPublicKey(File file)
throws IOException, GeneralSecurityException {
@@ -208,11 +211,8 @@ class SignApk {
for (JarEntry entry: byName.values()) {
String name = entry.getName();
- if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) &&
- !name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) &&
- !name.equals(OTACERT_NAME) &&
- (stripPattern == null ||
- !stripPattern.matcher(name).matches())) {
+ if (!entry.isDirectory() &&
+ (stripPattern == null || !stripPattern.matcher(name).matches())) {
InputStream data = jar.getInputStream(entry);
while ((num = data.read(buffer)) > 0) {
md.update(buffer, 0, num);
@@ -499,13 +499,16 @@ class SignApk {
}
}
+ private static void usage() {
+ System.err.println("Usage: signapk [-w] " +
+ "publickey.x509[.pem] privatekey.pk8 " +
+ "[publickey2.x509[.pem] privatekey2.pk8 ...] " +
+ "input.jar output.jar");
+ System.exit(2);
+ }
+
public static void main(String[] args) {
- if (args.length != 4 && args.length != 5) {
- System.err.println("Usage: signapk [-w] " +
- "publickey.x509[.pem] privatekey.pk8 " +
- "input.jar output.jar");
- System.exit(2);
- }
+ if (args.length < 4) usage();
sBouncyCastleProvider = new BouncyCastleProvider();
Security.addProvider(sBouncyCastleProvider);
@@ -517,25 +520,46 @@ class SignApk {
argstart = 1;
}
+ if ((args.length - argstart) % 2 == 1) usage();
+ int numKeys = ((args.length - argstart) / 2) - 1;
+ if (signWholeFile && numKeys > 1) {
+ System.err.println("Only one key may be used with -w.");
+ System.exit(2);
+ }
+
+ String inputFilename = args[args.length-2];
+ String outputFilename = args[args.length-1];
+
JarFile inputJar = null;
JarOutputStream outputJar = null;
FileOutputStream outputFile = null;
try {
- File publicKeyFile = new File(args[argstart+0]);
- X509Certificate publicKey = readPublicKey(publicKeyFile);
+ File firstPublicKeyFile = new File(args[argstart+0]);
- // Assume the certificate is valid for at least an hour.
- long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
+ X509Certificate[] publicKey = new X509Certificate[numKeys];
+ for (int i = 0; i < numKeys; ++i) {
+ int argNum = argstart + i*2;
+ publicKey[i] = readPublicKey(new File(args[argNum]));
+ }
- PrivateKey privateKey = readPrivateKey(new File(args[argstart+1]));
- inputJar = new JarFile(new File(args[argstart+2]), false); // Don't verify.
+ // Set the ZIP file timestamp to the starting valid time
+ // of the 0th certificate plus one hour (to match what
+ // we've historically done).
+ long timestamp = publicKey[0].getNotBefore().getTime() + 3600L * 1000;
+
+ PrivateKey[] privateKey = new PrivateKey[numKeys];
+ for (int i = 0; i < numKeys; ++i) {
+ int argNum = argstart + i*2 + 1;
+ privateKey[i] = readPrivateKey(new File(args[argNum]));
+ }
+ inputJar = new JarFile(new File(inputFilename), false); // Don't verify.
OutputStream outputStream = null;
if (signWholeFile) {
outputStream = new ByteArrayOutputStream();
} else {
- outputStream = outputFile = new FileOutputStream(args[argstart+3]);
+ outputStream = outputFile = new FileOutputStream(outputFilename);
}
outputJar = new JarOutputStream(outputStream);
@@ -558,7 +582,7 @@ class SignApk {
// otacert
if (signWholeFile) {
- addOtacert(outputJar, publicKeyFile, timestamp, manifest);
+ addOtacert(outputJar, firstPublicKeyFile, timestamp, manifest);
}
// MANIFEST.MF
@@ -567,30 +591,40 @@ class SignApk {
outputJar.putNextEntry(je);
manifest.write(outputJar);
- // CERT.SF
- je = new JarEntry(CERT_SF_NAME);
- je.setTime(timestamp);
- outputJar.putNextEntry(je);
+ // In the case of multiple keys, all the .SF files will be
+ // identical, but as far as I can tell the jarsigner docs
+ // don't allow there to be just one copy in the zipfile;
+ // there hase to be one per .RSA file.
+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
writeSignatureFile(manifest, baos);
byte[] signedData = baos.toByteArray();
- outputJar.write(signedData);
- // CERT.RSA
- je = new JarEntry(CERT_RSA_NAME);
- je.setTime(timestamp);
- outputJar.putNextEntry(je);
- writeSignatureBlock(new CMSProcessableByteArray(signedData),
- publicKey, privateKey, outputJar);
+ for (int k = 0; k < numKeys; ++k) {
+ // CERT.SF / CERT#.SF
+ je = new JarEntry(numKeys == 1 ? CERT_SF_NAME :
+ (String.format(CERT_SF_MULTI_NAME, k)));
+ je.setTime(timestamp);
+ outputJar.putNextEntry(je);
+ outputJar.write(signedData);
+
+ // CERT.RSA / CERT#.RSA
+ je = new JarEntry(numKeys == 1 ? CERT_RSA_NAME :
+ (String.format(CERT_RSA_MULTI_NAME, k)));
+ je.setTime(timestamp);
+ outputJar.putNextEntry(je);
+ writeSignatureBlock(new CMSProcessableByteArray(signedData),
+ publicKey[k], privateKey[k], outputJar);
+ }
outputJar.close();
outputJar = null;
outputStream.flush();
if (signWholeFile) {
- outputFile = new FileOutputStream(args[argstart+3]);
+ outputFile = new FileOutputStream(outputFilename);
signWholeOutputFile(((ByteArrayOutputStream)outputStream).toByteArray(),
- outputFile, publicKey, privateKey);
+ outputFile, publicKey[0], privateKey[0]);
}
} catch (Exception e) {
e.printStackTrace();