188b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project/* 288b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project * Copyright (C) 2008 The Android Open Source Project 388b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project * 488b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License"); 588b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project * you may not use this file except in compliance with the License. 688b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project * You may obtain a copy of the License at 788b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project * 888b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project * http://www.apache.org/licenses/LICENSE-2.0 988b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project * 1088b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project * Unless required by applicable law or agreed to in writing, software 1188b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS, 1288b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1388b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project * See the License for the specific language governing permissions and 1488b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project * limitations under the License. 1588b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project */ 1688b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project 1788b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectpackage com.android.signapk; 1888b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project 19147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongkerimport org.bouncycastle.asn1.ASN1InputStream; 20147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongkerimport org.bouncycastle.asn1.ASN1ObjectIdentifier; 21147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongkerimport org.bouncycastle.asn1.DEROutputStream; 22147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongkerimport org.bouncycastle.asn1.cms.CMSObjectIdentifiers; 2362ea4a5c3cf5da5c64e881e6986e5753304fe8beKenny Rootimport org.bouncycastle.asn1.pkcs.PrivateKeyInfo; 24147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongkerimport org.bouncycastle.cert.jcajce.JcaCertStore; 25147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongkerimport org.bouncycastle.cms.CMSException; 26147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongkerimport org.bouncycastle.cms.CMSSignedData; 27147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongkerimport org.bouncycastle.cms.CMSSignedDataGenerator; 28147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongkerimport org.bouncycastle.cms.CMSTypedData; 29147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongkerimport org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder; 30147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongkerimport org.bouncycastle.jce.provider.BouncyCastleProvider; 31147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongkerimport org.bouncycastle.operator.ContentSigner; 32147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongkerimport org.bouncycastle.operator.OperatorCreationException; 33147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongkerimport org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; 34147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongkerimport org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; 359667b18f2300e4bf0f33d3aef51d2f48bcb6778bAlex Klyubinimport org.conscrypt.OpenSSLProvider; 3688b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project 371b09a508bae3158c8496b9cf26d35910fae7954fAlex Klyubinimport com.android.apksig.ApkSignerEngine; 381b09a508bae3158c8496b9cf26d35910fae7954fAlex Klyubinimport com.android.apksig.DefaultApkSignerEngine; 391b09a508bae3158c8496b9cf26d35910fae7954fAlex Klyubinimport com.android.apksig.apk.ApkUtils; 409b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubinimport com.android.apksig.apk.MinSdkVersionException; 411b09a508bae3158c8496b9cf26d35910fae7954fAlex Klyubinimport com.android.apksig.util.DataSink; 421b09a508bae3158c8496b9cf26d35910fae7954fAlex Klyubinimport com.android.apksig.util.DataSources; 431b09a508bae3158c8496b9cf26d35910fae7954fAlex Klyubinimport com.android.apksig.zip.ZipFormatException; 44fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin 4522717f9f9ee764e8126bcbc8c4cd9dc58b713feezhang junimport java.io.Console; 4688b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.io.BufferedReader; 4762ea4a5c3cf5da5c64e881e6986e5753304fe8beKenny Rootimport java.io.ByteArrayInputStream; 4888b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.io.ByteArrayOutputStream; 4988b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.io.DataInputStream; 5088b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.io.File; 5188b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.io.FileInputStream; 5288b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.io.FileOutputStream; 5388b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.io.FilterOutputStream; 5488b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.io.IOException; 5588b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.io.InputStream; 5688b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.io.InputStreamReader; 5788b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.io.OutputStream; 5889c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Rootimport java.lang.reflect.Constructor; 59dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubinimport java.nio.ByteBuffer; 6026f00cda4b979d7e74db6872990682335b36612bAlex Klyubinimport java.nio.ByteOrder; 61d4761a19b884d69c684c34ec0e5b74aaba2cfe95Alex Klyubinimport java.nio.charset.StandardCharsets; 6288b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.security.GeneralSecurityException; 6388b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.security.Key; 6488b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.security.KeyFactory; 6588b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.security.PrivateKey; 66147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongkerimport java.security.Provider; 67147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongkerimport java.security.Security; 68147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongkerimport java.security.cert.CertificateEncodingException; 6988b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.security.cert.CertificateFactory; 7088b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.security.cert.X509Certificate; 7188b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.security.spec.InvalidKeySpecException; 7288b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.security.spec.PKCS8EncodedKeySpec; 7388b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.util.ArrayList; 7488b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.util.Collections; 7588b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.util.Enumeration; 76dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubinimport java.util.List; 773d2365c068539941bec258d461c149a63c4fbf98Kenny Rootimport java.util.Locale; 786c41036bcf35fe39162b50d27533f0f3bfab3028Alex Klyubinimport java.util.TimeZone; 7988b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.util.jar.JarEntry; 8088b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.util.jar.JarFile; 8188b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport java.util.jar.JarOutputStream; 82af482b62e50b70e469426681584503eab1b308e9Doug Zongkerimport java.util.regex.Pattern; 83b141ded82bef9af3c65b4192f986605f6c0dc21eAlex Klyubin 8488b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport javax.crypto.Cipher; 8588b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport javax.crypto.EncryptedPrivateKeyInfo; 8688b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport javax.crypto.SecretKeyFactory; 8788b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectimport javax.crypto.spec.PBEKeySpec; 8888b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project 8988b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project/** 908562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker * HISTORICAL NOTE: 918562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker * 928562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker * Prior to the keylimepie release, SignApk ignored the signature 938562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker * algorithm specified in the certificate and always used SHA1withRSA. 948562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker * 953d2365c068539941bec258d461c149a63c4fbf98Kenny Root * Starting with JB-MR2, the platform supports SHA256withRSA, so we use 963d2365c068539941bec258d461c149a63c4fbf98Kenny Root * the signature algorithm in the certificate to select which to use 973d2365c068539941bec258d461c149a63c4fbf98Kenny Root * (SHA256withRSA or SHA1withRSA). Also in JB-MR2, EC keys are supported. 988562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker * 998562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker * Because there are old keys still in use whose certificate actually 1008562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker * says "MD5withRSA", we treat these as though they say "SHA1withRSA" 1018562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker * for compatibility with older releases. This can be changed by 1028562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker * altering the getAlgorithm() function below. 1038562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker */ 1048562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker 1058562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker 1068562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker/** 1073d2365c068539941bec258d461c149a63c4fbf98Kenny Root * Command line tool to sign JAR files (including APKs and OTA updates) in a way 1083d2365c068539941bec258d461c149a63c4fbf98Kenny Root * compatible with the mincrypt verifier, using EC or RSA keys and SHA1 or 109dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubin * SHA-256 (see historical note). The tool can additionally sign APKs using 110dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubin * APK Signature Scheme v2. 11188b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project */ 11288b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Projectclass SignApk { 1137bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker private static final String OTACERT_NAME = "META-INF/com/android/otacert"; 1147bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker 11526f00cda4b979d7e74db6872990682335b36612bAlex Klyubin /** 11626f00cda4b979d7e74db6872990682335b36612bAlex Klyubin * Extensible data block/field header ID used for storing information about alignment of 11726f00cda4b979d7e74db6872990682335b36612bAlex Klyubin * uncompressed entries as well as for aligning the entries's data. See ZIP appnote.txt section 11826f00cda4b979d7e74db6872990682335b36612bAlex Klyubin * 4.5 Extensible data fields. 11926f00cda4b979d7e74db6872990682335b36612bAlex Klyubin */ 12026f00cda4b979d7e74db6872990682335b36612bAlex Klyubin private static final short ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID = (short) 0xd935; 12126f00cda4b979d7e74db6872990682335b36612bAlex Klyubin 12226f00cda4b979d7e74db6872990682335b36612bAlex Klyubin /** 12326f00cda4b979d7e74db6872990682335b36612bAlex Klyubin * Minimum size (in bytes) of the extensible data block/field used for alignment of uncompressed 12426f00cda4b979d7e74db6872990682335b36612bAlex Klyubin * entries. 12526f00cda4b979d7e74db6872990682335b36612bAlex Klyubin */ 12626f00cda4b979d7e74db6872990682335b36612bAlex Klyubin private static final short ALIGNMENT_ZIP_EXTRA_DATA_FIELD_MIN_SIZE_BYTES = 6; 12726f00cda4b979d7e74db6872990682335b36612bAlex Klyubin 1288562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker // bitmasks for which hash algorithms we need the manifest to include. 1298562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker private static final int USE_SHA1 = 1; 1308562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker private static final int USE_SHA256 = 2; 1318562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker 1323f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin /** 1333f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin * Returns the digest algorithm ID (one of {@code USE_SHA1} or {@code USE_SHA256}) to be used 1343f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin * for signing an OTA update package using the private key corresponding to the provided 1353f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin * certificate. 1363f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin */ 1373f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin private static int getDigestAlgorithmForOta(X509Certificate cert) { 1383f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US); 1393f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin if ("SHA1WITHRSA".equals(sigAlg) || "MD5WITHRSA".equals(sigAlg)) { 1403f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin // see "HISTORICAL NOTE" above. 1413f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin return USE_SHA1; 1423f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin } else if (sigAlg.startsWith("SHA256WITH")) { 1433f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin return USE_SHA256; 1443d2365c068539941bec258d461c149a63c4fbf98Kenny Root } else { 1453f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin throw new IllegalArgumentException("unsupported signature algorithm \"" + sigAlg + 1463f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin "\" in cert [" + cert.getSubjectDN()); 1473f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin } 1483f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin } 1493f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin 1503f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin /** 1513f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin * Returns the JCA {@link java.security.Signature} algorithm to be used for signing and OTA 152fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin * update package using the private key corresponding to the provided certificate and the 1533f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin * provided digest algorithm (see {@code USE_SHA1} and {@code USE_SHA256} constants). 1543f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin */ 155fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin private static String getJcaSignatureAlgorithmForOta( 1563f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin X509Certificate cert, int hash) { 1573f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin String sigAlgDigestPrefix; 1583f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin switch (hash) { 1593f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin case USE_SHA1: 1603f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin sigAlgDigestPrefix = "SHA1"; 1613f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin break; 1623f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin case USE_SHA256: 1633f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin sigAlgDigestPrefix = "SHA256"; 1643f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin break; 1653f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin default: 1663f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin throw new IllegalArgumentException("Unknown hash ID: " + hash); 1673f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin } 1683f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin 1693f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin String keyAlgorithm = cert.getPublicKey().getAlgorithm(); 1703f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin if ("RSA".equalsIgnoreCase(keyAlgorithm)) { 1713f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin return sigAlgDigestPrefix + "withRSA"; 1723f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin } else if ("EC".equalsIgnoreCase(keyAlgorithm)) { 1733f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin return sigAlgDigestPrefix + "withECDSA"; 1743f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin } else { 1753f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm); 1763d2365c068539941bec258d461c149a63c4fbf98Kenny Root } 1773d2365c068539941bec258d461c149a63c4fbf98Kenny Root } 1783d2365c068539941bec258d461c149a63c4fbf98Kenny Root 17988b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project private static X509Certificate readPublicKey(File file) 18029706d155a7516e36b63c5201d3e294de6589814Koushik Dutta throws IOException, GeneralSecurityException { 18188b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project FileInputStream input = new FileInputStream(file); 18288b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project try { 18388b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project CertificateFactory cf = CertificateFactory.getInstance("X.509"); 18488b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project return (X509Certificate) cf.generateCertificate(input); 18588b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project } finally { 18688b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project input.close(); 18788b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project } 18888b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project } 18988b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project 19088b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project /** 19150c7c5a72208517237f84b15be6d15c96fa3b2ecadattatr * If a console doesn't exist, reads the password from stdin 19250c7c5a72208517237f84b15be6d15c96fa3b2ecadattatr * If a console exists, reads the password from console and returns it as a string. 19388b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project * 19488b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project * @param keyFile The file containing the private key. Used to prompt the user. 19588b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project */ 19688b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project private static String readPassword(File keyFile) { 19722717f9f9ee764e8126bcbc8c4cd9dc58b713feezhang jun Console console; 19822717f9f9ee764e8126bcbc8c4cd9dc58b713feezhang jun char[] pwd; 19950c7c5a72208517237f84b15be6d15c96fa3b2ecadattatr if ((console = System.console()) == null) { 20050c7c5a72208517237f84b15be6d15c96fa3b2ecadattatr System.out.print("Enter password for " + keyFile + " (password will not be hidden): "); 20150c7c5a72208517237f84b15be6d15c96fa3b2ecadattatr System.out.flush(); 20250c7c5a72208517237f84b15be6d15c96fa3b2ecadattatr BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); 20350c7c5a72208517237f84b15be6d15c96fa3b2ecadattatr try { 20450c7c5a72208517237f84b15be6d15c96fa3b2ecadattatr return stdin.readLine(); 20550c7c5a72208517237f84b15be6d15c96fa3b2ecadattatr } catch (IOException ex) { 20650c7c5a72208517237f84b15be6d15c96fa3b2ecadattatr return null; 20750c7c5a72208517237f84b15be6d15c96fa3b2ecadattatr } 20822717f9f9ee764e8126bcbc8c4cd9dc58b713feezhang jun } else { 20950c7c5a72208517237f84b15be6d15c96fa3b2ecadattatr if ((pwd = console.readPassword("[%s]", "Enter password for " + keyFile)) != null) { 21050c7c5a72208517237f84b15be6d15c96fa3b2ecadattatr return String.valueOf(pwd); 21150c7c5a72208517237f84b15be6d15c96fa3b2ecadattatr } else { 21250c7c5a72208517237f84b15be6d15c96fa3b2ecadattatr return null; 21350c7c5a72208517237f84b15be6d15c96fa3b2ecadattatr } 21488b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project } 21588b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project } 21688b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project 21788b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project /** 21862ea4a5c3cf5da5c64e881e6986e5753304fe8beKenny Root * Decrypt an encrypted PKCS#8 format private key. 21988b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project * 22088b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project * Based on ghstark's post on Aug 6, 2006 at 22188b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project * http://forums.sun.com/thread.jspa?threadID=758133&messageID=4330949 22288b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project * 22388b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project * @param encryptedPrivateKey The raw data of the private key 22488b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project * @param keyFile The file containing the private key 22588b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project */ 22662ea4a5c3cf5da5c64e881e6986e5753304fe8beKenny Root private static PKCS8EncodedKeySpec decryptPrivateKey(byte[] encryptedPrivateKey, File keyFile) 22729706d155a7516e36b63c5201d3e294de6589814Koushik Dutta throws GeneralSecurityException { 22888b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project EncryptedPrivateKeyInfo epkInfo; 22988b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project try { 23088b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project epkInfo = new EncryptedPrivateKeyInfo(encryptedPrivateKey); 23188b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project } catch (IOException ex) { 23288b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project // Probably not an encrypted key. 23388b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project return null; 23488b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project } 23588b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project 23688b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project char[] password = readPassword(keyFile).toCharArray(); 23788b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project 23888b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project SecretKeyFactory skFactory = SecretKeyFactory.getInstance(epkInfo.getAlgName()); 23988b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project Key key = skFactory.generateSecret(new PBEKeySpec(password)); 24088b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project 24188b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project Cipher cipher = Cipher.getInstance(epkInfo.getAlgName()); 24288b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project cipher.init(Cipher.DECRYPT_MODE, key, epkInfo.getAlgParameters()); 24388b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project 24488b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project try { 24588b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project return epkInfo.getKeySpec(cipher); 24688b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project } catch (InvalidKeySpecException ex) { 24788b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project System.err.println("signapk: Password for " + keyFile + " may be bad."); 24888b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project throw ex; 24988b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project } 25088b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project } 25188b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project 25262ea4a5c3cf5da5c64e881e6986e5753304fe8beKenny Root /** Read a PKCS#8 format private key. */ 25388b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project private static PrivateKey readPrivateKey(File file) 25429706d155a7516e36b63c5201d3e294de6589814Koushik Dutta throws IOException, GeneralSecurityException { 25588b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project DataInputStream input = new DataInputStream(new FileInputStream(file)); 25688b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project try { 25788b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project byte[] bytes = new byte[(int) file.length()]; 25888b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project input.read(bytes); 25988b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project 26062ea4a5c3cf5da5c64e881e6986e5753304fe8beKenny Root /* Check to see if this is in an EncryptedPrivateKeyInfo structure. */ 26162ea4a5c3cf5da5c64e881e6986e5753304fe8beKenny Root PKCS8EncodedKeySpec spec = decryptPrivateKey(bytes, file); 26288b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project if (spec == null) { 26388b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project spec = new PKCS8EncodedKeySpec(bytes); 26488b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project } 26588b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project 26662ea4a5c3cf5da5c64e881e6986e5753304fe8beKenny Root /* 26762ea4a5c3cf5da5c64e881e6986e5753304fe8beKenny Root * Now it's in a PKCS#8 PrivateKeyInfo structure. Read its Algorithm 26862ea4a5c3cf5da5c64e881e6986e5753304fe8beKenny Root * OID and use that to construct a KeyFactory. 26962ea4a5c3cf5da5c64e881e6986e5753304fe8beKenny Root */ 270c218d3eca3476eaaf4a52b9faaa24275dc7cf9d1Alex Klyubin PrivateKeyInfo pki; 271c218d3eca3476eaaf4a52b9faaa24275dc7cf9d1Alex Klyubin try (ASN1InputStream bIn = 272c218d3eca3476eaaf4a52b9faaa24275dc7cf9d1Alex Klyubin new ASN1InputStream(new ByteArrayInputStream(spec.getEncoded()))) { 273c218d3eca3476eaaf4a52b9faaa24275dc7cf9d1Alex Klyubin pki = PrivateKeyInfo.getInstance(bIn.readObject()); 274c218d3eca3476eaaf4a52b9faaa24275dc7cf9d1Alex Klyubin } 27562ea4a5c3cf5da5c64e881e6986e5753304fe8beKenny Root String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId(); 2763d2365c068539941bec258d461c149a63c4fbf98Kenny Root 27762ea4a5c3cf5da5c64e881e6986e5753304fe8beKenny Root return KeyFactory.getInstance(algOid).generatePrivate(spec); 27888b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project } finally { 27988b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project input.close(); 28088b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project } 28188b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project } 28288b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project 2838562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker /** 2847bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker * Add a copy of the public key to the archive; this should 2857bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker * exactly match one of the files in 2867bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker * /system/etc/security/otacerts.zip on the device. (The same 2870caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin * cert can be extracted from the OTA update package's signature 2880caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin * block but this is much easier to get at.) 2897bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker */ 2907bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker private static void addOtacert(JarOutputStream outputJar, 2917bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker File publicKeyFile, 2920caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin long timestamp) 293fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin throws IOException { 2947bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker 2957bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker JarEntry je = new JarEntry(OTACERT_NAME); 2967bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker je.setTime(timestamp); 2977bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker outputJar.putNextEntry(je); 2987bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker FileInputStream input = new FileInputStream(publicKeyFile); 2997bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker byte[] b = new byte[4096]; 3007bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker int read; 3017bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker while ((read = input.read(b)) != -1) { 3027bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker outputJar.write(b, 0, read); 3037bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker } 3047bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker input.close(); 3057bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker } 3067bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker 3077bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker 308147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongker /** Sign data and write the digital signature to 'out'. */ 30988b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project private static void writeSignatureBlock( 3103f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin CMSTypedData data, X509Certificate publicKey, PrivateKey privateKey, int hash, 311147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongker OutputStream out) 312147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongker throws IOException, 313147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongker CertificateEncodingException, 314147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongker OperatorCreationException, 315147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongker CMSException { 316147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongker ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>(1); 317147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongker certList.add(publicKey); 318147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongker JcaCertStore certs = new JcaCertStore(certList); 319147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongker 320147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongker CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); 321c2c49ed0c13846f7f96249c7419971dfcddc9215Alex Klyubin ContentSigner signer = 3223f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin new JcaContentSignerBuilder( 323fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin getJcaSignatureAlgorithmForOta(publicKey, hash)) 324c2c49ed0c13846f7f96249c7419971dfcddc9215Alex Klyubin .build(privateKey); 325147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongker gen.addSignerInfoGenerator( 326147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongker new JcaSignerInfoGeneratorBuilder( 327147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongker new JcaDigestCalculatorProviderBuilder() 328147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongker .build()) 329147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongker .setDirectSignature(true) 3308562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker .build(signer, publicKey)); 331147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongker gen.addCertificates(certs); 332147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongker CMSSignedData sigData = gen.generate(data, false); 333147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongker 334c218d3eca3476eaaf4a52b9faaa24275dc7cf9d1Alex Klyubin try (ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded())) { 335c218d3eca3476eaaf4a52b9faaa24275dc7cf9d1Alex Klyubin DEROutputStream dos = new DEROutputStream(out); 336c218d3eca3476eaaf4a52b9faaa24275dc7cf9d1Alex Klyubin dos.writeObject(asn1.readObject()); 337c218d3eca3476eaaf4a52b9faaa24275dc7cf9d1Alex Klyubin } 33888b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project } 33988b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project 34029706d155a7516e36b63c5201d3e294de6589814Koushik Dutta /** 341fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin * Adds ZIP entries which represent the v1 signature (JAR signature scheme). 342fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin */ 343fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin private static void addV1Signature( 344fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ApkSignerEngine apkSigner, 345fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ApkSignerEngine.OutputJarSignatureRequest v1Signature, 346fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin JarOutputStream out, 347fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin long timestamp) throws IOException { 348fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin for (ApkSignerEngine.OutputJarSignatureRequest.JarEntry entry 349fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin : v1Signature.getAdditionalJarEntries()) { 350fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin String entryName = entry.getName(); 351fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin JarEntry outEntry = new JarEntry(entryName); 352fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin outEntry.setTime(timestamp); 353fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin out.putNextEntry(outEntry); 354fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin byte[] entryData = entry.getData(); 355fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin out.write(entryData); 356fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest = 357fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin apkSigner.outputJarEntry(entryName); 358fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin if (inspectEntryRequest != null) { 359fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin inspectEntryRequest.getDataSink().consume(entryData, 0, entryData.length); 360fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin inspectEntryRequest.done(); 361fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin } 362fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin } 363fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin } 364fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin 365fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin /** 3660caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin * Copy all JAR entries from input to output. We set the modification times in the output to a 3670caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin * fixed time, so as to reduce variation in the output file and make incremental OTAs more 3680caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin * efficient. 36929706d155a7516e36b63c5201d3e294de6589814Koushik Dutta */ 370fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin private static void copyFiles( 371fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin JarFile in, 3720caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin Pattern ignoredFilenamePattern, 373fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ApkSignerEngine apkSigner, 3740caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin JarOutputStream out, 3750caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin long timestamp, 3760caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin int defaultAlignment) throws IOException { 37729706d155a7516e36b63c5201d3e294de6589814Koushik Dutta byte[] buffer = new byte[4096]; 37829706d155a7516e36b63c5201d3e294de6589814Koushik Dutta int num; 37929706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 3800caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin ArrayList<String> names = new ArrayList<String>(); 3810caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin for (Enumeration<JarEntry> e = in.entries(); e.hasMoreElements();) { 3820caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin JarEntry entry = e.nextElement(); 3830caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin if (entry.isDirectory()) { 3840caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin continue; 3850caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin } 3860caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin String entryName = entry.getName(); 3870caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin if ((ignoredFilenamePattern != null) 3880caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin && (ignoredFilenamePattern.matcher(entryName).matches())) { 3890caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin continue; 3900caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin } 3910caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin names.add(entryName); 3920caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin } 39329706d155a7516e36b63c5201d3e294de6589814Koushik Dutta Collections.sort(names); 3941d67eec191b76e299acfa54d95f3897e25536174Doug Zongker 3951d67eec191b76e299acfa54d95f3897e25536174Doug Zongker boolean firstEntry = true; 3961d67eec191b76e299acfa54d95f3897e25536174Doug Zongker long offset = 0L; 3971d67eec191b76e299acfa54d95f3897e25536174Doug Zongker 3981d67eec191b76e299acfa54d95f3897e25536174Doug Zongker // We do the copy in two passes -- first copying all the 3991d67eec191b76e299acfa54d95f3897e25536174Doug Zongker // entries that are STORED, then copying all the entries that 4001d67eec191b76e299acfa54d95f3897e25536174Doug Zongker // have any other compression flag (which in practice means 4011d67eec191b76e299acfa54d95f3897e25536174Doug Zongker // DEFLATED). This groups all the stored entries together at 4021d67eec191b76e299acfa54d95f3897e25536174Doug Zongker // the start of the file and makes it easier to do alignment 4031d67eec191b76e299acfa54d95f3897e25536174Doug Zongker // on them (since only stored entries are aligned). 4041d67eec191b76e299acfa54d95f3897e25536174Doug Zongker 405fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin List<String> remainingNames = new ArrayList<>(names.size()); 40629706d155a7516e36b63c5201d3e294de6589814Koushik Dutta for (String name : names) { 40729706d155a7516e36b63c5201d3e294de6589814Koushik Dutta JarEntry inEntry = in.getJarEntry(name); 408fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin if (inEntry.getMethod() != JarEntry.STORED) { 409fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin // Defer outputting this entry until we're ready to output compressed entries. 410fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin remainingNames.add(name); 411fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin continue; 412fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin } 413fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin 414fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin if (!shouldOutputApkEntry(apkSigner, in, inEntry, buffer)) { 415fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin continue; 416fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin } 417fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin 4181d67eec191b76e299acfa54d95f3897e25536174Doug Zongker // Preserve the STORED method of the input entry. 419fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin JarEntry outEntry = new JarEntry(inEntry); 4201d67eec191b76e299acfa54d95f3897e25536174Doug Zongker outEntry.setTime(timestamp); 42137a0ecd32935685151f0cfb5a5466e6e360a0b8fAlex Klyubin // Discard comment and extra fields of this entry to 42237a0ecd32935685151f0cfb5a5466e6e360a0b8fAlex Klyubin // simplify alignment logic below and for consistency with 42337a0ecd32935685151f0cfb5a5466e6e360a0b8fAlex Klyubin // how compressed entries are handled later. 42437a0ecd32935685151f0cfb5a5466e6e360a0b8fAlex Klyubin outEntry.setComment(null); 42537a0ecd32935685151f0cfb5a5466e6e360a0b8fAlex Klyubin outEntry.setExtra(null); 4261d67eec191b76e299acfa54d95f3897e25536174Doug Zongker 42726f00cda4b979d7e74db6872990682335b36612bAlex Klyubin int alignment = getStoredEntryDataAlignment(name, defaultAlignment); 42826f00cda4b979d7e74db6872990682335b36612bAlex Klyubin // Alignment of the entry's data is achieved by adding a data block to the entry's Local 42926f00cda4b979d7e74db6872990682335b36612bAlex Klyubin // File Header extra field. The data block contains information about the alignment 43026f00cda4b979d7e74db6872990682335b36612bAlex Klyubin // value and the necessary padding bytes (0x00) to achieve the alignment. This works 43126f00cda4b979d7e74db6872990682335b36612bAlex Klyubin // because the entry's data will be located immediately after the extra field. 43226f00cda4b979d7e74db6872990682335b36612bAlex Klyubin // See ZIP APPNOTE.txt section "4.5 Extensible data fields" for details about the format 43326f00cda4b979d7e74db6872990682335b36612bAlex Klyubin // of the extra field. 43426f00cda4b979d7e74db6872990682335b36612bAlex Klyubin 43526f00cda4b979d7e74db6872990682335b36612bAlex Klyubin // 'offset' is the offset into the file at which we expect the entry's data to begin. 43626f00cda4b979d7e74db6872990682335b36612bAlex Klyubin // This is the value we need to make a multiple of 'alignment'. 4371d67eec191b76e299acfa54d95f3897e25536174Doug Zongker offset += JarFile.LOCHDR + outEntry.getName().length(); 4381d67eec191b76e299acfa54d95f3897e25536174Doug Zongker if (firstEntry) { 43926f00cda4b979d7e74db6872990682335b36612bAlex Klyubin // The first entry in a jar file has an extra field of four bytes that you can't get 44026f00cda4b979d7e74db6872990682335b36612bAlex Klyubin // rid of; any extra data you specify in the JarEntry is appended to these forced 44126f00cda4b979d7e74db6872990682335b36612bAlex Klyubin // four bytes. This is JAR_MAGIC in JarOutputStream; the bytes are 0xfeca0000. 44226f00cda4b979d7e74db6872990682335b36612bAlex Klyubin // See http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6808540 44326f00cda4b979d7e74db6872990682335b36612bAlex Klyubin // and http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4138619. 4441d67eec191b76e299acfa54d95f3897e25536174Doug Zongker offset += 4; 4451d67eec191b76e299acfa54d95f3897e25536174Doug Zongker firstEntry = false; 4461d67eec191b76e299acfa54d95f3897e25536174Doug Zongker } 44726f00cda4b979d7e74db6872990682335b36612bAlex Klyubin int extraPaddingSizeBytes = 0; 44826f00cda4b979d7e74db6872990682335b36612bAlex Klyubin if (alignment > 0) { 44926f00cda4b979d7e74db6872990682335b36612bAlex Klyubin long paddingStartOffset = offset + ALIGNMENT_ZIP_EXTRA_DATA_FIELD_MIN_SIZE_BYTES; 450ab2a3b0061f26ebc95bc320fcfac316ccf14f567Alex Klyubin extraPaddingSizeBytes = 451ab2a3b0061f26ebc95bc320fcfac316ccf14f567Alex Klyubin (alignment - (int) (paddingStartOffset % alignment)) % alignment; 4521d67eec191b76e299acfa54d95f3897e25536174Doug Zongker } 45326f00cda4b979d7e74db6872990682335b36612bAlex Klyubin byte[] extra = 45426f00cda4b979d7e74db6872990682335b36612bAlex Klyubin new byte[ALIGNMENT_ZIP_EXTRA_DATA_FIELD_MIN_SIZE_BYTES + extraPaddingSizeBytes]; 45526f00cda4b979d7e74db6872990682335b36612bAlex Klyubin ByteBuffer extraBuf = ByteBuffer.wrap(extra); 45626f00cda4b979d7e74db6872990682335b36612bAlex Klyubin extraBuf.order(ByteOrder.LITTLE_ENDIAN); 45726f00cda4b979d7e74db6872990682335b36612bAlex Klyubin extraBuf.putShort(ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID); // Header ID 45826f00cda4b979d7e74db6872990682335b36612bAlex Klyubin extraBuf.putShort((short) (2 + extraPaddingSizeBytes)); // Data Size 45926f00cda4b979d7e74db6872990682335b36612bAlex Klyubin extraBuf.putShort((short) alignment); 46026f00cda4b979d7e74db6872990682335b36612bAlex Klyubin outEntry.setExtra(extra); 46126f00cda4b979d7e74db6872990682335b36612bAlex Klyubin offset += extra.length; 4621d67eec191b76e299acfa54d95f3897e25536174Doug Zongker 4631d67eec191b76e299acfa54d95f3897e25536174Doug Zongker out.putNextEntry(outEntry); 464fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest = 465fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin (apkSigner != null) ? apkSigner.outputJarEntry(name) : null; 466fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin DataSink entryDataSink = 467fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin (inspectEntryRequest != null) ? inspectEntryRequest.getDataSink() : null; 4681d67eec191b76e299acfa54d95f3897e25536174Doug Zongker 469fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin try (InputStream data = in.getInputStream(inEntry)) { 470fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin while ((num = data.read(buffer)) > 0) { 471fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin out.write(buffer, 0, num); 472fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin if (entryDataSink != null) { 473fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin entryDataSink.consume(buffer, 0, num); 474fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin } 475fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin offset += num; 476fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin } 47729706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 4781d67eec191b76e299acfa54d95f3897e25536174Doug Zongker out.flush(); 479fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin if (inspectEntryRequest != null) { 480fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin inspectEntryRequest.done(); 481fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin } 4821d67eec191b76e299acfa54d95f3897e25536174Doug Zongker } 4831d67eec191b76e299acfa54d95f3897e25536174Doug Zongker 4841d67eec191b76e299acfa54d95f3897e25536174Doug Zongker // Copy all the non-STORED entries. We don't attempt to 4851d67eec191b76e299acfa54d95f3897e25536174Doug Zongker // maintain the 'offset' variable past this point; we don't do 4861d67eec191b76e299acfa54d95f3897e25536174Doug Zongker // alignment on these entries. 4871d67eec191b76e299acfa54d95f3897e25536174Doug Zongker 488fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin for (String name : remainingNames) { 4891d67eec191b76e299acfa54d95f3897e25536174Doug Zongker JarEntry inEntry = in.getJarEntry(name); 490fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin if (!shouldOutputApkEntry(apkSigner, in, inEntry, buffer)) { 491fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin continue; 492fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin } 493fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin 4941d67eec191b76e299acfa54d95f3897e25536174Doug Zongker // Create a new entry so that the compressed len is recomputed. 495fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin JarEntry outEntry = new JarEntry(name); 49629706d155a7516e36b63c5201d3e294de6589814Koushik Dutta outEntry.setTime(timestamp); 49729706d155a7516e36b63c5201d3e294de6589814Koushik Dutta out.putNextEntry(outEntry); 498fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest = 499fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin (apkSigner != null) ? apkSigner.outputJarEntry(name) : null; 500fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin DataSink entryDataSink = 501fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin (inspectEntryRequest != null) ? inspectEntryRequest.getDataSink() : null; 50229706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 50329706d155a7516e36b63c5201d3e294de6589814Koushik Dutta InputStream data = in.getInputStream(inEntry); 50429706d155a7516e36b63c5201d3e294de6589814Koushik Dutta while ((num = data.read(buffer)) > 0) { 50529706d155a7516e36b63c5201d3e294de6589814Koushik Dutta out.write(buffer, 0, num); 506fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin if (entryDataSink != null) { 507fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin entryDataSink.consume(buffer, 0, num); 508fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin } 50929706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 51029706d155a7516e36b63c5201d3e294de6589814Koushik Dutta out.flush(); 511fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin if (inspectEntryRequest != null) { 512fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin inspectEntryRequest.done(); 513fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin } 514fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin } 515fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin } 516fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin 517fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin private static boolean shouldOutputApkEntry( 518fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ApkSignerEngine apkSigner, JarFile inFile, JarEntry inEntry, byte[] tmpbuf) 519fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin throws IOException { 520fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin if (apkSigner == null) { 521fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin return true; 522fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin } 523fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin 524fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ApkSignerEngine.InputJarEntryInstructions instructions = 525fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin apkSigner.inputJarEntry(inEntry.getName()); 526fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest = 527fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin instructions.getInspectJarEntryRequest(); 528fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin if (inspectEntryRequest != null) { 529fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin provideJarEntry(inFile, inEntry, inspectEntryRequest, tmpbuf); 530fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin } 531fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin switch (instructions.getOutputPolicy()) { 532fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin case OUTPUT: 533fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin return true; 534fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin case SKIP: 535fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin case OUTPUT_BY_ENGINE: 536fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin return false; 537fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin default: 538fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin throw new RuntimeException( 539fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin "Unsupported output policy: " + instructions.getOutputPolicy()); 540fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin } 541fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin } 542fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin 543fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin private static void provideJarEntry( 544fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin JarFile jarFile, 545fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin JarEntry jarEntry, 546fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ApkSignerEngine.InspectJarEntryRequest request, 547fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin byte[] tmpbuf) throws IOException { 548fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin DataSink dataSink = request.getDataSink(); 549fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin try (InputStream in = jarFile.getInputStream(jarEntry)) { 550fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin int chunkSize; 551fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin while ((chunkSize = in.read(tmpbuf)) > 0) { 552fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin dataSink.consume(tmpbuf, 0, chunkSize); 553fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin } 554fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin request.done(); 55529706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 55629706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 55729706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 558924a6837609359d09076391fa363fdcf3c8544bdAlex Klyubin /** 559924a6837609359d09076391fa363fdcf3c8544bdAlex Klyubin * Returns the multiple (in bytes) at which the provided {@code STORED} entry's data must start 560924a6837609359d09076391fa363fdcf3c8544bdAlex Klyubin * relative to start of file or {@code 0} if alignment of this entry's data is not important. 561924a6837609359d09076391fa363fdcf3c8544bdAlex Klyubin */ 562924a6837609359d09076391fa363fdcf3c8544bdAlex Klyubin private static int getStoredEntryDataAlignment(String entryName, int defaultAlignment) { 563924a6837609359d09076391fa363fdcf3c8544bdAlex Klyubin if (defaultAlignment <= 0) { 564924a6837609359d09076391fa363fdcf3c8544bdAlex Klyubin return 0; 565924a6837609359d09076391fa363fdcf3c8544bdAlex Klyubin } 566924a6837609359d09076391fa363fdcf3c8544bdAlex Klyubin 567924a6837609359d09076391fa363fdcf3c8544bdAlex Klyubin if (entryName.endsWith(".so")) { 568924a6837609359d09076391fa363fdcf3c8544bdAlex Klyubin // Align .so contents to memory page boundary to enable memory-mapped 569924a6837609359d09076391fa363fdcf3c8544bdAlex Klyubin // execution. 570924a6837609359d09076391fa363fdcf3c8544bdAlex Klyubin return 4096; 571924a6837609359d09076391fa363fdcf3c8544bdAlex Klyubin } else { 572924a6837609359d09076391fa363fdcf3c8544bdAlex Klyubin return defaultAlignment; 573924a6837609359d09076391fa363fdcf3c8544bdAlex Klyubin } 574924a6837609359d09076391fa363fdcf3c8544bdAlex Klyubin } 575924a6837609359d09076391fa363fdcf3c8544bdAlex Klyubin 57629706d155a7516e36b63c5201d3e294de6589814Koushik Dutta private static class WholeFileSignerOutputStream extends FilterOutputStream { 57729706d155a7516e36b63c5201d3e294de6589814Koushik Dutta private boolean closing = false; 57829706d155a7516e36b63c5201d3e294de6589814Koushik Dutta private ByteArrayOutputStream footer = new ByteArrayOutputStream(); 57929706d155a7516e36b63c5201d3e294de6589814Koushik Dutta private OutputStream tee; 58029706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 58129706d155a7516e36b63c5201d3e294de6589814Koushik Dutta public WholeFileSignerOutputStream(OutputStream out, OutputStream tee) { 58229706d155a7516e36b63c5201d3e294de6589814Koushik Dutta super(out); 58329706d155a7516e36b63c5201d3e294de6589814Koushik Dutta this.tee = tee; 58429706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 58529706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 58629706d155a7516e36b63c5201d3e294de6589814Koushik Dutta public void notifyClosing() { 58729706d155a7516e36b63c5201d3e294de6589814Koushik Dutta closing = true; 58829706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 58929706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 59029706d155a7516e36b63c5201d3e294de6589814Koushik Dutta public void finish() throws IOException { 59129706d155a7516e36b63c5201d3e294de6589814Koushik Dutta closing = false; 59229706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 59329706d155a7516e36b63c5201d3e294de6589814Koushik Dutta byte[] data = footer.toByteArray(); 59429706d155a7516e36b63c5201d3e294de6589814Koushik Dutta if (data.length < 2) 59529706d155a7516e36b63c5201d3e294de6589814Koushik Dutta throw new IOException("Less than two bytes written to footer"); 59629706d155a7516e36b63c5201d3e294de6589814Koushik Dutta write(data, 0, data.length - 2); 597c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker } 598c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker 59929706d155a7516e36b63c5201d3e294de6589814Koushik Dutta public byte[] getTail() { 60029706d155a7516e36b63c5201d3e294de6589814Koushik Dutta return footer.toByteArray(); 60129706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 60229706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 60329706d155a7516e36b63c5201d3e294de6589814Koushik Dutta @Override 60429706d155a7516e36b63c5201d3e294de6589814Koushik Dutta public void write(byte[] b) throws IOException { 60529706d155a7516e36b63c5201d3e294de6589814Koushik Dutta write(b, 0, b.length); 60629706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 60729706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 60829706d155a7516e36b63c5201d3e294de6589814Koushik Dutta @Override 60929706d155a7516e36b63c5201d3e294de6589814Koushik Dutta public void write(byte[] b, int off, int len) throws IOException { 61029706d155a7516e36b63c5201d3e294de6589814Koushik Dutta if (closing) { 61129706d155a7516e36b63c5201d3e294de6589814Koushik Dutta // if the jar is about to close, save the footer that will be written 61229706d155a7516e36b63c5201d3e294de6589814Koushik Dutta footer.write(b, off, len); 61329706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 61429706d155a7516e36b63c5201d3e294de6589814Koushik Dutta else { 61529706d155a7516e36b63c5201d3e294de6589814Koushik Dutta // write to both output streams. out is the CMSTypedData signer and tee is the file. 61629706d155a7516e36b63c5201d3e294de6589814Koushik Dutta out.write(b, off, len); 61729706d155a7516e36b63c5201d3e294de6589814Koushik Dutta tee.write(b, off, len); 61829706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 61929706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 62029706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 62129706d155a7516e36b63c5201d3e294de6589814Koushik Dutta @Override 62229706d155a7516e36b63c5201d3e294de6589814Koushik Dutta public void write(int b) throws IOException { 62329706d155a7516e36b63c5201d3e294de6589814Koushik Dutta if (closing) { 62429706d155a7516e36b63c5201d3e294de6589814Koushik Dutta // if the jar is about to close, save the footer that will be written 62529706d155a7516e36b63c5201d3e294de6589814Koushik Dutta footer.write(b); 62629706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 62729706d155a7516e36b63c5201d3e294de6589814Koushik Dutta else { 62829706d155a7516e36b63c5201d3e294de6589814Koushik Dutta // write to both output streams. out is the CMSTypedData signer and tee is the file. 62929706d155a7516e36b63c5201d3e294de6589814Koushik Dutta out.write(b); 63029706d155a7516e36b63c5201d3e294de6589814Koushik Dutta tee.write(b); 63129706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 63229706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 63329706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 63429706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 63529706d155a7516e36b63c5201d3e294de6589814Koushik Dutta private static class CMSSigner implements CMSTypedData { 636c2c49ed0c13846f7f96249c7419971dfcddc9215Alex Klyubin private final JarFile inputJar; 637c2c49ed0c13846f7f96249c7419971dfcddc9215Alex Klyubin private final File publicKeyFile; 638c2c49ed0c13846f7f96249c7419971dfcddc9215Alex Klyubin private final X509Certificate publicKey; 639c2c49ed0c13846f7f96249c7419971dfcddc9215Alex Klyubin private final PrivateKey privateKey; 6403f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin private final int hash; 6416c41036bcf35fe39162b50d27533f0f3bfab3028Alex Klyubin private final long timestamp; 642c2c49ed0c13846f7f96249c7419971dfcddc9215Alex Klyubin private final OutputStream outputStream; 64329706d155a7516e36b63c5201d3e294de6589814Koushik Dutta private final ASN1ObjectIdentifier type; 64429706d155a7516e36b63c5201d3e294de6589814Koushik Dutta private WholeFileSignerOutputStream signer; 64529706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 646fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin // Files matching this pattern are not copied to the output. 647fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin private static final Pattern STRIP_PATTERN = 648fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA|EC)|com/android/otacert))|(" 649fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin + Pattern.quote(JarFile.MANIFEST_NAME) + ")$"); 650fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin 65129706d155a7516e36b63c5201d3e294de6589814Koushik Dutta public CMSSigner(JarFile inputJar, File publicKeyFile, 6523f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin X509Certificate publicKey, PrivateKey privateKey, int hash, 6533f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin long timestamp, OutputStream outputStream) { 65429706d155a7516e36b63c5201d3e294de6589814Koushik Dutta this.inputJar = inputJar; 65529706d155a7516e36b63c5201d3e294de6589814Koushik Dutta this.publicKeyFile = publicKeyFile; 65629706d155a7516e36b63c5201d3e294de6589814Koushik Dutta this.publicKey = publicKey; 65729706d155a7516e36b63c5201d3e294de6589814Koushik Dutta this.privateKey = privateKey; 6583f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin this.hash = hash; 6596c41036bcf35fe39162b50d27533f0f3bfab3028Alex Klyubin this.timestamp = timestamp; 66029706d155a7516e36b63c5201d3e294de6589814Koushik Dutta this.outputStream = outputStream; 66129706d155a7516e36b63c5201d3e294de6589814Koushik Dutta this.type = new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId()); 66229706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 66329706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 664bda807d3a1f39024fad99b0afc9eed834d4f9b81Kenny Root /** 665bda807d3a1f39024fad99b0afc9eed834d4f9b81Kenny Root * This should actually return byte[] or something similar, but nothing 666bda807d3a1f39024fad99b0afc9eed834d4f9b81Kenny Root * actually checks it currently. 667bda807d3a1f39024fad99b0afc9eed834d4f9b81Kenny Root */ 668c218d3eca3476eaaf4a52b9faaa24275dc7cf9d1Alex Klyubin @Override 66929706d155a7516e36b63c5201d3e294de6589814Koushik Dutta public Object getContent() { 670bda807d3a1f39024fad99b0afc9eed834d4f9b81Kenny Root return this; 67129706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 67229706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 673c218d3eca3476eaaf4a52b9faaa24275dc7cf9d1Alex Klyubin @Override 67429706d155a7516e36b63c5201d3e294de6589814Koushik Dutta public ASN1ObjectIdentifier getContentType() { 67529706d155a7516e36b63c5201d3e294de6589814Koushik Dutta return type; 67629706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 67729706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 678c218d3eca3476eaaf4a52b9faaa24275dc7cf9d1Alex Klyubin @Override 67929706d155a7516e36b63c5201d3e294de6589814Koushik Dutta public void write(OutputStream out) throws IOException { 68029706d155a7516e36b63c5201d3e294de6589814Koushik Dutta try { 68129706d155a7516e36b63c5201d3e294de6589814Koushik Dutta signer = new WholeFileSignerOutputStream(out, outputStream); 68229706d155a7516e36b63c5201d3e294de6589814Koushik Dutta JarOutputStream outputJar = new JarOutputStream(signer); 68329706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 684fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin copyFiles(inputJar, STRIP_PATTERN, null, outputJar, timestamp, 0); 6850caa16a6d1b4349654956c895aab925c9522d2cfAlex Klyubin addOtacert(outputJar, publicKeyFile, timestamp); 68629706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 68729706d155a7516e36b63c5201d3e294de6589814Koushik Dutta signer.notifyClosing(); 68829706d155a7516e36b63c5201d3e294de6589814Koushik Dutta outputJar.close(); 68929706d155a7516e36b63c5201d3e294de6589814Koushik Dutta signer.finish(); 69029706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 69129706d155a7516e36b63c5201d3e294de6589814Koushik Dutta catch (Exception e) { 69229706d155a7516e36b63c5201d3e294de6589814Koushik Dutta throw new IOException(e); 69329706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 69429706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 69529706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 69629706d155a7516e36b63c5201d3e294de6589814Koushik Dutta public void writeSignatureBlock(ByteArrayOutputStream temp) 69729706d155a7516e36b63c5201d3e294de6589814Koushik Dutta throws IOException, 69829706d155a7516e36b63c5201d3e294de6589814Koushik Dutta CertificateEncodingException, 69929706d155a7516e36b63c5201d3e294de6589814Koushik Dutta OperatorCreationException, 70029706d155a7516e36b63c5201d3e294de6589814Koushik Dutta CMSException { 7013f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin SignApk.writeSignatureBlock(this, publicKey, privateKey, hash, temp); 70229706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 70329706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 70429706d155a7516e36b63c5201d3e294de6589814Koushik Dutta public WholeFileSignerOutputStream getSigner() { 70529706d155a7516e36b63c5201d3e294de6589814Koushik Dutta return signer; 70629706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 70729706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 70829706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 70929706d155a7516e36b63c5201d3e294de6589814Koushik Dutta private static void signWholeFile(JarFile inputJar, File publicKeyFile, 71029706d155a7516e36b63c5201d3e294de6589814Koushik Dutta X509Certificate publicKey, PrivateKey privateKey, 7113f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin int hash, long timestamp, 71229706d155a7516e36b63c5201d3e294de6589814Koushik Dutta OutputStream outputStream) throws Exception { 71329706d155a7516e36b63c5201d3e294de6589814Koushik Dutta CMSSigner cmsOut = new CMSSigner(inputJar, publicKeyFile, 7143f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin publicKey, privateKey, hash, timestamp, outputStream); 71529706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 716c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker ByteArrayOutputStream temp = new ByteArrayOutputStream(); 717c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker 718c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker // put a readable message and a null char at the start of the 719c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker // archive comment, so that tools that display the comment 720c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker // (hopefully) show something sensible. 721c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker // TODO: anything more useful we can put in this message? 722d4761a19b884d69c684c34ec0e5b74aaba2cfe95Alex Klyubin byte[] message = "signed by SignApk".getBytes(StandardCharsets.UTF_8); 723c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker temp.write(message); 724c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker temp.write(0); 725147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongker 72629706d155a7516e36b63c5201d3e294de6589814Koushik Dutta cmsOut.writeSignatureBlock(temp); 72729706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 72829706d155a7516e36b63c5201d3e294de6589814Koushik Dutta byte[] zipData = cmsOut.getSigner().getTail(); 72929706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 73029706d155a7516e36b63c5201d3e294de6589814Koushik Dutta // For a zip with no archive comment, the 73129706d155a7516e36b63c5201d3e294de6589814Koushik Dutta // end-of-central-directory record will be 22 bytes long, so 73229706d155a7516e36b63c5201d3e294de6589814Koushik Dutta // we expect to find the EOCD marker 22 bytes from the end. 73329706d155a7516e36b63c5201d3e294de6589814Koushik Dutta if (zipData[zipData.length-22] != 0x50 || 73429706d155a7516e36b63c5201d3e294de6589814Koushik Dutta zipData[zipData.length-21] != 0x4b || 73529706d155a7516e36b63c5201d3e294de6589814Koushik Dutta zipData[zipData.length-20] != 0x05 || 73629706d155a7516e36b63c5201d3e294de6589814Koushik Dutta zipData[zipData.length-19] != 0x06) { 73729706d155a7516e36b63c5201d3e294de6589814Koushik Dutta throw new IllegalArgumentException("zip data already has an archive comment"); 73829706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } 73929706d155a7516e36b63c5201d3e294de6589814Koushik Dutta 740c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker int total_size = temp.size() + 6; 741c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker if (total_size > 0xffff) { 742c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker throw new IllegalArgumentException("signature is too big for ZIP file comment"); 743c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker } 744c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker // signature starts this many bytes from the end of the file 745c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker int signature_start = total_size - message.length - 1; 746c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker temp.write(signature_start & 0xff); 747c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker temp.write((signature_start >> 8) & 0xff); 748badd2ca451ee7a408f55632025cbe69649b426b5Doug Zongker // Why the 0xff bytes? In a zip file with no archive comment, 749badd2ca451ee7a408f55632025cbe69649b426b5Doug Zongker // bytes [-6:-2] of the file are the little-endian offset from 750badd2ca451ee7a408f55632025cbe69649b426b5Doug Zongker // the start of the file to the central directory. So for the 751badd2ca451ee7a408f55632025cbe69649b426b5Doug Zongker // two high bytes to be 0xff 0xff, the archive would have to 752147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongker // be nearly 4GB in size. So it's unlikely that a real 753badd2ca451ee7a408f55632025cbe69649b426b5Doug Zongker // commentless archive would have 0xffs here, and lets us tell 754badd2ca451ee7a408f55632025cbe69649b426b5Doug Zongker // an old signed archive from a new one. 755badd2ca451ee7a408f55632025cbe69649b426b5Doug Zongker temp.write(0xff); 756badd2ca451ee7a408f55632025cbe69649b426b5Doug Zongker temp.write(0xff); 757c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker temp.write(total_size & 0xff); 758c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker temp.write((total_size >> 8) & 0xff); 759c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker temp.flush(); 760c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker 761badd2ca451ee7a408f55632025cbe69649b426b5Doug Zongker // Signature verification checks that the EOCD header is the 762badd2ca451ee7a408f55632025cbe69649b426b5Doug Zongker // last such sequence in the file (to avoid minzip finding a 763badd2ca451ee7a408f55632025cbe69649b426b5Doug Zongker // fake EOCD appended after the signature in its scan). The 764badd2ca451ee7a408f55632025cbe69649b426b5Doug Zongker // odds of producing this sequence by chance are very low, but 765badd2ca451ee7a408f55632025cbe69649b426b5Doug Zongker // let's catch it here if it does. 766badd2ca451ee7a408f55632025cbe69649b426b5Doug Zongker byte[] b = temp.toByteArray(); 767badd2ca451ee7a408f55632025cbe69649b426b5Doug Zongker for (int i = 0; i < b.length-3; ++i) { 768badd2ca451ee7a408f55632025cbe69649b426b5Doug Zongker if (b[i] == 0x50 && b[i+1] == 0x4b && b[i+2] == 0x05 && b[i+3] == 0x06) { 769badd2ca451ee7a408f55632025cbe69649b426b5Doug Zongker throw new IllegalArgumentException("found spurious EOCD header at " + i); 770badd2ca451ee7a408f55632025cbe69649b426b5Doug Zongker } 771badd2ca451ee7a408f55632025cbe69649b426b5Doug Zongker } 772badd2ca451ee7a408f55632025cbe69649b426b5Doug Zongker 773c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker outputStream.write(total_size & 0xff); 774c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker outputStream.write((total_size >> 8) & 0xff); 775c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker temp.writeTo(outputStream); 776c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker } 777c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker 77889c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root /** 77989c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root * Tries to load a JSE Provider by class name. This is for custom PrivateKey 78089c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root * types that might be stored in PKCS#11-like storage. 78189c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root */ 78289c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root private static void loadProviderIfNecessary(String providerClassName) { 78389c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root if (providerClassName == null) { 78489c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root return; 78589c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root } 78689c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root 78789c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root final Class<?> klass; 78889c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root try { 78989c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root final ClassLoader sysLoader = ClassLoader.getSystemClassLoader(); 79089c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root if (sysLoader != null) { 79189c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root klass = sysLoader.loadClass(providerClassName); 79289c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root } else { 79389c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root klass = Class.forName(providerClassName); 79489c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root } 79589c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root } catch (ClassNotFoundException e) { 79689c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root e.printStackTrace(); 79789c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root System.exit(1); 79889c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root return; 79989c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root } 80089c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root 80189c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root Constructor<?> constructor = null; 80289c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root for (Constructor<?> c : klass.getConstructors()) { 80389c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root if (c.getParameterTypes().length == 0) { 80489c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root constructor = c; 80589c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root break; 80689c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root } 80789c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root } 80889c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root if (constructor == null) { 80989c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root System.err.println("No zero-arg constructor found for " + providerClassName); 81089c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root System.exit(1); 81189c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root return; 81289c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root } 81389c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root 81489c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root final Object o; 81589c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root try { 81689c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root o = constructor.newInstance(); 81789c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root } catch (Exception e) { 81889c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root e.printStackTrace(); 81989c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root System.exit(1); 82089c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root return; 82189c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root } 82289c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root if (!(o instanceof Provider)) { 82389c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root System.err.println("Not a Provider class: " + providerClassName); 82489c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root System.exit(1); 82589c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root } 82689c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root 82789c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root Security.insertProviderAt((Provider) o, 1); 82889c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root } 82989c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root 830fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin private static List<DefaultApkSignerEngine.SignerConfig> createSignerConfigs( 831fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin PrivateKey[] privateKeys, X509Certificate[] certificates) { 832dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubin if (privateKeys.length != certificates.length) { 833dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubin throw new IllegalArgumentException( 834dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubin "The number of private keys must match the number of certificates: " 835dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubin + privateKeys.length + " vs" + certificates.length); 836dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubin } 837fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin List<DefaultApkSignerEngine.SignerConfig> signerConfigs = new ArrayList<>(); 838fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin String signerNameFormat = (privateKeys.length == 1) ? "CERT" : "CERT%s"; 839dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubin for (int i = 0; i < privateKeys.length; i++) { 840fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin String signerName = String.format(Locale.US, signerNameFormat, (i + 1)); 841fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin DefaultApkSignerEngine.SignerConfig signerConfig = 842fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin new DefaultApkSignerEngine.SignerConfig.Builder( 843fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin signerName, 844fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin privateKeys[i], 845fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin Collections.singletonList(certificates[i])) 846fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin .build(); 847fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin signerConfigs.add(signerConfig); 848dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubin } 849fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin return signerConfigs; 850dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubin } 851dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubin 852fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin private static class ZipSections { 853fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ByteBuffer beforeCentralDir; 854fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ByteBuffer centralDir; 855fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ByteBuffer eocd; 856fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin } 857fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin 858fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin private static ZipSections findMainZipSections(ByteBuffer apk) 859fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin throws IOException, ZipFormatException { 860fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin apk.slice(); 861fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ApkUtils.ZipSections sections = ApkUtils.findZipSections(DataSources.asDataSource(apk)); 862fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin long centralDirStartOffset = sections.getZipCentralDirectoryOffset(); 863fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin long centralDirSizeBytes = sections.getZipCentralDirectorySizeBytes(); 864fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin long centralDirEndOffset = centralDirStartOffset + centralDirSizeBytes; 865fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin long eocdStartOffset = sections.getZipEndOfCentralDirectoryOffset(); 866fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin if (centralDirEndOffset != eocdStartOffset) { 867fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin throw new ZipFormatException( 868fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin "ZIP Central Directory is not immediately followed by End of Central Directory" 869fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin + ". CD end: " + centralDirEndOffset 870fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin + ", EoCD start: " + eocdStartOffset); 871dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubin } 872fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin apk.position(0); 873fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin apk.limit((int) centralDirStartOffset); 874fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ByteBuffer beforeCentralDir = apk.slice(); 875fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin 876fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin apk.position((int) centralDirStartOffset); 877fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin apk.limit((int) centralDirEndOffset); 878fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ByteBuffer centralDir = apk.slice(); 879fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin 880fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin apk.position((int) eocdStartOffset); 881fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin apk.limit(apk.capacity()); 882fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ByteBuffer eocd = apk.slice(); 883fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin 884fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin apk.position(0); 885fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin apk.limit(apk.capacity()); 886fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin 887fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ZipSections result = new ZipSections(); 888fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin result.beforeCentralDir = beforeCentralDir; 889fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin result.centralDir = centralDir; 890fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin result.eocd = eocd; 891fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin return result; 892dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubin } 893dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubin 8949b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin /** 8959b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin * Returns the API Level corresponding to the APK's minSdkVersion. 8969b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin * 8979b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin * @throws MinSdkVersionException if the API Level cannot be determined from the APK. 8989b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin */ 8999b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin private static final int getMinSdkVersion(JarFile apk) throws MinSdkVersionException { 9009b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin JarEntry manifestEntry = apk.getJarEntry("AndroidManifest.xml"); 9019b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin if (manifestEntry == null) { 9029b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin throw new MinSdkVersionException("No AndroidManifest.xml in APK"); 9039b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin } 9049b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin byte[] manifestBytes; 9059b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin try { 9069b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin try (InputStream manifestIn = apk.getInputStream(manifestEntry)) { 9079b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin manifestBytes = toByteArray(manifestIn); 9089b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin } 9099b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin } catch (IOException e) { 9109b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin throw new MinSdkVersionException("Failed to read AndroidManifest.xml", e); 9119b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin } 9129b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin return ApkUtils.getMinSdkVersionFromBinaryAndroidManifest(ByteBuffer.wrap(manifestBytes)); 9139b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin } 9149b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin 9159b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin private static byte[] toByteArray(InputStream in) throws IOException { 9169b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin ByteArrayOutputStream result = new ByteArrayOutputStream(); 9179b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin byte[] buf = new byte[65536]; 9189b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin int chunkSize; 9199b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin while ((chunkSize = in.read(buf)) != -1) { 9209b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin result.write(buf, 0, chunkSize); 9219b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin } 9229b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin return result.toByteArray(); 9239b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin } 9249b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin 925b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker private static void usage() { 926b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker System.err.println("Usage: signapk [-w] " + 9271d67eec191b76e299acfa54d95f3897e25536174Doug Zongker "[-a <alignment>] " + 92889c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root "[-providerClass <className>] " + 9299b54a565c9ffd1e8ed0c3434ede022d9501eaa80Alex Klyubin "[--min-sdk-version <n>] " + 9309b54a565c9ffd1e8ed0c3434ede022d9501eaa80Alex Klyubin "[--disable-v2] " + 931b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker "publickey.x509[.pem] privatekey.pk8 " + 932b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker "[publickey2.x509[.pem] privatekey2.pk8 ...] " + 933b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker "input.jar output.jar"); 934b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker System.exit(2); 935b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker } 936b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker 93788b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project public static void main(String[] args) { 938b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker if (args.length < 4) usage(); 93988b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project 9409667b18f2300e4bf0f33d3aef51d2f48bcb6778bAlex Klyubin // Install Conscrypt as the highest-priority provider. Its crypto primitives are faster than 9419667b18f2300e4bf0f33d3aef51d2f48bcb6778bAlex Klyubin // the standard or Bouncy Castle ones. 9429667b18f2300e4bf0f33d3aef51d2f48bcb6778bAlex Klyubin Security.insertProviderAt(new OpenSSLProvider(), 1); 9439667b18f2300e4bf0f33d3aef51d2f48bcb6778bAlex Klyubin // Install Bouncy Castle (as the lowest-priority provider) because Conscrypt does not offer 9449667b18f2300e4bf0f33d3aef51d2f48bcb6778bAlex Klyubin // DSA which may still be needed. 9459667b18f2300e4bf0f33d3aef51d2f48bcb6778bAlex Klyubin // TODO: Stop installing Bouncy Castle provider once DSA is no longer needed. 9469667b18f2300e4bf0f33d3aef51d2f48bcb6778bAlex Klyubin Security.addProvider(new BouncyCastleProvider()); 947147626e624ec0b709e6dc8f156ccb391fffef9c8Doug Zongker 948c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker boolean signWholeFile = false; 94989c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root String providerClass = null; 9501d67eec191b76e299acfa54d95f3897e25536174Doug Zongker int alignment = 4; 9519b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin Integer minSdkVersionOverride = null; 952dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubin boolean signUsingApkSignatureSchemeV2 = true; 95389c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root 954c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker int argstart = 0; 95589c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root while (argstart < args.length && args[argstart].startsWith("-")) { 95689c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root if ("-w".equals(args[argstart])) { 95789c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root signWholeFile = true; 95889c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root ++argstart; 95989c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root } else if ("-providerClass".equals(args[argstart])) { 96089c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root if (argstart + 1 >= args.length) { 96189c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root usage(); 96289c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root } 96389c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root providerClass = args[++argstart]; 96489c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root ++argstart; 9651d67eec191b76e299acfa54d95f3897e25536174Doug Zongker } else if ("-a".equals(args[argstart])) { 9661d67eec191b76e299acfa54d95f3897e25536174Doug Zongker alignment = Integer.parseInt(args[++argstart]); 9671d67eec191b76e299acfa54d95f3897e25536174Doug Zongker ++argstart; 968c2c49ed0c13846f7f96249c7419971dfcddc9215Alex Klyubin } else if ("--min-sdk-version".equals(args[argstart])) { 969c2c49ed0c13846f7f96249c7419971dfcddc9215Alex Klyubin String minSdkVersionString = args[++argstart]; 970c2c49ed0c13846f7f96249c7419971dfcddc9215Alex Klyubin try { 9719b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin minSdkVersionOverride = Integer.parseInt(minSdkVersionString); 972c2c49ed0c13846f7f96249c7419971dfcddc9215Alex Klyubin } catch (NumberFormatException e) { 973c2c49ed0c13846f7f96249c7419971dfcddc9215Alex Klyubin throw new IllegalArgumentException( 9749b54a565c9ffd1e8ed0c3434ede022d9501eaa80Alex Klyubin "--min-sdk-version must be a decimal number: " + minSdkVersionString); 975c2c49ed0c13846f7f96249c7419971dfcddc9215Alex Klyubin } 976c2c49ed0c13846f7f96249c7419971dfcddc9215Alex Klyubin ++argstart; 9779b54a565c9ffd1e8ed0c3434ede022d9501eaa80Alex Klyubin } else if ("--disable-v2".equals(args[argstart])) { 978dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubin signUsingApkSignatureSchemeV2 = false; 9799b54a565c9ffd1e8ed0c3434ede022d9501eaa80Alex Klyubin ++argstart; 98089c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root } else { 98189c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root usage(); 98289c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root } 983c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker } 984c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker 985b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker if ((args.length - argstart) % 2 == 1) usage(); 986b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker int numKeys = ((args.length - argstart) / 2) - 1; 987b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker if (signWholeFile && numKeys > 1) { 988b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker System.err.println("Only one key may be used with -w."); 989b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker System.exit(2); 990b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker } 991b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker 99289c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root loadProviderIfNecessary(providerClass); 99389c961aa11ed7539306061d7c31f1bb1fcf33e62Kenny Root 994b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker String inputFilename = args[args.length-2]; 995b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker String outputFilename = args[args.length-1]; 996b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker 99788b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project JarFile inputJar = null; 998c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker FileOutputStream outputFile = null; 99988b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project 100088b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project try { 1001b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker File firstPublicKeyFile = new File(args[argstart+0]); 100288b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project 1003b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker X509Certificate[] publicKey = new X509Certificate[numKeys]; 10048562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker try { 10058562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker for (int i = 0; i < numKeys; ++i) { 10068562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker int argNum = argstart + i*2; 10078562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker publicKey[i] = readPublicKey(new File(args[argNum])); 10088562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker } 10098562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker } catch (IllegalArgumentException e) { 10108562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker System.err.println(e); 10118562fd478d7f1b1b693de5db67928f1993522c0aDoug Zongker System.exit(1); 1012b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker } 101388b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project 10146c41036bcf35fe39162b50d27533f0f3bfab3028Alex Klyubin // Set all ZIP file timestamps to Jan 1 2009 00:00:00. 10156c41036bcf35fe39162b50d27533f0f3bfab3028Alex Klyubin long timestamp = 1230768000000L; 10166c41036bcf35fe39162b50d27533f0f3bfab3028Alex Klyubin // The Java ZipEntry API we're using converts milliseconds since epoch into MS-DOS 10176c41036bcf35fe39162b50d27533f0f3bfab3028Alex Klyubin // timestamp using the current timezone. We thus adjust the milliseconds since epoch 10186c41036bcf35fe39162b50d27533f0f3bfab3028Alex Klyubin // value to end up with MS-DOS timestamp of Jan 1 2009 00:00:00. 10196c41036bcf35fe39162b50d27533f0f3bfab3028Alex Klyubin timestamp -= TimeZone.getDefault().getOffset(timestamp); 1020b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker 1021b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker PrivateKey[] privateKey = new PrivateKey[numKeys]; 1022b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker for (int i = 0; i < numKeys; ++i) { 1023b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker int argNum = argstart + i*2 + 1; 1024b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker privateKey[i] = readPrivateKey(new File(args[argNum])); 1025b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker } 1026b14c97621bb748d717fd9c91b21cb4d7cf0bd85eDoug Zongker inputJar = new JarFile(new File(inputFilename), false); // Don't verify. 1027c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker 102829706d155a7516e36b63c5201d3e294de6589814Koushik Dutta outputFile = new FileOutputStream(outputFilename); 10297bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker 1030dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubin // NOTE: Signing currently recompresses any compressed entries using Deflate (default 1031dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubin // compression level for OTA update files and maximum compession level for APKs). 10327bb042317ab4df86b9962f30399c7728e8b84599Doug Zongker if (signWholeFile) { 10333f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin int digestAlgorithm = getDigestAlgorithmForOta(publicKey[0]); 10343f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin signWholeFile(inputJar, firstPublicKeyFile, 10353f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin publicKey[0], privateKey[0], digestAlgorithm, 10363f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin timestamp, 10373f52653abfb017a7fdc5819b28bed61f3eaf4498Alex Klyubin outputFile); 103829706d155a7516e36b63c5201d3e294de6589814Koushik Dutta } else { 10399b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin // Determine the value to use as minSdkVersion of the APK being signed 10409b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin int minSdkVersion; 10419b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin if (minSdkVersionOverride != null) { 10429b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin minSdkVersion = minSdkVersionOverride; 10439b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin } else { 10449b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin try { 10459b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin minSdkVersion = getMinSdkVersion(inputJar); 10469b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin } catch (MinSdkVersionException e) { 10479b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin throw new IllegalArgumentException( 10489b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin "Cannot detect minSdkVersion. Use --min-sdk-version to override", 10499b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin e); 10509b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin } 10519b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin } 10529b75e272b6bbcc79dcdf8eef0524a30872c84ef6Alex Klyubin 1053fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin try (ApkSignerEngine apkSigner = 1054fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin new DefaultApkSignerEngine.Builder( 1055fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin createSignerConfigs(privateKey, publicKey), minSdkVersion) 1056fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin .setV1SigningEnabled(true) 1057fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin .setV2SigningEnabled(signUsingApkSignatureSchemeV2) 1058fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin .setOtherSignersSignaturesPreserved(false) 1059969e354b1f5c83f22ba1727da19d3b803c7b3fccAlex Klyubin .setCreatedBy("1.0 (Android SignApk)") 1060fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin .build()) { 1061fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin // We don't preserve the input APK's APK Signing Block (which contains v2 1062fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin // signatures) 1063fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin apkSigner.inputApkSigningBlock(null); 1064fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin 1065fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin // Build the output APK in memory, by copying input APK's ZIP entries across 1066fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin // and then signing the output APK. 1067fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ByteArrayOutputStream v1SignedApkBuf = new ByteArrayOutputStream(); 1068fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin JarOutputStream outputJar = new JarOutputStream(v1SignedApkBuf); 1069fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin // Use maximum compression for compressed entries because the APK lives forever 1070fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin // on the system partition. 1071fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin outputJar.setLevel(9); 1072fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin copyFiles(inputJar, null, apkSigner, outputJar, timestamp, alignment); 1073fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ApkSignerEngine.OutputJarSignatureRequest addV1SignatureRequest = 1074fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin apkSigner.outputJarEntries(); 1075fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin if (addV1SignatureRequest != null) { 1076fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin addV1Signature(apkSigner, addV1SignatureRequest, outputJar, timestamp); 1077fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin addV1SignatureRequest.done(); 1078fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin } 1079fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin outputJar.close(); 1080fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ByteBuffer v1SignedApk = ByteBuffer.wrap(v1SignedApkBuf.toByteArray()); 1081fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin v1SignedApkBuf.reset(); 1082fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ByteBuffer[] outputChunks = new ByteBuffer[] {v1SignedApk}; 1083fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin 1084fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ZipSections zipSections = findMainZipSections(v1SignedApk); 1085fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ApkSignerEngine.OutputApkSigningBlockRequest addV2SignatureRequest = 1086fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin apkSigner.outputZipSections( 1087fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin DataSources.asDataSource(zipSections.beforeCentralDir), 1088fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin DataSources.asDataSource(zipSections.centralDir), 1089fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin DataSources.asDataSource(zipSections.eocd)); 1090fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin if (addV2SignatureRequest != null) { 1091fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin // Need to insert the returned APK Signing Block before ZIP Central 1092fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin // Directory. 1093fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin byte[] apkSigningBlock = addV2SignatureRequest.getApkSigningBlock(); 1094fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin // Because the APK Signing Block is inserted before the Central Directory, 1095fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin // we need to adjust accordingly the offset of Central Directory inside the 1096fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin // ZIP End of Central Directory (EoCD) record. 1097fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ByteBuffer modifiedEocd = ByteBuffer.allocate(zipSections.eocd.remaining()); 1098fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin modifiedEocd.put(zipSections.eocd); 1099fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin modifiedEocd.flip(); 1100fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin modifiedEocd.order(ByteOrder.LITTLE_ENDIAN); 1101fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ApkUtils.setZipEocdCentralDirectoryOffset( 1102fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin modifiedEocd, 1103fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin zipSections.beforeCentralDir.remaining() + apkSigningBlock.length); 1104fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin outputChunks = 1105fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin new ByteBuffer[] { 1106fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin zipSections.beforeCentralDir, 1107fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin ByteBuffer.wrap(apkSigningBlock), 1108fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin zipSections.centralDir, 1109fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin modifiedEocd}; 1110fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin addV2SignatureRequest.done(); 1111fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin } 1112fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin 1113fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin // This assumes outputChunks are array-backed. To avoid this assumption, the 1114fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin // code could be rewritten to use FileChannel. 1115fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin for (ByteBuffer outputChunk : outputChunks) { 1116fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin outputFile.write( 1117fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin outputChunk.array(), 1118fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin outputChunk.arrayOffset() + outputChunk.position(), 1119fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin outputChunk.remaining()); 1120fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin outputChunk.position(outputChunk.limit()); 1121fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin } 1122dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubin 1123fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin outputFile.close(); 1124fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin outputFile = null; 1125fa1da6c3114d9f0c0cd0e27025c07f73f1810b76Alex Klyubin apkSigner.outputDone(); 1126dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubin } 1127dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubin 1128dd910c5945272e9820dfd9d7798ba32aa7dfc73fAlex Klyubin return; 1129c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker } 113088b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project } catch (Exception e) { 113188b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project e.printStackTrace(); 113288b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project System.exit(1); 113388b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project } finally { 113488b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project try { 113588b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project if (inputJar != null) inputJar.close(); 1136c6cf01a1170c3a7a5b03135b01cf97f06e1b953dDoug Zongker if (outputFile != null) outputFile.close(); 113788b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project } catch (IOException e) { 113888b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project e.printStackTrace(); 113988b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project System.exit(1); 114088b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project } 114188b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project } 114288b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project } 114388b607994a148f4af5bffee163e39ce8296750c6The Android Open Source Project} 1144