Home | History | Annotate | Download | only in v2
      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.v2;
     18 
     19 import com.android.apksig.ApkVerifier.Issue;
     20 import com.android.apksig.apk.ApkFormatException;
     21 import com.android.apksig.apk.ApkUtils;
     22 import com.android.apksig.internal.apk.ApkSigningBlockUtils;
     23 import com.android.apksig.internal.apk.ContentDigestAlgorithm;
     24 import com.android.apksig.internal.apk.SignatureAlgorithm;
     25 import com.android.apksig.internal.apk.SignatureInfo;
     26 import com.android.apksig.internal.util.ByteBufferUtils;
     27 import com.android.apksig.internal.util.GuaranteedEncodedFormX509Certificate;
     28 import com.android.apksig.util.DataSource;
     29 
     30 import java.io.ByteArrayInputStream;
     31 import java.io.IOException;
     32 import java.nio.BufferUnderflowException;
     33 import java.nio.ByteBuffer;
     34 import java.security.InvalidAlgorithmParameterException;
     35 import java.security.InvalidKeyException;
     36 import java.security.KeyFactory;
     37 import java.security.NoSuchAlgorithmException;
     38 import java.security.PublicKey;
     39 import java.security.Signature;
     40 import java.security.SignatureException;
     41 import java.security.cert.CertificateException;
     42 import java.security.cert.CertificateFactory;
     43 import java.security.cert.X509Certificate;
     44 import java.security.spec.AlgorithmParameterSpec;
     45 import java.security.spec.X509EncodedKeySpec;
     46 import java.util.ArrayList;
     47 import java.util.Arrays;
     48 import java.util.HashSet;
     49 import java.util.List;
     50 import java.util.Map;
     51 import java.util.Set;
     52 
     53 /**
     54  * APK Signature Scheme v2 verifier.
     55  *
     56  * <p>APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single
     57  * bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and
     58  * uncompressed contents of ZIP entries.
     59  *
     60  * @see <a href="https://source.android.com/security/apksigning/v2.html">APK Signature Scheme v2</a>
     61  */
     62 public abstract class V2SchemeVerifier {
     63 
     64     private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
     65 
     66     /** Hidden constructor to prevent instantiation. */
     67     private V2SchemeVerifier() {}
     68 
     69     /**
     70      * Verifies the provided APK's APK Signature Scheme v2 signatures and returns the result of
     71      * verification. The APK must be considered verified only if
     72      * {@link ApkSigningBlockUtils.Result#verified} is
     73      * {@code true}. If verification fails, the result will contain errors -- see
     74      * {@link ApkSigningBlockUtils.Result#getErrors()}.
     75      *
     76      * <p>Verification succeeds iff the APK's APK Signature Scheme v2 signatures are expected to
     77      * verify on all Android platform versions in the {@code [minSdkVersion, maxSdkVersion]} range.
     78      * If the APK's signature is expected to not verify on any of the specified platform versions,
     79      * this method returns a result with one or more errors and whose
     80      * {@code Result.verified == false}, or this method throws an exception.
     81      *
     82      * @throws ApkFormatException if the APK is malformed
     83      * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a
     84      *         required cryptographic algorithm implementation is missing
     85      * @throws ApkSigningBlockUtils.SignatureNotFoundException if no APK Signature Scheme v2
     86      * signatures are found
     87      * @throws IOException if an I/O error occurs when reading the APK
     88      */
     89     public static ApkSigningBlockUtils.Result verify(
     90             DataSource apk,
     91             ApkUtils.ZipSections zipSections,
     92             Map<Integer, String> supportedApkSigSchemeNames,
     93             Set<Integer> foundSigSchemeIds,
     94             int minSdkVersion,
     95             int maxSdkVersion)
     96             throws IOException, ApkFormatException, NoSuchAlgorithmException,
     97             ApkSigningBlockUtils.SignatureNotFoundException {
     98         ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result(
     99                 ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2);
    100         SignatureInfo signatureInfo =
    101                 ApkSigningBlockUtils.findSignature(apk, zipSections,
    102                         APK_SIGNATURE_SCHEME_V2_BLOCK_ID , result);
    103 
    104         DataSource beforeApkSigningBlock = apk.slice(0, signatureInfo.apkSigningBlockOffset);
    105         DataSource centralDir =
    106                 apk.slice(
    107                         signatureInfo.centralDirOffset,
    108                         signatureInfo.eocdOffset - signatureInfo.centralDirOffset);
    109         ByteBuffer eocd = signatureInfo.eocd;
    110 
    111         verify(beforeApkSigningBlock,
    112                 signatureInfo.signatureBlock,
    113                 centralDir,
    114                 eocd,
    115                 supportedApkSigSchemeNames,
    116                 foundSigSchemeIds,
    117                 minSdkVersion,
    118                 maxSdkVersion,
    119                 result);
    120         return result;
    121     }
    122 
    123     /**
    124      * Verifies the provided APK's v2 signatures and outputs the results into the provided
    125      * {@code result}. APK is considered verified only if there are no errors reported in the
    126      * {@code result}. See {@link #verify(DataSource, ApkUtils.ZipSections, Map, Set, int, int)} for
    127      * more information about the contract of this method.
    128      *
    129      * @param result result populated by this method with interesting information about the APK,
    130      *        such as information about signers, and verification errors and warnings.
    131      */
    132     private static void verify(
    133             DataSource beforeApkSigningBlock,
    134             ByteBuffer apkSignatureSchemeV2Block,
    135             DataSource centralDir,
    136             ByteBuffer eocd,
    137             Map<Integer, String> supportedApkSigSchemeNames,
    138             Set<Integer> foundSigSchemeIds,
    139             int minSdkVersion,
    140             int maxSdkVersion,
    141             ApkSigningBlockUtils.Result result)
    142             throws IOException, NoSuchAlgorithmException {
    143         Set<ContentDigestAlgorithm> contentDigestsToVerify = new HashSet<>(1);
    144         parseSigners(
    145                 apkSignatureSchemeV2Block,
    146                 contentDigestsToVerify,
    147                 supportedApkSigSchemeNames,
    148                 foundSigSchemeIds,
    149                 minSdkVersion,
    150                 maxSdkVersion,
    151                 result);
    152         if (result.containsErrors()) {
    153             return;
    154         }
    155         ApkSigningBlockUtils.verifyIntegrity(
    156                 beforeApkSigningBlock, centralDir, eocd, contentDigestsToVerify, result);
    157         if (!result.containsErrors()) {
    158             result.verified = true;
    159         }
    160     }
    161 
    162     /**
    163      * Parses each signer in the provided APK Signature Scheme v2 block and populates corresponding
    164      * {@code signerInfos} of the provided {@code result}.
    165      *
    166      * <p>This verifies signatures over {@code signed-data} block contained in each signer block.
    167      * However, this does not verify the integrity of the rest of the APK but rather simply reports
    168      * the expected digests of the rest of the APK (see {@code contentDigestsToVerify}).
    169      *
    170      * <p>This method adds one or more errors to the {@code result} if a verification error is
    171      * expected to be encountered on an Android platform version in the
    172      * {@code [minSdkVersion, maxSdkVersion]} range.
    173      */
    174     private static void parseSigners(
    175             ByteBuffer apkSignatureSchemeV2Block,
    176             Set<ContentDigestAlgorithm> contentDigestsToVerify,
    177             Map<Integer, String> supportedApkSigSchemeNames,
    178             Set<Integer> foundApkSigSchemeIds,
    179             int minSdkVersion,
    180             int maxSdkVersion,
    181             ApkSigningBlockUtils.Result result) throws NoSuchAlgorithmException {
    182         ByteBuffer signers;
    183         try {
    184             signers = ApkSigningBlockUtils.getLengthPrefixedSlice(apkSignatureSchemeV2Block);
    185         } catch (ApkFormatException e) {
    186             result.addError(Issue.V2_SIG_MALFORMED_SIGNERS);
    187             return;
    188         }
    189         if (!signers.hasRemaining()) {
    190             result.addError(Issue.V2_SIG_NO_SIGNERS);
    191             return;
    192         }
    193 
    194         CertificateFactory certFactory;
    195         try {
    196             certFactory = CertificateFactory.getInstance("X.509");
    197         } catch (CertificateException e) {
    198             throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
    199         }
    200         int signerCount = 0;
    201         while (signers.hasRemaining()) {
    202             int signerIndex = signerCount;
    203             signerCount++;
    204             ApkSigningBlockUtils.Result.SignerInfo signerInfo =
    205                     new ApkSigningBlockUtils.Result.SignerInfo();
    206             signerInfo.index = signerIndex;
    207             result.signers.add(signerInfo);
    208             try {
    209                 ByteBuffer signer = ApkSigningBlockUtils.getLengthPrefixedSlice(signers);
    210                 parseSigner(
    211                         signer,
    212                         certFactory,
    213                         signerInfo,
    214                         contentDigestsToVerify,
    215                         supportedApkSigSchemeNames,
    216                         foundApkSigSchemeIds,
    217                         minSdkVersion,
    218                         maxSdkVersion);
    219             } catch (ApkFormatException | BufferUnderflowException e) {
    220                 signerInfo.addError(Issue.V2_SIG_MALFORMED_SIGNER);
    221                 return;
    222             }
    223         }
    224     }
    225 
    226     /**
    227      * Parses the provided signer block and populates the {@code result}.
    228      *
    229      * <p>This verifies signatures over {@code signed-data} contained in this block but does not
    230      * verify the integrity of the rest of the APK. To facilitate APK integrity verification, this
    231      * method adds the {@code contentDigestsToVerify}. These digests can then be used to verify the
    232      * integrity of the APK.
    233      *
    234      * <p>This method adds one or more errors to the {@code result} if a verification error is
    235      * expected to be encountered on an Android platform version in the
    236      * {@code [minSdkVersion, maxSdkVersion]} range.
    237      */
    238     private static void parseSigner(
    239             ByteBuffer signerBlock,
    240             CertificateFactory certFactory,
    241             ApkSigningBlockUtils.Result.SignerInfo result,
    242             Set<ContentDigestAlgorithm> contentDigestsToVerify,
    243             Map<Integer, String> supportedApkSigSchemeNames,
    244             Set<Integer> foundApkSigSchemeIds,
    245             int minSdkVersion,
    246             int maxSdkVersion)
    247                     throws ApkFormatException, NoSuchAlgorithmException {
    248         ByteBuffer signedData = ApkSigningBlockUtils.getLengthPrefixedSlice(signerBlock);
    249         byte[] signedDataBytes = new byte[signedData.remaining()];
    250         signedData.get(signedDataBytes);
    251         signedData.flip();
    252         result.signedData = signedDataBytes;
    253 
    254         ByteBuffer signatures = ApkSigningBlockUtils.getLengthPrefixedSlice(signerBlock);
    255         byte[] publicKeyBytes = ApkSigningBlockUtils.readLengthPrefixedByteArray(signerBlock);
    256 
    257         // Parse the signatures block and identify supported signatures
    258         int signatureCount = 0;
    259         List<ApkSigningBlockUtils.SupportedSignature> supportedSignatures = new ArrayList<>(1);
    260         while (signatures.hasRemaining()) {
    261             signatureCount++;
    262             try {
    263                 ByteBuffer signature = ApkSigningBlockUtils.getLengthPrefixedSlice(signatures);
    264                 int sigAlgorithmId = signature.getInt();
    265                 byte[] sigBytes = ApkSigningBlockUtils.readLengthPrefixedByteArray(signature);
    266                 result.signatures.add(
    267                         new ApkSigningBlockUtils.Result.SignerInfo.Signature(
    268                                 sigAlgorithmId, sigBytes));
    269                 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.findById(sigAlgorithmId);
    270                 if (signatureAlgorithm == null) {
    271                     result.addWarning(Issue.V2_SIG_UNKNOWN_SIG_ALGORITHM, sigAlgorithmId);
    272                     continue;
    273                 }
    274                 supportedSignatures.add(
    275                         new ApkSigningBlockUtils.SupportedSignature(signatureAlgorithm, sigBytes));
    276             } catch (ApkFormatException | BufferUnderflowException e) {
    277                 result.addError(Issue.V2_SIG_MALFORMED_SIGNATURE, signatureCount);
    278                 return;
    279             }
    280         }
    281         if (result.signatures.isEmpty()) {
    282             result.addError(Issue.V2_SIG_NO_SIGNATURES);
    283             return;
    284         }
    285 
    286         // Verify signatures over signed-data block using the public key
    287         List<ApkSigningBlockUtils.SupportedSignature> signaturesToVerify = null;
    288         try {
    289             signaturesToVerify =
    290                     ApkSigningBlockUtils.getSignaturesToVerify(
    291                             supportedSignatures, minSdkVersion, maxSdkVersion);
    292         } catch (ApkSigningBlockUtils.NoSupportedSignaturesException e) {
    293             result.addError(Issue.V2_SIG_NO_SUPPORTED_SIGNATURES);
    294             return;
    295         }
    296         for (ApkSigningBlockUtils.SupportedSignature signature : signaturesToVerify) {
    297             SignatureAlgorithm signatureAlgorithm = signature.algorithm;
    298             String jcaSignatureAlgorithm =
    299                     signatureAlgorithm.getJcaSignatureAlgorithmAndParams().getFirst();
    300             AlgorithmParameterSpec jcaSignatureAlgorithmParams =
    301                     signatureAlgorithm.getJcaSignatureAlgorithmAndParams().getSecond();
    302             String keyAlgorithm = signatureAlgorithm.getJcaKeyAlgorithm();
    303             PublicKey publicKey;
    304             try {
    305                 publicKey =
    306                         KeyFactory.getInstance(keyAlgorithm).generatePublic(
    307                                 new X509EncodedKeySpec(publicKeyBytes));
    308             } catch (Exception e) {
    309                 result.addError(Issue.V2_SIG_MALFORMED_PUBLIC_KEY, e);
    310                 return;
    311             }
    312             try {
    313                 Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
    314                 sig.initVerify(publicKey);
    315                 if (jcaSignatureAlgorithmParams != null) {
    316                     sig.setParameter(jcaSignatureAlgorithmParams);
    317                 }
    318                 signedData.position(0);
    319                 sig.update(signedData);
    320                 byte[] sigBytes = signature.signature;
    321                 if (!sig.verify(sigBytes)) {
    322                     result.addError(Issue.V2_SIG_DID_NOT_VERIFY, signatureAlgorithm);
    323                     return;
    324                 }
    325                 result.verifiedSignatures.put(signatureAlgorithm, sigBytes);
    326                 contentDigestsToVerify.add(signatureAlgorithm.getContentDigestAlgorithm());
    327             } catch (InvalidKeyException | InvalidAlgorithmParameterException
    328                     | SignatureException e) {
    329                 result.addError(Issue.V2_SIG_VERIFY_EXCEPTION, signatureAlgorithm, e);
    330                 return;
    331             }
    332         }
    333 
    334         // At least one signature over signedData has verified. We can now parse signed-data.
    335         signedData.position(0);
    336         ByteBuffer digests = ApkSigningBlockUtils.getLengthPrefixedSlice(signedData);
    337         ByteBuffer certificates = ApkSigningBlockUtils.getLengthPrefixedSlice(signedData);
    338         ByteBuffer additionalAttributes = ApkSigningBlockUtils.getLengthPrefixedSlice(signedData);
    339 
    340         // Parse the certificates block
    341         int certificateIndex = -1;
    342         while (certificates.hasRemaining()) {
    343             certificateIndex++;
    344             byte[] encodedCert = ApkSigningBlockUtils.readLengthPrefixedByteArray(certificates);
    345             X509Certificate certificate;
    346             try {
    347                 certificate =
    348                         (X509Certificate)
    349                                 certFactory.generateCertificate(
    350                                         new ByteArrayInputStream(encodedCert));
    351             } catch (CertificateException e) {
    352                 result.addError(
    353                         Issue.V2_SIG_MALFORMED_CERTIFICATE,
    354                         certificateIndex,
    355                         certificateIndex + 1,
    356                         e);
    357                 return;
    358             }
    359             // Wrap the cert so that the result's getEncoded returns exactly the original encoded
    360             // form. Without this, getEncoded may return a different form from what was stored in
    361             // the signature. This is because some X509Certificate(Factory) implementations
    362             // re-encode certificates.
    363             certificate = new GuaranteedEncodedFormX509Certificate(certificate, encodedCert);
    364             result.certs.add(certificate);
    365         }
    366 
    367         if (result.certs.isEmpty()) {
    368             result.addError(Issue.V2_SIG_NO_CERTIFICATES);
    369             return;
    370         }
    371         X509Certificate mainCertificate = result.certs.get(0);
    372         byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
    373         if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
    374             result.addError(
    375                     Issue.V2_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD,
    376                     ApkSigningBlockUtils.toHex(certificatePublicKeyBytes),
    377                     ApkSigningBlockUtils.toHex(publicKeyBytes));
    378             return;
    379         }
    380 
    381         // Parse the digests block
    382         int digestCount = 0;
    383         while (digests.hasRemaining()) {
    384             digestCount++;
    385             try {
    386                 ByteBuffer digest = ApkSigningBlockUtils.getLengthPrefixedSlice(digests);
    387                 int sigAlgorithmId = digest.getInt();
    388                 byte[] digestBytes = ApkSigningBlockUtils.readLengthPrefixedByteArray(digest);
    389                 result.contentDigests.add(
    390                         new ApkSigningBlockUtils.Result.SignerInfo.ContentDigest(
    391                                 sigAlgorithmId, digestBytes));
    392             } catch (ApkFormatException | BufferUnderflowException e) {
    393                 result.addError(Issue.V2_SIG_MALFORMED_DIGEST, digestCount);
    394                 return;
    395             }
    396         }
    397 
    398         List<Integer> sigAlgsFromSignaturesRecord = new ArrayList<>(result.signatures.size());
    399         for (ApkSigningBlockUtils.Result.SignerInfo.Signature signature : result.signatures) {
    400             sigAlgsFromSignaturesRecord.add(signature.getAlgorithmId());
    401         }
    402         List<Integer> sigAlgsFromDigestsRecord = new ArrayList<>(result.contentDigests.size());
    403         for (ApkSigningBlockUtils.Result.SignerInfo.ContentDigest digest : result.contentDigests) {
    404             sigAlgsFromDigestsRecord.add(digest.getSignatureAlgorithmId());
    405         }
    406 
    407         if (!sigAlgsFromSignaturesRecord.equals(sigAlgsFromDigestsRecord)) {
    408             result.addError(
    409                     Issue.V2_SIG_SIG_ALG_MISMATCH_BETWEEN_SIGNATURES_AND_DIGESTS_RECORDS,
    410                     sigAlgsFromSignaturesRecord,
    411                     sigAlgsFromDigestsRecord);
    412             return;
    413         }
    414 
    415         // Parse the additional attributes block.
    416         int additionalAttributeCount = 0;
    417         Set<Integer> supportedApkSigSchemeIds = supportedApkSigSchemeNames.keySet();
    418         Set<Integer> supportedExpectedApkSigSchemeIds = new HashSet<>(1);
    419         while (additionalAttributes.hasRemaining()) {
    420             additionalAttributeCount++;
    421             try {
    422                 ByteBuffer attribute =
    423                         ApkSigningBlockUtils.getLengthPrefixedSlice(additionalAttributes);
    424                 int id = attribute.getInt();
    425                 byte[] value = ByteBufferUtils.toByteArray(attribute);
    426                 result.additionalAttributes.add(
    427                         new ApkSigningBlockUtils.Result.SignerInfo.AdditionalAttribute(id, value));
    428                 switch (id) {
    429                     case V2SchemeSigner.STRIPPING_PROTECTION_ATTR_ID:
    430                         // stripping protection added when signing with a newer scheme
    431                         int foundId = attribute.getInt();
    432                         if (supportedApkSigSchemeIds.contains(foundId)) {
    433                             supportedExpectedApkSigSchemeIds.add(id);
    434                         } else {
    435                             result.addWarning(
    436                                     Issue.V2_SIG_UNKNOWN_APK_SIG_SCHEME_ID, result.index, foundId);
    437                         }
    438                         break;
    439                     default:
    440                         result.addWarning(Issue.V2_SIG_UNKNOWN_ADDITIONAL_ATTRIBUTE, id);
    441                 }
    442             } catch (ApkFormatException | BufferUnderflowException e) {
    443                 result.addError(
    444                         Issue.V2_SIG_MALFORMED_ADDITIONAL_ATTRIBUTE, additionalAttributeCount);
    445                 return;
    446             }
    447         }
    448 
    449         // make sure that all known IDs indicated in stripping protection have already verified
    450         for (int id : supportedExpectedApkSigSchemeIds) {
    451             if (!foundApkSigSchemeIds.contains(id)) {
    452                 String apkSigSchemeName = supportedApkSigSchemeNames.get(id);
    453                 result.addError(
    454                         Issue.V2_SIG_MISSING_APK_SIG_REFERENCED,
    455                         result.index,
    456                         apkSigSchemeName);
    457             }
    458         }
    459     }
    460 }
    461