• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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             // We need to always run v2/v3 verifier to figure out which block they use so we can
264             // return the past signers as well as the current one - the rotation chain is important
265             // for many callers who verify the signature origin as well as the apk integrity.
266             if (android.content.pm.Flags.alwaysLoadPastCertsV4()
267                     || verifyFull || signingInfos.signingInfoBlocks.length > 0) {
268                 try {
269                     // v4 is an add-on and requires v2 or v3 signature to validate against its
270                     // certificate and digest
271                     ApkSignatureSchemeV3Verifier.VerifiedSigner v3Signer =
272                             ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath);
273                     nonstreamingDigests = v3Signer.contentDigests;
274                     nonstreamingCerts = new Certificate[][]{v3Signer.certs};
275                     if (v3Signer.por != null) {
276                         // populate proof-of-rotation information
277                         pastSignerSigs = new Signature[v3Signer.por.certs.size()];
278                         for (int i = 0; i < pastSignerSigs.length; i++) {
279                             pastSignerSigs[i] = new Signature(
280                                     v3Signer.por.certs.get(i).getEncoded());
281                             pastSignerSigs[i].setFlags(v3Signer.por.flagsList.get(i));
282                         }
283                     }
284                     v3BlockId = v3Signer.blockId;
285                 } catch (SignatureNotFoundException e) {
286                     try {
287                         ApkSignatureSchemeV2Verifier.VerifiedSigner v2Signer =
288                                 ApkSignatureSchemeV2Verifier.verify(apkPath, false);
289                         nonstreamingDigests = v2Signer.contentDigests;
290                         nonstreamingCerts = v2Signer.certs;
291                     } catch (SignatureNotFoundException ee) {
292                         throw new SecurityException(
293                                 "V4 verification failed to collect V2/V3 certificates from : "
294                                         + apkPath, ee);
295                     }
296                 }
297             }
298 
299             ApkSignatureSchemeV4Verifier.VerifiedSigner vSigner =
300                     ApkSignatureSchemeV4Verifier.verify(apkPath, hashingInfo, signingInfos,
301                             v3BlockId);
302             Certificate[][] signerCerts = new Certificate[][]{vSigner.certs};
303             Signature[] signerSigs = convertToSignatures(signerCerts);
304 
305             if (verifyFull) {
306                 Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts);
307                 if (nonstreamingSigs.length != signerSigs.length) {
308                     throw new SecurityException(
309                             "Invalid number of certificates: " + nonstreamingSigs.length);
310                 }
311 
312                 for (int i = 0, size = signerSigs.length; i < size; ++i) {
313                     if (!nonstreamingSigs[i].equals(signerSigs[i])) {
314                         throw new SecurityException(
315                                 "V4 signature certificate does not match V2/V3");
316                     }
317                 }
318 
319                 boolean found = false;
320                 for (byte[] nonstreamingDigest : nonstreamingDigests.values()) {
321                     if (ArrayUtils.equals(vSigner.apkDigest, nonstreamingDigest,
322                             vSigner.apkDigest.length)) {
323                         found = true;
324                         break;
325                     }
326                 }
327                 if (!found) {
328                     throw new SecurityException("APK digest in V4 signature does not match V2/V3");
329                 }
330             }
331 
332             return input.success(new SigningDetailsWithDigests(new SigningDetails(signerSigs,
333                     SignatureSchemeVersion.SIGNING_BLOCK_V4, pastSignerSigs),
334                     vSigner.contentDigests));
335         } catch (SignatureNotFoundException e) {
336             throw e;
337         } catch (Exception e) {
338             // APK Signature Scheme v4 signature found but did not verify.
339             return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
340                     "Failed to collect certificates from " + apkPath
341                             + " using APK Signature Scheme v4", e);
342         } finally {
343             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
344         }
345     }
346 
347     /**
348      * Verifies the provided APK using V3 schema.
349      *
350      * @param verifyFull whether to verify all contents of this APK or just collect certificates.
351      * @return the certificates associated with each signer.
352      * @throws SignatureNotFoundException if there are no V3 signatures in the APK
353      */
verifyV3Signature(ParseInput input, String apkPath, boolean verifyFull)354     private static ParseResult<SigningDetailsWithDigests> verifyV3Signature(ParseInput input,
355             String apkPath, boolean verifyFull) throws SignatureNotFoundException {
356         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV3" : "certsOnlyV3");
357         try {
358             ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
359                     verifyFull ? ApkSignatureSchemeV3Verifier.verify(apkPath)
360                             : ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(
361                                     apkPath);
362             Certificate[][] signerCerts = new Certificate[][]{vSigner.certs};
363             Signature[] signerSigs = convertToSignatures(signerCerts);
364             Signature[] pastSignerSigs = null;
365             if (vSigner.por != null) {
366                 // populate proof-of-rotation information
367                 pastSignerSigs = new Signature[vSigner.por.certs.size()];
368                 for (int i = 0; i < pastSignerSigs.length; i++) {
369                     pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded());
370                     pastSignerSigs[i].setFlags(vSigner.por.flagsList.get(i));
371                 }
372             }
373             return input.success(new SigningDetailsWithDigests(new SigningDetails(signerSigs,
374                     SignatureSchemeVersion.SIGNING_BLOCK_V3, pastSignerSigs),
375                     vSigner.contentDigests));
376         } catch (SignatureNotFoundException e) {
377             throw e;
378         } catch (Exception e) {
379             // APK Signature Scheme v3 signature found but did not verify
380             return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
381                     "Failed to collect certificates from " + apkPath
382                             + " using APK Signature Scheme v3", e);
383         } finally {
384             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
385         }
386     }
387 
388     /**
389      * Verifies the provided APK using V2 schema.
390      *
391      * @param verifyFull whether to verify all contents of this APK or just collect certificates.
392      * @return the certificates associated with each signer.
393      * @throws SignatureNotFoundException if there are no V2 signatures in the APK
394      */
verifyV2Signature(ParseInput input, String apkPath, boolean verifyFull)395     private static ParseResult<SigningDetailsWithDigests> verifyV2Signature(ParseInput input,
396             String apkPath, boolean verifyFull) throws SignatureNotFoundException {
397         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV2" : "certsOnlyV2");
398         try {
399             ApkSignatureSchemeV2Verifier.VerifiedSigner vSigner =
400                     ApkSignatureSchemeV2Verifier.verify(apkPath, verifyFull);
401             Certificate[][] signerCerts = vSigner.certs;
402             Signature[] signerSigs = convertToSignatures(signerCerts);
403             return input.success(new SigningDetailsWithDigests(new SigningDetails(signerSigs,
404                     SignatureSchemeVersion.SIGNING_BLOCK_V2), vSigner.contentDigests));
405         } catch (SignatureNotFoundException e) {
406             throw e;
407         } catch (Exception e) {
408             // APK Signature Scheme v2 signature found but did not verify
409             return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
410                     "Failed to collect certificates from " + apkPath
411                             + " using APK Signature Scheme v2", e);
412         } finally {
413             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
414         }
415     }
416 
417     /**
418      * Verifies the provided APK using JAR schema.
419      * @return the certificates associated with each signer.
420      * @param verifyFull whether to verify all contents of this APK or just collect certificates.
421      */
verifyV1Signature(ParseInput input, String apkPath, boolean verifyFull)422     private static ParseResult<SigningDetailsWithDigests> verifyV1Signature(ParseInput input,
423             String apkPath, boolean verifyFull) {
424         StrictJarFile jarFile = null;
425 
426         try {
427             final Certificate[][] lastCerts;
428             final Signature[] lastSigs;
429 
430             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor");
431 
432             // we still pass verify = true to ctor to collect certs, even though we're not checking
433             // the whole jar.
434             jarFile = new StrictJarFile(
435                     apkPath,
436                     true, // collect certs
437                     verifyFull); // whether to reject APK with stripped v2 signatures (b/27887819)
438             final List<ZipEntry> toVerify = new ArrayList<>();
439 
440             // Gather certs from AndroidManifest.xml, which every APK must have, as an optimization
441             // to not need to verify the whole APK when verifyFUll == false.
442             final ZipEntry manifestEntry = jarFile.findEntry(
443                     ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME);
444             if (manifestEntry == null) {
445                 return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST,
446                         "Package " + apkPath + " has no manifest");
447             }
448             final ParseResult<Certificate[][]> result =
449                     loadCertificates(input, jarFile, manifestEntry);
450             if (result.isError()) {
451                 return input.error(result);
452             }
453             lastCerts = result.getResult();
454             if (ArrayUtils.isEmpty(lastCerts)) {
455                 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Package "
456                         + apkPath + " has no certificates at entry "
457                         + ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME);
458             }
459             lastSigs = convertToSignatures(lastCerts);
460 
461             // fully verify all contents, except for AndroidManifest.xml  and the META-INF/ files.
462             if (verifyFull) {
463                 final Iterator<ZipEntry> i = jarFile.iterator();
464                 while (i.hasNext()) {
465                     final ZipEntry entry = i.next();
466                     if (entry.isDirectory()) continue;
467 
468                     final String entryName = entry.getName();
469                     if (entryName.startsWith("META-INF/")) continue;
470                     if (entryName.equals(ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME)) continue;
471 
472                     toVerify.add(entry);
473                 }
474 
475                 for (ZipEntry entry : toVerify) {
476                     final Certificate[][] entryCerts;
477                     final ParseResult<Certificate[][]> ret =
478                             loadCertificates(input, jarFile, entry);
479                     if (ret.isError()) {
480                         return input.error(ret);
481                     }
482                     entryCerts = ret.getResult();
483                     if (ArrayUtils.isEmpty(entryCerts)) {
484                         return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
485                                 "Package " + apkPath + " has no certificates at entry "
486                                         + entry.getName());
487                     }
488 
489                     // make sure all entries use the same signing certs
490                     final Signature[] entrySigs = convertToSignatures(entryCerts);
491                     if (!Arrays.equals(lastSigs, entrySigs)) {
492                         return input.error(
493                                 INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
494                                 "Package " + apkPath + " has mismatched certificates at entry "
495                                         + entry.getName());
496                     }
497                 }
498             }
499             return input.success(new SigningDetailsWithDigests(
500                     new SigningDetails(lastSigs, SignatureSchemeVersion.JAR), null));
501         } catch (GeneralSecurityException e) {
502             return input.error(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
503                     "Failed to collect certificates from " + apkPath, e);
504         } catch (IOException | RuntimeException e) {
505             return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
506                     "Failed to collect certificates from " + apkPath, e);
507         } finally {
508             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
509             closeQuietly(jarFile);
510         }
511     }
512 
loadCertificates(ParseInput input, StrictJarFile jarFile, ZipEntry entry)513     private static ParseResult<Certificate[][]> loadCertificates(ParseInput input,
514             StrictJarFile jarFile, ZipEntry entry) {
515         InputStream is = null;
516         try {
517             // We must read the stream for the JarEntry to retrieve
518             // its certificates.
519             is = jarFile.getInputStream(entry);
520             readFullyIgnoringContents(is);
521             return input.success(jarFile.getCertificateChains(entry));
522         } catch (IOException | RuntimeException e) {
523             return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
524                     "Failed reading " + entry.getName() + " in " + jarFile, e);
525         } finally {
526             IoUtils.closeQuietly(is);
527         }
528     }
529 
readFullyIgnoringContents(InputStream in)530     private static void readFullyIgnoringContents(InputStream in) throws IOException {
531         byte[] buffer = sBuffer.getAndSet(null);
532         if (buffer == null) {
533             buffer = new byte[4096];
534         }
535 
536         int n = 0;
537         int count = 0;
538         while ((n = in.read(buffer, 0, buffer.length)) != -1) {
539             count += n;
540         }
541 
542         sBuffer.set(buffer);
543         return;
544     }
545 
546     /**
547      * Converts an array of certificate chains into the {@code Signature} equivalent used by the
548      * PackageManager.
549      *
550      * @throws CertificateEncodingException if it is unable to create a Signature object.
551      */
convertToSignatures(Certificate[][] certs)552     private static Signature[] convertToSignatures(Certificate[][] certs)
553             throws CertificateEncodingException {
554         final Signature[] res = new Signature[certs.length];
555         for (int i = 0; i < certs.length; i++) {
556             res[i] = new Signature(certs[i]);
557         }
558         return res;
559     }
560 
closeQuietly(StrictJarFile jarFile)561     private static void closeQuietly(StrictJarFile jarFile) {
562         if (jarFile != null) {
563             try {
564                 jarFile.close();
565             } catch (Exception ignored) {
566             }
567         }
568     }
569 
570     /**
571      * Returns the minimum signature scheme version required for an app targeting the specified
572      * {@code targetSdk}.
573      */
getMinimumSignatureSchemeVersionForTargetSdk(int targetSdk)574     public static int getMinimumSignatureSchemeVersionForTargetSdk(int targetSdk) {
575         if (targetSdk >= Build.VERSION_CODES.R) {
576             return SignatureSchemeVersion.SIGNING_BLOCK_V2;
577         }
578         return SignatureSchemeVersion.JAR;
579     }
580 
581     /**
582      * Result of a successful APK verification operation.
583      */
584     public static class Result {
585         public final Certificate[][] certs;
586         public final Signature[] sigs;
587         public final int signatureSchemeVersion;
588 
Result(Certificate[][] certs, Signature[] sigs, int signingVersion)589         public Result(Certificate[][] certs, Signature[] sigs, int signingVersion) {
590             this.certs = certs;
591             this.sigs = sigs;
592             this.signatureSchemeVersion = signingVersion;
593         }
594     }
595 
596     /**
597      * @return the verity root hash in the Signing Block.
598      */
getVerityRootHash(String apkPath)599     public static byte[] getVerityRootHash(String apkPath) throws IOException, SecurityException {
600         // first try v3
601         try {
602             return ApkSignatureSchemeV3Verifier.getVerityRootHash(apkPath);
603         } catch (SignatureNotFoundException e) {
604             // try older version
605         }
606         try {
607             return ApkSignatureSchemeV2Verifier.getVerityRootHash(apkPath);
608         } catch (SignatureNotFoundException e) {
609             return null;
610         }
611     }
612 
613     /**
614      * Generates the Merkle tree and verity metadata to the buffer allocated by the {@code
615      * ByteBufferFactory}.
616      *
617      * @return the verity root hash of the generated Merkle tree.
618      */
generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)619     public static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)
620             throws IOException, SignatureNotFoundException, SecurityException, DigestException,
621             NoSuchAlgorithmException {
622         // first try v3
623         try {
624             return ApkSignatureSchemeV3Verifier.generateApkVerity(apkPath, bufferFactory);
625         } catch (SignatureNotFoundException e) {
626             // try older version
627         }
628         return ApkSignatureSchemeV2Verifier.generateApkVerity(apkPath, bufferFactory);
629     }
630 
631     /**
632      * Extended signing details.
633      * @hide for internal use only.
634      */
635     public static class SigningDetailsWithDigests {
636         public final SigningDetails signingDetails;
637 
638         /**
639          * APK Signature Schemes v2/v3/v4 might contain multiple content digests.
640          * SignatureVerifier usually chooses one of them to verify.
641          * For certain signature schemes, e.g. v4, this digest is verified continuously.
642          * For others, e.g. v2, the caller has to specify if they want to verify.
643          * Please refer to documentation for more details.
644          */
645         public final Map<Integer, byte[]> contentDigests;
646 
SigningDetailsWithDigests(SigningDetails signingDetails, Map<Integer, byte[]> contentDigests)647         SigningDetailsWithDigests(SigningDetails signingDetails,
648                 Map<Integer, byte[]> contentDigests) {
649             this.signingDetails = signingDetails;
650             this.contentDigests = contentDigests;
651         }
652     }
653 }
654