diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 18:28:14 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 18:28:14 -0800 |
commit | 05806d7af62e07c6225b2e7103a1b115ecf6c9ad (patch) | |
tree | 4b825dc642cb6eb9a060e54bf8d69288fbee4904 /tools/signapk/SignApk.java | |
parent | 094268cf8cb37b9d904c8a1e3559cdd46d73cf66 (diff) | |
download | build-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.java | 390 |
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); - } - } - } -} |