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