首页 > 代码库 > 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
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。