summaryrefslogtreecommitdiffstats
path: root/tools/signapk/SignApk.java
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-03 18:28:14 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-03 18:28:14 -0800
commit05806d7af62e07c6225b2e7103a1b115ecf6c9ad (patch)
tree4b825dc642cb6eb9a060e54bf8d69288fbee4904 /tools/signapk/SignApk.java
parent094268cf8cb37b9d904c8a1e3559cdd46d73cf66 (diff)
downloadbuild-05806d7af62e07c6225b2e7103a1b115ecf6c9ad.zip
build-05806d7af62e07c6225b2e7103a1b115ecf6c9ad.tar.gz
build-05806d7af62e07c6225b2e7103a1b115ecf6c9ad.tar.bz2
auto import from //depot/cupcake/@135843
Diffstat (limited to 'tools/signapk/SignApk.java')
-rw-r--r--tools/signapk/SignApk.java390
1 files changed, 0 insertions, 390 deletions
diff --git a/tools/signapk/SignApk.java b/tools/signapk/SignApk.java
deleted file mode 100644
index 340a9f5..0000000
--- a/tools/signapk/SignApk.java
+++ /dev/null
@@ -1,390 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.signapk;
-
-import sun.misc.BASE64Encoder;
-import sun.security.pkcs.ContentInfo;
-import sun.security.pkcs.PKCS7;
-import sun.security.pkcs.SignerInfo;
-import sun.security.x509.AlgorithmId;
-import sun.security.x509.X500Name;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FilterOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.PrintStream;
-import java.security.AlgorithmParameters;
-import java.security.DigestOutputStream;
-import java.security.GeneralSecurityException;
-import java.security.Key;
-import java.security.KeyFactory;
-import java.security.MessageDigest;
-import java.security.PrivateKey;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.KeySpec;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.jar.Attributes;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-import java.util.jar.JarOutputStream;
-import java.util.jar.Manifest;
-import javax.crypto.Cipher;
-import javax.crypto.EncryptedPrivateKeyInfo;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.PBEKeySpec;
-
-/**
- * Command line tool to sign JAR files (including APKs and OTA updates) in
- * a way compatible with the mincrypt verifier, using SHA1 and RSA keys.
- */
-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 X509Certificate readPublicKey(File file)
- throws IOException, GeneralSecurityException {
- FileInputStream input = new FileInputStream(file);
- try {
- CertificateFactory cf = CertificateFactory.getInstance("X.509");
- return (X509Certificate) cf.generateCertificate(input);
- } finally {
- input.close();
- }
- }
-
- /**
- * Reads the password from stdin and returns it as a string.
- *
- * @param keyFile The file containing the private key. Used to prompt the user.
- */
- private static String readPassword(File keyFile) {
- // TODO: use Console.readPassword() when it's available.
- System.out.print("Enter password for " + keyFile + " (password will not be hidden): ");
- System.out.flush();
- BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
- try {
- return stdin.readLine();
- } catch (IOException ex) {
- return null;
- }
- }
-
- /**
- * Decrypt an encrypted PKCS 8 format private key.
- *
- * Based on ghstark's post on Aug 6, 2006 at
- * http://forums.sun.com/thread.jspa?threadID=758133&messageID=4330949
- *
- * @param encryptedPrivateKey The raw data of the private key
- * @param keyFile The file containing the private key
- */
- private static KeySpec decryptPrivateKey(byte[] encryptedPrivateKey, File keyFile)
- throws GeneralSecurityException {
- EncryptedPrivateKeyInfo epkInfo;
- try {
- epkInfo = new EncryptedPrivateKeyInfo(encryptedPrivateKey);
- } catch (IOException ex) {
- // Probably not an encrypted key.
- return null;
- }
-
- char[] password = readPassword(keyFile).toCharArray();
-
- SecretKeyFactory skFactory = SecretKeyFactory.getInstance(epkInfo.getAlgName());
- Key key = skFactory.generateSecret(new PBEKeySpec(password));
-
- Cipher cipher = Cipher.getInstance(epkInfo.getAlgName());
- cipher.init(Cipher.DECRYPT_MODE, key, epkInfo.getAlgParameters());
-
- try {
- return epkInfo.getKeySpec(cipher);
- } catch (InvalidKeySpecException ex) {
- System.err.println("signapk: Password for " + keyFile + " may be bad.");
- throw ex;
- }
- }
-
- /** Read a PKCS 8 format private key. */
- private static PrivateKey readPrivateKey(File file)
- throws IOException, GeneralSecurityException {
- DataInputStream input = new DataInputStream(new FileInputStream(file));
- try {
- byte[] bytes = new byte[(int) file.length()];
- input.read(bytes);
-
- KeySpec spec = decryptPrivateKey(bytes, file);
- if (spec == null) {
- spec = new PKCS8EncodedKeySpec(bytes);
- }
-
- try {
- return KeyFactory.getInstance("RSA").generatePrivate(spec);
- } catch (InvalidKeySpecException ex) {
- return KeyFactory.getInstance("DSA").generatePrivate(spec);
- }
- } finally {
- input.close();
- }
- }
-
- /** Add the SHA1 of every file to the manifest, creating it if necessary. */
- private static Manifest addDigestsToManifest(JarFile jar)
- throws IOException, GeneralSecurityException {
- Manifest input = jar.getManifest();
- Manifest output = new Manifest();
- Attributes main = output.getMainAttributes();
- if (input != null) {
- main.putAll(input.getMainAttributes());
- } else {
- main.putValue("Manifest-Version", "1.0");
- main.putValue("Created-By", "1.0 (Android SignApk)");
- }
-
- BASE64Encoder base64 = new BASE64Encoder();
- MessageDigest md = MessageDigest.getInstance("SHA1");
- byte[] buffer = new byte[4096];
- int num;
-
- // We sort the input entries by name, and add them to the
- // output manifest in sorted order. We expect that the output
- // map will be deterministic.
-
- TreeMap<String, JarEntry> byName = new TreeMap<String, JarEntry>();
-
- for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) {
- JarEntry entry = e.nextElement();
- byName.put(entry.getName(), entry);
- }
-
- 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)) {
- InputStream data = jar.getInputStream(entry);
- while ((num = data.read(buffer)) > 0) {
- md.update(buffer, 0, num);
- }
-
- Attributes attr = null;
- if (input != null) attr = input.getAttributes(name);
- attr = attr != null ? new Attributes(attr) : new Attributes();
- attr.putValue("SHA1-Digest", base64.encode(md.digest()));
- output.getEntries().put(name, attr);
- }
- }
-
- return output;
- }
-
- /** Write to another stream and also feed it to the Signature object. */
- private static class SignatureOutputStream extends FilterOutputStream {
- private Signature mSignature;
-
- public SignatureOutputStream(OutputStream out, Signature sig) {
- super(out);
- mSignature = sig;
- }
-
- @Override
- public void write(int b) throws IOException {
- try {
- mSignature.update((byte) b);
- } catch (SignatureException e) {
- throw new IOException("SignatureException: " + e);
- }
- super.write(b);
- }
-
- @Override
- public void write(byte[] b, int off, int len) throws IOException {
- try {
- mSignature.update(b, off, len);
- } catch (SignatureException e) {
- throw new IOException("SignatureException: " + e);
- }
- super.write(b, off, len);
- }
- }
-
- /** Write a .SF file with a digest the specified manifest. */
- private static void writeSignatureFile(Manifest manifest, OutputStream out)
- throws IOException, GeneralSecurityException {
- Manifest sf = new Manifest();
- Attributes main = sf.getMainAttributes();
- main.putValue("Signature-Version", "1.0");
- main.putValue("Created-By", "1.0 (Android SignApk)");
-
- BASE64Encoder base64 = new BASE64Encoder();
- MessageDigest md = MessageDigest.getInstance("SHA1");
- PrintStream print = new PrintStream(
- new DigestOutputStream(new ByteArrayOutputStream(), md),
- true, "UTF-8");
-
- // Digest of the entire manifest
- manifest.write(print);
- print.flush();
- main.putValue("SHA1-Digest-Manifest", base64.encode(md.digest()));
-
- Map<String, Attributes> entries = manifest.getEntries();
- for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
- // Digest of the manifest stanza for this entry.
- print.print("Name: " + entry.getKey() + "\r\n");
- for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {
- print.print(att.getKey() + ": " + att.getValue() + "\r\n");
- }
- print.print("\r\n");
- print.flush();
-
- Attributes sfAttr = new Attributes();
- sfAttr.putValue("SHA1-Digest", base64.encode(md.digest()));
- sf.getEntries().put(entry.getKey(), sfAttr);
- }
-
- sf.write(out);
- }
-
- /** Write a .RSA file with a digital signature. */
- private static void writeSignatureBlock(
- Signature signature, X509Certificate publicKey, OutputStream out)
- throws IOException, GeneralSecurityException {
- SignerInfo signerInfo = new SignerInfo(
- new X500Name(publicKey.getIssuerX500Principal().getName()),
- publicKey.getSerialNumber(),
- AlgorithmId.get("SHA1"),
- AlgorithmId.get("RSA"),
- signature.sign());
-
- PKCS7 pkcs7 = new PKCS7(
- new AlgorithmId[] { AlgorithmId.get("SHA1") },
- new ContentInfo(ContentInfo.DATA_OID, null),
- new X509Certificate[] { publicKey },
- new SignerInfo[] { signerInfo });
-
- pkcs7.encodeSignedData(out);
- }
-
- /** Copy all the files in a manifest from input to output. */
- private static void copyFiles(Manifest manifest,
- JarFile in, JarOutputStream out) throws IOException {
- byte[] buffer = new byte[4096];
- int num;
-
- Map<String, Attributes> entries = manifest.getEntries();
- List<String> names = new ArrayList(entries.keySet());
- Collections.sort(names);
- for (String name : names) {
- JarEntry inEntry = in.getJarEntry(name);
- if (inEntry.getMethod() == JarEntry.STORED) {
- // Preserve the STORED method of the input entry.
- out.putNextEntry(new JarEntry(inEntry));
- } else {
- // Create a new entry so that the compressed len is recomputed.
- JarEntry je = new JarEntry(name);
- je.setTime(inEntry.getTime());
- out.putNextEntry(je);
- }
-
- InputStream data = in.getInputStream(inEntry);
- while ((num = data.read(buffer)) > 0) {
- out.write(buffer, 0, num);
- }
- out.flush();
- }
- }
-
- public static void main(String[] args) {
- if (args.length != 4) {
- System.err.println("Usage: signapk " +
- "publickey.x509[.pem] privatekey.pk8 " +
- "input.jar output.jar");
- System.exit(2);
- }
-
- JarFile inputJar = null;
- JarOutputStream outputJar = null;
-
- try {
- X509Certificate publicKey = readPublicKey(new File(args[0]));
-
- // Assume the certificate is valid for at least an hour.
- long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
-
- PrivateKey privateKey = readPrivateKey(new File(args[1]));
- inputJar = new JarFile(new File(args[2]), false); // Don't verify.
- outputJar = new JarOutputStream(new FileOutputStream(args[3]));
- outputJar.setLevel(9);
-
- JarEntry je;
-
- // MANIFEST.MF
- Manifest manifest = addDigestsToManifest(inputJar);
- je = new JarEntry(JarFile.MANIFEST_NAME);
- je.setTime(timestamp);
- outputJar.putNextEntry(je);
- manifest.write(outputJar);
-
- // CERT.SF
- Signature signature = Signature.getInstance("SHA1withRSA");
- signature.initSign(privateKey);
- je = new JarEntry(CERT_SF_NAME);
- je.setTime(timestamp);
- outputJar.putNextEntry(je);
- writeSignatureFile(manifest,
- new SignatureOutputStream(outputJar, signature));
-
- // CERT.RSA
- je = new JarEntry(CERT_RSA_NAME);
- je.setTime(timestamp);
- outputJar.putNextEntry(je);
- writeSignatureBlock(signature, publicKey, outputJar);
-
- // Everything else
- copyFiles(manifest, inputJar, outputJar);
- } catch (Exception e) {
- e.printStackTrace();
- System.exit(1);
- } finally {
- try {
- if (inputJar != null) inputJar.close();
- if (outputJar != null) outputJar.close();
- } catch (IOException e) {
- e.printStackTrace();
- System.exit(1);
- }
- }
- }
-}