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 
26 import android.content.pm.PackageParser;
27 import android.content.pm.PackageParser.PackageParserException;
28 import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
29 import android.content.pm.Signature;
30 import android.os.Build;
31 import android.os.Trace;
32 import android.util.jar.StrictJarFile;
33 
34 import com.android.internal.util.ArrayUtils;
35 
36 import libcore.io.IoUtils;
37 
38 import java.io.IOException;
39 import java.io.InputStream;
40 import java.security.DigestException;
41 import java.security.GeneralSecurityException;
42 import java.security.NoSuchAlgorithmException;
43 import java.security.cert.Certificate;
44 import java.security.cert.CertificateEncodingException;
45 import java.util.ArrayList;
46 import java.util.Iterator;
47 import java.util.List;
48 import java.util.concurrent.atomic.AtomicReference;
49 import java.util.zip.ZipEntry;
50 
51 /**
52  * Facade class that takes care of the details of APK verification on
53  * behalf of PackageParser.
54  *
55  * @hide for internal use only.
56  */
57 public class ApkSignatureVerifier {
58 
59     private static final AtomicReference<byte[]> sBuffer = new AtomicReference<>();
60 
61     /**
62      * Verifies the provided APK and returns the certificates associated with each signer.
63      *
64      * @throws PackageParserException if the APK's signature failed to verify.
65      */
verify(String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion)66     public static PackageParser.SigningDetails verify(String apkPath,
67             @SignatureSchemeVersion int minSignatureSchemeVersion)
68             throws PackageParserException {
69         return verifySignatures(apkPath, minSignatureSchemeVersion, true);
70     }
71 
72     /**
73      * Returns the certificates associated with each signer for the given APK without verification.
74      * This method is dangerous and should not be used, unless the caller is absolutely certain the
75      * APK is trusted.
76      *
77      * @throws PackageParserException if there was a problem collecting certificates.
78      */
unsafeGetCertsWithoutVerification( String apkPath, int minSignatureSchemeVersion)79     public static PackageParser.SigningDetails unsafeGetCertsWithoutVerification(
80             String apkPath, int minSignatureSchemeVersion)
81             throws PackageParserException {
82         return verifySignatures(apkPath, minSignatureSchemeVersion, false);
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      * @throws PackageParserException if there was a problem collecting certificates
90      */
verifySignatures(String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)91     private static PackageParser.SigningDetails verifySignatures(String apkPath,
92             @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
93             throws PackageParserException {
94 
95         if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V4) {
96             // V3 and before are older than the requested minimum signing version
97             throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
98                     "No signature found in package of version " + minSignatureSchemeVersion
99                             + " or newer for package " + apkPath);
100         }
101 
102         // first try v4
103         try {
104             return verifyV4Signature(apkPath, minSignatureSchemeVersion, verifyFull);
105         } catch (SignatureNotFoundException e) {
106             // not signed with v4, try older if allowed
107             if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V4) {
108                 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
109                         "No APK Signature Scheme v4 signature in package " + apkPath, e);
110             }
111         }
112 
113         if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V3) {
114             // V3 and before are older than the requested minimum signing version
115             throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
116                     "No signature found in package of version " + minSignatureSchemeVersion
117                             + " or newer for package " + apkPath);
118         }
119 
120         return verifyV3AndBelowSignatures(apkPath, minSignatureSchemeVersion, verifyFull);
121     }
122 
verifyV3AndBelowSignatures(String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)123     private static PackageParser.SigningDetails verifyV3AndBelowSignatures(String apkPath,
124             @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
125             throws PackageParserException {
126         // try v3
127         try {
128             return verifyV3Signature(apkPath, verifyFull);
129         } catch (SignatureNotFoundException e) {
130             // not signed with v3, try older if allowed
131             if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) {
132                 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
133                         "No APK Signature Scheme v3 signature in package " + apkPath, e);
134             }
135         }
136 
137         // redundant, protective version check
138         if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V2) {
139             // V2 and before are older than the requested minimum signing version
140             throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
141                     "No signature found in package of version " + minSignatureSchemeVersion
142                             + " or newer for package " + apkPath);
143         }
144 
145         // try v2
146         try {
147             return verifyV2Signature(apkPath, verifyFull);
148         } catch (SignatureNotFoundException e) {
149             // not signed with v2, try older if allowed
150             if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V2) {
151                 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
152                         "No APK Signature Scheme v2 signature in package " + apkPath, e);
153             }
154         }
155 
156         // redundant, protective version check
157         if (minSignatureSchemeVersion > SignatureSchemeVersion.JAR) {
158             // V1 and is older than the requested minimum signing version
159             throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
160                     "No signature found in package of version " + minSignatureSchemeVersion
161                             + " or newer for package " + apkPath);
162         }
163 
164         // v2 didn't work, try jarsigner
165         return verifyV1Signature(apkPath, verifyFull);
166     }
167 
168     /**
169      * Verifies the provided APK using V4 schema.
170      *
171      * @param verifyFull whether to verify (V4 vs V3) or just collect certificates.
172      * @return the certificates associated with each signer.
173      * @throws SignatureNotFoundException if there are no V4 signatures in the APK
174      * @throws PackageParserException     if there was a problem collecting certificates
175      */
verifyV4Signature(String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)176     private static PackageParser.SigningDetails verifyV4Signature(String apkPath,
177             @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
178             throws SignatureNotFoundException, PackageParserException {
179         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV4" : "certsOnlyV4");
180         try {
181             ApkSignatureSchemeV4Verifier.VerifiedSigner vSigner =
182                     ApkSignatureSchemeV4Verifier.extractCertificates(apkPath);
183             Certificate[][] signerCerts = new Certificate[][]{vSigner.certs};
184             Signature[] signerSigs = convertToSignatures(signerCerts);
185 
186             if (verifyFull) {
187                 byte[] nonstreamingDigest = null;
188                 Certificate[][] nonstreamingCerts = null;
189 
190                 try {
191                     // v4 is an add-on and requires v2 or v3 signature to validate against its
192                     // certificate and digest
193                     ApkSignatureSchemeV3Verifier.VerifiedSigner v3Signer =
194                             ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath);
195                     nonstreamingDigest = v3Signer.digest;
196                     nonstreamingCerts = new Certificate[][]{v3Signer.certs};
197                 } catch (SignatureNotFoundException e) {
198                     try {
199                         ApkSignatureSchemeV2Verifier.VerifiedSigner v2Signer =
200                                 ApkSignatureSchemeV2Verifier.verify(apkPath, false);
201                         nonstreamingDigest = v2Signer.digest;
202                         nonstreamingCerts = v2Signer.certs;
203                     } catch (SignatureNotFoundException ee) {
204                         throw new SecurityException(
205                                 "V4 verification failed to collect V2/V3 certificates from : "
206                                         + apkPath, ee);
207                     }
208                 }
209 
210                 Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts);
211                 if (nonstreamingSigs.length != signerSigs.length) {
212                     throw new SecurityException(
213                             "Invalid number of certificates: " + nonstreamingSigs.length);
214                 }
215 
216                 for (int i = 0, size = signerSigs.length; i < size; ++i) {
217                     if (!nonstreamingSigs[i].equals(signerSigs[i])) {
218                         throw new SecurityException(
219                                 "V4 signature certificate does not match V2/V3");
220                     }
221                 }
222 
223                 if (!ArrayUtils.equals(vSigner.apkDigest, nonstreamingDigest,
224                         vSigner.apkDigest.length)) {
225                     throw new SecurityException("APK digest in V4 signature does not match V2/V3");
226                 }
227             }
228 
229             return new PackageParser.SigningDetails(signerSigs,
230                     SignatureSchemeVersion.SIGNING_BLOCK_V4);
231         } catch (SignatureNotFoundException e) {
232             throw e;
233         } catch (Exception e) {
234             // APK Signature Scheme v4 signature found but did not verify
235             throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
236                     "Failed to collect certificates from " + apkPath
237                             + " using APK Signature Scheme v4", e);
238         } finally {
239             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
240         }
241     }
242 
243     /**
244      * Verifies the provided APK using V3 schema.
245      *
246      * @param verifyFull whether to verify all contents of this APK or just collect certificates.
247      * @return the certificates associated with each signer.
248      * @throws SignatureNotFoundException if there are no V3 signatures in the APK
249      * @throws PackageParserException     if there was a problem collecting certificates
250      */
verifyV3Signature(String apkPath, boolean verifyFull)251     private static PackageParser.SigningDetails verifyV3Signature(String apkPath,
252             boolean verifyFull) throws SignatureNotFoundException, PackageParserException {
253         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV3" : "certsOnlyV3");
254         try {
255             ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
256                     verifyFull ? ApkSignatureSchemeV3Verifier.verify(apkPath)
257                             : ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(
258                                     apkPath);
259             Certificate[][] signerCerts = new Certificate[][]{vSigner.certs};
260             Signature[] signerSigs = convertToSignatures(signerCerts);
261             Signature[] pastSignerSigs = null;
262             if (vSigner.por != null) {
263                 // populate proof-of-rotation information
264                 pastSignerSigs = new Signature[vSigner.por.certs.size()];
265                 for (int i = 0; i < pastSignerSigs.length; i++) {
266                     pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded());
267                     pastSignerSigs[i].setFlags(vSigner.por.flagsList.get(i));
268                 }
269             }
270             return new PackageParser.SigningDetails(signerSigs,
271                     SignatureSchemeVersion.SIGNING_BLOCK_V3, pastSignerSigs);
272         } catch (SignatureNotFoundException e) {
273             throw e;
274         } catch (Exception e) {
275             // APK Signature Scheme v3 signature found but did not verify
276             throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
277                     "Failed to collect certificates from " + apkPath
278                             + " using APK Signature Scheme v3", e);
279         } finally {
280             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
281         }
282     }
283 
284     /**
285      * Verifies the provided APK using V2 schema.
286      *
287      * @param verifyFull whether to verify all contents of this APK or just collect certificates.
288      * @return the certificates associated with each signer.
289      * @throws SignatureNotFoundException if there are no V2 signatures in the APK
290      * @throws PackageParserException     if there was a problem collecting certificates
291      */
verifyV2Signature(String apkPath, boolean verifyFull)292     private static PackageParser.SigningDetails verifyV2Signature(String apkPath,
293             boolean verifyFull) throws SignatureNotFoundException, PackageParserException {
294         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV2" : "certsOnlyV2");
295         try {
296             Certificate[][] signerCerts = verifyFull ? ApkSignatureSchemeV2Verifier.verify(apkPath)
297                     : ApkSignatureSchemeV2Verifier.unsafeGetCertsWithoutVerification(apkPath);
298             Signature[] signerSigs = convertToSignatures(signerCerts);
299             return new PackageParser.SigningDetails(signerSigs,
300                     SignatureSchemeVersion.SIGNING_BLOCK_V2);
301         } catch (SignatureNotFoundException e) {
302             throw e;
303         } catch (Exception e) {
304             // APK Signature Scheme v2 signature found but did not verify
305             throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
306                     "Failed to collect certificates from " + apkPath
307                             + " using APK Signature Scheme v2", e);
308         } finally {
309             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
310         }
311     }
312 
313     /**
314      * Verifies the provided APK using JAR schema.
315      * @return the certificates associated with each signer.
316      * @param verifyFull whether to verify all contents of this APK or just collect certificates.
317      * @throws PackageParserException if there was a problem collecting certificates
318      */
verifyV1Signature( String apkPath, boolean verifyFull)319     private static PackageParser.SigningDetails verifyV1Signature(
320             String apkPath, boolean verifyFull)
321             throws PackageParserException {
322         StrictJarFile jarFile = null;
323 
324         try {
325             final Certificate[][] lastCerts;
326             final Signature[] lastSigs;
327 
328             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor");
329 
330             // we still pass verify = true to ctor to collect certs, even though we're not checking
331             // the whole jar.
332             jarFile = new StrictJarFile(
333                     apkPath,
334                     true, // collect certs
335                     verifyFull); // whether to reject APK with stripped v2 signatures (b/27887819)
336             final List<ZipEntry> toVerify = new ArrayList<>();
337 
338             // Gather certs from AndroidManifest.xml, which every APK must have, as an optimization
339             // to not need to verify the whole APK when verifyFUll == false.
340             final ZipEntry manifestEntry = jarFile.findEntry(
341                     PackageParser.ANDROID_MANIFEST_FILENAME);
342             if (manifestEntry == null) {
343                 throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
344                         "Package " + apkPath + " has no manifest");
345             }
346             lastCerts = loadCertificates(jarFile, manifestEntry);
347             if (ArrayUtils.isEmpty(lastCerts)) {
348                 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Package "
349                         + apkPath + " has no certificates at entry "
350                         + PackageParser.ANDROID_MANIFEST_FILENAME);
351             }
352             lastSigs = convertToSignatures(lastCerts);
353 
354             // fully verify all contents, except for AndroidManifest.xml  and the META-INF/ files.
355             if (verifyFull) {
356                 final Iterator<ZipEntry> i = jarFile.iterator();
357                 while (i.hasNext()) {
358                     final ZipEntry entry = i.next();
359                     if (entry.isDirectory()) continue;
360 
361                     final String entryName = entry.getName();
362                     if (entryName.startsWith("META-INF/")) continue;
363                     if (entryName.equals(PackageParser.ANDROID_MANIFEST_FILENAME)) continue;
364 
365                     toVerify.add(entry);
366                 }
367 
368                 for (ZipEntry entry : toVerify) {
369                     final Certificate[][] entryCerts = loadCertificates(jarFile, entry);
370                     if (ArrayUtils.isEmpty(entryCerts)) {
371                         throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
372                                 "Package " + apkPath + " has no certificates at entry "
373                                         + entry.getName());
374                     }
375 
376                     // make sure all entries use the same signing certs
377                     final Signature[] entrySigs = convertToSignatures(entryCerts);
378                     if (!Signature.areExactMatch(lastSigs, entrySigs)) {
379                         throw new PackageParserException(
380                                 INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
381                                 "Package " + apkPath + " has mismatched certificates at entry "
382                                         + entry.getName());
383                     }
384                 }
385             }
386             return new PackageParser.SigningDetails(lastSigs, SignatureSchemeVersion.JAR);
387         } catch (GeneralSecurityException e) {
388             throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
389                     "Failed to collect certificates from " + apkPath, e);
390         } catch (IOException | RuntimeException e) {
391             throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
392                     "Failed to collect certificates from " + apkPath, e);
393         } finally {
394             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
395             closeQuietly(jarFile);
396         }
397     }
398 
loadCertificates(StrictJarFile jarFile, ZipEntry entry)399     private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry entry)
400             throws PackageParserException {
401         InputStream is = null;
402         try {
403             // We must read the stream for the JarEntry to retrieve
404             // its certificates.
405             is = jarFile.getInputStream(entry);
406             readFullyIgnoringContents(is);
407             return jarFile.getCertificateChains(entry);
408         } catch (IOException | RuntimeException e) {
409             throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
410                     "Failed reading " + entry.getName() + " in " + jarFile, e);
411         } finally {
412             IoUtils.closeQuietly(is);
413         }
414     }
415 
readFullyIgnoringContents(InputStream in)416     private static void readFullyIgnoringContents(InputStream in) throws IOException {
417         byte[] buffer = sBuffer.getAndSet(null);
418         if (buffer == null) {
419             buffer = new byte[4096];
420         }
421 
422         int n = 0;
423         int count = 0;
424         while ((n = in.read(buffer, 0, buffer.length)) != -1) {
425             count += n;
426         }
427 
428         sBuffer.set(buffer);
429         return;
430     }
431 
432     /**
433      * Converts an array of certificate chains into the {@code Signature} equivalent used by the
434      * PackageManager.
435      *
436      * @throws CertificateEncodingException if it is unable to create a Signature object.
437      */
convertToSignatures(Certificate[][] certs)438     private static Signature[] convertToSignatures(Certificate[][] certs)
439             throws CertificateEncodingException {
440         final Signature[] res = new Signature[certs.length];
441         for (int i = 0; i < certs.length; i++) {
442             res[i] = new Signature(certs[i]);
443         }
444         return res;
445     }
446 
closeQuietly(StrictJarFile jarFile)447     private static void closeQuietly(StrictJarFile jarFile) {
448         if (jarFile != null) {
449             try {
450                 jarFile.close();
451             } catch (Exception ignored) {
452             }
453         }
454     }
455 
456     /**
457      * Returns the minimum signature scheme version required for an app targeting the specified
458      * {@code targetSdk}.
459      */
getMinimumSignatureSchemeVersionForTargetSdk(int targetSdk)460     public static int getMinimumSignatureSchemeVersionForTargetSdk(int targetSdk) {
461         if (targetSdk >= Build.VERSION_CODES.R) {
462             return SignatureSchemeVersion.SIGNING_BLOCK_V2;
463         }
464         return SignatureSchemeVersion.JAR;
465     }
466 
467     /**
468      * Result of a successful APK verification operation.
469      */
470     public static class Result {
471         public final Certificate[][] certs;
472         public final Signature[] sigs;
473         public final int signatureSchemeVersion;
474 
Result(Certificate[][] certs, Signature[] sigs, int signingVersion)475         public Result(Certificate[][] certs, Signature[] sigs, int signingVersion) {
476             this.certs = certs;
477             this.sigs = sigs;
478             this.signatureSchemeVersion = signingVersion;
479         }
480     }
481 
482     /**
483      * @return the verity root hash in the Signing Block.
484      */
getVerityRootHash(String apkPath)485     public static byte[] getVerityRootHash(String apkPath) throws IOException, SecurityException {
486         // first try v3
487         try {
488             return ApkSignatureSchemeV3Verifier.getVerityRootHash(apkPath);
489         } catch (SignatureNotFoundException e) {
490             // try older version
491         }
492         try {
493             return ApkSignatureSchemeV2Verifier.getVerityRootHash(apkPath);
494         } catch (SignatureNotFoundException e) {
495             return null;
496         }
497     }
498 
499     /**
500      * Generates the Merkle tree and verity metadata to the buffer allocated by the {@code
501      * ByteBufferFactory}.
502      *
503      * @return the verity root hash of the generated Merkle tree.
504      */
generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)505     public static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)
506             throws IOException, SignatureNotFoundException, SecurityException, DigestException,
507             NoSuchAlgorithmException {
508         // first try v3
509         try {
510             return ApkSignatureSchemeV3Verifier.generateApkVerity(apkPath, bufferFactory);
511         } catch (SignatureNotFoundException e) {
512             // try older version
513         }
514         return ApkSignatureSchemeV2Verifier.generateApkVerity(apkPath, bufferFactory);
515     }
516 
517     /**
518      * Generates the FSVerity root hash from FSVerity header, extensions and Merkle tree root hash
519      * in Signing Block.
520      *
521      * @return FSverity root hash
522      */
generateApkVerityRootHash(String apkPath)523     public static byte[] generateApkVerityRootHash(String apkPath)
524             throws NoSuchAlgorithmException, DigestException, IOException {
525         // first try v3
526         try {
527             return ApkSignatureSchemeV3Verifier.generateApkVerityRootHash(apkPath);
528         } catch (SignatureNotFoundException e) {
529             // try older version
530         }
531         try {
532             return ApkSignatureSchemeV2Verifier.generateApkVerityRootHash(apkPath);
533         } catch (SignatureNotFoundException e) {
534             return null;
535         }
536     }
537 }
538