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