首页 > 代码库 > Android源码-SignApk.java

Android源码-SignApk.java

  1 /*  2  * Copyright (C) 2008 The Android Open Source Project  3  *  4  * Licensed under the Apache License, Version 2.0 (the "License");  5  * you may not use this file except in compliance with the License.  6  * You may obtain a copy of the License at  7  *  8  *      http://www.apache.org/licenses/LICENSE-2.0  9  * 10  * Unless required by applicable law or agreed to in writing, software 11  * distributed under the License is distributed on an "AS IS" BASIS, 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13  * See the License for the specific language governing permissions and 14  * limitations under the License. 15  */ 16  17 package com.android.signapk; 18  19 import sun.misc.BASE64Encoder; 20 import sun.security.pkcs.ContentInfo; 21 import sun.security.pkcs.PKCS7; 22 import sun.security.pkcs.SignerInfo; 23 import sun.security.x509.AlgorithmId; 24 import sun.security.x509.X500Name; 25  26 import java.io.BufferedReader; 27 import java.io.ByteArrayOutputStream; 28 import java.io.DataInputStream; 29 import java.io.File; 30 import java.io.FileInputStream; 31 import java.io.FileOutputStream; 32 import java.io.FilterOutputStream; 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.io.InputStreamReader; 36 import java.io.OutputStream; 37 import java.io.PrintStream; 38 import java.security.AlgorithmParameters; 39 import java.security.DigestOutputStream; 40 import java.security.GeneralSecurityException; 41 import java.security.Key; 42 import java.security.KeyFactory; 43 import java.security.MessageDigest; 44 import java.security.PrivateKey; 45 import java.security.Signature; 46 import java.security.SignatureException; 47 import java.security.cert.Certificate; 48 import java.security.cert.CertificateFactory; 49 import java.security.cert.X509Certificate; 50 import java.security.spec.InvalidKeySpecException; 51 import java.security.spec.KeySpec; 52 import java.security.spec.PKCS8EncodedKeySpec; 53 import java.util.ArrayList; 54 import java.util.Collections; 55 import java.util.Date; 56 import java.util.Enumeration; 57 import java.util.List; 58 import java.util.Map; 59 import java.util.TreeMap; 60 import java.util.jar.Attributes; 61 import java.util.jar.JarEntry; 62 import java.util.jar.JarFile; 63 import java.util.jar.JarOutputStream; 64 import java.util.jar.Manifest; 65 import java.util.regex.Pattern; 66 import javax.crypto.Cipher; 67 import javax.crypto.EncryptedPrivateKeyInfo; 68 import javax.crypto.SecretKeyFactory; 69 import javax.crypto.spec.PBEKeySpec; 70  71 /** 72  * Command line tool to sign JAR files (including APKs and OTA updates) in 73  * a way compatible with the mincrypt verifier, using SHA1 and RSA keys. 74  */ 75 class SignApk { 76     private static final String CERT_SF_NAME = "META-INF/CERT.SF"; 77     private static final String CERT_RSA_NAME = "META-INF/CERT.RSA"; 78  79     // Files matching this pattern are not copied to the output. 80     private static Pattern stripPattern = 81             Pattern.compile("^META-INF/(.*)[.](SF|RSA|DSA)$"); 82  83     private static X509Certificate readPublicKey(File file) 84             throws IOException, GeneralSecurityException { 85         FileInputStream input = new FileInputStream(file); 86         try { 87             CertificateFactory cf = CertificateFactory.getInstance("X.509"); 88             return (X509Certificate) cf.generateCertificate(input); 89         } finally { 90             input.close(); 91         } 92     } 93  94     /** 95      * Reads the password from stdin and returns it as a string. 96      * 97      * @param keyFile The file containing the private key.  Used to prompt the user. 98      */ 99     private static String readPassword(File keyFile) {100         // TODO: use Console.readPassword() when it‘s available.101         System.out.print("Enter password for " + keyFile + " (password will not be hidden): ");102         System.out.flush();103         BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));104         try {105             return stdin.readLine();106         } catch (IOException ex) {107             return null;108         }109     }110 111     /**112      * Decrypt an encrypted PKCS 8 format private key.113      *114      * Based on ghstark‘s post on Aug 6, 2006 at115      * http://forums.sun.com/thread.jspa?threadID=758133&messageID=4330949116      *117      * @param encryptedPrivateKey The raw data of the private key118      * @param keyFile The file containing the private key119      */120     private static KeySpec decryptPrivateKey(byte[] encryptedPrivateKey, File keyFile)121             throws GeneralSecurityException {122         EncryptedPrivateKeyInfo epkInfo;123         try {124             epkInfo = new EncryptedPrivateKeyInfo(encryptedPrivateKey);125         } catch (IOException ex) {126             // Probably not an encrypted key.127             return null;128         }129 130         char[] password = readPassword(keyFile).toCharArray();131 132         SecretKeyFactory skFactory = SecretKeyFactory.getInstance(epkInfo.getAlgName());133         Key key = skFactory.generateSecret(new PBEKeySpec(password));134 135         Cipher cipher = Cipher.getInstance(epkInfo.getAlgName());136         cipher.init(Cipher.DECRYPT_MODE, key, epkInfo.getAlgParameters());137 138         try {139             return epkInfo.getKeySpec(cipher);140         } catch (InvalidKeySpecException ex) {141             System.err.println("signapk: Password for " + keyFile + " may be bad.");142             throw ex;143         }144     }145 146     /** Read a PKCS 8 format private key. */147     private static PrivateKey readPrivateKey(File file)148             throws IOException, GeneralSecurityException {149         DataInputStream input = new DataInputStream(new FileInputStream(file));150         try {151             byte[] bytes = new byte[(int) file.length()];152             input.read(bytes);153 154             KeySpec spec = decryptPrivateKey(bytes, file);155             if (spec == null) {156                 spec = new PKCS8EncodedKeySpec(bytes);157             }158 159             try {160                 return KeyFactory.getInstance("RSA").generatePrivate(spec);161             } catch (InvalidKeySpecException ex) {162                 return KeyFactory.getInstance("DSA").generatePrivate(spec);163             }164         } finally {165             input.close();166         }167     }168 169     /** Add the SHA1 of every file to the manifest, creating it if necessary. */170     private static Manifest addDigestsToManifest(JarFile jar)171             throws IOException, GeneralSecurityException {172         Manifest input = jar.getManifest();173         Manifest output = new Manifest();174         Attributes main = output.getMainAttributes();175         if (input != null) {176             main.putAll(input.getMainAttributes());177         } else {178             main.putValue("Manifest-Version", "1.0");179             main.putValue("Created-By", "1.0 (Android SignApk)");180         }181 182         BASE64Encoder base64 = new BASE64Encoder();183         MessageDigest md = MessageDigest.getInstance("SHA1");184         byte[] buffer = new byte[4096];185         int num;186 187         // We sort the input entries by name, and add them to the188         // output manifest in sorted order.  We expect that the output189         // map will be deterministic.190 191         TreeMap<String, JarEntry> byName = new TreeMap<String, JarEntry>();192 193         for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) {194             JarEntry entry = e.nextElement();195             byName.put(entry.getName(), entry);196         }197 198         for (JarEntry entry: byName.values()) {199             String name = entry.getName();200             if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) &&201                 !name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) &&202                 (stripPattern == null ||203                  !stripPattern.matcher(name).matches())) {204                 InputStream data =http://www.mamicode.com/ jar.getInputStream(entry);205                 while ((num = data.read(buffer)) > 0) {206                     md.update(buffer, 0, num);207                 }208 209                 Attributes attr = null;210                 if (input != null) attr = input.getAttributes(name);211                 attr = attr != null ? new Attributes(attr) : new Attributes();212                 attr.putValue("SHA1-Digest", base64.encode(md.digest()));213                 output.getEntries().put(name, attr);214             }215         }216 217         return output;218     }219 220     /** Write to another stream and also feed it to the Signature object. */221     private static class SignatureOutputStream extends FilterOutputStream {222         private Signature mSignature;223 224         public SignatureOutputStream(OutputStream out, Signature sig) {225             super(out);226             mSignature = sig;227         }228 229         @Override230         public void write(int b) throws IOException {231             try {232                 mSignature.update((byte) b);233             } catch (SignatureException e) {234                 throw new IOException("SignatureException: " + e);235             }236             super.write(b);237         }238 239         @Override240         public void write(byte[] b, int off, int len) throws IOException {241             try {242                 mSignature.update(b, off, len);243             } catch (SignatureException e) {244                 throw new IOException("SignatureException: " + e);245             }246             super.write(b, off, len);247         }248     }249 250     /** Write a .SF file with a digest the specified manifest. */251     private static void writeSignatureFile(Manifest manifest, OutputStream out)252             throws IOException, GeneralSecurityException {253         Manifest sf = new Manifest();254         Attributes main = sf.getMainAttributes();255         main.putValue("Signature-Version", "1.0");256         main.putValue("Created-By", "1.0 (Android SignApk)");257 258         BASE64Encoder base64 = new BASE64Encoder();259         MessageDigest md = MessageDigest.getInstance("SHA1");260         PrintStream print = new PrintStream(261                 new DigestOutputStream(new ByteArrayOutputStream(), md),262                 true, "UTF-8");263 264         // Digest of the entire manifest265         manifest.write(print);266         print.flush();267         main.putValue("SHA1-Digest-Manifest", base64.encode(md.digest()));268 269         Map<String, Attributes> entries = manifest.getEntries();270         for (Map.Entry<String, Attributes> entry : entries.entrySet()) {271             // Digest of the manifest stanza for this entry.272             print.print("Name: " + entry.getKey() + "\r\n");273             for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {274                 print.print(att.getKey() + ": " + att.getValue() + "\r\n");275             }276             print.print("\r\n");277             print.flush();278 279             Attributes sfAttr = new Attributes();280             sfAttr.putValue("SHA1-Digest", base64.encode(md.digest()));281             sf.getEntries().put(entry.getKey(), sfAttr);282         }283 284         sf.write(out);285     }286 287     /** Write a .RSA file with a digital signature. */288     private static void writeSignatureBlock(289             Signature signature, X509Certificate publicKey, OutputStream out)290             throws IOException, GeneralSecurityException {291         SignerInfo signerInfo = new SignerInfo(292                 new X500Name(publicKey.getIssuerX500Principal().getName()),293                 publicKey.getSerialNumber(),294                 AlgorithmId.get("SHA1"),295                 AlgorithmId.get("RSA"),296                 signature.sign());297 298         PKCS7 pkcs7 = new PKCS7(299                 new AlgorithmId[] { AlgorithmId.get("SHA1") },300                 new ContentInfo(ContentInfo.DATA_OID, null),301                 new X509Certificate[] { publicKey },302                 new SignerInfo[] { signerInfo });303 304         pkcs7.encodeSignedData(out);305     }306 307     /**308      * Copy all the files in a manifest from input to output.  We set309      * the modification times in the output to a fixed time, so as to310      * reduce variation in the output file and make incremental OTAs311      * more efficient.312      */313     private static void copyFiles(Manifest manifest,314         JarFile in, JarOutputStream out, long timestamp) throws IOException {315         byte[] buffer = new byte[4096];316         int num;317 318         Map<String, Attributes> entries = manifest.getEntries();319         List<String> names = new ArrayList(entries.keySet());320         Collections.sort(names);321         for (String name : names) {322             JarEntry inEntry = in.getJarEntry(name);323             JarEntry outEntry = null;324             if (inEntry.getMethod() == JarEntry.STORED) {325                 // Preserve the STORED method of the input entry.326                 outEntry = new JarEntry(inEntry);327             } else {328                 // Create a new entry so that the compressed len is recomputed.329                 outEntry = new JarEntry(name);330             }331             outEntry.setTime(timestamp);332             out.putNextEntry(outEntry);333 334             InputStream data =http://www.mamicode.com/ in.getInputStream(inEntry);335             while ((num = data.read(buffer)) > 0) {336                 out.write(buffer, 0, num);337             }338             out.flush();339         }340     }341 342     public static void main(String[] args) {343         if (args.length != 4) {344             System.err.println("Usage: signapk " +345                     "publickey.x509[.pem] privatekey.pk8 " +346                     "input.jar output.jar");347             System.exit(2);348         }349 350         JarFile inputJar = null;351         JarOutputStream outputJar = null;352 353         try {354             X509Certificate publicKey = readPublicKey(new File(args[0]));355 356             // Assume the certificate is valid for at least an hour.357             long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;358 359             PrivateKey privateKey = readPrivateKey(new File(args[1]));360             inputJar = new JarFile(new File(args[2]), false);  // Don‘t verify.361             outputJar = new JarOutputStream(new FileOutputStream(args[3]));362             outputJar.setLevel(9);363 364             JarEntry je;365 366             // MANIFEST.MF367             Manifest manifest = addDigestsToManifest(inputJar);368             je = new JarEntry(JarFile.MANIFEST_NAME);369             je.setTime(timestamp);370             outputJar.putNextEntry(je);371             manifest.write(outputJar);372 373             // CERT.SF374             Signature signature = Signature.getInstance("SHA1withRSA");375             signature.initSign(privateKey);376             je = new JarEntry(CERT_SF_NAME);377             je.setTime(timestamp);378             outputJar.putNextEntry(je);379             writeSignatureFile(manifest,380                     new SignatureOutputStream(outputJar, signature));381 382             // CERT.RSA383             je = new JarEntry(CERT_RSA_NAME);384             je.setTime(timestamp);385             outputJar.putNextEntry(je);386             writeSignatureBlock(signature, publicKey, outputJar);387 388             // Everything else389             copyFiles(manifest, inputJar, outputJar, timestamp);390         } catch (Exception e) {391             e.printStackTrace();392             System.exit(1);393         } finally {394             try {395                 if (inputJar != null) inputJar.close();396                 if (outputJar != null) outputJar.close();397             } catch (IOException e) {398                 e.printStackTrace();399                 System.exit(1);400             }401         }402     }403 }

 

Android源码-SignApk.java