Home | History | Annotate | Download | only in v1
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.apksig.internal.apk.v1;
     18 
     19 import com.android.apksig.ApkVerifier.Issue;
     20 import com.android.apksig.ApkVerifier.IssueWithParams;
     21 import com.android.apksig.apk.ApkFormatException;
     22 import com.android.apksig.apk.ApkUtils;
     23 import com.android.apksig.internal.jar.ManifestParser;
     24 import com.android.apksig.internal.util.AndroidSdkVersion;
     25 import com.android.apksig.internal.util.InclusiveIntRange;
     26 import com.android.apksig.internal.util.MessageDigestSink;
     27 import com.android.apksig.internal.zip.CentralDirectoryRecord;
     28 import com.android.apksig.internal.zip.LocalFileRecord;
     29 import com.android.apksig.util.DataSource;
     30 import com.android.apksig.zip.ZipFormatException;
     31 import java.io.IOException;
     32 import java.nio.ByteBuffer;
     33 import java.nio.ByteOrder;
     34 import java.security.MessageDigest;
     35 import java.security.NoSuchAlgorithmException;
     36 import java.security.SignatureException;
     37 import java.security.cert.CertificateException;
     38 import java.security.cert.X509Certificate;
     39 import java.util.ArrayList;
     40 import java.util.Arrays;
     41 import java.util.Base64;
     42 import java.util.Base64.Decoder;
     43 import java.util.Collection;
     44 import java.util.Collections;
     45 import java.util.HashMap;
     46 import java.util.HashSet;
     47 import java.util.List;
     48 import java.util.Locale;
     49 import java.util.Map;
     50 import java.util.Set;
     51 import java.util.StringTokenizer;
     52 import java.util.jar.Attributes;
     53 import sun.security.pkcs.PKCS7;
     54 import sun.security.pkcs.SignerInfo;
     55 
     56 /**
     57  * APK verifier which uses JAR signing (aka v1 signing scheme).
     58  *
     59  * @see <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File">Signed JAR File</a>
     60  */
     61 public abstract class V1SchemeVerifier {
     62 
     63     private static final String MANIFEST_ENTRY_NAME = V1SchemeSigner.MANIFEST_ENTRY_NAME;
     64 
     65     private V1SchemeVerifier() {}
     66 
     67     /**
     68      * Verifies the provided APK's JAR signatures and returns the result of verification. APK is
     69      * considered verified only if {@link Result#verified} is {@code true}. If verification fails,
     70      * the result will contain errors -- see {@link Result#getErrors()}.
     71      *
     72      * @throws ApkFormatException if the APK is malformed
     73      * @throws IOException if an I/O error occurs when reading the APK
     74      * @throws NoSuchAlgorithmException if the APK's JAR signatures cannot be verified because a
     75      *         required cryptographic algorithm implementation is missing
     76      */
     77     public static Result verify(
     78             DataSource apk,
     79             ApkUtils.ZipSections apkSections,
     80             Map<Integer, String> supportedApkSigSchemeNames,
     81             Set<Integer> foundApkSigSchemeIds,
     82             int minSdkVersion,
     83             int maxSdkVersion) throws IOException, ApkFormatException, NoSuchAlgorithmException {
     84         if (minSdkVersion > maxSdkVersion) {
     85             throw new IllegalArgumentException(
     86                     "minSdkVersion (" + minSdkVersion + ") > maxSdkVersion (" + maxSdkVersion
     87                             + ")");
     88         }
     89 
     90         Result result = new Result();
     91 
     92         // Parse the ZIP Central Directory and check that there are no entries with duplicate names.
     93         List<CentralDirectoryRecord> cdRecords = parseZipCentralDirectory(apk, apkSections);
     94         Set<String> cdEntryNames = checkForDuplicateEntries(cdRecords, result);
     95         if (result.containsErrors()) {
     96             return result;
     97         }
     98 
     99         // Verify JAR signature(s).
    100         Signers.verify(
    101                 apk,
    102                 apkSections.getZipCentralDirectoryOffset(),
    103                 cdRecords,
    104                 cdEntryNames,
    105                 supportedApkSigSchemeNames,
    106                 foundApkSigSchemeIds,
    107                 minSdkVersion,
    108                 maxSdkVersion,
    109                 result);
    110 
    111         return result;
    112     }
    113 
    114     /**
    115      * Returns the set of entry names and reports any duplicate entry names in the {@code result}
    116      * as errors.
    117      */
    118     private static Set<String> checkForDuplicateEntries(
    119             List<CentralDirectoryRecord> cdRecords, Result result) {
    120         Set<String> cdEntryNames = new HashSet<>(cdRecords.size());
    121         Set<String> duplicateCdEntryNames = null;
    122         for (CentralDirectoryRecord cdRecord : cdRecords) {
    123             String entryName = cdRecord.getName();
    124             if (!cdEntryNames.add(entryName)) {
    125                 // This is an error. Report this once per duplicate name.
    126                 if (duplicateCdEntryNames == null) {
    127                     duplicateCdEntryNames = new HashSet<>();
    128                 }
    129                 if (duplicateCdEntryNames.add(entryName)) {
    130                     result.addError(Issue.JAR_SIG_DUPLICATE_ZIP_ENTRY, entryName);
    131                 }
    132             }
    133         }
    134         return cdEntryNames;
    135     }
    136 
    137     /**
    138      * All JAR signers of an APK.
    139      */
    140     private static class Signers {
    141 
    142         /**
    143          * Verifies JAR signatures of the provided APK and populates the provided result container
    144          * with errors, warnings, and information about signers. The APK is considered verified if
    145          * the {@link Result#verified} is {@code true}.
    146          */
    147         private static void verify(
    148                 DataSource apk,
    149                 long cdStartOffset,
    150                 List<CentralDirectoryRecord> cdRecords,
    151                 Set<String> cdEntryNames,
    152                 Map<Integer, String> supportedApkSigSchemeNames,
    153                 Set<Integer> foundApkSigSchemeIds,
    154                 int minSdkVersion,
    155                 int maxSdkVersion,
    156                 Result result) throws ApkFormatException, IOException, NoSuchAlgorithmException {
    157 
    158             // Find JAR manifest and signature block files.
    159             CentralDirectoryRecord manifestEntry = null;
    160             Map<String, CentralDirectoryRecord> sigFileEntries = new HashMap<>(1);
    161             List<CentralDirectoryRecord> sigBlockEntries = new ArrayList<>(1);
    162             for (CentralDirectoryRecord cdRecord : cdRecords) {
    163                 String entryName = cdRecord.getName();
    164                 if (!entryName.startsWith("META-INF/")) {
    165                     continue;
    166                 }
    167                 if ((manifestEntry == null) && (MANIFEST_ENTRY_NAME.equals(entryName))) {
    168                     manifestEntry = cdRecord;
    169                     continue;
    170                 }
    171                 if (entryName.endsWith(".SF")) {
    172                     sigFileEntries.put(entryName, cdRecord);
    173                     continue;
    174                 }
    175                 if ((entryName.endsWith(".RSA"))
    176                         || (entryName.endsWith(".DSA"))
    177                         || (entryName.endsWith(".EC"))) {
    178                     sigBlockEntries.add(cdRecord);
    179                     continue;
    180                 }
    181             }
    182             if (manifestEntry == null) {
    183                 result.addError(Issue.JAR_SIG_NO_MANIFEST);
    184                 return;
    185             }
    186 
    187             // Parse the JAR manifest and check that all JAR entries it references exist in the APK.
    188             byte[] manifestBytes;
    189             try {
    190                 manifestBytes =
    191                         LocalFileRecord.getUncompressedData(apk, manifestEntry, cdStartOffset);
    192             } catch (ZipFormatException e) {
    193                 throw new ApkFormatException("Malformed ZIP entry: " + manifestEntry.getName(), e);
    194             }
    195             Map<String, ManifestParser.Section> entryNameToManifestSection = null;
    196             ManifestParser manifest = new ManifestParser(manifestBytes);
    197             ManifestParser.Section manifestMainSection = manifest.readSection();
    198             List<ManifestParser.Section> manifestIndividualSections = manifest.readAllSections();
    199             entryNameToManifestSection = new HashMap<>(manifestIndividualSections.size());
    200             int manifestSectionNumber = 0;
    201             for (ManifestParser.Section manifestSection : manifestIndividualSections) {
    202                 manifestSectionNumber++;
    203                 String entryName = manifestSection.getName();
    204                 if (entryName == null) {
    205                     result.addError(Issue.JAR_SIG_UNNNAMED_MANIFEST_SECTION, manifestSectionNumber);
    206                     continue;
    207                 }
    208                 if (entryNameToManifestSection.put(entryName, manifestSection) != null) {
    209                     result.addError(Issue.JAR_SIG_DUPLICATE_MANIFEST_SECTION, entryName);
    210                     continue;
    211                 }
    212                 if (!cdEntryNames.contains(entryName)) {
    213                     result.addError(
    214                             Issue.JAR_SIG_MISSING_ZIP_ENTRY_REFERENCED_IN_MANIFEST, entryName);
    215                     continue;
    216                 }
    217             }
    218             if (result.containsErrors()) {
    219                 return;
    220             }
    221             // STATE OF AFFAIRS:
    222             // * All JAR entries listed in JAR manifest are present in the APK.
    223 
    224             // Identify signers
    225             List<Signer> signers = new ArrayList<>(sigBlockEntries.size());
    226             for (CentralDirectoryRecord sigBlockEntry : sigBlockEntries) {
    227                 String sigBlockEntryName = sigBlockEntry.getName();
    228                 int extensionDelimiterIndex = sigBlockEntryName.lastIndexOf('.');
    229                 if (extensionDelimiterIndex == -1) {
    230                     throw new RuntimeException(
    231                             "Signature block file name does not contain extension: "
    232                                     + sigBlockEntryName);
    233                 }
    234                 String sigFileEntryName =
    235                         sigBlockEntryName.substring(0, extensionDelimiterIndex) + ".SF";
    236                 CentralDirectoryRecord sigFileEntry = sigFileEntries.get(sigFileEntryName);
    237                 if (sigFileEntry == null) {
    238                     result.addWarning(
    239                             Issue.JAR_SIG_MISSING_FILE, sigBlockEntryName, sigFileEntryName);
    240                     continue;
    241                 }
    242                 String signerName = sigBlockEntryName.substring("META-INF/".length());
    243                 Result.SignerInfo signerInfo =
    244                         new Result.SignerInfo(
    245                                 signerName, sigBlockEntryName, sigFileEntry.getName());
    246                 Signer signer = new Signer(signerName, sigBlockEntry, sigFileEntry, signerInfo);
    247                 signers.add(signer);
    248             }
    249             if (signers.isEmpty()) {
    250                 result.addError(Issue.JAR_SIG_NO_SIGNATURES);
    251                 return;
    252             }
    253 
    254             // Verify each signer's signature block file .(RSA|DSA|EC) against the corresponding
    255             // signature file .SF. Any error encountered for any signer terminates verification, to
    256             // mimic Android's behavior.
    257             for (Signer signer : signers) {
    258                 signer.verifySigBlockAgainstSigFile(
    259                         apk, cdStartOffset, minSdkVersion, maxSdkVersion);
    260                 if (signer.getResult().containsErrors()) {
    261                     result.signers.add(signer.getResult());
    262                 }
    263             }
    264             if (result.containsErrors()) {
    265                 return;
    266             }
    267             // STATE OF AFFAIRS:
    268             // * All JAR entries listed in JAR manifest are present in the APK.
    269             // * All signature files (.SF) verify against corresponding block files (.RSA|.DSA|.EC).
    270 
    271             // Verify each signer's signature file (.SF) against the JAR manifest.
    272             List<Signer> remainingSigners = new ArrayList<>(signers.size());
    273             for (Signer signer : signers) {
    274                 signer.verifySigFileAgainstManifest(
    275                         manifestBytes,
    276                         manifestMainSection,
    277                         entryNameToManifestSection,
    278                         supportedApkSigSchemeNames,
    279                         foundApkSigSchemeIds,
    280                         minSdkVersion,
    281                         maxSdkVersion);
    282                 if (signer.isIgnored()) {
    283                     result.ignoredSigners.add(signer.getResult());
    284                 } else {
    285                     if (signer.getResult().containsErrors()) {
    286                         result.signers.add(signer.getResult());
    287                     } else {
    288                         remainingSigners.add(signer);
    289                     }
    290                 }
    291             }
    292             if (result.containsErrors()) {
    293                 return;
    294             }
    295             signers = remainingSigners;
    296             if (signers.isEmpty()) {
    297                 result.addError(Issue.JAR_SIG_NO_SIGNATURES);
    298                 return;
    299             }
    300             // STATE OF AFFAIRS:
    301             // * All signature files (.SF) verify against corresponding block files (.RSA|.DSA|.EC).
    302             // * Contents of all JAR manifest sections listed in .SF files verify against .SF files.
    303             // * All JAR entries listed in JAR manifest are present in the APK.
    304 
    305             // Verify data of JAR entries against JAR manifest and .SF files. On Android, an APK's
    306             // JAR entry is considered signed by signers associated with an .SF file iff the entry
    307             // is mentioned in the .SF file and the entry's digest(s) mentioned in the JAR manifest
    308             // match theentry's uncompressed data. Android requires that all such JAR entries are
    309             // signed by the same set of signers. This set may be smaller than the set of signers
    310             // we've identified so far.
    311             Set<Signer> apkSigners =
    312                     verifyJarEntriesAgainstManifestAndSigners(
    313                             apk,
    314                             cdStartOffset,
    315                             cdRecords,
    316                             entryNameToManifestSection,
    317                             signers,
    318                             minSdkVersion,
    319                             maxSdkVersion,
    320                             result);
    321             if (result.containsErrors()) {
    322                 return;
    323             }
    324             // STATE OF AFFAIRS:
    325             // * All signature files (.SF) verify against corresponding block files (.RSA|.DSA|.EC).
    326             // * Contents of all JAR manifest sections listed in .SF files verify against .SF files.
    327             // * All JAR entries listed in JAR manifest are present in the APK.
    328             // * All JAR entries present in the APK and supposed to be covered by JAR signature
    329             //   (i.e., reside outside of META-INF/) are covered by signatures from the same set
    330             //   of signers.
    331 
    332             // Report any JAR entries which aren't covered by signature.
    333             Set<String> signatureEntryNames = new HashSet<>(1 + result.signers.size() * 2);
    334             signatureEntryNames.add(manifestEntry.getName());
    335             for (Signer signer : apkSigners) {
    336                 signatureEntryNames.add(signer.getSignatureBlockEntryName());
    337                 signatureEntryNames.add(signer.getSignatureFileEntryName());
    338             }
    339             for (CentralDirectoryRecord cdRecord : cdRecords) {
    340                 String entryName = cdRecord.getName();
    341                 if ((entryName.startsWith("META-INF/"))
    342                         && (!entryName.endsWith("/"))
    343                         && (!signatureEntryNames.contains(entryName))) {
    344                     result.addWarning(Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY, entryName);
    345                 }
    346             }
    347 
    348             // Reflect the sets of used signers and ignored signers in the result.
    349             for (Signer signer : signers) {
    350                 if (apkSigners.contains(signer)) {
    351                     result.signers.add(signer.getResult());
    352                 } else {
    353                     result.ignoredSigners.add(signer.getResult());
    354                 }
    355             }
    356 
    357             result.verified = true;
    358         }
    359     }
    360 
    361     private static class Signer {
    362         private final String mName;
    363         private final Result.SignerInfo mResult;
    364         private final CentralDirectoryRecord mSignatureFileEntry;
    365         private final CentralDirectoryRecord mSignatureBlockEntry;
    366         private boolean mIgnored;
    367 
    368         private byte[] mSigFileBytes;
    369         private Set<String> mSigFileEntryNames;
    370 
    371         private Signer(
    372                 String name,
    373                 CentralDirectoryRecord sigBlockEntry,
    374                 CentralDirectoryRecord sigFileEntry,
    375                 Result.SignerInfo result) {
    376             mName = name;
    377             mResult = result;
    378             mSignatureBlockEntry = sigBlockEntry;
    379             mSignatureFileEntry = sigFileEntry;
    380         }
    381 
    382         public String getName() {
    383             return mName;
    384         }
    385 
    386         public String getSignatureFileEntryName() {
    387             return mSignatureFileEntry.getName();
    388         }
    389 
    390         public String getSignatureBlockEntryName() {
    391             return mSignatureBlockEntry.getName();
    392         }
    393 
    394         void setIgnored() {
    395             mIgnored = true;
    396         }
    397 
    398         public boolean isIgnored() {
    399             return mIgnored;
    400         }
    401 
    402         public Set<String> getSigFileEntryNames() {
    403             return mSigFileEntryNames;
    404         }
    405 
    406         public Result.SignerInfo getResult() {
    407             return mResult;
    408         }
    409 
    410         @SuppressWarnings("restriction")
    411         public void verifySigBlockAgainstSigFile(
    412                 DataSource apk, long cdStartOffset, int minSdkVersion, int maxSdkVersion)
    413                         throws IOException, ApkFormatException, NoSuchAlgorithmException {
    414             byte[] sigBlockBytes;
    415             try {
    416                 sigBlockBytes =
    417                         LocalFileRecord.getUncompressedData(
    418                                 apk, mSignatureBlockEntry, cdStartOffset);
    419             } catch (ZipFormatException e) {
    420                 throw new ApkFormatException(
    421                         "Malformed ZIP entry: " + mSignatureBlockEntry.getName(), e);
    422             }
    423             try {
    424                 mSigFileBytes =
    425                         LocalFileRecord.getUncompressedData(
    426                                 apk, mSignatureFileEntry, cdStartOffset);
    427             } catch (ZipFormatException e) {
    428                 throw new ApkFormatException(
    429                         "Malformed ZIP entry: " + mSignatureFileEntry.getName(), e);
    430             }
    431             PKCS7 sigBlock;
    432             try {
    433                 sigBlock = new PKCS7(sigBlockBytes);
    434             } catch (IOException e) {
    435                 if (e.getCause() instanceof CertificateException) {
    436                     mResult.addError(
    437                             Issue.JAR_SIG_MALFORMED_CERTIFICATE, mSignatureBlockEntry.getName(), e);
    438                 } else {
    439                     mResult.addError(
    440                             Issue.JAR_SIG_PARSE_EXCEPTION, mSignatureBlockEntry.getName(), e);
    441                 }
    442                 return;
    443             }
    444             SignerInfo[] unverifiedSignerInfos = sigBlock.getSignerInfos();
    445             if ((unverifiedSignerInfos == null) || (unverifiedSignerInfos.length == 0)) {
    446                 mResult.addError(Issue.JAR_SIG_NO_SIGNERS, mSignatureBlockEntry.getName());
    447                 return;
    448             }
    449 
    450             SignerInfo verifiedSignerInfo = null;
    451             if ((unverifiedSignerInfos != null) && (unverifiedSignerInfos.length > 0)) {
    452                 for (int i = 0; i < unverifiedSignerInfos.length; i++) {
    453                     SignerInfo unverifiedSignerInfo = unverifiedSignerInfos[i];
    454                     String digestAlgorithmOid =
    455                             unverifiedSignerInfo.getDigestAlgorithmId().getOID().toString();
    456                     String signatureAlgorithmOid =
    457                             unverifiedSignerInfo
    458                                     .getDigestEncryptionAlgorithmId().getOID().toString();
    459                     InclusiveIntRange desiredApiLevels =
    460                             InclusiveIntRange.fromTo(minSdkVersion, maxSdkVersion);
    461                     List<InclusiveIntRange> apiLevelsWhereDigestAndSigAlgorithmSupported =
    462                             getSigAlgSupportedApiLevels(digestAlgorithmOid, signatureAlgorithmOid);
    463                     List<InclusiveIntRange> apiLevelsWhereDigestAlgorithmNotSupported =
    464                             desiredApiLevels.getValuesNotIn(apiLevelsWhereDigestAndSigAlgorithmSupported);
    465                     if (!apiLevelsWhereDigestAlgorithmNotSupported.isEmpty()) {
    466                         mResult.addError(
    467                                 Issue.JAR_SIG_UNSUPPORTED_SIG_ALG,
    468                                 mSignatureBlockEntry.getName(),
    469                                 digestAlgorithmOid,
    470                                 signatureAlgorithmOid,
    471                                 String.valueOf(apiLevelsWhereDigestAlgorithmNotSupported));
    472                         return;
    473                     }
    474                     try {
    475                         verifiedSignerInfo = sigBlock.verify(unverifiedSignerInfo, mSigFileBytes);
    476                     } catch (SignatureException e) {
    477                         mResult.addError(
    478                                 Issue.JAR_SIG_VERIFY_EXCEPTION,
    479                                 mSignatureBlockEntry.getName(),
    480                                 mSignatureFileEntry.getName(),
    481                                 e);
    482                         return;
    483                     }
    484                     if (verifiedSignerInfo != null) {
    485                         // Verified
    486                         break;
    487                     }
    488 
    489                     // Did not verify
    490                     if (minSdkVersion < AndroidSdkVersion.N) {
    491                         // Prior to N, Android attempted to verify only the first SignerInfo.
    492                         mResult.addError(
    493                                 Issue.JAR_SIG_DID_NOT_VERIFY,
    494                                 mSignatureBlockEntry.getName(),
    495                                 mSignatureFileEntry.getName());
    496                         return;
    497                     }
    498                 }
    499             }
    500             if (verifiedSignerInfo == null) {
    501                 mResult.addError(Issue.JAR_SIG_NO_SIGNERS, mSignatureBlockEntry.getName());
    502                 return;
    503             }
    504 
    505             // TODO: PKCS7 class doesn't guarantee that returned certificates' getEncoded returns
    506             // the original encoded form of certificates rather than the DER re-encoded form. We
    507             // need to replace the PKCS7 parser/verifier.
    508             List<X509Certificate> certChain;
    509             try {
    510                 certChain = verifiedSignerInfo.getCertificateChain(sigBlock);
    511             } catch (IOException e) {
    512                 throw new RuntimeException(
    513                         "Failed to obtain cert chain from " + mSignatureBlockEntry.getName(), e);
    514             }
    515             if ((certChain == null) || (certChain.isEmpty())) {
    516                 throw new RuntimeException("Verified SignerInfo does not have a certificate chain");
    517             }
    518             mResult.certChain.clear();
    519             mResult.certChain.addAll(certChain);
    520         }
    521 
    522         private static final String OID_DIGEST_MD5 = "1.2.840.113549.2.5";
    523         private static final String OID_DIGEST_SHA1 = "1.3.14.3.2.26";
    524         private static final String OID_DIGEST_SHA224 = "2.16.840.1.101.3.4.2.4";
    525         private static final String OID_DIGEST_SHA256 = "2.16.840.1.101.3.4.2.1";
    526         private static final String OID_DIGEST_SHA384 = "2.16.840.1.101.3.4.2.2";
    527         private static final String OID_DIGEST_SHA512 = "2.16.840.1.101.3.4.2.3";
    528 
    529         private static final String OID_SIG_RSA = "1.2.840.113549.1.1.1";
    530         private static final String OID_SIG_MD5_WITH_RSA = "1.2.840.113549.1.1.4";
    531         private static final String OID_SIG_SHA1_WITH_RSA = "1.2.840.113549.1.1.5";
    532         private static final String OID_SIG_SHA224_WITH_RSA = "1.2.840.113549.1.1.14";
    533         private static final String OID_SIG_SHA256_WITH_RSA = "1.2.840.113549.1.1.11";
    534         private static final String OID_SIG_SHA384_WITH_RSA = "1.2.840.113549.1.1.12";
    535         private static final String OID_SIG_SHA512_WITH_RSA = "1.2.840.113549.1.1.13";
    536 
    537         private static final String OID_SIG_DSA = "1.2.840.10040.4.1";
    538         private static final String OID_SIG_SHA1_WITH_DSA = "1.2.840.10040.4.3";
    539         private static final String OID_SIG_SHA224_WITH_DSA = "2.16.840.1.101.3.4.3.1";
    540         private static final String OID_SIG_SHA256_WITH_DSA = "2.16.840.1.101.3.4.3.2";
    541 
    542         private static final String OID_SIG_EC_PUBLIC_KEY = "1.2.840.10045.2.1";
    543         private static final String OID_SIG_SHA1_WITH_ECDSA = "1.2.840.10045.4.1";
    544         private static final String OID_SIG_SHA224_WITH_ECDSA = "1.2.840.10045.4.3.1";
    545         private static final String OID_SIG_SHA256_WITH_ECDSA = "1.2.840.10045.4.3.2";
    546         private static final String OID_SIG_SHA384_WITH_ECDSA = "1.2.840.10045.4.3.3";
    547         private static final String OID_SIG_SHA512_WITH_ECDSA = "1.2.840.10045.4.3.4";
    548 
    549         private static final Map<String, List<InclusiveIntRange>> SUPPORTED_SIG_ALG_OIDS =
    550                 new HashMap<>();
    551         {
    552             addSupportedSigAlg(
    553                     OID_DIGEST_MD5, OID_SIG_RSA,
    554                     InclusiveIntRange.from(0));
    555             addSupportedSigAlg(
    556                     OID_DIGEST_MD5, OID_SIG_MD5_WITH_RSA,
    557                     InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(21));
    558             addSupportedSigAlg(
    559                     OID_DIGEST_MD5, OID_SIG_SHA1_WITH_RSA,
    560                     InclusiveIntRange.fromTo(21, 23));
    561             addSupportedSigAlg(
    562                     OID_DIGEST_MD5, OID_SIG_SHA224_WITH_RSA,
    563                     InclusiveIntRange.fromTo(21, 23));
    564             addSupportedSigAlg(
    565                     OID_DIGEST_MD5, OID_SIG_SHA256_WITH_RSA,
    566                     InclusiveIntRange.fromTo(21, 23));
    567             addSupportedSigAlg(
    568                     OID_DIGEST_MD5, OID_SIG_SHA384_WITH_RSA,
    569                     InclusiveIntRange.fromTo(21, 23));
    570             addSupportedSigAlg(
    571                     OID_DIGEST_MD5, OID_SIG_SHA512_WITH_RSA,
    572                     InclusiveIntRange.fromTo(21, 23));
    573 
    574             addSupportedSigAlg(
    575                     OID_DIGEST_SHA1, OID_SIG_RSA,
    576                     InclusiveIntRange.from(0));
    577             addSupportedSigAlg(
    578                     OID_DIGEST_SHA1, OID_SIG_MD5_WITH_RSA,
    579                     InclusiveIntRange.fromTo(21, 23));
    580             addSupportedSigAlg(
    581                     OID_DIGEST_SHA1, OID_SIG_SHA1_WITH_RSA,
    582                     InclusiveIntRange.from(0));
    583             addSupportedSigAlg(
    584                     OID_DIGEST_SHA1, OID_SIG_SHA224_WITH_RSA,
    585                     InclusiveIntRange.fromTo(21, 23));
    586             addSupportedSigAlg(
    587                     OID_DIGEST_SHA1, OID_SIG_SHA256_WITH_RSA,
    588                     InclusiveIntRange.fromTo(21, 23));
    589             addSupportedSigAlg(
    590                     OID_DIGEST_SHA1, OID_SIG_SHA384_WITH_RSA,
    591                     InclusiveIntRange.fromTo(21, 23));
    592             addSupportedSigAlg(
    593                     OID_DIGEST_SHA1, OID_SIG_SHA512_WITH_RSA,
    594                     InclusiveIntRange.fromTo(21, 23));
    595 
    596             addSupportedSigAlg(
    597                     OID_DIGEST_SHA224, OID_SIG_RSA,
    598                     InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(21));
    599             addSupportedSigAlg(
    600                     OID_DIGEST_SHA224, OID_SIG_MD5_WITH_RSA,
    601                     InclusiveIntRange.fromTo(21, 23));
    602             addSupportedSigAlg(
    603                     OID_DIGEST_SHA224, OID_SIG_SHA1_WITH_RSA,
    604                     InclusiveIntRange.fromTo(21, 23));
    605             addSupportedSigAlg(
    606                     OID_DIGEST_SHA224, OID_SIG_SHA224_WITH_RSA,
    607                     InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(21));
    608             addSupportedSigAlg(
    609                     OID_DIGEST_SHA224, OID_SIG_SHA256_WITH_RSA,
    610                     InclusiveIntRange.fromTo(21, 21));
    611             addSupportedSigAlg(
    612                     OID_DIGEST_SHA224, OID_SIG_SHA384_WITH_RSA,
    613                     InclusiveIntRange.fromTo(21, 23));
    614             addSupportedSigAlg(
    615                     OID_DIGEST_SHA224, OID_SIG_SHA512_WITH_RSA,
    616                     InclusiveIntRange.fromTo(21, 23));
    617 
    618             addSupportedSigAlg(
    619                     OID_DIGEST_SHA256, OID_SIG_RSA,
    620                     InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(18));
    621             addSupportedSigAlg(
    622                     OID_DIGEST_SHA256, OID_SIG_MD5_WITH_RSA,
    623                     InclusiveIntRange.fromTo(21, 23));
    624             addSupportedSigAlg(
    625                     OID_DIGEST_SHA256, OID_SIG_SHA1_WITH_RSA,
    626                     InclusiveIntRange.fromTo(21, 21));
    627             addSupportedSigAlg(
    628                     OID_DIGEST_SHA256, OID_SIG_SHA224_WITH_RSA,
    629                     InclusiveIntRange.fromTo(21, 23));
    630             addSupportedSigAlg(
    631                     OID_DIGEST_SHA256, OID_SIG_SHA256_WITH_RSA,
    632                     InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(18));
    633             addSupportedSigAlg(
    634                     OID_DIGEST_SHA256, OID_SIG_SHA384_WITH_RSA,
    635                     InclusiveIntRange.fromTo(21, 23));
    636             addSupportedSigAlg(
    637                     OID_DIGEST_SHA256, OID_SIG_SHA512_WITH_RSA,
    638                     InclusiveIntRange.fromTo(21, 23));
    639 
    640             addSupportedSigAlg(
    641                     OID_DIGEST_SHA384, OID_SIG_RSA,
    642                     InclusiveIntRange.from(18));
    643             addSupportedSigAlg(
    644                     OID_DIGEST_SHA384, OID_SIG_MD5_WITH_RSA,
    645                     InclusiveIntRange.fromTo(21, 23));
    646             addSupportedSigAlg(
    647                     OID_DIGEST_SHA384, OID_SIG_SHA1_WITH_RSA,
    648                     InclusiveIntRange.fromTo(21, 23));
    649             addSupportedSigAlg(
    650                     OID_DIGEST_SHA384, OID_SIG_SHA224_WITH_RSA,
    651                     InclusiveIntRange.fromTo(21, 23));
    652             addSupportedSigAlg(
    653                     OID_DIGEST_SHA384, OID_SIG_SHA256_WITH_RSA,
    654                     InclusiveIntRange.fromTo(21, 23));
    655             addSupportedSigAlg(
    656                     OID_DIGEST_SHA384, OID_SIG_SHA384_WITH_RSA,
    657                     InclusiveIntRange.from(21));
    658             addSupportedSigAlg(
    659                     OID_DIGEST_SHA384, OID_SIG_SHA512_WITH_RSA,
    660                     InclusiveIntRange.fromTo(21, 23));
    661 
    662             addSupportedSigAlg(
    663                     OID_DIGEST_SHA512, OID_SIG_RSA,
    664                     InclusiveIntRange.from(18));
    665             addSupportedSigAlg(
    666                     OID_DIGEST_SHA512, OID_SIG_MD5_WITH_RSA,
    667                     InclusiveIntRange.fromTo(21, 23));
    668             addSupportedSigAlg(
    669                     OID_DIGEST_SHA512, OID_SIG_SHA1_WITH_RSA,
    670                     InclusiveIntRange.fromTo(21, 23));
    671             addSupportedSigAlg(
    672                     OID_DIGEST_SHA512, OID_SIG_SHA224_WITH_RSA,
    673                     InclusiveIntRange.fromTo(21, 23));
    674             addSupportedSigAlg(
    675                     OID_DIGEST_SHA512, OID_SIG_SHA256_WITH_RSA,
    676                     InclusiveIntRange.fromTo(21, 23));
    677             addSupportedSigAlg(
    678                     OID_DIGEST_SHA512, OID_SIG_SHA384_WITH_RSA,
    679                     InclusiveIntRange.fromTo(21, 21));
    680             addSupportedSigAlg(
    681                     OID_DIGEST_SHA512, OID_SIG_SHA512_WITH_RSA,
    682                     InclusiveIntRange.from(21));
    683 
    684             addSupportedSigAlg(
    685                     OID_DIGEST_MD5, OID_SIG_SHA1_WITH_DSA,
    686                     InclusiveIntRange.fromTo(21, 23));
    687             addSupportedSigAlg(
    688                     OID_DIGEST_MD5, OID_SIG_SHA224_WITH_DSA,
    689                     InclusiveIntRange.fromTo(21, 23));
    690             addSupportedSigAlg(
    691                     OID_DIGEST_MD5, OID_SIG_SHA256_WITH_DSA,
    692                     InclusiveIntRange.fromTo(21, 23));
    693 
    694             addSupportedSigAlg(
    695                     OID_DIGEST_SHA1, OID_SIG_DSA,
    696                     InclusiveIntRange.from(0));
    697             addSupportedSigAlg(
    698                     OID_DIGEST_SHA1, OID_SIG_SHA1_WITH_DSA,
    699                     InclusiveIntRange.from(9));
    700             addSupportedSigAlg(
    701                     OID_DIGEST_SHA1, OID_SIG_SHA224_WITH_DSA,
    702                     InclusiveIntRange.fromTo(21, 23));
    703             addSupportedSigAlg(
    704                     OID_DIGEST_SHA1, OID_SIG_SHA256_WITH_DSA,
    705                     InclusiveIntRange.fromTo(21, 23));
    706 
    707             addSupportedSigAlg(
    708                     OID_DIGEST_SHA224, OID_SIG_DSA,
    709                     InclusiveIntRange.from(22));
    710             addSupportedSigAlg(
    711                     OID_DIGEST_SHA224, OID_SIG_SHA1_WITH_DSA,
    712                     InclusiveIntRange.fromTo(21, 23));
    713             addSupportedSigAlg(
    714                     OID_DIGEST_SHA224, OID_SIG_SHA224_WITH_DSA,
    715                     InclusiveIntRange.from(21));
    716             addSupportedSigAlg(
    717                     OID_DIGEST_SHA224, OID_SIG_SHA256_WITH_DSA,
    718                     InclusiveIntRange.fromTo(21, 23));
    719 
    720             addSupportedSigAlg(
    721                     OID_DIGEST_SHA256, OID_SIG_DSA,
    722                     InclusiveIntRange.from(22));
    723             addSupportedSigAlg(
    724                     OID_DIGEST_SHA256, OID_SIG_SHA1_WITH_DSA,
    725                     InclusiveIntRange.fromTo(21, 23));
    726             addSupportedSigAlg(
    727                     OID_DIGEST_SHA256, OID_SIG_SHA224_WITH_DSA,
    728                     InclusiveIntRange.fromTo(21, 23));
    729             addSupportedSigAlg(
    730                     OID_DIGEST_SHA256, OID_SIG_SHA256_WITH_DSA,
    731                     InclusiveIntRange.from(21));
    732 
    733             addSupportedSigAlg(
    734                     OID_DIGEST_SHA384, OID_SIG_SHA1_WITH_DSA,
    735                     InclusiveIntRange.fromTo(21, 23));
    736             addSupportedSigAlg(
    737                     OID_DIGEST_SHA384, OID_SIG_SHA224_WITH_DSA,
    738                     InclusiveIntRange.fromTo(21, 23));
    739             addSupportedSigAlg(
    740                     OID_DIGEST_SHA384, OID_SIG_SHA256_WITH_DSA,
    741                     InclusiveIntRange.fromTo(21, 23));
    742 
    743             addSupportedSigAlg(
    744                     OID_DIGEST_SHA512, OID_SIG_SHA1_WITH_DSA,
    745                     InclusiveIntRange.fromTo(21, 23));
    746             addSupportedSigAlg(
    747                     OID_DIGEST_SHA512, OID_SIG_SHA224_WITH_DSA,
    748                     InclusiveIntRange.fromTo(21, 23));
    749             addSupportedSigAlg(
    750                     OID_DIGEST_SHA512, OID_SIG_SHA256_WITH_DSA,
    751                     InclusiveIntRange.fromTo(21, 23));
    752 
    753             addSupportedSigAlg(
    754                     OID_DIGEST_SHA1, OID_SIG_EC_PUBLIC_KEY,
    755                     InclusiveIntRange.from(18));
    756             addSupportedSigAlg(
    757                     OID_DIGEST_SHA224, OID_SIG_EC_PUBLIC_KEY,
    758                     InclusiveIntRange.from(21));
    759             addSupportedSigAlg(
    760                     OID_DIGEST_SHA256, OID_SIG_EC_PUBLIC_KEY,
    761                     InclusiveIntRange.from(18));
    762             addSupportedSigAlg(
    763                     OID_DIGEST_SHA384, OID_SIG_EC_PUBLIC_KEY,
    764                     InclusiveIntRange.from(18));
    765             addSupportedSigAlg(
    766                     OID_DIGEST_SHA512, OID_SIG_EC_PUBLIC_KEY,
    767                     InclusiveIntRange.from(18));
    768 
    769             addSupportedSigAlg(
    770                     OID_DIGEST_MD5, OID_SIG_SHA1_WITH_ECDSA,
    771                     InclusiveIntRange.fromTo(21, 23));
    772             addSupportedSigAlg(
    773                     OID_DIGEST_MD5, OID_SIG_SHA224_WITH_ECDSA,
    774                     InclusiveIntRange.fromTo(21, 23));
    775             addSupportedSigAlg(
    776                     OID_DIGEST_MD5, OID_SIG_SHA256_WITH_ECDSA,
    777                     InclusiveIntRange.fromTo(21, 23));
    778             addSupportedSigAlg(
    779                     OID_DIGEST_MD5, OID_SIG_SHA384_WITH_ECDSA,
    780                     InclusiveIntRange.fromTo(21, 23));
    781             addSupportedSigAlg(
    782                     OID_DIGEST_MD5, OID_SIG_SHA512_WITH_ECDSA,
    783                     InclusiveIntRange.fromTo(21, 23));
    784 
    785             addSupportedSigAlg(
    786                     OID_DIGEST_SHA1, OID_SIG_SHA1_WITH_ECDSA,
    787                     InclusiveIntRange.from(18));
    788             addSupportedSigAlg(
    789                     OID_DIGEST_SHA1, OID_SIG_SHA224_WITH_ECDSA,
    790                     InclusiveIntRange.fromTo(21, 23));
    791             addSupportedSigAlg(
    792                     OID_DIGEST_SHA1, OID_SIG_SHA256_WITH_ECDSA,
    793                     InclusiveIntRange.fromTo(21, 23));
    794             addSupportedSigAlg(
    795                     OID_DIGEST_SHA1, OID_SIG_SHA384_WITH_ECDSA,
    796                     InclusiveIntRange.fromTo(21, 23));
    797             addSupportedSigAlg(
    798                     OID_DIGEST_SHA1, OID_SIG_SHA512_WITH_ECDSA,
    799                     InclusiveIntRange.fromTo(21, 23));
    800 
    801             addSupportedSigAlg(
    802                     OID_DIGEST_SHA224, OID_SIG_SHA1_WITH_ECDSA,
    803                     InclusiveIntRange.fromTo(21, 23));
    804             addSupportedSigAlg(
    805                     OID_DIGEST_SHA224, OID_SIG_SHA224_WITH_ECDSA,
    806                     InclusiveIntRange.from(21));
    807             addSupportedSigAlg(
    808                     OID_DIGEST_SHA224, OID_SIG_SHA256_WITH_ECDSA,
    809                     InclusiveIntRange.fromTo(21, 23));
    810             addSupportedSigAlg(
    811                     OID_DIGEST_SHA224, OID_SIG_SHA384_WITH_ECDSA,
    812                     InclusiveIntRange.fromTo(21, 23));
    813             addSupportedSigAlg(
    814                     OID_DIGEST_SHA224, OID_SIG_SHA512_WITH_ECDSA,
    815                     InclusiveIntRange.fromTo(21, 23));
    816 
    817             addSupportedSigAlg(
    818                     OID_DIGEST_SHA256, OID_SIG_SHA1_WITH_ECDSA,
    819                     InclusiveIntRange.fromTo(21, 23));
    820             addSupportedSigAlg(
    821                     OID_DIGEST_SHA256, OID_SIG_SHA224_WITH_ECDSA,
    822                     InclusiveIntRange.fromTo(21, 23));
    823             addSupportedSigAlg(
    824                     OID_DIGEST_SHA256, OID_SIG_SHA256_WITH_ECDSA,
    825                     InclusiveIntRange.from(21));
    826             addSupportedSigAlg(
    827                     OID_DIGEST_SHA256, OID_SIG_SHA384_WITH_ECDSA,
    828                     InclusiveIntRange.fromTo(21, 23));
    829             addSupportedSigAlg(
    830                     OID_DIGEST_SHA256, OID_SIG_SHA512_WITH_ECDSA,
    831                     InclusiveIntRange.fromTo(21, 23));
    832 
    833             addSupportedSigAlg(
    834                     OID_DIGEST_SHA384, OID_SIG_SHA1_WITH_ECDSA,
    835                     InclusiveIntRange.fromTo(21, 23));
    836             addSupportedSigAlg(
    837                     OID_DIGEST_SHA384, OID_SIG_SHA224_WITH_ECDSA,
    838                     InclusiveIntRange.fromTo(21, 23));
    839             addSupportedSigAlg(
    840                     OID_DIGEST_SHA384, OID_SIG_SHA256_WITH_ECDSA,
    841                     InclusiveIntRange.fromTo(21, 23));
    842             addSupportedSigAlg(
    843                     OID_DIGEST_SHA384, OID_SIG_SHA384_WITH_ECDSA,
    844                     InclusiveIntRange.from(21));
    845             addSupportedSigAlg(
    846                     OID_DIGEST_SHA384, OID_SIG_SHA512_WITH_ECDSA,
    847                     InclusiveIntRange.fromTo(21, 23));
    848 
    849             addSupportedSigAlg(
    850                     OID_DIGEST_SHA512, OID_SIG_SHA1_WITH_ECDSA,
    851                     InclusiveIntRange.fromTo(21, 23));
    852             addSupportedSigAlg(
    853                     OID_DIGEST_SHA512, OID_SIG_SHA224_WITH_ECDSA,
    854                     InclusiveIntRange.fromTo(21, 23));
    855             addSupportedSigAlg(
    856                     OID_DIGEST_SHA512, OID_SIG_SHA256_WITH_ECDSA,
    857                     InclusiveIntRange.fromTo(21, 23));
    858             addSupportedSigAlg(
    859                     OID_DIGEST_SHA512, OID_SIG_SHA384_WITH_ECDSA,
    860                     InclusiveIntRange.fromTo(21, 23));
    861             addSupportedSigAlg(
    862                     OID_DIGEST_SHA512, OID_SIG_SHA512_WITH_ECDSA,
    863                     InclusiveIntRange.from(21));
    864         }
    865 
    866         private static void addSupportedSigAlg(
    867                 String digestAlgorithmOid,
    868                 String signatureAlgorithmOid,
    869                 InclusiveIntRange... supportedApiLevels) {
    870             SUPPORTED_SIG_ALG_OIDS.put(
    871                     digestAlgorithmOid + "with" + signatureAlgorithmOid,
    872                     Arrays.asList(supportedApiLevels));
    873         }
    874 
    875         private List<InclusiveIntRange> getSigAlgSupportedApiLevels(
    876                 String digestAlgorithmOid,
    877                 String signatureAlgorithmOid) {
    878             List<InclusiveIntRange> result =
    879                     SUPPORTED_SIG_ALG_OIDS.get(digestAlgorithmOid + "with" + signatureAlgorithmOid);
    880             return (result != null) ? result : Collections.emptyList();
    881         }
    882 
    883         public void verifySigFileAgainstManifest(
    884                 byte[] manifestBytes,
    885                 ManifestParser.Section manifestMainSection,
    886                 Map<String, ManifestParser.Section> entryNameToManifestSection,
    887                 Map<Integer, String> supportedApkSigSchemeNames,
    888                 Set<Integer> foundApkSigSchemeIds,
    889                 int minSdkVersion,
    890                 int maxSdkVersion) throws NoSuchAlgorithmException {
    891             // Inspect the main section of the .SF file.
    892             ManifestParser sf = new ManifestParser(mSigFileBytes);
    893             ManifestParser.Section sfMainSection = sf.readSection();
    894             if (sfMainSection.getAttributeValue(Attributes.Name.SIGNATURE_VERSION) == null) {
    895                 mResult.addError(
    896                         Issue.JAR_SIG_MISSING_VERSION_ATTR_IN_SIG_FILE,
    897                         mSignatureFileEntry.getName());
    898                 setIgnored();
    899                 return;
    900             }
    901 
    902             if (maxSdkVersion >= AndroidSdkVersion.N) {
    903                 // Android N and newer rejects APKs whose .SF file says they were supposed to be
    904                 // signed with APK Signature Scheme v2 (or newer) and yet no such signature was
    905                 // found.
    906                 checkForStrippedApkSignatures(
    907                         sfMainSection, supportedApkSigSchemeNames, foundApkSigSchemeIds);
    908                 if (mResult.containsErrors()) {
    909                     return;
    910                 }
    911             }
    912 
    913             boolean createdBySigntool = false;
    914             String createdBy = sfMainSection.getAttributeValue("Created-By");
    915             if (createdBy != null) {
    916                 createdBySigntool = createdBy.indexOf("signtool") != -1;
    917             }
    918             boolean manifestDigestVerified =
    919                     verifyManifestDigest(
    920                             sfMainSection,
    921                             createdBySigntool,
    922                             manifestBytes,
    923                             minSdkVersion,
    924                             maxSdkVersion);
    925             if (!createdBySigntool) {
    926                 verifyManifestMainSectionDigest(
    927                         sfMainSection,
    928                         manifestMainSection,
    929                         manifestBytes,
    930                         minSdkVersion,
    931                         maxSdkVersion);
    932             }
    933             if (mResult.containsErrors()) {
    934                 return;
    935             }
    936 
    937             // Inspect per-entry sections of .SF file. Technically, if the digest of JAR manifest
    938             // verifies, per-entry sections should be ignored. However, most Android platform
    939             // implementations require that such sections exist.
    940             List<ManifestParser.Section> sfSections = sf.readAllSections();
    941             Set<String> sfEntryNames = new HashSet<>(sfSections.size());
    942             int sfSectionNumber = 0;
    943             for (ManifestParser.Section sfSection : sfSections) {
    944                 sfSectionNumber++;
    945                 String entryName = sfSection.getName();
    946                 if (entryName == null) {
    947                     mResult.addError(
    948                             Issue.JAR_SIG_UNNNAMED_SIG_FILE_SECTION,
    949                             mSignatureFileEntry.getName(),
    950                             sfSectionNumber);
    951                     setIgnored();
    952                     return;
    953                 }
    954                 if (!sfEntryNames.add(entryName)) {
    955                     mResult.addError(
    956                             Issue.JAR_SIG_DUPLICATE_SIG_FILE_SECTION,
    957                             mSignatureFileEntry.getName(),
    958                             entryName);
    959                     setIgnored();
    960                     return;
    961                 }
    962                 if (manifestDigestVerified) {
    963                     // No need to verify this entry's corresponding JAR manifest entry because the
    964                     // JAR manifest verifies in full.
    965                     continue;
    966                 }
    967                 // Whole-file digest of JAR manifest hasn't been verified. Thus, we need to verify
    968                 // the digest of the JAR manifest section corresponding to this .SF section.
    969                 ManifestParser.Section manifestSection = entryNameToManifestSection.get(entryName);
    970                 if (manifestSection == null) {
    971                     mResult.addError(
    972                             Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_SIG_FILE,
    973                             entryName,
    974                             mSignatureFileEntry.getName());
    975                     setIgnored();
    976                     continue;
    977                 }
    978                 verifyManifestIndividualSectionDigest(
    979                         sfSection,
    980                         createdBySigntool,
    981                         manifestSection,
    982                         manifestBytes,
    983                         minSdkVersion,
    984                         maxSdkVersion);
    985             }
    986             mSigFileEntryNames = sfEntryNames;
    987         }
    988 
    989 
    990         /**
    991          * Returns {@code true} if the whole-file digest of the manifest against the main section of
    992          * the .SF file.
    993          */
    994         private boolean verifyManifestDigest(
    995                 ManifestParser.Section sfMainSection,
    996                 boolean createdBySigntool,
    997                 byte[] manifestBytes,
    998                 int minSdkVersion,
    999                 int maxSdkVersion) throws NoSuchAlgorithmException {
   1000             Collection<NamedDigest> expectedDigests =
   1001                     getDigestsToVerify(
   1002                             sfMainSection,
   1003                             ((createdBySigntool) ? "-Digest" : "-Digest-Manifest"),
   1004                             minSdkVersion,
   1005                             maxSdkVersion);
   1006             boolean digestFound = !expectedDigests.isEmpty();
   1007             if (!digestFound) {
   1008                 mResult.addWarning(
   1009                         Issue.JAR_SIG_NO_MANIFEST_DIGEST_IN_SIG_FILE,
   1010                         mSignatureFileEntry.getName());
   1011                 return false;
   1012             }
   1013 
   1014             boolean verified = true;
   1015             for (NamedDigest expectedDigest : expectedDigests) {
   1016                 String jcaDigestAlgorithm = expectedDigest.jcaDigestAlgorithm;
   1017                 byte[] actual = digest(jcaDigestAlgorithm, manifestBytes);
   1018                 byte[] expected = expectedDigest.digest;
   1019                 if (!Arrays.equals(expected, actual)) {
   1020                     mResult.addWarning(
   1021                             Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY,
   1022                             V1SchemeSigner.MANIFEST_ENTRY_NAME,
   1023                             jcaDigestAlgorithm,
   1024                             mSignatureFileEntry.getName(),
   1025                             Base64.getEncoder().encodeToString(actual),
   1026                             Base64.getEncoder().encodeToString(expected));
   1027                     verified = false;
   1028                 }
   1029             }
   1030             return verified;
   1031         }
   1032 
   1033         /**
   1034          * Verifies the digest of the manifest's main section against the main section of the .SF
   1035          * file.
   1036          */
   1037         private void verifyManifestMainSectionDigest(
   1038                 ManifestParser.Section sfMainSection,
   1039                 ManifestParser.Section manifestMainSection,
   1040                 byte[] manifestBytes,
   1041                 int minSdkVersion,
   1042                 int maxSdkVersion) throws NoSuchAlgorithmException {
   1043             Collection<NamedDigest> expectedDigests =
   1044                     getDigestsToVerify(
   1045                             sfMainSection,
   1046                             "-Digest-Manifest-Main-Attributes",
   1047                             minSdkVersion,
   1048                             maxSdkVersion);
   1049             if (expectedDigests.isEmpty()) {
   1050                 return;
   1051             }
   1052 
   1053             for (NamedDigest expectedDigest : expectedDigests) {
   1054                 String jcaDigestAlgorithm = expectedDigest.jcaDigestAlgorithm;
   1055                 byte[] actual =
   1056                         digest(
   1057                                 jcaDigestAlgorithm,
   1058                                 manifestBytes,
   1059                                 manifestMainSection.getStartOffset(),
   1060                                 manifestMainSection.getSizeBytes());
   1061                 byte[] expected = expectedDigest.digest;
   1062                 if (!Arrays.equals(expected, actual)) {
   1063                     mResult.addError(
   1064                             Issue.JAR_SIG_MANIFEST_MAIN_SECTION_DIGEST_DID_NOT_VERIFY,
   1065                             jcaDigestAlgorithm,
   1066                             mSignatureFileEntry.getName(),
   1067                             Base64.getEncoder().encodeToString(actual),
   1068                             Base64.getEncoder().encodeToString(expected));
   1069                 }
   1070             }
   1071         }
   1072 
   1073         /**
   1074          * Verifies the digest of the manifest's individual section against the corresponding
   1075          * individual section of the .SF file.
   1076          */
   1077         private void verifyManifestIndividualSectionDigest(
   1078                 ManifestParser.Section sfIndividualSection,
   1079                 boolean createdBySigntool,
   1080                 ManifestParser.Section manifestIndividualSection,
   1081                 byte[] manifestBytes,
   1082                 int minSdkVersion,
   1083                 int maxSdkVersion) throws NoSuchAlgorithmException {
   1084             String entryName = sfIndividualSection.getName();
   1085             Collection<NamedDigest> expectedDigests =
   1086                     getDigestsToVerify(
   1087                             sfIndividualSection, "-Digest", minSdkVersion, maxSdkVersion);
   1088             if (expectedDigests.isEmpty()) {
   1089                 mResult.addError(
   1090                         Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_SIG_FILE,
   1091                         entryName,
   1092                         mSignatureFileEntry.getName());
   1093                 return;
   1094             }
   1095 
   1096             int sectionStartIndex = manifestIndividualSection.getStartOffset();
   1097             int sectionSizeBytes = manifestIndividualSection.getSizeBytes();
   1098             if (createdBySigntool) {
   1099                 int sectionEndIndex = sectionStartIndex + sectionSizeBytes;
   1100                 if ((manifestBytes[sectionEndIndex - 1] == '\n')
   1101                         && (manifestBytes[sectionEndIndex - 2] == '\n')) {
   1102                     sectionSizeBytes--;
   1103                 }
   1104             }
   1105             for (NamedDigest expectedDigest : expectedDigests) {
   1106                 String jcaDigestAlgorithm = expectedDigest.jcaDigestAlgorithm;
   1107                 byte[] actual =
   1108                         digest(
   1109                                 jcaDigestAlgorithm,
   1110                                 manifestBytes,
   1111                                 sectionStartIndex,
   1112                                 sectionSizeBytes);
   1113                 byte[] expected = expectedDigest.digest;
   1114                 if (!Arrays.equals(expected, actual)) {
   1115                     mResult.addError(
   1116                             Issue.JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY,
   1117                             entryName,
   1118                             jcaDigestAlgorithm,
   1119                             mSignatureFileEntry.getName(),
   1120                             Base64.getEncoder().encodeToString(actual),
   1121                             Base64.getEncoder().encodeToString(expected));
   1122                 }
   1123             }
   1124         }
   1125 
   1126         private void checkForStrippedApkSignatures(
   1127                 ManifestParser.Section sfMainSection,
   1128                 Map<Integer, String> supportedApkSigSchemeNames,
   1129                 Set<Integer> foundApkSigSchemeIds) {
   1130             String signedWithApkSchemes =
   1131                     sfMainSection.getAttributeValue(
   1132                             V1SchemeSigner.SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME_STR);
   1133             // This field contains a comma-separated list of APK signature scheme IDs which were
   1134             // used to sign this APK. Android rejects APKs where an ID is known to the platform but
   1135             // the APK didn't verify using that scheme.
   1136 
   1137             if (signedWithApkSchemes == null) {
   1138                 // APK signature (e.g., v2 scheme) stripping protections not enabled.
   1139                 if (!foundApkSigSchemeIds.isEmpty()) {
   1140                     // APK is signed with an APK signature scheme such as v2 scheme.
   1141                     mResult.addWarning(
   1142                             Issue.JAR_SIG_NO_APK_SIG_STRIP_PROTECTION,
   1143                             mSignatureFileEntry.getName());
   1144                 }
   1145                 return;
   1146             }
   1147 
   1148             if (supportedApkSigSchemeNames.isEmpty()) {
   1149                 return;
   1150             }
   1151 
   1152             Set<Integer> supportedApkSigSchemeIds = supportedApkSigSchemeNames.keySet();
   1153             Set<Integer> supportedExpectedApkSigSchemeIds = new HashSet<>(1);
   1154             StringTokenizer tokenizer = new StringTokenizer(signedWithApkSchemes, ",");
   1155             while (tokenizer.hasMoreTokens()) {
   1156                 String idText = tokenizer.nextToken().trim();
   1157                 if (idText.isEmpty()) {
   1158                     continue;
   1159                 }
   1160                 int id;
   1161                 try {
   1162                     id = Integer.parseInt(idText);
   1163                 } catch (Exception ignored) {
   1164                     continue;
   1165                 }
   1166                 // This APK was supposed to be signed with the APK signature scheme having
   1167                 // this ID.
   1168                 if (supportedApkSigSchemeIds.contains(id)) {
   1169                     supportedExpectedApkSigSchemeIds.add(id);
   1170                 } else {
   1171                     mResult.addWarning(
   1172                             Issue.JAR_SIG_UNKNOWN_APK_SIG_SCHEME_ID,
   1173                             mSignatureFileEntry.getName(),
   1174                             id);
   1175                 }
   1176             }
   1177 
   1178             for (int id : supportedExpectedApkSigSchemeIds) {
   1179                 if (!foundApkSigSchemeIds.contains(id)) {
   1180                     String apkSigSchemeName = supportedApkSigSchemeNames.get(id);
   1181                     mResult.addError(
   1182                             Issue.JAR_SIG_MISSING_APK_SIG_REFERENCED,
   1183                             mSignatureFileEntry.getName(),
   1184                             id,
   1185                             apkSigSchemeName);
   1186                 }
   1187             }
   1188         }
   1189     }
   1190 
   1191     private static Collection<NamedDigest> getDigestsToVerify(
   1192             ManifestParser.Section section,
   1193             String digestAttrSuffix,
   1194             int minSdkVersion,
   1195             int maxSdkVersion) {
   1196         Decoder base64Decoder = Base64.getDecoder();
   1197         List<NamedDigest> result = new ArrayList<>(1);
   1198         if (minSdkVersion < AndroidSdkVersion.JELLY_BEAN_MR2) {
   1199             // Prior to JB MR2, Android platform's logic for picking a digest algorithm to verify is
   1200             // to rely on the ancient Digest-Algorithms attribute which contains
   1201             // whitespace-separated list of digest algorithms (defaulting to SHA-1) to try. The
   1202             // first digest attribute (with supported digest algorithm) found using the list is
   1203             // used.
   1204             String algs = section.getAttributeValue("Digest-Algorithms");
   1205             if (algs == null) {
   1206                 algs = "SHA SHA1";
   1207             }
   1208             StringTokenizer tokens = new StringTokenizer(algs);
   1209             while (tokens.hasMoreTokens()) {
   1210                 String alg = tokens.nextToken();
   1211                 String attrName = alg + digestAttrSuffix;
   1212                 String digestBase64 = section.getAttributeValue(attrName);
   1213                 if (digestBase64 == null) {
   1214                     // Attribute not found
   1215                     continue;
   1216                 }
   1217                 alg = getCanonicalJcaMessageDigestAlgorithm(alg);
   1218                 if ((alg == null)
   1219                         || (getMinSdkVersionFromWhichSupportedInManifestOrSignatureFile(alg)
   1220                                 > minSdkVersion)) {
   1221                     // Unsupported digest algorithm
   1222                     continue;
   1223                 }
   1224                 // Supported digest algorithm
   1225                 result.add(new NamedDigest(alg, base64Decoder.decode(digestBase64)));
   1226                 break;
   1227             }
   1228             // No supported digests found -- this will fail to verify on pre-JB MR2 Androids.
   1229             if (result.isEmpty()) {
   1230                 return result;
   1231             }
   1232         }
   1233 
   1234         if (maxSdkVersion >= AndroidSdkVersion.JELLY_BEAN_MR2) {
   1235             // On JB MR2 and newer, Android platform picks the strongest algorithm out of:
   1236             // SHA-512, SHA-384, SHA-256, SHA-1.
   1237             for (String alg : JB_MR2_AND_NEWER_DIGEST_ALGS) {
   1238                 String attrName = getJarDigestAttributeName(alg, digestAttrSuffix);
   1239                 String digestBase64 = section.getAttributeValue(attrName);
   1240                 if (digestBase64 == null) {
   1241                     // Attribute not found
   1242                     continue;
   1243                 }
   1244                 byte[] digest = base64Decoder.decode(digestBase64);
   1245                 byte[] digestInResult = getDigest(result, alg);
   1246                 if ((digestInResult == null) || (!Arrays.equals(digestInResult, digest))) {
   1247                     result.add(new NamedDigest(alg, digest));
   1248                 }
   1249                 break;
   1250             }
   1251         }
   1252 
   1253         return result;
   1254     }
   1255 
   1256     private static final String[] JB_MR2_AND_NEWER_DIGEST_ALGS = {
   1257             "SHA-512",
   1258             "SHA-384",
   1259             "SHA-256",
   1260             "SHA-1",
   1261     };
   1262 
   1263     private static String getCanonicalJcaMessageDigestAlgorithm(String algorithm) {
   1264         return UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.get(algorithm.toUpperCase(Locale.US));
   1265     }
   1266 
   1267     public static int getMinSdkVersionFromWhichSupportedInManifestOrSignatureFile(
   1268             String jcaAlgorithmName) {
   1269         Integer result =
   1270                 MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.get(
   1271                         jcaAlgorithmName.toUpperCase(Locale.US));
   1272         return (result != null) ? result : Integer.MAX_VALUE;
   1273     }
   1274 
   1275     private static String getJarDigestAttributeName(
   1276             String jcaDigestAlgorithm, String attrNameSuffix) {
   1277         if ("SHA-1".equalsIgnoreCase(jcaDigestAlgorithm)) {
   1278             return "SHA1" + attrNameSuffix;
   1279         } else {
   1280             return jcaDigestAlgorithm + attrNameSuffix;
   1281         }
   1282     }
   1283 
   1284     private static final Map<String, String> UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL;
   1285     static {
   1286         UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL = new HashMap<>(8);
   1287         UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("MD5", "MD5");
   1288         UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA", "SHA-1");
   1289         UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA1", "SHA-1");
   1290         UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA-1", "SHA-1");
   1291         UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA-256", "SHA-256");
   1292         UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA-384", "SHA-384");
   1293         UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA-512", "SHA-512");
   1294     }
   1295 
   1296     private static final Map<String, Integer>
   1297             MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST;
   1298     static {
   1299         MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST = new HashMap<>(5);
   1300         MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put("MD5", 0);
   1301         MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put("SHA-1", 0);
   1302         MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put("SHA-256", 0);
   1303         MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put(
   1304                 "SHA-384", AndroidSdkVersion.GINGERBREAD);
   1305         MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put(
   1306                 "SHA-512", AndroidSdkVersion.GINGERBREAD);
   1307     }
   1308 
   1309     private static byte[] getDigest(Collection<NamedDigest> digests, String jcaDigestAlgorithm) {
   1310         for (NamedDigest digest : digests) {
   1311             if (digest.jcaDigestAlgorithm.equalsIgnoreCase(jcaDigestAlgorithm)) {
   1312                 return digest.digest;
   1313             }
   1314         }
   1315         return null;
   1316     }
   1317 
   1318     public static List<CentralDirectoryRecord> parseZipCentralDirectory(
   1319             DataSource apk,
   1320             ApkUtils.ZipSections apkSections)
   1321                     throws IOException, ApkFormatException {
   1322         // Read the ZIP Central Directory
   1323         long cdSizeBytes = apkSections.getZipCentralDirectorySizeBytes();
   1324         if (cdSizeBytes > Integer.MAX_VALUE) {
   1325             throw new ApkFormatException("ZIP Central Directory too large: " + cdSizeBytes);
   1326         }
   1327         long cdOffset = apkSections.getZipCentralDirectoryOffset();
   1328         ByteBuffer cd = apk.getByteBuffer(cdOffset, (int) cdSizeBytes);
   1329         cd.order(ByteOrder.LITTLE_ENDIAN);
   1330 
   1331         // Parse the ZIP Central Directory
   1332         int expectedCdRecordCount = apkSections.getZipCentralDirectoryRecordCount();
   1333         List<CentralDirectoryRecord> cdRecords = new ArrayList<>(expectedCdRecordCount);
   1334         for (int i = 0; i < expectedCdRecordCount; i++) {
   1335             CentralDirectoryRecord cdRecord;
   1336             int offsetInsideCd = cd.position();
   1337             try {
   1338                 cdRecord = CentralDirectoryRecord.getRecord(cd);
   1339             } catch (ZipFormatException e) {
   1340                 throw new ApkFormatException(
   1341                         "Malformed ZIP Central Directory record #" + (i + 1)
   1342                                 + " at file offset " + (cdOffset + offsetInsideCd),
   1343                         e);
   1344             }
   1345             String entryName = cdRecord.getName();
   1346             if (entryName.endsWith("/")) {
   1347                 // Ignore directory entries
   1348                 continue;
   1349             }
   1350             cdRecords.add(cdRecord);
   1351         }
   1352         // There may be more data in Central Directory, but we don't warn or throw because Android
   1353         // ignores unused CD data.
   1354 
   1355         return cdRecords;
   1356     }
   1357 
   1358     /**
   1359      * Returns {@code true} if the provided JAR entry must be mentioned in signed JAR archive's
   1360      * manifest for the APK to verify on Android.
   1361      */
   1362     private static boolean isJarEntryDigestNeededInManifest(String entryName) {
   1363         // NOTE: This logic is different from what's required by the JAR signing scheme. This is
   1364         // because Android's APK verification logic differs from that spec. In particular, JAR
   1365         // signing spec includes into JAR manifest all files in subdirectories of META-INF and
   1366         // any files inside META-INF not related to signatures.
   1367         if (entryName.startsWith("META-INF/")) {
   1368             return false;
   1369         }
   1370         return !entryName.endsWith("/");
   1371     }
   1372 
   1373     private static Set<Signer> verifyJarEntriesAgainstManifestAndSigners(
   1374             DataSource apk,
   1375             long cdOffsetInApk,
   1376             Collection<CentralDirectoryRecord> cdRecords,
   1377             Map<String, ManifestParser.Section> entryNameToManifestSection,
   1378             List<Signer> signers,
   1379             int minSdkVersion,
   1380             int maxSdkVersion,
   1381             Result result) throws ApkFormatException, IOException, NoSuchAlgorithmException {
   1382         // Iterate over APK contents as sequentially as possible to improve performance.
   1383         List<CentralDirectoryRecord> cdRecordsSortedByLocalFileHeaderOffset =
   1384                 new ArrayList<>(cdRecords);
   1385         Collections.sort(
   1386                 cdRecordsSortedByLocalFileHeaderOffset,
   1387                 CentralDirectoryRecord.BY_LOCAL_FILE_HEADER_OFFSET_COMPARATOR);
   1388         Set<String> manifestEntryNamesMissingFromApk =
   1389                 new HashSet<>(entryNameToManifestSection.keySet());
   1390         List<Signer> firstSignedEntrySigners = null;
   1391         String firstSignedEntryName = null;
   1392         for (CentralDirectoryRecord cdRecord : cdRecordsSortedByLocalFileHeaderOffset) {
   1393             String entryName = cdRecord.getName();
   1394             manifestEntryNamesMissingFromApk.remove(entryName);
   1395             if (!isJarEntryDigestNeededInManifest(entryName)) {
   1396                 continue;
   1397             }
   1398 
   1399             ManifestParser.Section manifestSection = entryNameToManifestSection.get(entryName);
   1400             if (manifestSection == null) {
   1401                 result.addError(Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_MANIFEST, entryName);
   1402                 continue;
   1403             }
   1404 
   1405             List<Signer> entrySigners = new ArrayList<>(signers.size());
   1406             for (Signer signer : signers) {
   1407                 if (signer.getSigFileEntryNames().contains(entryName)) {
   1408                     entrySigners.add(signer);
   1409                 }
   1410             }
   1411             if (entrySigners.isEmpty()) {
   1412                 result.addError(Issue.JAR_SIG_ZIP_ENTRY_NOT_SIGNED, entryName);
   1413                 continue;
   1414             }
   1415             if (firstSignedEntrySigners == null) {
   1416                 firstSignedEntrySigners = entrySigners;
   1417                 firstSignedEntryName = entryName;
   1418             } else if (!entrySigners.equals(firstSignedEntrySigners)) {
   1419                 result.addError(
   1420                         Issue.JAR_SIG_ZIP_ENTRY_SIGNERS_MISMATCH,
   1421                         firstSignedEntryName,
   1422                         getSignerNames(firstSignedEntrySigners),
   1423                         entryName,
   1424                         getSignerNames(entrySigners));
   1425                 continue;
   1426             }
   1427 
   1428             List<NamedDigest> expectedDigests =
   1429                     new ArrayList<>(
   1430                             getDigestsToVerify(
   1431                                     manifestSection, "-Digest", minSdkVersion, maxSdkVersion));
   1432             if (expectedDigests.isEmpty()) {
   1433                 result.addError(Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_MANIFEST, entryName);
   1434                 continue;
   1435             }
   1436 
   1437             MessageDigest[] mds = new MessageDigest[expectedDigests.size()];
   1438             for (int i = 0; i < expectedDigests.size(); i++) {
   1439                 mds[i] = getMessageDigest(expectedDigests.get(i).jcaDigestAlgorithm);
   1440             }
   1441 
   1442             try {
   1443                 LocalFileRecord.outputUncompressedData(
   1444                         apk,
   1445                         cdRecord,
   1446                         cdOffsetInApk,
   1447                         new MessageDigestSink(mds));
   1448             } catch (ZipFormatException e) {
   1449                 throw new ApkFormatException("Malformed ZIP entry: " + entryName, e);
   1450             } catch (IOException e) {
   1451                 throw new IOException("Failed to read entry: " + entryName, e);
   1452             }
   1453 
   1454             for (int i = 0; i < expectedDigests.size(); i++) {
   1455                 NamedDigest expectedDigest = expectedDigests.get(i);
   1456                 byte[] actualDigest = mds[i].digest();
   1457                 if (!Arrays.equals(expectedDigest.digest, actualDigest)) {
   1458                     result.addError(
   1459                             Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY,
   1460                             entryName,
   1461                             expectedDigest.jcaDigestAlgorithm,
   1462                             V1SchemeSigner.MANIFEST_ENTRY_NAME,
   1463                             Base64.getEncoder().encodeToString(actualDigest),
   1464                             Base64.getEncoder().encodeToString(expectedDigest.digest));
   1465                 }
   1466             }
   1467         }
   1468 
   1469         if (firstSignedEntrySigners == null) {
   1470             result.addError(Issue.JAR_SIG_NO_SIGNED_ZIP_ENTRIES);
   1471             return Collections.emptySet();
   1472         } else {
   1473             return new HashSet<>(firstSignedEntrySigners);
   1474         }
   1475     }
   1476 
   1477     private static List<String> getSignerNames(List<Signer> signers) {
   1478         if (signers.isEmpty()) {
   1479             return Collections.emptyList();
   1480         }
   1481         List<String> result = new ArrayList<>(signers.size());
   1482         for (Signer signer : signers) {
   1483             result.add(signer.getName());
   1484         }
   1485         return result;
   1486     }
   1487 
   1488     private static MessageDigest getMessageDigest(String algorithm)
   1489             throws NoSuchAlgorithmException {
   1490         return MessageDigest.getInstance(algorithm);
   1491     }
   1492 
   1493     private static byte[] digest(String algorithm, byte[] data, int offset, int length)
   1494             throws NoSuchAlgorithmException {
   1495         MessageDigest md = getMessageDigest(algorithm);
   1496         md.update(data, offset, length);
   1497         return md.digest();
   1498     }
   1499 
   1500     private static byte[] digest(String algorithm, byte[] data) throws NoSuchAlgorithmException {
   1501         return getMessageDigest(algorithm).digest(data);
   1502     }
   1503 
   1504     private static class NamedDigest {
   1505         private final String jcaDigestAlgorithm;
   1506         private final byte[] digest;
   1507 
   1508         private NamedDigest(String jcaDigestAlgorithm, byte[] digest) {
   1509             this.jcaDigestAlgorithm = jcaDigestAlgorithm;
   1510             this.digest = digest;
   1511         }
   1512     }
   1513 
   1514     public static class Result {
   1515 
   1516         /** Whether the APK's JAR signature verifies. */
   1517         public boolean verified;
   1518 
   1519         /** List of APK's signers. These signers are used by Android. */
   1520         public final List<SignerInfo> signers = new ArrayList<>();
   1521 
   1522         /**
   1523          * Signers encountered in the APK but not included in the set of the APK's signers. These
   1524          * signers are ignored by Android.
   1525          */
   1526         public final List<SignerInfo> ignoredSigners = new ArrayList<>();
   1527 
   1528         private final List<IssueWithParams> mWarnings = new ArrayList<>();
   1529         private final List<IssueWithParams> mErrors = new ArrayList<>();
   1530 
   1531         private boolean containsErrors() {
   1532             if (!mErrors.isEmpty()) {
   1533                 return true;
   1534             }
   1535             for (SignerInfo signer : signers) {
   1536                 if (signer.containsErrors()) {
   1537                     return true;
   1538                 }
   1539             }
   1540             return false;
   1541         }
   1542 
   1543         private void addError(Issue msg, Object... parameters) {
   1544             mErrors.add(new IssueWithParams(msg, parameters));
   1545         }
   1546 
   1547         private void addWarning(Issue msg, Object... parameters) {
   1548             mWarnings.add(new IssueWithParams(msg, parameters));
   1549         }
   1550 
   1551         public List<IssueWithParams> getErrors() {
   1552             return mErrors;
   1553         }
   1554 
   1555         public List<IssueWithParams> getWarnings() {
   1556             return mWarnings;
   1557         }
   1558 
   1559         public static class SignerInfo {
   1560             public final String name;
   1561             public final String signatureFileName;
   1562             public final String signatureBlockFileName;
   1563             public final List<X509Certificate> certChain = new ArrayList<>();
   1564 
   1565             private final List<IssueWithParams> mWarnings = new ArrayList<>();
   1566             private final List<IssueWithParams> mErrors = new ArrayList<>();
   1567 
   1568             private SignerInfo(
   1569                     String name, String signatureBlockFileName, String signatureFileName) {
   1570                 this.name = name;
   1571                 this.signatureBlockFileName = signatureBlockFileName;
   1572                 this.signatureFileName = signatureFileName;
   1573             }
   1574 
   1575             private boolean containsErrors() {
   1576                 return !mErrors.isEmpty();
   1577             }
   1578 
   1579             private void addError(Issue msg, Object... parameters) {
   1580                 mErrors.add(new IssueWithParams(msg, parameters));
   1581             }
   1582 
   1583             private void addWarning(Issue msg, Object... parameters) {
   1584                 mWarnings.add(new IssueWithParams(msg, parameters));
   1585             }
   1586 
   1587             public List<IssueWithParams> getErrors() {
   1588                 return mErrors;
   1589             }
   1590 
   1591             public List<IssueWithParams> getWarnings() {
   1592                 return mWarnings;
   1593             }
   1594         }
   1595     }
   1596 }
   1597