标签:des android style blog http color os io 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 at 115 * http://forums.sun.com/thread.jspa?threadID=758133&messageID=4330949 116 * 117 * @param encryptedPrivateKey The raw data of the private key 118 * @param keyFile The file containing the private key 119 */ 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 the 188 // output manifest in sorted order. We expect that the output 189 // 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 = 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 @Override 230 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 @Override 240 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 manifest 265 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 set 309 * the modification times in the output to a fixed time, so as to 310 * reduce variation in the output file and make incremental OTAs 311 * 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 = 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.MF 367 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.SF 374 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.RSA 383 je = new JarEntry(CERT_RSA_NAME); 384 je.setTime(timestamp); 385 outputJar.putNextEntry(je); 386 writeSignatureBlock(signature, publicKey, outputJar); 387 388 // Everything else 389 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 }
标签:des android style blog http color os io java
原文地址:http://www.cnblogs.com/royi123/p/3960031.html