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