1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.util.apk;
18 
19 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
20 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
21 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
22 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
23 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
24 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
25 import static android.util.apk.ApkSignatureSchemeV4Verifier.APK_SIGNATURE_SCHEME_DEFAULT;
26 
27 import android.content.pm.Signature;
28 import android.content.pm.SigningDetails;
29 import android.content.pm.SigningDetails.SignatureSchemeVersion;
30 import android.content.pm.parsing.ApkLiteParseUtils;
31 import android.content.pm.parsing.result.ParseInput;
32 import android.content.pm.parsing.result.ParseResult;
33 import android.os.Build;
34 import android.os.Trace;
35 import android.os.incremental.V4Signature;
36 import android.util.Pair;
37 import android.util.jar.StrictJarFile;
38 
39 import com.android.internal.util.ArrayUtils;
40 
41 import libcore.io.IoUtils;
42 
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.security.DigestException;
46 import java.security.GeneralSecurityException;
47 import java.security.NoSuchAlgorithmException;
48 import java.security.cert.Certificate;
49 import java.security.cert.CertificateEncodingException;
50 import java.util.ArrayList;
51 import java.util.Iterator;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.concurrent.atomic.AtomicReference;
55 import java.util.zip.ZipEntry;
56 
57 /**
58  * Facade class that takes care of the details of APK verification on
59  * behalf of ParsingPackageUtils.
60  *
61  * @hide for internal use only.
62  */
63 public class ApkSignatureVerifier {
64 
65     private static final AtomicReference<byte[]> sBuffer = new AtomicReference<>();
66 
67     /**
68      * Verifies the provided APK and returns the certificates associated with each signer.
69      */
verify(ParseInput input, String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion)70     public static ParseResult<SigningDetails> verify(ParseInput input, String apkPath,
71             @SignatureSchemeVersion int minSignatureSchemeVersion) {
72         return verifySignatures(input, apkPath, minSignatureSchemeVersion, true /* verifyFull */);
73     }
74 
75     /**
76      * Returns the certificates associated with each signer for the given APK without verification.
77      * This method is dangerous and should not be used, unless the caller is absolutely certain the
78      * APK is trusted.
79      */
unsafeGetCertsWithoutVerification( ParseInput input, String apkPath, int minSignatureSchemeVersion)80     public static ParseResult<SigningDetails> unsafeGetCertsWithoutVerification(
81             ParseInput input, String apkPath, int minSignatureSchemeVersion) {
82         return verifySignatures(input, apkPath, minSignatureSchemeVersion, false /* verifyFull */);
83     }
84 
85     /**
86      * Verifies the provided APK using all allowed signing schemas.
87      * @return the certificates associated with each signer.
88      * @param verifyFull whether to verify all contents of this APK or just collect certificates.
89      */
verifySignatures(ParseInput input, String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)90     private static ParseResult<SigningDetails> verifySignatures(ParseInput input, String apkPath,
91             @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull) {
92         final ParseResult<SigningDetailsWithDigests> result =
93                 verifySignaturesInternal(input, apkPath, minSignatureSchemeVersion, verifyFull);
94         if (result.isError()) {
95             return input.error(result);
96         }
97         return input.success(result.getResult().signingDetails);
98     }
99 
100     /**
101      * Verifies the provided APK using all allowed signing schemas.
102      * @return the certificates associated with each signer and content digests.
103      * @param verifyFull whether to verify all contents of this APK or just collect certificates.
104      * @hide
105      */
verifySignaturesInternal(ParseInput input, String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)106     public static ParseResult<SigningDetailsWithDigests> verifySignaturesInternal(ParseInput input,
107             String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion,
108             boolean verifyFull) {
109 
110         if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V4) {
111             // V4 and before are older than the requested minimum signing version
112             return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
113                     "No signature found in package of version " + minSignatureSchemeVersion
114                             + " or newer for package " + apkPath);
115         }
116 
117         // first try v4
118         try {
119             return verifyV4Signature(input, apkPath, minSignatureSchemeVersion, verifyFull);
120         } catch (SignatureNotFoundException e) {
121             // not signed with v4, try older if allowed
122             if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V4) {
123                 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
124                         "No APK Signature Scheme v4 signature in package " + apkPath, e);
125             }
126         }
127 
128         if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V3) {
129             // V3 and before are older than the requested minimum signing version
130             return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
131                     "No signature found in package of version " + minSignatureSchemeVersion
132                             + " or newer for package " + apkPath);
133         }
134 
135         return verifyV3AndBelowSignatures(input, apkPath, minSignatureSchemeVersion, verifyFull);
136     }
137 
verifyV3AndBelowSignatures( ParseInput input, String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)138     private static ParseResult<SigningDetailsWithDigests> verifyV3AndBelowSignatures(
139             ParseInput input, String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion,
140             boolean verifyFull) {
141         // try v3
142         try {
143             return verifyV3Signature(input, apkPath, verifyFull);
144         } catch (SignatureNotFoundException e) {
145             // not signed with v3, try older if allowed
146             if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) {
147                 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
148                         "No APK Signature Scheme v3 signature in package " + apkPath, e);
149             }
150         }
151 
152         // redundant, protective version check
153         if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V2) {
154             // V2 and before are older than the requested minimum signing version
155             return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
156                     "No signature found in package of version " + minSignatureSchemeVersion
157                             + " or newer for package " + apkPath);
158         }
159 
160         // try v2
161         try {
162             return verifyV2Signature(input, apkPath, verifyFull);
163         } catch (SignatureNotFoundException e) {
164             // not signed with v2, try older if allowed
165             if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V2) {
166                 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
167                         "No APK Signature Scheme v2 signature in package " + apkPath, e);
168             }
169         }
170 
171         // redundant, protective version check
172         if (minSignatureSchemeVersion > SignatureSchemeVersion.JAR) {
173             // V1 and is older than the requested minimum signing version
174             return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
175                     "No signature found in package of version " + minSignatureSchemeVersion
176                             + " or newer for package " + apkPath);
177         }
178 
179         // v2 didn't work, try jarsigner
180         return verifyV1Signature(input, apkPath, verifyFull);
181     }
182 
183     /**
184      * Verifies the provided APK using V4 schema.
185      *
186      * @param verifyFull whether to verify (V4 vs V3) or just collect certificates.
187      * @return the certificates associated with each signer.
188      * @throws SignatureNotFoundException if there are no V4 signatures in the APK
189      */
verifyV4Signature(ParseInput input, String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)190     private static ParseResult<SigningDetailsWithDigests> verifyV4Signature(ParseInput input,
191             String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion,
192             boolean verifyFull) throws SignatureNotFoundException {
193         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV4" : "certsOnlyV4");
194         try {
195             final Pair<V4Signature.HashingInfo, V4Signature.SigningInfos> v4Pair =
196                     ApkSignatureSchemeV4Verifier.extractSignature(apkPath);
197             final V4Signature.HashingInfo hashingInfo = v4Pair.first;
198             final V4Signature.SigningInfos signingInfos = v4Pair.second;
199 
200             Signature[] pastSignerSigs = null;
201             Map<Integer, byte[]> nonstreamingDigests = null;
202             Certificate[][] nonstreamingCerts = null;
203 
204             int v3BlockId = APK_SIGNATURE_SCHEME_DEFAULT;
205             // If V4 contains additional signing blocks then we need to always run v2/v3 verifier
206             // to figure out which block they use.
207             if (verifyFull || signingInfos.signingInfoBlocks.length > 0) {
208                 try {
209                     // v4 is an add-on and requires v2 or v3 signature to validate against its
210                     // certificate and digest
211                     ApkSignatureSchemeV3Verifier.VerifiedSigner v3Signer =
212                             ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath);
213                     nonstreamingDigests = v3Signer.contentDigests;
214                     nonstreamingCerts = new Certificate[][]{v3Signer.certs};
215                     if (v3Signer.por != null) {
216                         // populate proof-of-rotation information
217                         pastSignerSigs = new Signature[v3Signer.por.certs.size()];
218                         for (int i = 0; i < pastSignerSigs.length; i++) {
219                             pastSignerSigs[i] = new Signature(
220                                     v3Signer.por.certs.get(i).getEncoded());
221                             pastSignerSigs[i].setFlags(v3Signer.por.flagsList.get(i));
222                         }
223                     }
224                     v3BlockId = v3Signer.blockId;
225                 } catch (SignatureNotFoundException e) {
226                     try {
227                         ApkSignatureSchemeV2Verifier.VerifiedSigner v2Signer =
228                                 ApkSignatureSchemeV2Verifier.verify(apkPath, false);
229                         nonstreamingDigests = v2Signer.contentDigests;
230                         nonstreamingCerts = v2Signer.certs;
231                     } catch (SignatureNotFoundException ee) {
232                         throw new SecurityException(
233                                 "V4 verification failed to collect V2/V3 certificates from : "
234                                         + apkPath, ee);
235                     }
236                 }
237             }
238 
239             ApkSignatureSchemeV4Verifier.VerifiedSigner vSigner =
240                     ApkSignatureSchemeV4Verifier.verify(apkPath, hashingInfo, signingInfos,
241                             v3BlockId);
242             Certificate[][] signerCerts = new Certificate[][]{vSigner.certs};
243             Signature[] signerSigs = convertToSignatures(signerCerts);
244 
245             if (verifyFull) {
246                 Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts);
247                 if (nonstreamingSigs.length != signerSigs.length) {
248                     throw new SecurityException(
249                             "Invalid number of certificates: " + nonstreamingSigs.length);
250                 }
251 
252                 for (int i = 0, size = signerSigs.length; i < size; ++i) {
253                     if (!nonstreamingSigs[i].equals(signerSigs[i])) {
254                         throw new SecurityException(
255                                 "V4 signature certificate does not match V2/V3");
256                     }
257                 }
258 
259                 boolean found = false;
260                 for (byte[] nonstreamingDigest : nonstreamingDigests.values()) {
261                     if (ArrayUtils.equals(vSigner.apkDigest, nonstreamingDigest,
262                             vSigner.apkDigest.length)) {
263                         found = true;
264                         break;
265                     }
266                 }
267                 if (!found) {
268                     throw new SecurityException("APK digest in V4 signature does not match V2/V3");
269                 }
270             }
271 
272             return input.success(new SigningDetailsWithDigests(new SigningDetails(signerSigs,
273                     SignatureSchemeVersion.SIGNING_BLOCK_V4, pastSignerSigs),
274                     vSigner.contentDigests));
275         } catch (SignatureNotFoundException e) {
276             throw e;
277         } catch (Exception e) {
278             // APK Signature Scheme v4 signature found but did not verify.
279             return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
280                     "Failed to collect certificates from " + apkPath
281                             + " using APK Signature Scheme v4", e);
282         } finally {
283             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
284         }
285     }
286 
287     /**
288      * Verifies the provided APK using V3 schema.
289      *
290      * @param verifyFull whether to verify all contents of this APK or just collect certificates.
291      * @return the certificates associated with each signer.
292      * @throws SignatureNotFoundException if there are no V3 signatures in the APK
293      */
verifyV3Signature(ParseInput input, String apkPath, boolean verifyFull)294     private static ParseResult<SigningDetailsWithDigests> verifyV3Signature(ParseInput input,
295             String apkPath, boolean verifyFull) throws SignatureNotFoundException {
296         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV3" : "certsOnlyV3");
297         try {
298             ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
299                     verifyFull ? ApkSignatureSchemeV3Verifier.verify(apkPath)
300                             : ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(
301                                     apkPath);
302             Certificate[][] signerCerts = new Certificate[][]{vSigner.certs};
303             Signature[] signerSigs = convertToSignatures(signerCerts);
304             Signature[] pastSignerSigs = null;
305             if (vSigner.por != null) {
306                 // populate proof-of-rotation information
307                 pastSignerSigs = new Signature[vSigner.por.certs.size()];
308                 for (int i = 0; i < pastSignerSigs.length; i++) {
309                     pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded());
310                     pastSignerSigs[i].setFlags(vSigner.por.flagsList.get(i));
311                 }
312             }
313             return input.success(new SigningDetailsWithDigests(new SigningDetails(signerSigs,
314                     SignatureSchemeVersion.SIGNING_BLOCK_V3, pastSignerSigs),
315                     vSigner.contentDigests));
316         } catch (SignatureNotFoundException e) {
317             throw e;
318         } catch (Exception e) {
319             // APK Signature Scheme v3 signature found but did not verify
320             return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
321                     "Failed to collect certificates from " + apkPath
322                             + " using APK Signature Scheme v3", e);
323         } finally {
324             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
325         }
326     }
327 
328     /**
329      * Verifies the provided APK using V2 schema.
330      *
331      * @param verifyFull whether to verify all contents of this APK or just collect certificates.
332      * @return the certificates associated with each signer.
333      * @throws SignatureNotFoundException if there are no V2 signatures in the APK
334      */
verifyV2Signature(ParseInput input, String apkPath, boolean verifyFull)335     private static ParseResult<SigningDetailsWithDigests> verifyV2Signature(ParseInput input,
336             String apkPath, boolean verifyFull) throws SignatureNotFoundException {
337         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV2" : "certsOnlyV2");
338         try {
339             ApkSignatureSchemeV2Verifier.VerifiedSigner vSigner =
340                     ApkSignatureSchemeV2Verifier.verify(apkPath, verifyFull);
341             Certificate[][] signerCerts = vSigner.certs;
342             Signature[] signerSigs = convertToSignatures(signerCerts);
343             return input.success(new SigningDetailsWithDigests(new SigningDetails(signerSigs,
344                     SignatureSchemeVersion.SIGNING_BLOCK_V2), vSigner.contentDigests));
345         } catch (SignatureNotFoundException e) {
346             throw e;
347         } catch (Exception e) {
348             // APK Signature Scheme v2 signature found but did not verify
349             return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
350                     "Failed to collect certificates from " + apkPath
351                             + " using APK Signature Scheme v2", e);
352         } finally {
353             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
354         }
355     }
356 
357     /**
358      * Verifies the provided APK using JAR schema.
359      * @return the certificates associated with each signer.
360      * @param verifyFull whether to verify all contents of this APK or just collect certificates.
361      */
verifyV1Signature(ParseInput input, String apkPath, boolean verifyFull)362     private static ParseResult<SigningDetailsWithDigests> verifyV1Signature(ParseInput input,
363             String apkPath, boolean verifyFull) {
364         StrictJarFile jarFile = null;
365 
366         try {
367             final Certificate[][] lastCerts;
368             final Signature[] lastSigs;
369 
370             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor");
371 
372             // we still pass verify = true to ctor to collect certs, even though we're not checking
373             // the whole jar.
374             jarFile = new StrictJarFile(
375                     apkPath,
376                     true, // collect certs
377                     verifyFull); // whether to reject APK with stripped v2 signatures (b/27887819)
378             final List<ZipEntry> toVerify = new ArrayList<>();
379 
380             // Gather certs from AndroidManifest.xml, which every APK must have, as an optimization
381             // to not need to verify the whole APK when verifyFUll == false.
382             final ZipEntry manifestEntry = jarFile.findEntry(
383                     ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME);
384             if (manifestEntry == null) {
385                 return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST,
386                         "Package " + apkPath + " has no manifest");
387             }
388             final ParseResult<Certificate[][]> result =
389                     loadCertificates(input, jarFile, manifestEntry);
390             if (result.isError()) {
391                 return input.error(result);
392             }
393             lastCerts = result.getResult();
394             if (ArrayUtils.isEmpty(lastCerts)) {
395                 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Package "
396                         + apkPath + " has no certificates at entry "
397                         + ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME);
398             }
399             lastSigs = convertToSignatures(lastCerts);
400 
401             // fully verify all contents, except for AndroidManifest.xml  and the META-INF/ files.
402             if (verifyFull) {
403                 final Iterator<ZipEntry> i = jarFile.iterator();
404                 while (i.hasNext()) {
405                     final ZipEntry entry = i.next();
406                     if (entry.isDirectory()) continue;
407 
408                     final String entryName = entry.getName();
409                     if (entryName.startsWith("META-INF/")) continue;
410                     if (entryName.equals(ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME)) continue;
411 
412                     toVerify.add(entry);
413                 }
414 
415                 for (ZipEntry entry : toVerify) {
416                     final Certificate[][] entryCerts;
417                     final ParseResult<Certificate[][]> ret =
418                             loadCertificates(input, jarFile, entry);
419                     if (ret.isError()) {
420                         return input.error(ret);
421                     }
422                     entryCerts = ret.getResult();
423                     if (ArrayUtils.isEmpty(entryCerts)) {
424                         return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
425                                 "Package " + apkPath + " has no certificates at entry "
426                                         + entry.getName());
427                     }
428 
429                     // make sure all entries use the same signing certs
430                     final Signature[] entrySigs = convertToSignatures(entryCerts);
431                     if (!Signature.areExactMatch(lastSigs, entrySigs)) {
432                         return input.error(
433                                 INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
434                                 "Package " + apkPath + " has mismatched certificates at entry "
435                                         + entry.getName());
436                     }
437                 }
438             }
439             return input.success(new SigningDetailsWithDigests(
440                     new SigningDetails(lastSigs, SignatureSchemeVersion.JAR), null));
441         } catch (GeneralSecurityException e) {
442             return input.error(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
443                     "Failed to collect certificates from " + apkPath, e);
444         } catch (IOException | RuntimeException e) {
445             return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
446                     "Failed to collect certificates from " + apkPath, e);
447         } finally {
448             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
449             closeQuietly(jarFile);
450         }
451     }
452 
loadCertificates(ParseInput input, StrictJarFile jarFile, ZipEntry entry)453     private static ParseResult<Certificate[][]> loadCertificates(ParseInput input,
454             StrictJarFile jarFile, ZipEntry entry) {
455         InputStream is = null;
456         try {
457             // We must read the stream for the JarEntry to retrieve
458             // its certificates.
459             is = jarFile.getInputStream(entry);
460             readFullyIgnoringContents(is);
461             return input.success(jarFile.getCertificateChains(entry));
462         } catch (IOException | RuntimeException e) {
463             return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
464                     "Failed reading " + entry.getName() + " in " + jarFile, e);
465         } finally {
466             IoUtils.closeQuietly(is);
467         }
468     }
469 
readFullyIgnoringContents(InputStream in)470     private static void readFullyIgnoringContents(InputStream in) throws IOException {
471         byte[] buffer = sBuffer.getAndSet(null);
472         if (buffer == null) {
473             buffer = new byte[4096];
474         }
475 
476         int n = 0;
477         int count = 0;
478         while ((n = in.read(buffer, 0, buffer.length)) != -1) {
479             count += n;
480         }
481 
482         sBuffer.set(buffer);
483         return;
484     }
485 
486     /**
487      * Converts an array of certificate chains into the {@code Signature} equivalent used by the
488      * PackageManager.
489      *
490      * @throws CertificateEncodingException if it is unable to create a Signature object.
491      */
convertToSignatures(Certificate[][] certs)492     private static Signature[] convertToSignatures(Certificate[][] certs)
493             throws CertificateEncodingException {
494         final Signature[] res = new Signature[certs.length];
495         for (int i = 0; i < certs.length; i++) {
496             res[i] = new Signature(certs[i]);
497         }
498         return res;
499     }
500 
closeQuietly(StrictJarFile jarFile)501     private static void closeQuietly(StrictJarFile jarFile) {
502         if (jarFile != null) {
503             try {
504                 jarFile.close();
505             } catch (Exception ignored) {
506             }
507         }
508     }
509 
510     /**
511      * Returns the minimum signature scheme version required for an app targeting the specified
512      * {@code targetSdk}.
513      */
getMinimumSignatureSchemeVersionForTargetSdk(int targetSdk)514     public static int getMinimumSignatureSchemeVersionForTargetSdk(int targetSdk) {
515         if (targetSdk >= Build.VERSION_CODES.R) {
516             return SignatureSchemeVersion.SIGNING_BLOCK_V2;
517         }
518         return SignatureSchemeVersion.JAR;
519     }
520 
521     /**
522      * Result of a successful APK verification operation.
523      */
524     public static class Result {
525         public final Certificate[][] certs;
526         public final Signature[] sigs;
527         public final int signatureSchemeVersion;
528 
Result(Certificate[][] certs, Signature[] sigs, int signingVersion)529         public Result(Certificate[][] certs, Signature[] sigs, int signingVersion) {
530             this.certs = certs;
531             this.sigs = sigs;
532             this.signatureSchemeVersion = signingVersion;
533         }
534     }
535 
536     /**
537      * @return the verity root hash in the Signing Block.
538      */
getVerityRootHash(String apkPath)539     public static byte[] getVerityRootHash(String apkPath) throws IOException, SecurityException {
540         // first try v3
541         try {
542             return ApkSignatureSchemeV3Verifier.getVerityRootHash(apkPath);
543         } catch (SignatureNotFoundException e) {
544             // try older version
545         }
546         try {
547             return ApkSignatureSchemeV2Verifier.getVerityRootHash(apkPath);
548         } catch (SignatureNotFoundException e) {
549             return null;
550         }
551     }
552 
553     /**
554      * Generates the Merkle tree and verity metadata to the buffer allocated by the {@code
555      * ByteBufferFactory}.
556      *
557      * @return the verity root hash of the generated Merkle tree.
558      */
generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)559     public static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)
560             throws IOException, SignatureNotFoundException, SecurityException, DigestException,
561             NoSuchAlgorithmException {
562         // first try v3
563         try {
564             return ApkSignatureSchemeV3Verifier.generateApkVerity(apkPath, bufferFactory);
565         } catch (SignatureNotFoundException e) {
566             // try older version
567         }
568         return ApkSignatureSchemeV2Verifier.generateApkVerity(apkPath, bufferFactory);
569     }
570 
571     /**
572      * Extended signing details.
573      * @hide for internal use only.
574      */
575     public static class SigningDetailsWithDigests {
576         public final SigningDetails signingDetails;
577 
578         /**
579          * APK Signature Schemes v2/v3/v4 might contain multiple content digests.
580          * SignatureVerifier usually chooses one of them to verify.
581          * For certain signature schemes, e.g. v4, this digest is verified continuously.
582          * For others, e.g. v2, the caller has to specify if they want to verify.
583          * Please refer to documentation for more details.
584          */
585         public final Map<Integer, byte[]> contentDigests;
586 
SigningDetailsWithDigests(SigningDetails signingDetails, Map<Integer, byte[]> contentDigests)587         SigningDetailsWithDigests(SigningDetails signingDetails,
588                 Map<Integer, byte[]> contentDigests) {
589             this.signingDetails = signingDetails;
590             this.contentDigests = contentDigests;
591         }
592     }
593 }
594