Home | History | Annotate | Download | only in apk
      1 /*
      2  * Copyright (C) 2017 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 android.util.apk;
     18 
     19 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
     20 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
     21 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
     22 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
     23 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
     24 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
     25 
     26 import android.content.pm.PackageParser;
     27 import android.content.pm.PackageParser.PackageParserException;
     28 import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
     29 import android.content.pm.Signature;
     30 import android.os.Trace;
     31 import android.util.jar.StrictJarFile;
     32 
     33 import com.android.internal.util.ArrayUtils;
     34 
     35 import libcore.io.IoUtils;
     36 
     37 import java.io.IOException;
     38 import java.io.InputStream;
     39 import java.security.DigestException;
     40 import java.security.GeneralSecurityException;
     41 import java.security.NoSuchAlgorithmException;
     42 import java.security.cert.Certificate;
     43 import java.security.cert.CertificateEncodingException;
     44 import java.util.ArrayList;
     45 import java.util.Iterator;
     46 import java.util.List;
     47 import java.util.concurrent.atomic.AtomicReference;
     48 import java.util.zip.ZipEntry;
     49 
     50 /**
     51  * Facade class that takes care of the details of APK verification on
     52  * behalf of PackageParser.
     53  *
     54  * @hide for internal use only.
     55  */
     56 public class ApkSignatureVerifier {
     57 
     58     private static final AtomicReference<byte[]> sBuffer = new AtomicReference<>();
     59 
     60     /**
     61      * Verifies the provided APK and returns the certificates associated with each signer.
     62      *
     63      * @throws PackageParserException if the APK's signature failed to verify.
     64      */
     65     public static PackageParser.SigningDetails verify(String apkPath,
     66             @SignatureSchemeVersion int minSignatureSchemeVersion)
     67             throws PackageParserException {
     68 
     69         if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V3) {
     70             // V3 and before are older than the requested minimum signing version
     71             throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
     72                     "No signature found in package of version " + minSignatureSchemeVersion
     73             + " or newer for package " + apkPath);
     74         }
     75 
     76         // first try v3
     77         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV3");
     78         try {
     79             ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
     80                     ApkSignatureSchemeV3Verifier.verify(apkPath);
     81             Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
     82             Signature[] signerSigs = convertToSignatures(signerCerts);
     83             Signature[] pastSignerSigs = null;
     84             int[] pastSignerSigsFlags = null;
     85             if (vSigner.por != null) {
     86                 // populate proof-of-rotation information
     87                 pastSignerSigs = new Signature[vSigner.por.certs.size()];
     88                 pastSignerSigsFlags = new int[vSigner.por.flagsList.size()];
     89                 for (int i = 0; i < pastSignerSigs.length; i++) {
     90                     pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded());
     91                     pastSignerSigsFlags[i] = vSigner.por.flagsList.get(i);
     92                 }
     93             }
     94             return new PackageParser.SigningDetails(
     95                     signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V3,
     96                     pastSignerSigs, pastSignerSigsFlags);
     97         } catch (SignatureNotFoundException e) {
     98             // not signed with v3, try older if allowed
     99             if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) {
    100                 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
    101                         "No APK Signature Scheme v3 signature in package " + apkPath, e);
    102             }
    103         } catch (Exception e) {
    104             // APK Signature Scheme v2 signature found but did not verify
    105             throw new  PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
    106                     "Failed to collect certificates from " + apkPath
    107                             + " using APK Signature Scheme v3", e);
    108         } finally {
    109             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    110         }
    111 
    112         // redundant, protective version check
    113         if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V2) {
    114             // V2 and before are older than the requested minimum signing version
    115             throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
    116                     "No signature found in package of version " + minSignatureSchemeVersion
    117                             + " or newer for package " + apkPath);
    118         }
    119 
    120         // try v2
    121         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2");
    122         try {
    123             Certificate[][] signerCerts = ApkSignatureSchemeV2Verifier.verify(apkPath);
    124             Signature[] signerSigs = convertToSignatures(signerCerts);
    125 
    126             return new PackageParser.SigningDetails(
    127                     signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V2);
    128         } catch (SignatureNotFoundException e) {
    129             // not signed with v2, try older if allowed
    130             if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V2) {
    131                 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
    132                         "No APK Signature Scheme v2 signature in package " + apkPath, e);
    133             }
    134         } catch (Exception e) {
    135             // APK Signature Scheme v2 signature found but did not verify
    136             throw new  PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
    137                     "Failed to collect certificates from " + apkPath
    138                             + " using APK Signature Scheme v2", e);
    139         } finally {
    140             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    141         }
    142 
    143         // redundant, protective version check
    144         if (minSignatureSchemeVersion > SignatureSchemeVersion.JAR) {
    145             // V1 and is older than the requested minimum signing version
    146             throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
    147                     "No signature found in package of version " + minSignatureSchemeVersion
    148                             + " or newer for package " + apkPath);
    149         }
    150 
    151         // v2 didn't work, try jarsigner
    152         return verifyV1Signature(apkPath, true);
    153     }
    154 
    155     /**
    156      * Verifies the provided APK and returns the certificates associated with each signer.
    157      *
    158      * @param verifyFull whether to verify all contents of this APK or just collect certificates.
    159      *
    160      * @throws PackageParserException if there was a problem collecting certificates
    161      */
    162     private static PackageParser.SigningDetails verifyV1Signature(
    163             String apkPath, boolean verifyFull)
    164             throws PackageParserException {
    165         StrictJarFile jarFile = null;
    166 
    167         try {
    168             final Certificate[][] lastCerts;
    169             final Signature[] lastSigs;
    170 
    171             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor");
    172 
    173             // we still pass verify = true to ctor to collect certs, even though we're not checking
    174             // the whole jar.
    175             jarFile = new StrictJarFile(
    176                     apkPath,
    177                     true, // collect certs
    178                     verifyFull); // whether to reject APK with stripped v2 signatures (b/27887819)
    179             final List<ZipEntry> toVerify = new ArrayList<>();
    180 
    181             // Gather certs from AndroidManifest.xml, which every APK must have, as an optimization
    182             // to not need to verify the whole APK when verifyFUll == false.
    183             final ZipEntry manifestEntry = jarFile.findEntry(
    184                     PackageParser.ANDROID_MANIFEST_FILENAME);
    185             if (manifestEntry == null) {
    186                 throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
    187                         "Package " + apkPath + " has no manifest");
    188             }
    189             lastCerts = loadCertificates(jarFile, manifestEntry);
    190             if (ArrayUtils.isEmpty(lastCerts)) {
    191                 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Package "
    192                         + apkPath + " has no certificates at entry "
    193                         + PackageParser.ANDROID_MANIFEST_FILENAME);
    194             }
    195             lastSigs = convertToSignatures(lastCerts);
    196 
    197             // fully verify all contents, except for AndroidManifest.xml  and the META-INF/ files.
    198             if (verifyFull) {
    199                 final Iterator<ZipEntry> i = jarFile.iterator();
    200                 while (i.hasNext()) {
    201                     final ZipEntry entry = i.next();
    202                     if (entry.isDirectory()) continue;
    203 
    204                     final String entryName = entry.getName();
    205                     if (entryName.startsWith("META-INF/")) continue;
    206                     if (entryName.equals(PackageParser.ANDROID_MANIFEST_FILENAME)) continue;
    207 
    208                     toVerify.add(entry);
    209                 }
    210 
    211                 for (ZipEntry entry : toVerify) {
    212                     final Certificate[][] entryCerts = loadCertificates(jarFile, entry);
    213                     if (ArrayUtils.isEmpty(entryCerts)) {
    214                         throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
    215                                 "Package " + apkPath + " has no certificates at entry "
    216                                         + entry.getName());
    217                     }
    218 
    219                     // make sure all entries use the same signing certs
    220                     final Signature[] entrySigs = convertToSignatures(entryCerts);
    221                     if (!Signature.areExactMatch(lastSigs, entrySigs)) {
    222                         throw new PackageParserException(
    223                                 INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
    224                                 "Package " + apkPath + " has mismatched certificates at entry "
    225                                         + entry.getName());
    226                     }
    227                 }
    228             }
    229             return new PackageParser.SigningDetails(lastSigs, SignatureSchemeVersion.JAR);
    230         } catch (GeneralSecurityException e) {
    231             throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
    232                     "Failed to collect certificates from " + apkPath, e);
    233         } catch (IOException | RuntimeException e) {
    234             throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
    235                     "Failed to collect certificates from " + apkPath, e);
    236         } finally {
    237             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    238             closeQuietly(jarFile);
    239         }
    240     }
    241 
    242     private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry entry)
    243             throws PackageParserException {
    244         InputStream is = null;
    245         try {
    246             // We must read the stream for the JarEntry to retrieve
    247             // its certificates.
    248             is = jarFile.getInputStream(entry);
    249             readFullyIgnoringContents(is);
    250             return jarFile.getCertificateChains(entry);
    251         } catch (IOException | RuntimeException e) {
    252             throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
    253                     "Failed reading " + entry.getName() + " in " + jarFile, e);
    254         } finally {
    255             IoUtils.closeQuietly(is);
    256         }
    257     }
    258 
    259     private static void readFullyIgnoringContents(InputStream in) throws IOException {
    260         byte[] buffer = sBuffer.getAndSet(null);
    261         if (buffer == null) {
    262             buffer = new byte[4096];
    263         }
    264 
    265         int n = 0;
    266         int count = 0;
    267         while ((n = in.read(buffer, 0, buffer.length)) != -1) {
    268             count += n;
    269         }
    270 
    271         sBuffer.set(buffer);
    272         return;
    273     }
    274 
    275     /**
    276      * Converts an array of certificate chains into the {@code Signature} equivalent used by the
    277      * PackageManager.
    278      *
    279      * @throws CertificateEncodingException if it is unable to create a Signature object.
    280      */
    281     public static Signature[] convertToSignatures(Certificate[][] certs)
    282             throws CertificateEncodingException {
    283         final Signature[] res = new Signature[certs.length];
    284         for (int i = 0; i < certs.length; i++) {
    285             res[i] = new Signature(certs[i]);
    286         }
    287         return res;
    288     }
    289 
    290     private static void closeQuietly(StrictJarFile jarFile) {
    291         if (jarFile != null) {
    292             try {
    293                 jarFile.close();
    294             } catch (Exception ignored) {
    295             }
    296         }
    297     }
    298 
    299     /**
    300      * Returns the certificates associated with each signer for the given APK without verification.
    301      * This method is dangerous and should not be used, unless the caller is absolutely certain the
    302      * APK is trusted.
    303      *
    304      * @throws PackageParserException if the APK's signature failed to verify.
    305      * or greater is not found, except in the case of no JAR signature.
    306      */
    307     public static PackageParser.SigningDetails plsCertsNoVerifyOnlyCerts(
    308             String apkPath, int minSignatureSchemeVersion)
    309             throws PackageParserException {
    310 
    311         if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V3) {
    312             // V3 and before are older than the requested minimum signing version
    313             throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
    314                     "No signature found in package of version " + minSignatureSchemeVersion
    315                             + " or newer for package " + apkPath);
    316         }
    317 
    318         // first try v3
    319         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "certsOnlyV3");
    320         try {
    321             ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
    322                     ApkSignatureSchemeV3Verifier.plsCertsNoVerifyOnlyCerts(apkPath);
    323             Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
    324             Signature[] signerSigs = convertToSignatures(signerCerts);
    325             Signature[] pastSignerSigs = null;
    326             int[] pastSignerSigsFlags = null;
    327             if (vSigner.por != null) {
    328                 // populate proof-of-rotation information
    329                 pastSignerSigs = new Signature[vSigner.por.certs.size()];
    330                 pastSignerSigsFlags = new int[vSigner.por.flagsList.size()];
    331                 for (int i = 0; i < pastSignerSigs.length; i++) {
    332                     pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded());
    333                     pastSignerSigsFlags[i] = vSigner.por.flagsList.get(i);
    334                 }
    335             }
    336             return new PackageParser.SigningDetails(
    337                     signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V3,
    338                     pastSignerSigs, pastSignerSigsFlags);
    339         } catch (SignatureNotFoundException e) {
    340             // not signed with v3, try older if allowed
    341             if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) {
    342                 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
    343                         "No APK Signature Scheme v3 signature in package " + apkPath, e);
    344             }
    345         } catch (Exception e) {
    346             // APK Signature Scheme v3 signature found but did not verify
    347             throw new  PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
    348                     "Failed to collect certificates from " + apkPath
    349                             + " using APK Signature Scheme v3", e);
    350         } finally {
    351             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    352         }
    353 
    354         // redundant, protective version check
    355         if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V2) {
    356             // V2 and before are older than the requested minimum signing version
    357             throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
    358                     "No signature found in package of version " + minSignatureSchemeVersion
    359                             + " or newer for package " + apkPath);
    360         }
    361 
    362         // first try v2
    363         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "certsOnlyV2");
    364         try {
    365             Certificate[][] signerCerts =
    366                     ApkSignatureSchemeV2Verifier.plsCertsNoVerifyOnlyCerts(apkPath);
    367             Signature[] signerSigs = convertToSignatures(signerCerts);
    368             return new PackageParser.SigningDetails(signerSigs,
    369                     SignatureSchemeVersion.SIGNING_BLOCK_V2);
    370         } catch (SignatureNotFoundException e) {
    371             // not signed with v2, try older if allowed
    372             if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V2) {
    373                 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
    374                         "No APK Signature Scheme v2 signature in package " + apkPath, e);
    375             }
    376         } catch (Exception e) {
    377             // APK Signature Scheme v2 signature found but did not verify
    378             throw new  PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
    379                     "Failed to collect certificates from " + apkPath
    380                             + " using APK Signature Scheme v2", e);
    381         } finally {
    382             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    383         }
    384 
    385         // redundant, protective version check
    386         if (minSignatureSchemeVersion > SignatureSchemeVersion.JAR) {
    387             // V1 and is older than the requested minimum signing version
    388             throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
    389                     "No signature found in package of version " + minSignatureSchemeVersion
    390                             + " or newer for package " + apkPath);
    391         }
    392 
    393         // v2 didn't work, try jarsigner
    394         return verifyV1Signature(apkPath, false);
    395     }
    396 
    397     /**
    398      * @return the verity root hash in the Signing Block.
    399      */
    400     public static byte[] getVerityRootHash(String apkPath)
    401             throws IOException, SignatureNotFoundException, SecurityException {
    402         // first try v3
    403         try {
    404             return ApkSignatureSchemeV3Verifier.getVerityRootHash(apkPath);
    405         } catch (SignatureNotFoundException e) {
    406             // try older version
    407         }
    408         return ApkSignatureSchemeV2Verifier.getVerityRootHash(apkPath);
    409     }
    410 
    411     /**
    412      * Generates the Merkle tree and verity metadata to the buffer allocated by the {@code
    413      * ByteBufferFactory}.
    414      *
    415      * @return the verity root hash of the generated Merkle tree.
    416      */
    417     public static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)
    418             throws IOException, SignatureNotFoundException, SecurityException, DigestException,
    419                    NoSuchAlgorithmException {
    420         // first try v3
    421         try {
    422             return ApkSignatureSchemeV3Verifier.generateApkVerity(apkPath, bufferFactory);
    423         } catch (SignatureNotFoundException e) {
    424             // try older version
    425         }
    426         return ApkSignatureSchemeV2Verifier.generateApkVerity(apkPath, bufferFactory);
    427     }
    428 
    429     /**
    430      * Generates the FSVerity root hash from FSVerity header, extensions and Merkle tree root hash
    431      * in Signing Block.
    432      *
    433      * @return FSverity root hash
    434      */
    435     public static byte[] generateFsverityRootHash(String apkPath)
    436             throws NoSuchAlgorithmException, DigestException, IOException {
    437         // first try v3
    438         try {
    439             return ApkSignatureSchemeV3Verifier.generateFsverityRootHash(apkPath);
    440         } catch (SignatureNotFoundException e) {
    441             // try older version
    442         }
    443         try {
    444             return ApkSignatureSchemeV2Verifier.generateFsverityRootHash(apkPath);
    445         } catch (SignatureNotFoundException e) {
    446             return null;
    447         }
    448     }
    449 
    450     /**
    451      * Result of a successful APK verification operation.
    452      */
    453     public static class Result {
    454         public final Certificate[][] certs;
    455         public final Signature[] sigs;
    456         public final int signatureSchemeVersion;
    457 
    458         public Result(Certificate[][] certs, Signature[] sigs, int signingVersion) {
    459             this.certs = certs;
    460             this.sigs = sigs;
    461             this.signatureSchemeVersion = signingVersion;
    462         }
    463     }
    464 }
    465