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