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;
18 
19 import com.android.apksig.apk.ApkFormatException;
20 import com.android.apksig.apk.ApkUtils;
21 import com.android.apksig.internal.apk.AndroidBinXmlParser;
22 import com.android.apksig.internal.apk.ApkSigningBlockUtils;
23 import com.android.apksig.internal.apk.v1.V1SchemeVerifier;
24 import com.android.apksig.internal.apk.ContentDigestAlgorithm;
25 import com.android.apksig.internal.apk.SignatureAlgorithm;
26 import com.android.apksig.internal.apk.v2.V2SchemeVerifier;
27 import com.android.apksig.internal.apk.v3.V3SchemeVerifier;
28 import com.android.apksig.internal.util.AndroidSdkVersion;
29 import com.android.apksig.internal.zip.CentralDirectoryRecord;
30 import com.android.apksig.util.DataSource;
31 import com.android.apksig.util.DataSources;
32 import com.android.apksig.util.RunnablesExecutor;
33 import com.android.apksig.zip.ZipFormatException;
34 import java.io.Closeable;
35 import java.io.File;
36 import java.io.IOException;
37 import java.io.RandomAccessFile;
38 import java.nio.ByteBuffer;
39 import java.security.NoSuchAlgorithmException;
40 import java.security.cert.CertificateEncodingException;
41 import java.security.cert.X509Certificate;
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.Collections;
45 import java.util.HashMap;
46 import java.util.HashSet;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Set;
50 
51 /**
52  * APK signature verifier which mimics the behavior of the Android platform.
53  *
54  * <p>The verifier is designed to closely mimic the behavior of Android platforms. This is to enable
55  * the verifier to be used for checking whether an APK's signatures are expected to verify on
56  * Android.
57  *
58  * <p>Use {@link Builder} to obtain instances of this verifier.
59  *
60  * @see <a href="https://source.android.com/security/apksigning/index.html">Application Signing</a>
61  */
62 public class ApkVerifier {
63 
64     private static final Map<Integer, String> SUPPORTED_APK_SIG_SCHEME_NAMES =
65             loadSupportedApkSigSchemeNames();
66 
loadSupportedApkSigSchemeNames()67     private static Map<Integer,String> loadSupportedApkSigSchemeNames() {
68         Map<Integer, String> supportedMap = new HashMap<>(2);
69         supportedMap.put(
70                 ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2, "APK Signature Scheme v2");
71         supportedMap.put(
72                 ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3, "APK Signature Scheme v3");
73         return supportedMap;
74     }
75 
76     private final File mApkFile;
77     private final DataSource mApkDataSource;
78 
79     private final Integer mMinSdkVersion;
80     private final int mMaxSdkVersion;
81 
ApkVerifier( File apkFile, DataSource apkDataSource, Integer minSdkVersion, int maxSdkVersion)82     private ApkVerifier(
83             File apkFile,
84             DataSource apkDataSource,
85             Integer minSdkVersion,
86             int maxSdkVersion) {
87         mApkFile = apkFile;
88         mApkDataSource = apkDataSource;
89         mMinSdkVersion = minSdkVersion;
90         mMaxSdkVersion = maxSdkVersion;
91     }
92 
93     /**
94      * Verifies the APK's signatures and returns the result of verification. The APK can be
95      * considered verified iff the result's {@link Result#isVerified()} returns {@code true}.
96      * The verification result also includes errors, warnings, and information about signers such
97      * as their signing certificates.
98      *
99      * <p>Verification succeeds iff the APK's signature is expected to verify on all Android
100      * platform versions specified via the {@link Builder}. If the APK's signature is expected to
101      * not verify on any of the specified platform versions, this method returns a result with one
102      * or more errors and whose {@link Result#isVerified()} returns {@code false}, or this method
103      * throws an exception.
104      *
105      * @throws IOException if an I/O error is encountered while reading the APK
106      * @throws ApkFormatException if the APK is malformed
107      * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a
108      *         required cryptographic algorithm implementation is missing
109      * @throws IllegalStateException if this verifier's configuration is missing required
110      *         information.
111      */
verify()112     public Result verify() throws IOException, ApkFormatException, NoSuchAlgorithmException,
113             IllegalStateException {
114         Closeable in = null;
115         try {
116             DataSource apk;
117             if (mApkDataSource != null) {
118                 apk = mApkDataSource;
119             } else if (mApkFile != null) {
120                 RandomAccessFile f = new RandomAccessFile(mApkFile, "r");
121                 in = f;
122                 apk = DataSources.asDataSource(f, 0, f.length());
123             } else {
124                 throw new IllegalStateException("APK not provided");
125             }
126             return verify(apk);
127         } finally {
128             if (in != null) {
129                 in.close();
130             }
131         }
132     }
133 
134     /**
135      * Verifies the APK's signatures and returns the result of verification. The APK can be
136      * considered verified iff the result's {@link Result#isVerified()} returns {@code true}.
137      * The verification result also includes errors, warnings, and information about signers.
138      *
139      * @param apk APK file contents
140      *
141      * @throws IOException if an I/O error is encountered while reading the APK
142      * @throws ApkFormatException if the APK is malformed
143      * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a
144      *         required cryptographic algorithm implementation is missing
145      */
verify(DataSource apk)146     private Result verify(DataSource apk)
147             throws IOException, ApkFormatException, NoSuchAlgorithmException {
148         if (mMinSdkVersion != null) {
149             if (mMinSdkVersion < 0) {
150                 throw new IllegalArgumentException(
151                         "minSdkVersion must not be negative: " + mMinSdkVersion);
152             }
153             if ((mMinSdkVersion != null) && (mMinSdkVersion > mMaxSdkVersion)) {
154                 throw new IllegalArgumentException(
155                         "minSdkVersion (" + mMinSdkVersion + ") > maxSdkVersion (" + mMaxSdkVersion
156                                 + ")");
157             }
158         }
159         int maxSdkVersion = mMaxSdkVersion;
160 
161         ApkUtils.ZipSections zipSections;
162         try {
163             zipSections = ApkUtils.findZipSections(apk);
164         } catch (ZipFormatException e) {
165             throw new ApkFormatException("Malformed APK: not a ZIP archive", e);
166         }
167 
168         ByteBuffer androidManifest = null;
169 
170         int minSdkVersion;
171         if (mMinSdkVersion != null) {
172             // No need to obtain minSdkVersion from the APK's AndroidManifest.xml
173             minSdkVersion = mMinSdkVersion;
174         } else {
175             // Need to obtain minSdkVersion from the APK's AndroidManifest.xml
176             if (androidManifest == null) {
177                 androidManifest = getAndroidManifestFromApk(apk, zipSections);
178             }
179             minSdkVersion =
180                     ApkUtils.getMinSdkVersionFromBinaryAndroidManifest(androidManifest.slice());
181             if (minSdkVersion > mMaxSdkVersion) {
182                 throw new IllegalArgumentException(
183                         "minSdkVersion from APK (" + minSdkVersion + ") > maxSdkVersion ("
184                                 + mMaxSdkVersion + ")");
185             }
186         }
187 
188         Result result = new Result();
189 
190         // The SUPPORTED_APK_SIG_SCHEME_NAMES contains the mapping from version number to scheme
191         // name, but the verifiers use this parameter as the schemes supported by the target SDK
192         // range. Since the code below skips signature verification based on max SDK the mapping of
193         // supported schemes needs to be modified to ensure the verifiers do not report a stripped
194         // signature for an SDK range that does not support that signature version. For instance an
195         // APK with V1, V2, and V3 signatures and a max SDK of O would skip the V3 signature
196         // verification, but the SUPPORTED_APK_SIG_SCHEME_NAMES contains version 3, so when the V2
197         // verification is performed it would see the stripping protection attribute, see that V3
198         // is in the list of supported signatures, and report a stripped signature.
199         Map<Integer, String> supportedSchemeNames;
200         if (maxSdkVersion >= AndroidSdkVersion.P) {
201             supportedSchemeNames = SUPPORTED_APK_SIG_SCHEME_NAMES;
202         } else if (maxSdkVersion >= AndroidSdkVersion.N) {
203             supportedSchemeNames = new HashMap<>(1);
204             supportedSchemeNames.put(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2,
205                     SUPPORTED_APK_SIG_SCHEME_NAMES.get(
206                             ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2));
207         } else {
208             supportedSchemeNames = Collections.EMPTY_MAP;
209         }
210         // Android N and newer attempts to verify APKs using the APK Signing Block, which can
211         // include v2 and/or v3 signatures.  If none is found, it falls back to JAR signature
212         // verification. If the signature is found but does not verify, the APK is rejected.
213         Set<Integer> foundApkSigSchemeIds = new HashSet<>(2);
214         if (maxSdkVersion >= AndroidSdkVersion.N) {
215             RunnablesExecutor executor = RunnablesExecutor.SINGLE_THREADED;
216             // Android P and newer attempts to verify APKs using APK Signature Scheme v3
217             if (maxSdkVersion >= AndroidSdkVersion.P) {
218                 try {
219                     ApkSigningBlockUtils.Result v3Result =
220                             V3SchemeVerifier.verify(
221                                     executor,
222                                     apk,
223                                     zipSections,
224                                     Math.max(minSdkVersion, AndroidSdkVersion.P),
225                                     maxSdkVersion);
226                     foundApkSigSchemeIds.add(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3);
227                     result.mergeFrom(v3Result);
228                 } catch (ApkSigningBlockUtils.SignatureNotFoundException ignored) {
229                     // v3 signature not required
230                 }
231                 if (result.containsErrors()) {
232                     return result;
233                 }
234             }
235 
236             // Attempt to verify the APK using v2 signing if necessary. Platforms prior to Android P
237             // ignore APK Signature Scheme v3 signatures and always attempt to verify either JAR or
238             // APK Signature Scheme v2 signatures.  Android P onwards verifies v2 signatures only if
239             // no APK Signature Scheme v3 (or newer scheme) signatures were found.
240             if (minSdkVersion < AndroidSdkVersion.P || foundApkSigSchemeIds.isEmpty()) {
241                 try {
242                     ApkSigningBlockUtils.Result v2Result =
243                             V2SchemeVerifier.verify(
244                                     executor,
245                                     apk,
246                                     zipSections,
247                                     supportedSchemeNames,
248                                     foundApkSigSchemeIds,
249                                     Math.max(minSdkVersion, AndroidSdkVersion.N),
250                                     maxSdkVersion);
251                     foundApkSigSchemeIds.add(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2);
252                     result.mergeFrom(v2Result);
253                 } catch (ApkSigningBlockUtils.SignatureNotFoundException ignored) {
254                     // v2 signature not required
255                 }
256                 if (result.containsErrors()) {
257                     return result;
258                 }
259             }
260         }
261 
262         // Android O and newer requires that APKs targeting security sandbox version 2 and higher
263         // are signed using APK Signature Scheme v2 or newer.
264         if (maxSdkVersion >= AndroidSdkVersion.O) {
265             if (androidManifest == null) {
266                 androidManifest = getAndroidManifestFromApk(apk, zipSections);
267             }
268             int targetSandboxVersion =
269                     getTargetSandboxVersionFromBinaryAndroidManifest(androidManifest.slice());
270             if (targetSandboxVersion > 1) {
271                 if (foundApkSigSchemeIds.isEmpty()) {
272                     result.addError(
273                             Issue.NO_SIG_FOR_TARGET_SANDBOX_VERSION,
274                             targetSandboxVersion);
275                 }
276             }
277         }
278 
279         // Attempt to verify the APK using JAR signing if necessary. Platforms prior to Android N
280         // ignore APK Signature Scheme v2 signatures and always attempt to verify JAR signatures.
281         // Android N onwards verifies JAR signatures only if no APK Signature Scheme v2 (or newer
282         // scheme) signatures were found.
283         if ((minSdkVersion < AndroidSdkVersion.N) || (foundApkSigSchemeIds.isEmpty())) {
284             V1SchemeVerifier.Result v1Result =
285                     V1SchemeVerifier.verify(
286                             apk,
287                             zipSections,
288                             supportedSchemeNames,
289                             foundApkSigSchemeIds,
290                             minSdkVersion,
291                             maxSdkVersion);
292             result.mergeFrom(v1Result);
293         }
294         if (result.containsErrors()) {
295             return result;
296         }
297 
298         // Check whether v1 and v2 scheme signer identifies match, provided both v1 and v2
299         // signatures verified.
300         if ((result.isVerifiedUsingV1Scheme()) && (result.isVerifiedUsingV2Scheme())) {
301             ArrayList<Result.V1SchemeSignerInfo> v1Signers =
302                     new ArrayList<>(result.getV1SchemeSigners());
303             ArrayList<Result.V2SchemeSignerInfo> v2Signers =
304                     new ArrayList<>(result.getV2SchemeSigners());
305             ArrayList<ByteArray> v1SignerCerts = new ArrayList<>();
306             ArrayList<ByteArray> v2SignerCerts = new ArrayList<>();
307             for (Result.V1SchemeSignerInfo signer : v1Signers) {
308                 try {
309                     v1SignerCerts.add(new ByteArray(signer.getCertificate().getEncoded()));
310                 } catch (CertificateEncodingException e) {
311                     throw new RuntimeException(
312                             "Failed to encode JAR signer " + signer.getName() + " certs", e);
313                 }
314             }
315             for (Result.V2SchemeSignerInfo signer : v2Signers) {
316                 try {
317                     v2SignerCerts.add(new ByteArray(signer.getCertificate().getEncoded()));
318                 } catch (CertificateEncodingException e) {
319                     throw new RuntimeException(
320                             "Failed to encode APK Signature Scheme v2 signer (index: "
321                                     + signer.getIndex() + ") certs",
322                             e);
323                 }
324             }
325 
326             for (int i = 0; i < v1SignerCerts.size(); i++) {
327                 ByteArray v1Cert = v1SignerCerts.get(i);
328                 if (!v2SignerCerts.contains(v1Cert)) {
329                     Result.V1SchemeSignerInfo v1Signer = v1Signers.get(i);
330                     v1Signer.addError(Issue.V2_SIG_MISSING);
331                     break;
332                 }
333             }
334             for (int i = 0; i < v2SignerCerts.size(); i++) {
335                 ByteArray v2Cert = v2SignerCerts.get(i);
336                 if (!v1SignerCerts.contains(v2Cert)) {
337                     Result.V2SchemeSignerInfo v2Signer = v2Signers.get(i);
338                     v2Signer.addError(Issue.JAR_SIG_MISSING);
339                     break;
340                 }
341             }
342         }
343 
344         // If there is a v3 scheme signer and an earlier scheme signer, make sure that there is a
345         // match, or in the event of signing certificate rotation, that the v1/v2 scheme signer
346         // matches the oldest signing certificate in the provided SigningCertificateLineage
347         if (result.isVerifiedUsingV3Scheme()
348                 && (result.isVerifiedUsingV1Scheme() || result.isVerifiedUsingV2Scheme())) {
349             SigningCertificateLineage lineage = result.getSigningCertificateLineage();
350             X509Certificate oldSignerCert;
351             if (result.isVerifiedUsingV1Scheme()) {
352                 List<Result.V1SchemeSignerInfo> v1Signers = result.getV1SchemeSigners();
353                 if (v1Signers.size() != 1) {
354                     // APK Signature Scheme v3 only supports single-signers, error to sign with
355                     // multiple and then only one
356                     result.addError(Issue.V3_SIG_MULTIPLE_PAST_SIGNERS);
357                 }
358                 oldSignerCert = v1Signers.get(0).mCertChain.get(0);
359             } else {
360                 List<Result.V2SchemeSignerInfo> v2Signers = result.getV2SchemeSigners();
361                 if (v2Signers.size() != 1) {
362                     // APK Signature Scheme v3 only supports single-signers, error to sign with
363                     // multiple and then only one
364                     result.addError(Issue.V3_SIG_MULTIPLE_PAST_SIGNERS);
365                 }
366                 oldSignerCert = v2Signers.get(0).mCerts.get(0);
367             }
368             if (lineage == null) {
369                 // no signing certificate history with which to contend, just make sure that v3
370                 // matches previous versions
371                 List<Result.V3SchemeSignerInfo> v3Signers = result.getV3SchemeSigners();
372                 if (v3Signers.size() != 1) {
373                     // multiple v3 signers should never exist without rotation history, since
374                     // multiple signers implies a different signer for different platform versions
375                     result.addError(Issue.V3_SIG_MULTIPLE_SIGNERS);
376                 }
377                 try {
378                     if (!Arrays.equals(oldSignerCert.getEncoded(),
379                            v3Signers.get(0).mCerts.get(0).getEncoded())) {
380                         result.addError(Issue.V3_SIG_PAST_SIGNERS_MISMATCH);
381                     }
382                 } catch (CertificateEncodingException e) {
383                     // we just go the encoding for the v1/v2 certs above, so must be v3
384                     throw new RuntimeException(
385                             "Failed to encode APK Signature Scheme v3 signer cert", e);
386                 }
387             } else {
388                 // we have some signing history, make sure that the root of the history is the same
389                 // as our v1/v2 signer
390                 try {
391                     lineage = lineage.getSubLineage(oldSignerCert);
392                     if (lineage.size() != 1) {
393                         // the v1/v2 signer was found, but not at the root of the lineage
394                         result.addError(Issue.V3_SIG_PAST_SIGNERS_MISMATCH);
395                     }
396                 } catch (IllegalArgumentException e) {
397                     // the v1/v2 signer was not found in the lineage
398                     result.addError(Issue.V3_SIG_PAST_SIGNERS_MISMATCH);
399                 }
400             }
401         }
402 
403         if (result.containsErrors()) {
404             return result;
405         }
406 
407         // Verified
408         result.setVerified();
409         if (result.isVerifiedUsingV3Scheme()) {
410             List<Result.V3SchemeSignerInfo> v3Signers = result.getV3SchemeSigners();
411             result.addSignerCertificate(v3Signers.get(v3Signers.size() - 1).getCertificate());
412         } else if (result.isVerifiedUsingV2Scheme()) {
413             for (Result.V2SchemeSignerInfo signerInfo : result.getV2SchemeSigners()) {
414                 result.addSignerCertificate(signerInfo.getCertificate());
415             }
416         } else if (result.isVerifiedUsingV1Scheme()) {
417             for (Result.V1SchemeSignerInfo signerInfo : result.getV1SchemeSigners()) {
418                 result.addSignerCertificate(signerInfo.getCertificate());
419             }
420         } else {
421             throw new RuntimeException(
422                     "APK verified, but has not verified using any of v1, v2 or v3schemes");
423         }
424 
425         return result;
426     }
427 
getAndroidManifestFromApk( DataSource apk, ApkUtils.ZipSections zipSections)428     private static ByteBuffer getAndroidManifestFromApk(
429             DataSource apk, ApkUtils.ZipSections zipSections)
430                     throws IOException, ApkFormatException {
431         List<CentralDirectoryRecord> cdRecords =
432                 V1SchemeVerifier.parseZipCentralDirectory(apk, zipSections);
433         try {
434             return ApkSigner.getAndroidManifestFromApk(
435                     cdRecords,
436                     apk.slice(0, zipSections.getZipCentralDirectoryOffset()));
437         } catch (ZipFormatException e) {
438             throw new ApkFormatException("Failed to read AndroidManifest.xml", e);
439         }
440     }
441 
442     /**
443      * Android resource ID of the {@code android:targetSandboxVersion} attribute in
444      * AndroidManifest.xml.
445      */
446     private static final int TARGET_SANDBOX_VERSION_ATTR_ID = 0x0101054c;
447 
448     /**
449      * Returns the security sandbox version targeted by an APK with the provided
450      * {@code AndroidManifest.xml}.
451      *
452      * @param androidManifestContents contents of {@code AndroidManifest.xml} in binary Android
453      *        resource format
454      *
455      * @throws ApkFormatException if an error occurred while determining the version
456      */
getTargetSandboxVersionFromBinaryAndroidManifest( ByteBuffer androidManifestContents)457     private static int getTargetSandboxVersionFromBinaryAndroidManifest(
458             ByteBuffer androidManifestContents) throws ApkFormatException {
459         // Return the value of the android:targetSandboxVersion attribute of the top-level manifest
460         // element
461         try {
462             AndroidBinXmlParser parser = new AndroidBinXmlParser(androidManifestContents);
463             int eventType = parser.getEventType();
464             while (eventType != AndroidBinXmlParser.EVENT_END_DOCUMENT) {
465                 if ((eventType == AndroidBinXmlParser.EVENT_START_ELEMENT)
466                         && (parser.getDepth() == 1)
467                         && ("manifest".equals(parser.getName()))
468                         && (parser.getNamespace().isEmpty())) {
469                     // In each manifest element, targetSandboxVersion defaults to 1
470                     int result = 1;
471                     for (int i = 0; i < parser.getAttributeCount(); i++) {
472                         if (parser.getAttributeNameResourceId(i)
473                                 == TARGET_SANDBOX_VERSION_ATTR_ID) {
474                             int valueType = parser.getAttributeValueType(i);
475                             switch (valueType) {
476                                 case AndroidBinXmlParser.VALUE_TYPE_INT:
477                                     result = parser.getAttributeIntValue(i);
478                                     break;
479                                 default:
480                                     throw new ApkFormatException(
481                                             "Failed to determine APK's target sandbox version"
482                                                     + ": unsupported value type of"
483                                                     + " AndroidManifest.xml"
484                                                     + " android:targetSandboxVersion"
485                                                     + ". Only integer values supported.");
486                             }
487                             break;
488                         }
489                     }
490                     return result;
491                 }
492                 eventType = parser.next();
493             }
494             throw new ApkFormatException(
495                     "Failed to determine APK's target sandbox version"
496                             + " : no manifest element in AndroidManifest.xml");
497         } catch (AndroidBinXmlParser.XmlParserException e) {
498             throw new ApkFormatException(
499                     "Failed to determine APK's target sandbox version"
500                             + ": malformed AndroidManifest.xml",
501                     e);
502         }
503     }
504 
505     /**
506      * Result of verifying an APKs signatures. The APK can be considered verified iff
507      * {@link #isVerified()} returns {@code true}.
508      */
509     public static class Result {
510         private final List<IssueWithParams> mErrors = new ArrayList<>();
511         private final List<IssueWithParams> mWarnings = new ArrayList<>();
512         private final List<X509Certificate> mSignerCerts = new ArrayList<>();
513         private final List<V1SchemeSignerInfo> mV1SchemeSigners = new ArrayList<>();
514         private final List<V1SchemeSignerInfo> mV1SchemeIgnoredSigners = new ArrayList<>();
515         private final List<V2SchemeSignerInfo> mV2SchemeSigners = new ArrayList<>();
516         private final List<V3SchemeSignerInfo> mV3SchemeSigners = new ArrayList<>();
517 
518         private boolean mVerified;
519         private boolean mVerifiedUsingV1Scheme;
520         private boolean mVerifiedUsingV2Scheme;
521         private boolean mVerifiedUsingV3Scheme;
522         private SigningCertificateLineage mSigningCertificateLineage;
523 
524         /**
525          * Returns {@code true} if the APK's signatures verified.
526          */
isVerified()527         public boolean isVerified() {
528             return mVerified;
529         }
530 
setVerified()531         private void setVerified() {
532             mVerified = true;
533         }
534 
535         /**
536          * Returns {@code true} if the APK's JAR signatures verified.
537          */
isVerifiedUsingV1Scheme()538         public boolean isVerifiedUsingV1Scheme() {
539             return mVerifiedUsingV1Scheme;
540         }
541 
542         /**
543          * Returns {@code true} if the APK's APK Signature Scheme v2 signatures verified.
544          */
isVerifiedUsingV2Scheme()545         public boolean isVerifiedUsingV2Scheme() {
546             return mVerifiedUsingV2Scheme;
547         }
548 
549         /**
550          * Returns {@code true} if the APK's APK Signature Scheme v3 signature verified.
551          */
isVerifiedUsingV3Scheme()552         public boolean isVerifiedUsingV3Scheme() {
553             return mVerifiedUsingV3Scheme;
554         }
555 
556         /**
557          * Returns the verified signers' certificates, one per signer.
558          */
getSignerCertificates()559         public List<X509Certificate> getSignerCertificates() {
560             return mSignerCerts;
561         }
562 
addSignerCertificate(X509Certificate cert)563         private void addSignerCertificate(X509Certificate cert) {
564             mSignerCerts.add(cert);
565         }
566 
567         /**
568          * Returns information about JAR signers associated with the APK's signature. These are the
569          * signers used by Android.
570          *
571          * @see #getV1SchemeIgnoredSigners()
572          */
getV1SchemeSigners()573         public List<V1SchemeSignerInfo> getV1SchemeSigners() {
574             return mV1SchemeSigners;
575         }
576 
577         /**
578          * Returns information about JAR signers ignored by the APK's signature verification
579          * process. These signers are ignored by Android. However, each signer's errors or warnings
580          * will contain information about why they are ignored.
581          *
582          * @see #getV1SchemeSigners()
583          */
getV1SchemeIgnoredSigners()584         public List<V1SchemeSignerInfo> getV1SchemeIgnoredSigners() {
585             return mV1SchemeIgnoredSigners;
586         }
587 
588         /**
589          * Returns information about APK Signature Scheme v2 signers associated with the APK's
590          * signature.
591          */
getV2SchemeSigners()592         public List<V2SchemeSignerInfo> getV2SchemeSigners() {
593             return mV2SchemeSigners;
594         }
595 
596         /**
597          * Returns information about APK Signature Scheme v3 signers associated with the APK's
598          * signature.
599          *
600          * <note> Multiple signers represent different targeted platform versions, not
601          * a signing identity of multiple signers.  APK Signature Scheme v3 only supports single
602          * signer identities.</note>
603          */
getV3SchemeSigners()604         public List<V3SchemeSignerInfo> getV3SchemeSigners() {
605             return mV3SchemeSigners;
606         }
607 
608         /**
609          * Returns the combined SigningCertificateLineage associated with this APK's APK Signature
610          * Scheme v3 signing block.
611          */
getSigningCertificateLineage()612         public SigningCertificateLineage getSigningCertificateLineage() {
613             return mSigningCertificateLineage;
614         }
615 
addError(Issue msg, Object... parameters)616         void addError(Issue msg, Object... parameters) {
617             mErrors.add(new IssueWithParams(msg, parameters));
618         }
619 
620         /**
621          * Returns errors encountered while verifying the APK's signatures.
622          */
getErrors()623         public List<IssueWithParams> getErrors() {
624             return mErrors;
625         }
626 
627         /**
628          * Returns warnings encountered while verifying the APK's signatures.
629          */
getWarnings()630         public List<IssueWithParams> getWarnings() {
631             return mWarnings;
632         }
633 
mergeFrom(V1SchemeVerifier.Result source)634         private void mergeFrom(V1SchemeVerifier.Result source) {
635             mVerifiedUsingV1Scheme = source.verified;
636             mErrors.addAll(source.getErrors());
637             mWarnings.addAll(source.getWarnings());
638             for (V1SchemeVerifier.Result.SignerInfo signer : source.signers) {
639                 mV1SchemeSigners.add(new V1SchemeSignerInfo(signer));
640             }
641             for (V1SchemeVerifier.Result.SignerInfo signer : source.ignoredSigners) {
642                 mV1SchemeIgnoredSigners.add(new V1SchemeSignerInfo(signer));
643             }
644         }
645 
mergeFrom(ApkSigningBlockUtils.Result source)646         private void mergeFrom(ApkSigningBlockUtils.Result source) {
647             switch (source.signatureSchemeVersion) {
648                 case ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2:
649                     mVerifiedUsingV2Scheme = source.verified;
650                     for (ApkSigningBlockUtils.Result.SignerInfo signer : source.signers) {
651                         mV2SchemeSigners.add(new V2SchemeSignerInfo(signer));
652                     }
653                     break;
654                 case ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3:
655                     mVerifiedUsingV3Scheme = source.verified;
656                     for (ApkSigningBlockUtils.Result.SignerInfo signer : source.signers) {
657                         mV3SchemeSigners.add(new V3SchemeSignerInfo(signer));
658                     }
659                     mSigningCertificateLineage = source.signingCertificateLineage;
660                     break;
661                 default:
662                     throw new IllegalArgumentException("Unknown Signing Block Scheme Id");
663             }
664             mErrors.addAll(source.getErrors());
665             mWarnings.addAll(source.getWarnings());
666         }
667 
668         /**
669          * Returns {@code true} if an error was encountered while verifying the APK. Any error
670          * prevents the APK from being considered verified.
671          */
containsErrors()672         public boolean containsErrors() {
673             if (!mErrors.isEmpty()) {
674                 return true;
675             }
676             if (!mV1SchemeSigners.isEmpty()) {
677                 for (V1SchemeSignerInfo signer : mV1SchemeSigners) {
678                     if (signer.containsErrors()) {
679                         return true;
680                     }
681                 }
682             }
683             if (!mV2SchemeSigners.isEmpty()) {
684                 for (V2SchemeSignerInfo signer : mV2SchemeSigners) {
685                     if (signer.containsErrors()) {
686                         return true;
687                     }
688                 }
689             }
690             if (!mV3SchemeSigners.isEmpty()) {
691                 for (V3SchemeSignerInfo signer : mV3SchemeSigners) {
692                     if (signer.containsErrors()) {
693                         return true;
694                     }
695                 }
696             }
697 
698             return false;
699         }
700 
701         /**
702          * Information about a JAR signer associated with the APK's signature.
703          */
704         public static class V1SchemeSignerInfo {
705             private final String mName;
706             private final List<X509Certificate> mCertChain;
707             private final String mSignatureBlockFileName;
708             private final String mSignatureFileName;
709 
710             private final List<IssueWithParams> mErrors;
711             private final List<IssueWithParams> mWarnings;
712 
V1SchemeSignerInfo(V1SchemeVerifier.Result.SignerInfo result)713             private V1SchemeSignerInfo(V1SchemeVerifier.Result.SignerInfo result) {
714                 mName = result.name;
715                 mCertChain = result.certChain;
716                 mSignatureBlockFileName = result.signatureBlockFileName;
717                 mSignatureFileName = result.signatureFileName;
718                 mErrors = result.getErrors();
719                 mWarnings = result.getWarnings();
720             }
721 
722             /**
723              * Returns a user-friendly name of the signer.
724              */
getName()725             public String getName() {
726                 return mName;
727             }
728 
729             /**
730              * Returns the name of the JAR entry containing this signer's JAR signature block file.
731              */
getSignatureBlockFileName()732             public String getSignatureBlockFileName() {
733                 return mSignatureBlockFileName;
734             }
735 
736             /**
737              * Returns the name of the JAR entry containing this signer's JAR signature file.
738              */
getSignatureFileName()739             public String getSignatureFileName() {
740                 return mSignatureFileName;
741             }
742 
743             /**
744              * Returns this signer's signing certificate or {@code null} if not available. The
745              * certificate is guaranteed to be available if no errors were encountered during
746              * verification (see {@link #containsErrors()}.
747              *
748              * <p>This certificate contains the signer's public key.
749              */
getCertificate()750             public X509Certificate getCertificate() {
751                 return mCertChain.isEmpty() ? null : mCertChain.get(0);
752             }
753 
754             /**
755              * Returns the certificate chain for the signer's public key. The certificate containing
756              * the public key is first, followed by the certificate (if any) which issued the
757              * signing certificate, and so forth. An empty list may be returned if an error was
758              * encountered during verification (see {@link #containsErrors()}).
759              */
getCertificateChain()760             public List<X509Certificate> getCertificateChain() {
761                 return mCertChain;
762             }
763 
764             /**
765              * Returns {@code true} if an error was encountered while verifying this signer's JAR
766              * signature. Any error prevents the signer's signature from being considered verified.
767              */
containsErrors()768             public boolean containsErrors() {
769                 return !mErrors.isEmpty();
770             }
771 
772             /**
773              * Returns errors encountered while verifying this signer's JAR signature. Any error
774              * prevents the signer's signature from being considered verified.
775              */
getErrors()776             public List<IssueWithParams> getErrors() {
777                 return mErrors;
778             }
779 
780             /**
781              * Returns warnings encountered while verifying this signer's JAR signature. Warnings
782              * do not prevent the signer's signature from being considered verified.
783              */
getWarnings()784             public List<IssueWithParams> getWarnings() {
785                 return mWarnings;
786             }
787 
addError(Issue msg, Object... parameters)788             private void addError(Issue msg, Object... parameters) {
789                 mErrors.add(new IssueWithParams(msg, parameters));
790             }
791         }
792 
793         /**
794          * Information about an APK Signature Scheme v2 signer associated with the APK's signature.
795          */
796         public static class V2SchemeSignerInfo {
797             private final int mIndex;
798             private final List<X509Certificate> mCerts;
799 
800             private final List<IssueWithParams> mErrors;
801             private final List<IssueWithParams> mWarnings;
802 
V2SchemeSignerInfo(ApkSigningBlockUtils.Result.SignerInfo result)803             private V2SchemeSignerInfo(ApkSigningBlockUtils.Result.SignerInfo result) {
804                 mIndex = result.index;
805                 mCerts = result.certs;
806                 mErrors = result.getErrors();
807                 mWarnings = result.getWarnings();
808             }
809 
810             /**
811              * Returns this signer's {@code 0}-based index in the list of signers contained in the
812              * APK's APK Signature Scheme v2 signature.
813              */
getIndex()814             public int getIndex() {
815                 return mIndex;
816             }
817 
818             /**
819              * Returns this signer's signing certificate or {@code null} if not available. The
820              * certificate is guaranteed to be available if no errors were encountered during
821              * verification (see {@link #containsErrors()}.
822              *
823              * <p>This certificate contains the signer's public key.
824              */
getCertificate()825             public X509Certificate getCertificate() {
826                 return mCerts.isEmpty() ? null : mCerts.get(0);
827             }
828 
829             /**
830              * Returns this signer's certificates. The first certificate is for the signer's public
831              * key. An empty list may be returned if an error was encountered during verification
832              * (see {@link #containsErrors()}).
833              */
getCertificates()834             public List<X509Certificate> getCertificates() {
835                 return mCerts;
836             }
837 
addError(Issue msg, Object... parameters)838             private void addError(Issue msg, Object... parameters) {
839                 mErrors.add(new IssueWithParams(msg, parameters));
840             }
841 
containsErrors()842             public boolean containsErrors() {
843                 return !mErrors.isEmpty();
844             }
845 
getErrors()846             public List<IssueWithParams> getErrors() {
847                 return mErrors;
848             }
849 
getWarnings()850             public List<IssueWithParams> getWarnings() {
851                 return mWarnings;
852             }
853         }
854 
855         /**
856          * Information about an APK Signature Scheme v3 signer associated with the APK's signature.
857          */
858         public static class V3SchemeSignerInfo {
859             private final int mIndex;
860             private final List<X509Certificate> mCerts;
861 
862             private final List<IssueWithParams> mErrors;
863             private final List<IssueWithParams> mWarnings;
864 
V3SchemeSignerInfo(ApkSigningBlockUtils.Result.SignerInfo result)865             private V3SchemeSignerInfo(ApkSigningBlockUtils.Result.SignerInfo result) {
866                 mIndex = result.index;
867                 mCerts = result.certs;
868                 mErrors = result.getErrors();
869                 mWarnings = result.getWarnings();
870             }
871 
872             /**
873              * Returns this signer's {@code 0}-based index in the list of signers contained in the
874              * APK's APK Signature Scheme v3 signature.
875              */
getIndex()876             public int getIndex() {
877                 return mIndex;
878             }
879 
880             /**
881              * Returns this signer's signing certificate or {@code null} if not available. The
882              * certificate is guaranteed to be available if no errors were encountered during
883              * verification (see {@link #containsErrors()}.
884              *
885              * <p>This certificate contains the signer's public key.
886              */
getCertificate()887             public X509Certificate getCertificate() {
888                 return mCerts.isEmpty() ? null : mCerts.get(0);
889             }
890 
891             /**
892              * Returns this signer's certificates. The first certificate is for the signer's public
893              * key. An empty list may be returned if an error was encountered during verification
894              * (see {@link #containsErrors()}).
895              */
getCertificates()896             public List<X509Certificate> getCertificates() {
897                 return mCerts;
898             }
899 
containsErrors()900             public boolean containsErrors() {
901                 return !mErrors.isEmpty();
902             }
903 
getErrors()904             public List<IssueWithParams> getErrors() {
905                 return mErrors;
906             }
907 
getWarnings()908             public List<IssueWithParams> getWarnings() {
909                 return mWarnings;
910             }
911         }
912     }
913 
914     /**
915      * Error or warning encountered while verifying an APK's signatures.
916      */
917     public static enum Issue {
918 
919         /**
920          * APK is not JAR-signed.
921          */
922         JAR_SIG_NO_SIGNATURES("No JAR signatures"),
923 
924         /**
925          * APK does not contain any entries covered by JAR signatures.
926          */
927         JAR_SIG_NO_SIGNED_ZIP_ENTRIES("No JAR entries covered by JAR signatures"),
928 
929         /**
930          * APK contains multiple entries with the same name.
931          *
932          * <ul>
933          * <li>Parameter 1: name ({@code String})</li>
934          * </ul>
935          */
936         JAR_SIG_DUPLICATE_ZIP_ENTRY("Duplicate entry: %1$s"),
937 
938         /**
939          * JAR manifest contains a section with a duplicate name.
940          *
941          * <ul>
942          * <li>Parameter 1: section name ({@code String})</li>
943          * </ul>
944          */
945         JAR_SIG_DUPLICATE_MANIFEST_SECTION("Duplicate section in META-INF/MANIFEST.MF: %1$s"),
946 
947         /**
948          * JAR manifest contains a section without a name.
949          *
950          * <ul>
951          * <li>Parameter 1: section index (1-based) ({@code Integer})</li>
952          * </ul>
953          */
954         JAR_SIG_UNNNAMED_MANIFEST_SECTION(
955                 "Malformed META-INF/MANIFEST.MF: invidual section #%1$d does not have a name"),
956 
957         /**
958          * JAR signature file contains a section without a name.
959          *
960          * <ul>
961          * <li>Parameter 1: signature file name ({@code String})</li>
962          * <li>Parameter 2: section index (1-based) ({@code Integer})</li>
963          * </ul>
964          */
965         JAR_SIG_UNNNAMED_SIG_FILE_SECTION(
966                 "Malformed %1$s: invidual section #%2$d does not have a name"),
967 
968         /** APK is missing the JAR manifest entry (META-INF/MANIFEST.MF). */
969         JAR_SIG_NO_MANIFEST("Missing META-INF/MANIFEST.MF"),
970 
971         /**
972          * JAR manifest references an entry which is not there in the APK.
973          *
974          * <ul>
975          * <li>Parameter 1: entry name ({@code String})</li>
976          * </ul>
977          */
978         JAR_SIG_MISSING_ZIP_ENTRY_REFERENCED_IN_MANIFEST(
979                 "%1$s entry referenced by META-INF/MANIFEST.MF not found in the APK"),
980 
981         /**
982          * JAR manifest does not list a digest for the specified entry.
983          *
984          * <ul>
985          * <li>Parameter 1: entry name ({@code String})</li>
986          * </ul>
987          */
988         JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_MANIFEST("No digest for %1$s in META-INF/MANIFEST.MF"),
989 
990         /**
991          * JAR signature does not list a digest for the specified entry.
992          *
993          * <ul>
994          * <li>Parameter 1: entry name ({@code String})</li>
995          * <li>Parameter 2: signature file name ({@code String})</li>
996          * </ul>
997          */
998         JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_SIG_FILE("No digest for %1$s in %2$s"),
999 
1000         /**
1001          * The specified JAR entry is not covered by JAR signature.
1002          *
1003          * <ul>
1004          * <li>Parameter 1: entry name ({@code String})</li>
1005          * </ul>
1006          */
1007         JAR_SIG_ZIP_ENTRY_NOT_SIGNED("%1$s entry not signed"),
1008 
1009         /**
1010          * JAR signature uses different set of signers to protect the two specified ZIP entries.
1011          *
1012          * <ul>
1013          * <li>Parameter 1: first entry name ({@code String})</li>
1014          * <li>Parameter 2: first entry signer names ({@code List<String>})</li>
1015          * <li>Parameter 3: second entry name ({@code String})</li>
1016          * <li>Parameter 4: second entry signer names ({@code List<String>})</li>
1017          * </ul>
1018          */
1019         JAR_SIG_ZIP_ENTRY_SIGNERS_MISMATCH(
1020                 "Entries %1$s and %3$s are signed with different sets of signers"
1021                         + " : <%2$s> vs <%4$s>"),
1022 
1023         /**
1024          * Digest of the specified ZIP entry's data does not match the digest expected by the JAR
1025          * signature.
1026          *
1027          * <ul>
1028          * <li>Parameter 1: entry name ({@code String})</li>
1029          * <li>Parameter 2: digest algorithm (e.g., SHA-256) ({@code String})</li>
1030          * <li>Parameter 3: name of the entry in which the expected digest is specified
1031          *     ({@code String})</li>
1032          * <li>Parameter 4: base64-encoded actual digest ({@code String})</li>
1033          * <li>Parameter 5: base64-encoded expected digest ({@code String})</li>
1034          * </ul>
1035          */
1036         JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY(
1037                 "%2$s digest of %1$s does not match the digest specified in %3$s"
1038                         + ". Expected: <%5$s>, actual: <%4$s>"),
1039 
1040         /**
1041          * Digest of the JAR manifest main section did not verify.
1042          *
1043          * <ul>
1044          * <li>Parameter 1: digest algorithm (e.g., SHA-256) ({@code String})</li>
1045          * <li>Parameter 2: name of the entry in which the expected digest is specified
1046          *     ({@code String})</li>
1047          * <li>Parameter 3: base64-encoded actual digest ({@code String})</li>
1048          * <li>Parameter 4: base64-encoded expected digest ({@code String})</li>
1049          * </ul>
1050          */
1051         JAR_SIG_MANIFEST_MAIN_SECTION_DIGEST_DID_NOT_VERIFY(
1052                 "%1$s digest of META-INF/MANIFEST.MF main section does not match the digest"
1053                         + " specified in %2$s. Expected: <%4$s>, actual: <%3$s>"),
1054 
1055         /**
1056          * Digest of the specified JAR manifest section does not match the digest expected by the
1057          * JAR signature.
1058          *
1059          * <ul>
1060          * <li>Parameter 1: section name ({@code String})</li>
1061          * <li>Parameter 2: digest algorithm (e.g., SHA-256) ({@code String})</li>
1062          * <li>Parameter 3: name of the signature file in which the expected digest is specified
1063          *     ({@code String})</li>
1064          * <li>Parameter 4: base64-encoded actual digest ({@code String})</li>
1065          * <li>Parameter 5: base64-encoded expected digest ({@code String})</li>
1066          * </ul>
1067          */
1068         JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY(
1069                 "%2$s digest of META-INF/MANIFEST.MF section for %1$s does not match the digest"
1070                         + " specified in %3$s. Expected: <%5$s>, actual: <%4$s>"),
1071 
1072         /**
1073          * JAR signature file does not contain the whole-file digest of the JAR manifest file. The
1074          * digest speeds up verification of JAR signature.
1075          *
1076          * <ul>
1077          * <li>Parameter 1: name of the signature file ({@code String})</li>
1078          * </ul>
1079          */
1080         JAR_SIG_NO_MANIFEST_DIGEST_IN_SIG_FILE(
1081                 "%1$s does not specify digest of META-INF/MANIFEST.MF"
1082                         + ". This slows down verification."),
1083 
1084         /**
1085          * APK is signed using APK Signature Scheme v2 or newer, but JAR signature file does not
1086          * contain protections against stripping of these newer scheme signatures.
1087          *
1088          * <ul>
1089          * <li>Parameter 1: name of the signature file ({@code String})</li>
1090          * </ul>
1091          */
1092         JAR_SIG_NO_APK_SIG_STRIP_PROTECTION(
1093                 "APK is signed using APK Signature Scheme v2 but these signatures may be stripped"
1094                         + " without being detected because %1$s does not contain anti-stripping"
1095                         + " protections."),
1096 
1097         /**
1098          * JAR signature of the signer is missing a file/entry.
1099          *
1100          * <ul>
1101          * <li>Parameter 1: name of the encountered file ({@code String})</li>
1102          * <li>Parameter 2: name of the missing file ({@code String})</li>
1103          * </ul>
1104          */
1105         JAR_SIG_MISSING_FILE("Partial JAR signature. Found: %1$s, missing: %2$s"),
1106 
1107         /**
1108          * An exception was encountered while verifying JAR signature contained in a signature block
1109          * against the signature file.
1110          *
1111          * <ul>
1112          * <li>Parameter 1: name of the signature block file ({@code String})</li>
1113          * <li>Parameter 2: name of the signature file ({@code String})</li>
1114          * <li>Parameter 3: exception ({@code Throwable})</li>
1115          * </ul>
1116          */
1117         JAR_SIG_VERIFY_EXCEPTION("Failed to verify JAR signature %1$s against %2$s: %3$s"),
1118 
1119         /**
1120          * JAR signature contains unsupported digest algorithm.
1121          *
1122          * <ul>
1123          * <li>Parameter 1: name of the signature block file ({@code String})</li>
1124          * <li>Parameter 2: digest algorithm OID ({@code String})</li>
1125          * <li>Parameter 3: signature algorithm OID ({@code String})</li>
1126          * <li>Parameter 4: API Levels on which this combination of algorithms is not supported
1127          *     ({@code String})</li>
1128          * <li>Parameter 5: user-friendly variant of digest algorithm ({@code String})</li>
1129          * <li>Parameter 6: user-friendly variant of signature algorithm ({@code String})</li>
1130          * </ul>
1131          */
1132         JAR_SIG_UNSUPPORTED_SIG_ALG(
1133                 "JAR signature %1$s uses digest algorithm %5$s and signature algorithm %6$s which"
1134                         + " is not supported on API Level(s) %4$s for which this APK is being"
1135                         + " verified"),
1136 
1137         /**
1138          * An exception was encountered while parsing JAR signature contained in a signature block.
1139          *
1140          * <ul>
1141          * <li>Parameter 1: name of the signature block file ({@code String})</li>
1142          * <li>Parameter 2: exception ({@code Throwable})</li>
1143          * </ul>
1144          */
1145         JAR_SIG_PARSE_EXCEPTION("Failed to parse JAR signature %1$s: %2$s"),
1146 
1147         /**
1148          * An exception was encountered while parsing a certificate contained in the JAR signature
1149          * block.
1150          *
1151          * <ul>
1152          * <li>Parameter 1: name of the signature block file ({@code String})</li>
1153          * <li>Parameter 2: exception ({@code Throwable})</li>
1154          * </ul>
1155          */
1156         JAR_SIG_MALFORMED_CERTIFICATE("Malformed certificate in JAR signature %1$s: %2$s"),
1157 
1158         /**
1159          * JAR signature contained in a signature block file did not verify against the signature
1160          * file.
1161          *
1162          * <ul>
1163          * <li>Parameter 1: name of the signature block file ({@code String})</li>
1164          * <li>Parameter 2: name of the signature file ({@code String})</li>
1165          * </ul>
1166          */
1167         JAR_SIG_DID_NOT_VERIFY("JAR signature %1$s did not verify against %2$s"),
1168 
1169         /**
1170          * JAR signature contains no verified signers.
1171          *
1172          * <ul>
1173          * <li>Parameter 1: name of the signature block file ({@code String})</li>
1174          * </ul>
1175          */
1176         JAR_SIG_NO_SIGNERS("JAR signature %1$s contains no signers"),
1177 
1178         /**
1179          * JAR signature file contains a section with a duplicate name.
1180          *
1181          * <ul>
1182          * <li>Parameter 1: signature file name ({@code String})</li>
1183          * <li>Parameter 1: section name ({@code String})</li>
1184          * </ul>
1185          */
1186         JAR_SIG_DUPLICATE_SIG_FILE_SECTION("Duplicate section in %1$s: %2$s"),
1187 
1188         /**
1189          * JAR signature file's main section doesn't contain the mandatory Signature-Version
1190          * attribute.
1191          *
1192          * <ul>
1193          * <li>Parameter 1: signature file name ({@code String})</li>
1194          * </ul>
1195          */
1196         JAR_SIG_MISSING_VERSION_ATTR_IN_SIG_FILE(
1197                 "Malformed %1$s: missing Signature-Version attribute"),
1198 
1199         /**
1200          * JAR signature file references an unknown APK signature scheme ID.
1201          *
1202          * <ul>
1203          * <li>Parameter 1: name of the signature file ({@code String})</li>
1204          * <li>Parameter 2: unknown APK signature scheme ID ({@code} Integer)</li>
1205          * </ul>
1206          */
1207         JAR_SIG_UNKNOWN_APK_SIG_SCHEME_ID(
1208                 "JAR signature %1$s references unknown APK signature scheme ID: %2$d"),
1209 
1210         /**
1211          * JAR signature file indicates that the APK is supposed to be signed with a supported APK
1212          * signature scheme (in addition to the JAR signature) but no such signature was found in
1213          * the APK.
1214          *
1215          * <ul>
1216          * <li>Parameter 1: name of the signature file ({@code String})</li>
1217          * <li>Parameter 2: APK signature scheme ID ({@code} Integer)</li>
1218          * <li>Parameter 3: APK signature scheme English name ({@code} String)</li>
1219          * </ul>
1220          */
1221         JAR_SIG_MISSING_APK_SIG_REFERENCED(
1222                 "JAR signature %1$s indicates the APK is signed using %3$s but no such signature"
1223                         + " was found. Signature stripped?"),
1224 
1225         /**
1226          * JAR entry is not covered by signature and thus unauthorized modifications to its contents
1227          * will not be detected.
1228          *
1229          * <ul>
1230          * <li>Parameter 1: entry name ({@code String})</li>
1231          * </ul>
1232          */
1233         JAR_SIG_UNPROTECTED_ZIP_ENTRY(
1234                 "%1$s not protected by signature. Unauthorized modifications to this JAR entry"
1235                         + " will not be detected. Delete or move the entry outside of META-INF/."),
1236 
1237         /**
1238          * APK which is both JAR-signed and signed using APK Signature Scheme v2 contains an APK
1239          * Signature Scheme v2 signature from this signer, but does not contain a JAR signature
1240          * from this signer.
1241          */
1242         JAR_SIG_MISSING("No JAR signature from this signer"),
1243 
1244         /**
1245          * APK is targeting a sandbox version which requires APK Signature Scheme v2 signature but
1246          * no such signature was found.
1247          *
1248          * <ul>
1249          * <li>Parameter 1: target sandbox version ({@code Integer})</li>
1250          * </ul>
1251          */
1252         NO_SIG_FOR_TARGET_SANDBOX_VERSION(
1253                 "Missing APK Signature Scheme v2 signature required for target sandbox version"
1254                         + " %1$d"),
1255 
1256         /**
1257          * APK which is both JAR-signed and signed using APK Signature Scheme v2 contains a JAR
1258          * signature from this signer, but does not contain an APK Signature Scheme v2 signature
1259          * from this signer.
1260          */
1261         V2_SIG_MISSING("No APK Signature Scheme v2 signature from this signer"),
1262 
1263         /**
1264          * Failed to parse the list of signers contained in the APK Signature Scheme v2 signature.
1265          */
1266         V2_SIG_MALFORMED_SIGNERS("Malformed list of signers"),
1267 
1268         /**
1269          * Failed to parse this signer's signer block contained in the APK Signature Scheme v2
1270          * signature.
1271          */
1272         V2_SIG_MALFORMED_SIGNER("Malformed signer block"),
1273 
1274         /**
1275          * Public key embedded in the APK Signature Scheme v2 signature of this signer could not be
1276          * parsed.
1277          *
1278          * <ul>
1279          * <li>Parameter 1: error details ({@code Throwable})</li>
1280          * </ul>
1281          */
1282         V2_SIG_MALFORMED_PUBLIC_KEY("Malformed public key: %1$s"),
1283 
1284         /**
1285          * This APK Signature Scheme v2 signer's certificate could not be parsed.
1286          *
1287          * <ul>
1288          * <li>Parameter 1: index ({@code 0}-based) of the certificate in the signer's list of
1289          *     certificates ({@code Integer})</li>
1290          * <li>Parameter 2: sequence number ({@code 1}-based) of the certificate in the signer's
1291          *     list of certificates ({@code Integer})</li>
1292          * <li>Parameter 3: error details ({@code Throwable})</li>
1293          * </ul>
1294          */
1295         V2_SIG_MALFORMED_CERTIFICATE("Malformed certificate #%2$d: %3$s"),
1296 
1297         /**
1298          * Failed to parse this signer's signature record contained in the APK Signature Scheme v2
1299          * signature.
1300          *
1301          * <ul>
1302          * <li>Parameter 1: record number (first record is {@code 1}) ({@code Integer})</li>
1303          * </ul>
1304          */
1305         V2_SIG_MALFORMED_SIGNATURE("Malformed APK Signature Scheme v2 signature record #%1$d"),
1306 
1307         /**
1308          * Failed to parse this signer's digest record contained in the APK Signature Scheme v2
1309          * signature.
1310          *
1311          * <ul>
1312          * <li>Parameter 1: record number (first record is {@code 1}) ({@code Integer})</li>
1313          * </ul>
1314          */
1315         V2_SIG_MALFORMED_DIGEST("Malformed APK Signature Scheme v2 digest record #%1$d"),
1316 
1317         /**
1318          * This APK Signature Scheme v2 signer contains a malformed additional attribute.
1319          *
1320          * <ul>
1321          * <li>Parameter 1: attribute number (first attribute is {@code 1}) {@code Integer})</li>
1322          * </ul>
1323          */
1324         V2_SIG_MALFORMED_ADDITIONAL_ATTRIBUTE("Malformed additional attribute #%1$d"),
1325 
1326         /**
1327          * APK Signature Scheme v2 signature references an unknown APK signature scheme ID.
1328          *
1329          * <ul>
1330          * <li>Parameter 1: signer index ({@code Integer})</li>
1331          * <li>Parameter 2: unknown APK signature scheme ID ({@code} Integer)</li>
1332          * </ul>
1333          */
1334         V2_SIG_UNKNOWN_APK_SIG_SCHEME_ID(
1335                 "APK Signature Scheme v2 signer: %1$s references unknown APK signature scheme ID: "
1336                         + "%2$d"),
1337 
1338         /**
1339          * APK Signature Scheme v2 signature indicates that the APK is supposed to be signed with a
1340          * supported APK signature scheme (in addition to the v2 signature) but no such signature
1341          * was found in the APK.
1342          *
1343          * <ul>
1344          * <li>Parameter 1: signer index ({@code Integer})</li>
1345          * <li>Parameter 2: APK signature scheme English name ({@code} String)</li>
1346          * </ul>
1347          */
1348         V2_SIG_MISSING_APK_SIG_REFERENCED(
1349                 "APK Signature Scheme v2 signature %1$s indicates the APK is signed using %2$s but "
1350                         + "no such signature was found. Signature stripped?"),
1351 
1352         /**
1353          * APK Signature Scheme v2 signature contains no signers.
1354          */
1355         V2_SIG_NO_SIGNERS("No signers in APK Signature Scheme v2 signature"),
1356 
1357         /**
1358          * This APK Signature Scheme v2 signer contains a signature produced using an unknown
1359          * algorithm.
1360          *
1361          * <ul>
1362          * <li>Parameter 1: algorithm ID ({@code Integer})</li>
1363          * </ul>
1364          */
1365         V2_SIG_UNKNOWN_SIG_ALGORITHM("Unknown signature algorithm: %1$#x"),
1366 
1367         /**
1368          * This APK Signature Scheme v2 signer contains an unknown additional attribute.
1369          *
1370          * <ul>
1371          * <li>Parameter 1: attribute ID ({@code Integer})</li>
1372          * </ul>
1373          */
1374         V2_SIG_UNKNOWN_ADDITIONAL_ATTRIBUTE("Unknown additional attribute: ID %1$#x"),
1375 
1376         /**
1377          * An exception was encountered while verifying APK Signature Scheme v2 signature of this
1378          * signer.
1379          *
1380          * <ul>
1381          * <li>Parameter 1: signature algorithm ({@link SignatureAlgorithm})</li>
1382          * <li>Parameter 2: exception ({@code Throwable})</li>
1383          * </ul>
1384          */
1385         V2_SIG_VERIFY_EXCEPTION("Failed to verify %1$s signature: %2$s"),
1386 
1387         /**
1388          * APK Signature Scheme v2 signature over this signer's signed-data block did not verify.
1389          *
1390          * <ul>
1391          * <li>Parameter 1: signature algorithm ({@link SignatureAlgorithm})</li>
1392          * </ul>
1393          */
1394         V2_SIG_DID_NOT_VERIFY("%1$s signature over signed-data did not verify"),
1395 
1396         /**
1397          * This APK Signature Scheme v2 signer offers no signatures.
1398          */
1399         V2_SIG_NO_SIGNATURES("No signatures"),
1400 
1401         /**
1402          * This APK Signature Scheme v2 signer offers signatures but none of them are supported.
1403          */
1404         V2_SIG_NO_SUPPORTED_SIGNATURES("No supported signatures"),
1405 
1406         /**
1407          * This APK Signature Scheme v2 signer offers no certificates.
1408          */
1409         V2_SIG_NO_CERTIFICATES("No certificates"),
1410 
1411         /**
1412          * This APK Signature Scheme v2 signer's public key listed in the signer's certificate does
1413          * not match the public key listed in the signatures record.
1414          *
1415          * <ul>
1416          * <li>Parameter 1: hex-encoded public key from certificate ({@code String})</li>
1417          * <li>Parameter 2: hex-encoded public key from signatures record ({@code String})</li>
1418          * </ul>
1419          */
1420         V2_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD(
1421                 "Public key mismatch between certificate and signature record: <%1$s> vs <%2$s>"),
1422 
1423         /**
1424          * This APK Signature Scheme v2 signer's signature algorithms listed in the signatures
1425          * record do not match the signature algorithms listed in the signatures record.
1426          *
1427          * <ul>
1428          * <li>Parameter 1: signature algorithms from signatures record ({@code List<Integer>})</li>
1429          * <li>Parameter 2: signature algorithms from digests record ({@code List<Integer>})</li>
1430          * </ul>
1431          */
1432         V2_SIG_SIG_ALG_MISMATCH_BETWEEN_SIGNATURES_AND_DIGESTS_RECORDS(
1433                 "Signature algorithms mismatch between signatures and digests records"
1434                         + ": %1$s vs %2$s"),
1435 
1436         /**
1437          * The APK's digest does not match the digest contained in the APK Signature Scheme v2
1438          * signature.
1439          *
1440          * <ul>
1441          * <li>Parameter 1: content digest algorithm ({@link ContentDigestAlgorithm})</li>
1442          * <li>Parameter 2: hex-encoded expected digest of the APK ({@code String})</li>
1443          * <li>Parameter 3: hex-encoded actual digest of the APK ({@code String})</li>
1444          * </ul>
1445          */
1446         V2_SIG_APK_DIGEST_DID_NOT_VERIFY(
1447                 "APK integrity check failed. %1$s digest mismatch."
1448                         + " Expected: <%2$s>, actual: <%3$s>"),
1449 
1450         /**
1451          * Failed to parse the list of signers contained in the APK Signature Scheme v3 signature.
1452          */
1453         V3_SIG_MALFORMED_SIGNERS("Malformed list of signers"),
1454 
1455         /**
1456          * Failed to parse this signer's signer block contained in the APK Signature Scheme v3
1457          * signature.
1458          */
1459         V3_SIG_MALFORMED_SIGNER("Malformed signer block"),
1460 
1461         /**
1462          * Public key embedded in the APK Signature Scheme v3 signature of this signer could not be
1463          * parsed.
1464          *
1465          * <ul>
1466          * <li>Parameter 1: error details ({@code Throwable})</li>
1467          * </ul>
1468          */
1469         V3_SIG_MALFORMED_PUBLIC_KEY("Malformed public key: %1$s"),
1470 
1471         /**
1472          * This APK Signature Scheme v3 signer's certificate could not be parsed.
1473          *
1474          * <ul>
1475          * <li>Parameter 1: index ({@code 0}-based) of the certificate in the signer's list of
1476          *     certificates ({@code Integer})</li>
1477          * <li>Parameter 2: sequence number ({@code 1}-based) of the certificate in the signer's
1478          *     list of certificates ({@code Integer})</li>
1479          * <li>Parameter 3: error details ({@code Throwable})</li>
1480          * </ul>
1481          */
1482         V3_SIG_MALFORMED_CERTIFICATE("Malformed certificate #%2$d: %3$s"),
1483 
1484         /**
1485          * Failed to parse this signer's signature record contained in the APK Signature Scheme v3
1486          * signature.
1487          *
1488          * <ul>
1489          * <li>Parameter 1: record number (first record is {@code 1}) ({@code Integer})</li>
1490          * </ul>
1491          */
1492         V3_SIG_MALFORMED_SIGNATURE("Malformed APK Signature Scheme v3 signature record #%1$d"),
1493 
1494         /**
1495          * Failed to parse this signer's digest record contained in the APK Signature Scheme v3
1496          * signature.
1497          *
1498          * <ul>
1499          * <li>Parameter 1: record number (first record is {@code 1}) ({@code Integer})</li>
1500          * </ul>
1501          */
1502         V3_SIG_MALFORMED_DIGEST("Malformed APK Signature Scheme v3 digest record #%1$d"),
1503 
1504         /**
1505          * This APK Signature Scheme v3 signer contains a malformed additional attribute.
1506          *
1507          * <ul>
1508          * <li>Parameter 1: attribute number (first attribute is {@code 1}) {@code Integer})</li>
1509          * </ul>
1510          */
1511         V3_SIG_MALFORMED_ADDITIONAL_ATTRIBUTE("Malformed additional attribute #%1$d"),
1512 
1513         /**
1514          * APK Signature Scheme v3 signature contains no signers.
1515          */
1516         V3_SIG_NO_SIGNERS("No signers in APK Signature Scheme v3 signature"),
1517 
1518         /**
1519          * APK Signature Scheme v3 signature contains multiple signers (only one allowed per
1520          * platform version).
1521          */
1522         V3_SIG_MULTIPLE_SIGNERS("Multiple APK Signature Scheme v3 signatures found for a single "
1523                 + " platform version."),
1524 
1525         /**
1526          * APK Signature Scheme v3 signature found, but multiple v1 and/or multiple v2 signers
1527          * found, where only one may be used with APK Signature Scheme v3
1528          */
1529         V3_SIG_MULTIPLE_PAST_SIGNERS("Multiple signatures found for pre-v3 signing with an APK "
1530                 + " Signature Scheme v3 signer.  Only one allowed."),
1531 
1532         /**
1533          * APK Signature Scheme v3 signature found, but its signer doesn't match the v1/v2 signers,
1534          * or have them as the root of its signing certificate history
1535          */
1536         V3_SIG_PAST_SIGNERS_MISMATCH(
1537                 "v3 signer differs from v1/v2 signer without proper signing certificate lineage."),
1538 
1539         /**
1540          * This APK Signature Scheme v3 signer contains a signature produced using an unknown
1541          * algorithm.
1542          *
1543          * <ul>
1544          * <li>Parameter 1: algorithm ID ({@code Integer})</li>
1545          * </ul>
1546          */
1547         V3_SIG_UNKNOWN_SIG_ALGORITHM("Unknown signature algorithm: %1$#x"),
1548 
1549         /**
1550          * This APK Signature Scheme v3 signer contains an unknown additional attribute.
1551          *
1552          * <ul>
1553          * <li>Parameter 1: attribute ID ({@code Integer})</li>
1554          * </ul>
1555          */
1556         V3_SIG_UNKNOWN_ADDITIONAL_ATTRIBUTE("Unknown additional attribute: ID %1$#x"),
1557 
1558         /**
1559          * An exception was encountered while verifying APK Signature Scheme v3 signature of this
1560          * signer.
1561          *
1562          * <ul>
1563          * <li>Parameter 1: signature algorithm ({@link SignatureAlgorithm})</li>
1564          * <li>Parameter 2: exception ({@code Throwable})</li>
1565          * </ul>
1566          */
1567         V3_SIG_VERIFY_EXCEPTION("Failed to verify %1$s signature: %2$s"),
1568 
1569         /**
1570          * The APK Signature Scheme v3 signer contained an invalid value for either min or max SDK
1571          * versions.
1572          *
1573          * <ul>
1574          * <li>Parameter 1: minSdkVersion ({@code Integer})
1575          * <li>Parameter 2: maxSdkVersion ({@code Integer})
1576          * </ul>
1577          */
1578         V3_SIG_INVALID_SDK_VERSIONS("Invalid SDK Version parameter(s) encountered in APK Signature "
1579                 + "scheme v3 signature: minSdkVersion %1$s maxSdkVersion: %2$s"),
1580 
1581         /**
1582          * APK Signature Scheme v3 signature over this signer's signed-data block did not verify.
1583          *
1584          * <ul>
1585          * <li>Parameter 1: signature algorithm ({@link SignatureAlgorithm})</li>
1586          * </ul>
1587          */
1588         V3_SIG_DID_NOT_VERIFY("%1$s signature over signed-data did not verify"),
1589 
1590         /**
1591          * This APK Signature Scheme v3 signer offers no signatures.
1592          */
1593         V3_SIG_NO_SIGNATURES("No signatures"),
1594 
1595         /**
1596          * This APK Signature Scheme v3 signer offers signatures but none of them are supported.
1597          */
1598         V3_SIG_NO_SUPPORTED_SIGNATURES("No supported signatures"),
1599 
1600         /**
1601          * This APK Signature Scheme v3 signer offers no certificates.
1602          */
1603         V3_SIG_NO_CERTIFICATES("No certificates"),
1604 
1605         /**
1606          * This APK Signature Scheme v3 signer's minSdkVersion listed in the signer's signed data
1607          * does not match the minSdkVersion listed in the signatures record.
1608          *
1609          * <ul>
1610          * <li>Parameter 1: minSdkVersion in signature record ({@code Integer}) </li>
1611          * <li>Parameter 2: minSdkVersion in signed data ({@code Integer}) </li>
1612          * </ul>
1613          */
1614         V3_MIN_SDK_VERSION_MISMATCH_BETWEEN_SIGNER_AND_SIGNED_DATA_RECORD(
1615                 "minSdkVersion mismatch between signed data and signature record:"
1616                         + " <%1$s> vs <%2$s>"),
1617 
1618         /**
1619          * This APK Signature Scheme v3 signer's maxSdkVersion listed in the signer's signed data
1620          * does not match the maxSdkVersion listed in the signatures record.
1621          *
1622          * <ul>
1623          * <li>Parameter 1: maxSdkVersion in signature record ({@code Integer}) </li>
1624          * <li>Parameter 2: maxSdkVersion in signed data ({@code Integer}) </li>
1625          * </ul>
1626          */
1627         V3_MAX_SDK_VERSION_MISMATCH_BETWEEN_SIGNER_AND_SIGNED_DATA_RECORD(
1628                 "maxSdkVersion mismatch between signed data and signature record:"
1629                         + " <%1$s> vs <%2$s>"),
1630 
1631         /**
1632          * This APK Signature Scheme v3 signer's public key listed in the signer's certificate does
1633          * not match the public key listed in the signatures record.
1634          *
1635          * <ul>
1636          * <li>Parameter 1: hex-encoded public key from certificate ({@code String})</li>
1637          * <li>Parameter 2: hex-encoded public key from signatures record ({@code String})</li>
1638          * </ul>
1639          */
1640         V3_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD(
1641                 "Public key mismatch between certificate and signature record: <%1$s> vs <%2$s>"),
1642 
1643         /**
1644          * This APK Signature Scheme v3 signer's signature algorithms listed in the signatures
1645          * record do not match the signature algorithms listed in the signatures record.
1646          *
1647          * <ul>
1648          * <li>Parameter 1: signature algorithms from signatures record ({@code List<Integer>})</li>
1649          * <li>Parameter 2: signature algorithms from digests record ({@code List<Integer>})</li>
1650          * </ul>
1651          */
1652         V3_SIG_SIG_ALG_MISMATCH_BETWEEN_SIGNATURES_AND_DIGESTS_RECORDS(
1653                 "Signature algorithms mismatch between signatures and digests records"
1654                         + ": %1$s vs %2$s"),
1655 
1656         /**
1657          * The APK's digest does not match the digest contained in the APK Signature Scheme v3
1658          * signature.
1659          *
1660          * <ul>
1661          * <li>Parameter 1: content digest algorithm ({@link ContentDigestAlgorithm})</li>
1662          * <li>Parameter 2: hex-encoded expected digest of the APK ({@code String})</li>
1663          * <li>Parameter 3: hex-encoded actual digest of the APK ({@code String})</li>
1664          * </ul>
1665          */
1666         V3_SIG_APK_DIGEST_DID_NOT_VERIFY(
1667                 "APK integrity check failed. %1$s digest mismatch."
1668                         + " Expected: <%2$s>, actual: <%3$s>"),
1669 
1670         /**
1671          * The signer's SigningCertificateLineage attribute containd a proof-of-rotation record with
1672          * signature(s) that did not verify.
1673          */
1674         V3_SIG_POR_DID_NOT_VERIFY("SigningCertificateLineage attribute containd a proof-of-rotation"
1675                 + " record with signature(s) that did not verify."),
1676 
1677         /**
1678          * Failed to parse the SigningCertificateLineage structure in the APK Signature Scheme v3
1679          * signature's additional attributes section.
1680          */
1681         V3_SIG_MALFORMED_LINEAGE("Failed to parse the SigningCertificateLineage structure in the "
1682                 + "APK Signature Scheme v3 signature's additional attributes section."),
1683 
1684         /**
1685          * The APK's signing certificate does not match the terminal node in the provided
1686          * proof-of-rotation structure describing the signing certificate history
1687          */
1688         V3_SIG_POR_CERT_MISMATCH(
1689                 "APK signing certificate differs from the associated certificate found in the "
1690                         + "signer's SigningCertificateLineage."),
1691 
1692         /**
1693          * The APK Signature Scheme v3 signers encountered do not offer a continuous set of
1694          * supported platform versions.  Either they overlap, resulting in potentially two
1695          * acceptable signers for a platform version, or there are holes which would create problems
1696          * in the event of platform version upgrades.
1697          */
1698         V3_INCONSISTENT_SDK_VERSIONS("APK Signature Scheme v3 signers supported min/max SDK "
1699                 + "versions are not continuous."),
1700 
1701         /**
1702          * The APK Signature Scheme v3 signers don't cover all requested SDK versions.
1703          *
1704          *  <ul>
1705          * <li>Parameter 1: minSdkVersion ({@code Integer})
1706          * <li>Parameter 2: maxSdkVersion ({@code Integer})
1707          * </ul>
1708          */
1709         V3_MISSING_SDK_VERSIONS("APK Signature Scheme v3 signers supported min/max SDK "
1710                 + "versions do not cover the entire desired range.  Found min:  %1$s max %2$s"),
1711 
1712         /**
1713          * The SigningCertificateLineages for different platform versions using APK Signature Scheme
1714          * v3 do not go together.  Specifically, each should be a subset of another, with the size
1715          * of each increasing as the platform level increases.
1716          */
1717         V3_INCONSISTENT_LINEAGES("SigningCertificateLineages targeting different platform versions"
1718                 + " using APK Signature Scheme v3 are not all a part of the same overall lineage."),
1719 
1720         /**
1721          * APK Signing Block contains an unknown entry.
1722          *
1723          * <ul>
1724          * <li>Parameter 1: entry ID ({@code Integer})</li>
1725          * </ul>
1726          */
1727         APK_SIG_BLOCK_UNKNOWN_ENTRY_ID("APK Signing Block contains unknown entry: ID %1$#x");
1728 
1729         private final String mFormat;
1730 
Issue(String format)1731         private Issue(String format) {
1732             mFormat = format;
1733         }
1734 
1735         /**
1736          * Returns the format string suitable for combining the parameters of this issue into a
1737          * readable string. See {@link java.util.Formatter} for format.
1738          */
getFormat()1739         private String getFormat() {
1740             return mFormat;
1741         }
1742     }
1743 
1744     /**
1745      * {@link Issue} with associated parameters. {@link #toString()} produces a readable formatted
1746      * form.
1747      */
1748     public static class IssueWithParams {
1749         private final Issue mIssue;
1750         private final Object[] mParams;
1751 
1752         /**
1753          * Constructs a new {@code IssueWithParams} of the specified type and with provided
1754          * parameters.
1755          */
IssueWithParams(Issue issue, Object[] params)1756         public IssueWithParams(Issue issue, Object[] params) {
1757             mIssue = issue;
1758             mParams = params;
1759         }
1760 
1761         /**
1762          * Returns the type of this issue.
1763          */
getIssue()1764         public Issue getIssue() {
1765             return mIssue;
1766         }
1767 
1768         /**
1769          * Returns the parameters of this issue.
1770          */
getParams()1771         public Object[] getParams() {
1772             return mParams.clone();
1773         }
1774 
1775         /**
1776          * Returns a readable form of this issue.
1777          */
1778         @Override
toString()1779         public String toString() {
1780             return String.format(mIssue.getFormat(), mParams);
1781         }
1782     }
1783 
1784     /**
1785      * Wrapped around {@code byte[]} which ensures that {@code equals} and {@code hashCode} operate
1786      * on the contents of the arrays rather than on references.
1787      */
1788     private static class ByteArray {
1789         private final byte[] mArray;
1790         private final int mHashCode;
1791 
ByteArray(byte[] arr)1792         private ByteArray(byte[] arr) {
1793             mArray = arr;
1794             mHashCode = Arrays.hashCode(mArray);
1795         }
1796 
1797         @Override
hashCode()1798         public int hashCode() {
1799             return mHashCode;
1800         }
1801 
1802         @Override
equals(Object obj)1803         public boolean equals(Object obj) {
1804             if (this == obj) {
1805                 return true;
1806             }
1807             if (obj == null) {
1808                 return false;
1809             }
1810             if (getClass() != obj.getClass()) {
1811                 return false;
1812             }
1813             ByteArray other = (ByteArray) obj;
1814             if (hashCode() != other.hashCode()) {
1815                 return false;
1816             }
1817             if (!Arrays.equals(mArray, other.mArray)) {
1818                 return false;
1819             }
1820             return true;
1821         }
1822     }
1823 
1824     /**
1825      * Builder of {@link ApkVerifier} instances.
1826      *
1827      * <p>The resulting verifier by default checks whether the APK will verify on all platform
1828      * versions supported by the APK, as specified by {@code android:minSdkVersion} attributes in
1829      * the APK's {@code AndroidManifest.xml}. The range of platform versions can be customized using
1830      * {@link #setMinCheckedPlatformVersion(int)} and {@link #setMaxCheckedPlatformVersion(int)}.
1831      */
1832     public static class Builder {
1833         private final File mApkFile;
1834         private final DataSource mApkDataSource;
1835 
1836         private Integer mMinSdkVersion;
1837         private int mMaxSdkVersion = Integer.MAX_VALUE;
1838 
1839         /**
1840          * Constructs a new {@code Builder} for verifying the provided APK file.
1841          */
Builder(File apk)1842         public Builder(File apk) {
1843             if (apk == null) {
1844                 throw new NullPointerException("apk == null");
1845             }
1846             mApkFile = apk;
1847             mApkDataSource = null;
1848         }
1849 
1850         /**
1851          * Constructs a new {@code Builder} for verifying the provided APK.
1852          */
Builder(DataSource apk)1853         public Builder(DataSource apk) {
1854             if (apk == null) {
1855                 throw new NullPointerException("apk == null");
1856             }
1857             mApkDataSource = apk;
1858             mApkFile = null;
1859         }
1860 
1861         /**
1862          * Sets the oldest Android platform version for which the APK is verified. APK verification
1863          * will confirm that the APK is expected to install successfully on all known Android
1864          * platforms starting from the platform version with the provided API Level. The upper end
1865          * of the platform versions range can be modified via
1866          * {@link #setMaxCheckedPlatformVersion(int)}.
1867          *
1868          * <p>This method is useful for overriding the default behavior which checks that the APK
1869          * will verify on all platform versions supported by the APK, as specified by
1870          * {@code android:minSdkVersion} attributes in the APK's {@code AndroidManifest.xml}.
1871          *
1872          * @param minSdkVersion API Level of the oldest platform for which to verify the APK
1873          *
1874          * @see #setMinCheckedPlatformVersion(int)
1875          */
setMinCheckedPlatformVersion(int minSdkVersion)1876         public Builder setMinCheckedPlatformVersion(int minSdkVersion) {
1877             mMinSdkVersion = minSdkVersion;
1878             return this;
1879         }
1880 
1881         /**
1882          * Sets the newest Android platform version for which the APK is verified. APK verification
1883          * will confirm that the APK is expected to install successfully on all platform versions
1884          * supported by the APK up until and including the provided version. The lower end
1885          * of the platform versions range can be modified via
1886          * {@link #setMinCheckedPlatformVersion(int)}.
1887          *
1888          * @param maxSdkVersion API Level of the newest platform for which to verify the APK
1889          *
1890          * @see #setMinCheckedPlatformVersion(int)
1891          */
setMaxCheckedPlatformVersion(int maxSdkVersion)1892         public Builder setMaxCheckedPlatformVersion(int maxSdkVersion) {
1893             mMaxSdkVersion = maxSdkVersion;
1894             return this;
1895         }
1896 
1897         /**
1898          * Returns an {@link ApkVerifier} initialized according to the configuration of this
1899          * builder.
1900          */
build()1901         public ApkVerifier build() {
1902             return new ApkVerifier(
1903                     mApkFile,
1904                     mApkDataSource,
1905                     mMinSdkVersion,
1906                     mMaxSdkVersion);
1907         }
1908     }
1909 }
1910