1 /*
2  * Copyright (C) 2020 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 com.android.server.pm;
18 
19 import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256;
20 import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512;
21 import static android.content.pm.Checksum.TYPE_WHOLE_MD5;
22 import static android.content.pm.Checksum.TYPE_WHOLE_MERKLE_ROOT_4K_SHA256;
23 import static android.content.pm.Checksum.TYPE_WHOLE_SHA1;
24 import static android.content.pm.Checksum.TYPE_WHOLE_SHA256;
25 import static android.content.pm.Checksum.TYPE_WHOLE_SHA512;
26 import static android.content.pm.parsing.ApkLiteParseUtils.APK_FILE_EXTENSION;
27 import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256;
28 import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512;
29 import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
30 
31 import android.annotation.NonNull;
32 import android.annotation.Nullable;
33 import android.content.Context;
34 import android.content.pm.ApkChecksum;
35 import android.content.pm.Checksum;
36 import android.content.pm.IOnChecksumsReadyListener;
37 import android.content.pm.PackageManagerInternal;
38 import android.content.pm.Signature;
39 import android.content.pm.SigningDetails.SignatureSchemeVersion;
40 import android.content.pm.parsing.ApkLiteParseUtils;
41 import android.content.pm.parsing.result.ParseResult;
42 import android.content.pm.parsing.result.ParseTypeImpl;
43 import android.os.Handler;
44 import android.os.RemoteException;
45 import android.os.SystemClock;
46 import android.os.incremental.IncrementalManager;
47 import android.os.incremental.IncrementalStorage;
48 import android.util.ArrayMap;
49 import android.util.ArraySet;
50 import android.util.Pair;
51 import android.util.Slog;
52 import android.util.apk.ApkSignatureSchemeV2Verifier;
53 import android.util.apk.ApkSignatureSchemeV3Verifier;
54 import android.util.apk.ApkSignatureSchemeV4Verifier;
55 import android.util.apk.ApkSignatureVerifier;
56 import android.util.apk.ApkSigningBlockUtils;
57 import android.util.apk.ByteBufferFactory;
58 import android.util.apk.SignatureInfo;
59 import android.util.apk.SignatureNotFoundException;
60 import android.util.apk.VerityBuilder;
61 
62 import com.android.internal.annotations.VisibleForTesting;
63 import com.android.internal.security.VerityUtils;
64 import com.android.server.pm.pkg.AndroidPackage;
65 
66 import java.io.ByteArrayOutputStream;
67 import java.io.DataInputStream;
68 import java.io.DataOutputStream;
69 import java.io.EOFException;
70 import java.io.File;
71 import java.io.FileInputStream;
72 import java.io.IOException;
73 import java.io.InputStream;
74 import java.io.OutputStream;
75 import java.io.RandomAccessFile;
76 import java.nio.ByteBuffer;
77 import java.nio.ByteOrder;
78 import java.nio.file.Files;
79 import java.security.DigestException;
80 import java.security.InvalidParameterException;
81 import java.security.MessageDigest;
82 import java.security.NoSuchAlgorithmException;
83 import java.security.SignatureException;
84 import java.security.cert.Certificate;
85 import java.security.cert.CertificateEncodingException;
86 import java.security.cert.X509Certificate;
87 import java.util.ArrayList;
88 import java.util.Arrays;
89 import java.util.List;
90 import java.util.Map;
91 import java.util.Set;
92 
93 import sun.security.pkcs.PKCS7;
94 import sun.security.pkcs.SignerInfo;
95 
96 /**
97  * Provides checksums for APK.
98  */
99 public class ApkChecksums {
100     static final String TAG = "ApkChecksums";
101 
102     private static final String DIGESTS_FILE_EXTENSION = ".digests";
103     private static final String DIGESTS_SIGNATURE_FILE_EXTENSION = ".signature";
104 
105     // MessageDigest algorithms.
106     static final String ALGO_MD5 = "MD5";
107     static final String ALGO_SHA1 = "SHA1";
108     static final String ALGO_SHA256 = "SHA256";
109     static final String ALGO_SHA512 = "SHA512";
110 
111     private static final Certificate[] EMPTY_CERTIFICATE_ARRAY = {};
112 
113     /**
114      * Arbitrary size restriction for the signature, used to sign the checksums.
115      */
116     private static final int MAX_SIGNATURE_SIZE_BYTES = 35 * 1024;
117 
118     /**
119      * Check back in 1 second after we detected we needed to wait for the APK to be fully available.
120      */
121     private static final long PROCESS_REQUIRED_CHECKSUMS_DELAY_MILLIS = 1000;
122 
123     /**
124      * 24 hours timeout to wait till all files are loaded.
125      */
126     private static final long PROCESS_REQUIRED_CHECKSUMS_TIMEOUT_MILLIS = 1000 * 3600 * 24;
127 
128     /**
129      * Unit tests will instantiate, extend and/or mock to mock dependencies / behaviors.
130      *
131      * NOTE: All getters should return the same instance for every call.
132      */
133     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
134     static class Injector {
135 
136         @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
137         interface Producer<T> {
138             /** Produce an instance of type {@link T} */
produce()139             T produce();
140         }
141 
142         private final Producer<Context> mContext;
143         private final Producer<Handler> mHandlerProducer;
144         private final Producer<IncrementalManager> mIncrementalManagerProducer;
145         private final Producer<PackageManagerInternal> mPackageManagerInternalProducer;
146 
Injector(Producer<Context> context, Producer<Handler> handlerProducer, Producer<IncrementalManager> incrementalManagerProducer, Producer<PackageManagerInternal> packageManagerInternalProducer)147         Injector(Producer<Context> context, Producer<Handler> handlerProducer,
148                 Producer<IncrementalManager> incrementalManagerProducer,
149                 Producer<PackageManagerInternal> packageManagerInternalProducer) {
150             mContext = context;
151             mHandlerProducer = handlerProducer;
152             mIncrementalManagerProducer = incrementalManagerProducer;
153             mPackageManagerInternalProducer = packageManagerInternalProducer;
154         }
155 
getContext()156         public Context getContext() {
157             return mContext.produce();
158         }
159 
getHandler()160         public Handler getHandler() {
161             return mHandlerProducer.produce();
162         }
163 
getIncrementalManager()164         public IncrementalManager getIncrementalManager() {
165             return mIncrementalManagerProducer.produce();
166         }
167 
getPackageManagerInternal()168         public PackageManagerInternal getPackageManagerInternal() {
169             return mPackageManagerInternalProducer.produce();
170         }
171     }
172 
173     /**
174      * Return the digests path associated with the given code path
175      * (replaces '.apk' extension with '.digests')
176      *
177      * @throws IllegalArgumentException if the code path is not an .apk.
178      */
buildDigestsPathForApk(String codePath)179     public static String buildDigestsPathForApk(String codePath) {
180         if (!ApkLiteParseUtils.isApkPath(codePath)) {
181             throw new IllegalStateException("Code path is not an apk " + codePath);
182         }
183         return codePath.substring(0, codePath.length() - APK_FILE_EXTENSION.length())
184                 + DIGESTS_FILE_EXTENSION;
185     }
186 
187     /**
188      * Return the signature path associated with the given digests path.
189      * (appends '.signature' to the end)
190      */
buildSignaturePathForDigests(String digestsPath)191     public static String buildSignaturePathForDigests(String digestsPath) {
192         return digestsPath + DIGESTS_SIGNATURE_FILE_EXTENSION;
193     }
194 
195     /** Returns true if the given file looks like containing digests or digests' signature. */
isDigestOrDigestSignatureFile(File file)196     public static boolean isDigestOrDigestSignatureFile(File file) {
197         final String name = file.getName();
198         return name.endsWith(DIGESTS_FILE_EXTENSION) || name.endsWith(
199                 DIGESTS_SIGNATURE_FILE_EXTENSION);
200     }
201 
202     /**
203      * Search for the digests file associated with the given target file.
204      * If it exists, the method returns the digests file; otherwise it returns null.
205      */
findDigestsForFile(File targetFile)206     public static File findDigestsForFile(File targetFile) {
207         String digestsPath = buildDigestsPathForApk(targetFile.getAbsolutePath());
208         File digestsFile = new File(digestsPath);
209         return digestsFile.exists() ? digestsFile : null;
210     }
211 
212     /**
213      * Search for the signature file associated with the given digests file.
214      * If it exists, the method returns the signature file; otherwise it returns null.
215      */
findSignatureForDigests(File digestsFile)216     public static File findSignatureForDigests(File digestsFile) {
217         String signaturePath = buildSignaturePathForDigests(digestsFile.getAbsolutePath());
218         File signatureFile = new File(signaturePath);
219         return signatureFile.exists() ? signatureFile : null;
220     }
221 
222     /**
223      * Serialize checksums to the stream in binary format.
224      */
writeChecksums(OutputStream os, Checksum[] checksums)225     public static void writeChecksums(OutputStream os, Checksum[] checksums)
226             throws IOException {
227         try (DataOutputStream dos = new DataOutputStream(os)) {
228             for (Checksum checksum : checksums) {
229                 Checksum.writeToStream(dos, checksum);
230             }
231         }
232     }
233 
readChecksums(File file)234     private static Checksum[] readChecksums(File file) throws IOException {
235         try (InputStream is = new FileInputStream(file)) {
236             return readChecksums(is);
237         }
238     }
239 
240     /**
241      * Deserialize array of checksums previously stored in
242      * {@link #writeChecksums(OutputStream, Checksum[])}.
243      */
readChecksums(InputStream is)244     public static Checksum[] readChecksums(InputStream is) throws IOException {
245         try (DataInputStream dis = new DataInputStream(is)) {
246             ArrayList<Checksum> checksums = new ArrayList<>();
247             try {
248                 // 100 is an arbitrary very big number. We should stop at EOF.
249                 for (int i = 0; i < 100; ++i) {
250                     checksums.add(Checksum.readFromStream(dis));
251                 }
252             } catch (EOFException e) {
253                 // expected
254             }
255             return checksums.toArray(new Checksum[checksums.size()]);
256         }
257     }
258 
259     /**
260      * Verifies signature over binary serialized checksums.
261      * @param checksums array of checksums
262      * @param signature detached PKCS7 signature in DER format
263      * @return all certificates that passed verification
264      * @throws SignatureException if verification fails
265      */
verifySignature(Checksum[] checksums, byte[] signature)266     public static @NonNull Certificate[] verifySignature(Checksum[] checksums, byte[] signature)
267             throws NoSuchAlgorithmException, IOException, SignatureException {
268         if (signature == null || signature.length > MAX_SIGNATURE_SIZE_BYTES) {
269             throw new SignatureException("Invalid signature");
270         }
271 
272         final byte[] blob;
273         try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
274             writeChecksums(os, checksums);
275             blob = os.toByteArray();
276         }
277 
278         PKCS7 pkcs7 = new PKCS7(signature);
279 
280         final Certificate[] certs = pkcs7.getCertificates();
281         if (certs == null || certs.length == 0) {
282             throw new SignatureException("Signature missing certificates");
283         }
284 
285         final SignerInfo[] signerInfos = pkcs7.verify(blob);
286         if (signerInfos == null || signerInfos.length == 0) {
287             throw new SignatureException("Verification failed");
288         }
289 
290         ArrayList<Certificate> certificates = new ArrayList<>(signerInfos.length);
291         for (SignerInfo signerInfo : signerInfos) {
292             ArrayList<X509Certificate> chain = signerInfo.getCertificateChain(pkcs7);
293             if (chain == null) {
294                 throw new SignatureException(
295                         "Verification passed, but certification chain is empty.");
296             }
297             certificates.addAll(chain);
298         }
299 
300         return certificates.toArray(new Certificate[certificates.size()]);
301     }
302 
303     /**
304      * Fetch or calculate checksums for the collection of files.
305      *
306      * @param filesToChecksum          split name, null for base and File to fetch checksums for
307      * @param optional                 mask to fetch readily available checksums
308      * @param required                 mask to forcefully calculate if not available
309      * @param installerPackageName     package name of the installer of the packages
310      * @param trustedInstallers        array of certificate to trust, two specific cases:
311      *                                 null - trust anybody,
312      *                                 [] - trust nobody.
313      * @param onChecksumsReadyListener to receive the resulting checksums
314      */
getChecksums(List<Pair<String, File>> filesToChecksum, @Checksum.TypeMask int optional, @Checksum.TypeMask int required, @Nullable String installerPackageName, @Nullable Certificate[] trustedInstallers, @NonNull IOnChecksumsReadyListener onChecksumsReadyListener, @NonNull Injector injector)315     public static void getChecksums(List<Pair<String, File>> filesToChecksum,
316             @Checksum.TypeMask int optional,
317             @Checksum.TypeMask int required,
318             @Nullable String installerPackageName,
319             @Nullable Certificate[] trustedInstallers,
320             @NonNull IOnChecksumsReadyListener onChecksumsReadyListener,
321             @NonNull Injector injector) {
322         List<Map<Integer, ApkChecksum>> result = new ArrayList<>(filesToChecksum.size());
323         for (int i = 0, size = filesToChecksum.size(); i < size; ++i) {
324             final String split = filesToChecksum.get(i).first;
325             final File file = filesToChecksum.get(i).second;
326             Map<Integer, ApkChecksum> checksums = new ArrayMap<>();
327             result.add(checksums);
328 
329             try {
330                 getAvailableApkChecksums(split, file, optional | required, installerPackageName,
331                         trustedInstallers, checksums, injector);
332             } catch (Throwable e) {
333                 Slog.e(TAG, "Preferred checksum calculation error", e);
334             }
335         }
336 
337         long startTime = SystemClock.uptimeMillis();
338         processRequiredChecksums(filesToChecksum, result, required, onChecksumsReadyListener,
339                 injector, startTime);
340     }
341 
processRequiredChecksums(List<Pair<String, File>> filesToChecksum, List<Map<Integer, ApkChecksum>> result, @Checksum.TypeMask int required, @NonNull IOnChecksumsReadyListener onChecksumsReadyListener, @NonNull Injector injector, long startTime)342     private static void processRequiredChecksums(List<Pair<String, File>> filesToChecksum,
343             List<Map<Integer, ApkChecksum>> result,
344             @Checksum.TypeMask int required,
345             @NonNull IOnChecksumsReadyListener onChecksumsReadyListener,
346             @NonNull Injector injector,
347             long startTime) {
348         final boolean timeout =
349                 SystemClock.uptimeMillis() - startTime >= PROCESS_REQUIRED_CHECKSUMS_TIMEOUT_MILLIS;
350         List<ApkChecksum> allChecksums = new ArrayList<>();
351         for (int i = 0, size = filesToChecksum.size(); i < size; ++i) {
352             final String split = filesToChecksum.get(i).first;
353             final File file = filesToChecksum.get(i).second;
354             Map<Integer, ApkChecksum> checksums = result.get(i);
355 
356             try {
357                 if (!timeout || required != 0) {
358                     if (needToWait(file, required, checksums, injector)) {
359                         // Not ready, come back later.
360                         injector.getHandler().postDelayed(() -> {
361                             processRequiredChecksums(filesToChecksum, result, required,
362                                     onChecksumsReadyListener, injector, startTime);
363                         }, PROCESS_REQUIRED_CHECKSUMS_DELAY_MILLIS);
364                         return;
365                     }
366 
367                     getRequiredApkChecksums(split, file, required, checksums);
368                 }
369                 allChecksums.addAll(checksums.values());
370             } catch (Throwable e) {
371                 Slog.e(TAG, "Required checksum calculation error", e);
372             }
373         }
374 
375         try {
376             onChecksumsReadyListener.onChecksumsReady(allChecksums);
377         } catch (RemoteException e) {
378             Slog.w(TAG, e);
379         }
380     }
381 
382     /**
383      * Fetch readily available checksums - enforced by kernel or provided by Installer.
384      *
385      * @param split                 split name, null for base
386      * @param file                  to fetch checksums for
387      * @param types                 mask to fetch checksums
388      * @param installerPackageName  package name of the installer of the packages
389      * @param trustedInstallers     array of certificate to trust, two specific cases:
390      *                              null - trust anybody,
391      *                              [] - trust nobody.
392      * @param checksums             resulting checksums
393      */
getAvailableApkChecksums(String split, File file, @Checksum.TypeMask int types, @Nullable String installerPackageName, @Nullable Certificate[] trustedInstallers, Map<Integer, ApkChecksum> checksums, @NonNull Injector injector)394     private static void getAvailableApkChecksums(String split, File file,
395             @Checksum.TypeMask int types,
396             @Nullable String installerPackageName,
397             @Nullable Certificate[] trustedInstallers,
398             Map<Integer, ApkChecksum> checksums,
399             @NonNull Injector injector) {
400         if (!file.exists()) {
401             return;
402         }
403         final String filePath = file.getAbsolutePath();
404 
405         // Always available: FSI or IncFs.
406         if (isRequired(TYPE_WHOLE_MERKLE_ROOT_4K_SHA256, types, checksums)) {
407             // Hashes in fs-verity and IncFS are always verified.
408             ApkChecksum checksum = extractHashFromFS(split, filePath);
409             if (checksum != null) {
410                 checksums.put(checksum.getType(), checksum);
411             }
412         }
413 
414         // System enforced: v2/v3.
415         if (isRequired(TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256, types, checksums) || isRequired(
416                 TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512, types, checksums)) {
417             Map<Integer, ApkChecksum> v2v3checksums = extractHashFromV2V3Signature(
418                     split, filePath, types);
419             if (v2v3checksums != null) {
420                 checksums.putAll(v2v3checksums);
421             }
422         }
423 
424         // Note: this compares installer and system digests internally and
425         // has to be called right after all system digests are populated.
426         getInstallerChecksums(split, file, types, installerPackageName, trustedInstallers,
427                 checksums, injector);
428     }
429 
getInstallerChecksums(String split, File file, @Checksum.TypeMask int types, @Nullable String installerPackageName, @Nullable Certificate[] trustedInstallers, Map<Integer, ApkChecksum> checksums, @NonNull Injector injector)430     private static void getInstallerChecksums(String split, File file,
431             @Checksum.TypeMask int types,
432             @Nullable String installerPackageName,
433             @Nullable Certificate[] trustedInstallers,
434             Map<Integer, ApkChecksum> checksums,
435             @NonNull Injector injector) {
436         if (PackageManagerServiceUtils.isInstalledByAdb(installerPackageName)) {
437             return;
438         }
439         if (trustedInstallers != null && trustedInstallers.length == 0) {
440             return;
441         }
442 
443         final File digestsFile = findDigestsForFile(file);
444         if (digestsFile == null) {
445             return;
446         }
447         final File signatureFile = findSignatureForDigests(digestsFile);
448 
449         try {
450             final Checksum[] digests = readChecksums(digestsFile);
451             final Signature[] certs;
452             final Signature[] pastCerts;
453 
454             if (signatureFile != null) {
455                 final Certificate[] certificates = verifySignature(digests,
456                         Files.readAllBytes(signatureFile.toPath()));
457                 if (certificates == null || certificates.length == 0) {
458                     Slog.e(TAG, "Error validating signature");
459                     return;
460                 }
461 
462                 certs = new Signature[certificates.length];
463                 for (int i = 0, size = certificates.length; i < size; i++) {
464                     certs[i] = new Signature(certificates[i].getEncoded());
465                 }
466 
467                 pastCerts = null;
468             } else {
469                 final AndroidPackage installer = injector.getPackageManagerInternal().getPackage(
470                         installerPackageName);
471                 if (installer == null) {
472                     Slog.e(TAG, "Installer package not found.");
473                     return;
474                 }
475 
476                 // Obtaining array of certificates used for signing the installer package.
477                 certs = installer.getSigningDetails().getSignatures();
478                 pastCerts = installer.getSigningDetails().getPastSigningCertificates();
479             }
480             if (certs == null || certs.length == 0 || certs[0] == null) {
481                 Slog.e(TAG, "Can't obtain certificates.");
482                 return;
483             }
484 
485             // According to V2/V3 signing schema, the first certificate corresponds to the public
486             // key in the signing block.
487             byte[] trustedCertBytes = certs[0].toByteArray();
488 
489             final Set<Signature> trusted = convertToSet(trustedInstallers);
490 
491             if (trusted != null && !trusted.isEmpty()) {
492                 // Obtaining array of certificates used for signing the installer package.
493                 Signature trustedCert = isTrusted(certs, trusted);
494                 if (trustedCert == null) {
495                     trustedCert = isTrusted(pastCerts, trusted);
496                 }
497                 if (trustedCert == null) {
498                     return;
499                 }
500                 trustedCertBytes = trustedCert.toByteArray();
501             }
502 
503             // Compare OS-enforced digests.
504             for (Checksum digest : digests) {
505                 final ApkChecksum system = checksums.get(digest.getType());
506                 if (system != null && !Arrays.equals(system.getValue(), digest.getValue())) {
507                     throw new InvalidParameterException("System digest " + digest.getType()
508                             + " mismatch, can't bind installer-provided digests to the APK.");
509                 }
510             }
511 
512             // Append missing digests.
513             for (Checksum digest : digests) {
514                 if (isRequired(digest.getType(), types, checksums)) {
515                     checksums.put(digest.getType(),
516                             new ApkChecksum(split, digest, installerPackageName, trustedCertBytes));
517                 }
518             }
519         } catch (IOException e) {
520             Slog.e(TAG, "Error reading .digests or .signature", e);
521         } catch (NoSuchAlgorithmException | SignatureException | InvalidParameterException e) {
522             Slog.e(TAG, "Error validating digests. Invalid digests will be removed", e);
523             try {
524                 Files.deleteIfExists(digestsFile.toPath());
525                 if (signatureFile != null) {
526                     Files.deleteIfExists(signatureFile.toPath());
527                 }
528             } catch (IOException ignored) {
529             }
530         } catch (CertificateEncodingException e) {
531             Slog.e(TAG, "Error encoding trustedInstallers", e);
532         }
533     }
534 
535     /**
536      * Whether the file is available for checksumming or we need to wait.
537      */
needToWait(File file, @Checksum.TypeMask int types, Map<Integer, ApkChecksum> checksums, @NonNull Injector injector)538     private static boolean needToWait(File file,
539             @Checksum.TypeMask int types,
540             Map<Integer, ApkChecksum> checksums,
541             @NonNull Injector injector) throws IOException {
542         if (!isRequired(TYPE_WHOLE_MERKLE_ROOT_4K_SHA256, types, checksums)
543                 && !isRequired(TYPE_WHOLE_MD5, types, checksums)
544                 && !isRequired(TYPE_WHOLE_SHA1, types, checksums)
545                 && !isRequired(TYPE_WHOLE_SHA256, types, checksums)
546                 && !isRequired(TYPE_WHOLE_SHA512, types, checksums)
547                 && !isRequired(TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256, types, checksums)
548                 && !isRequired(TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512, types, checksums)) {
549             return false;
550         }
551 
552         final String filePath = file.getAbsolutePath();
553         if (!IncrementalManager.isIncrementalPath(filePath)) {
554             return false;
555         }
556 
557         IncrementalManager manager = injector.getIncrementalManager();
558         if (manager == null) {
559             Slog.e(TAG, "IncrementalManager is missing.");
560             return false;
561         }
562         IncrementalStorage storage = manager.openStorage(filePath);
563         if (storage == null) {
564             Slog.e(TAG, "IncrementalStorage is missing for a path on IncFs: " + filePath);
565             return false;
566         }
567 
568         return !storage.isFileFullyLoaded(filePath);
569     }
570 
571     /**
572      * Fetch or calculate checksums for the specific file.
573      *
574      * @param split     split name, null for base
575      * @param file      to fetch checksums for
576      * @param types     mask to forcefully calculate if not available
577      * @param checksums resulting checksums
578      */
getRequiredApkChecksums(String split, File file, @Checksum.TypeMask int types, Map<Integer, ApkChecksum> checksums)579     private static void getRequiredApkChecksums(String split, File file,
580             @Checksum.TypeMask int types,
581             Map<Integer, ApkChecksum> checksums) {
582         final String filePath = file.getAbsolutePath();
583 
584         // Manually calculating required checksums if not readily available.
585         if (isRequired(TYPE_WHOLE_MERKLE_ROOT_4K_SHA256, types, checksums)) {
586             try {
587                 byte[] generatedRootHash = VerityBuilder.generateFsVerityRootHash(
588                         filePath, /*salt=*/null,
589                         new ByteBufferFactory() {
590                             @Override
591                             public ByteBuffer create(int capacity) {
592                                 return ByteBuffer.allocate(capacity);
593                             }
594                         });
595                 checksums.put(TYPE_WHOLE_MERKLE_ROOT_4K_SHA256,
596                         new ApkChecksum(split, TYPE_WHOLE_MERKLE_ROOT_4K_SHA256,
597                                 verityHashForFile(file, generatedRootHash)));
598             } catch (IOException | NoSuchAlgorithmException | DigestException e) {
599                 Slog.e(TAG, "Error calculating WHOLE_MERKLE_ROOT_4K_SHA256", e);
600             }
601         }
602 
603         calculateChecksumIfRequested(checksums, split, file, types, TYPE_WHOLE_MD5);
604         calculateChecksumIfRequested(checksums, split, file, types, TYPE_WHOLE_SHA1);
605         calculateChecksumIfRequested(checksums, split, file, types, TYPE_WHOLE_SHA256);
606         calculateChecksumIfRequested(checksums, split, file, types, TYPE_WHOLE_SHA512);
607 
608         calculatePartialChecksumsIfRequested(checksums, split, file, types);
609     }
610 
isRequired(@hecksum.Type int type, @Checksum.TypeMask int types, Map<Integer, ApkChecksum> checksums)611     private static boolean isRequired(@Checksum.Type int type,
612             @Checksum.TypeMask int types, Map<Integer, ApkChecksum> checksums) {
613         if ((types & type) == 0) {
614             return false;
615         }
616         if (checksums.containsKey(type)) {
617             return false;
618         }
619         return true;
620     }
621 
622     /**
623      * Signature class provides a fast way to compare certificates using their hashes.
624      * The hash is exactly the same as in X509/Certificate.
625      */
convertToSet(@ullable Certificate[] array)626     private static Set<Signature> convertToSet(@Nullable Certificate[] array) throws
627             CertificateEncodingException {
628         if (array == null) {
629             return null;
630         }
631         final Set<Signature> set = new ArraySet<>(array.length);
632         for (Certificate item : array) {
633             set.add(new Signature(item.getEncoded()));
634         }
635         return set;
636     }
637 
isTrusted(Signature[] signatures, Set<Signature> trusted)638     private static Signature isTrusted(Signature[] signatures, Set<Signature> trusted) {
639         if (signatures == null) {
640             return null;
641         }
642         for (Signature signature : signatures) {
643             if (trusted.contains(signature)) {
644                 return signature;
645             }
646         }
647         return null;
648     }
649 
extractHashFromFS(String split, String filePath)650     private static ApkChecksum extractHashFromFS(String split, String filePath) {
651         // verity first
652         if (VerityUtils.hasFsverity(filePath)) {
653             byte[] verityHash = VerityUtils.getFsverityDigest(filePath);
654             if (verityHash != null) {
655                 return new ApkChecksum(split, TYPE_WHOLE_MERKLE_ROOT_4K_SHA256, verityHash);
656             }
657         }
658         // v4 next
659         try {
660             ApkSignatureSchemeV4Verifier.VerifiedSigner signer =
661                     ApkSignatureSchemeV4Verifier.extractCertificates(filePath);
662             byte[] rootHash = signer.contentDigests.getOrDefault(
663                     CONTENT_DIGEST_VERITY_CHUNKED_SHA256, null);
664             if (rootHash != null) {
665                 return new ApkChecksum(split, TYPE_WHOLE_MERKLE_ROOT_4K_SHA256,
666                         verityHashForFile(new File(filePath), rootHash));
667             }
668         } catch (SignatureNotFoundException e) {
669             // Nothing
670         } catch (SignatureException | SecurityException e) {
671             Slog.e(TAG, "V4 signature error", e);
672         }
673         return null;
674     }
675 
676     /**
677      * Returns fs-verity digest as described in
678      * https://www.kernel.org/doc/html/latest/filesystems/fsverity.html#fs-verity-descriptor
679      * @param file the Merkle tree is built over
680      * @param rootHash Merkle tree root hash
681      */
verityHashForFile(File file, byte[] rootHash)682     static byte[] verityHashForFile(File file, byte[] rootHash) {
683         try {
684             ByteBuffer buffer = ByteBuffer.allocate(256); // sizeof(fsverity_descriptor)
685             buffer.order(ByteOrder.LITTLE_ENDIAN);
686             buffer.put((byte) 1);               // __u8 version, must be 1
687             buffer.put((byte) 1);               // __u8 hash_algorithm, FS_VERITY_HASH_ALG_SHA256
688             buffer.put((byte) 12);              // __u8, FS_VERITY_LOG_BLOCKSIZE
689             buffer.put((byte) 0);               // __u8, size of salt in bytes; 0 if none
690             buffer.putInt(0);                   // __le32 __reserved_0x04, must be 0
691             buffer.putLong(file.length());      // __le64 data_size
692             buffer.put(rootHash);               // root_hash, first 32 bytes
693             final int padding = 32 + 32 + 144;  // root_hash, last 32 bytes, we are using sha256.
694             // salt, 32 bytes
695             // reserved, 144 bytes
696             for (int i = 0; i < padding; ++i) {
697                 buffer.put((byte) 0);
698             }
699 
700             buffer.flip();
701 
702             final MessageDigest md = MessageDigest.getInstance(ALGO_SHA256);
703             md.update(buffer);
704             return md.digest();
705         } catch (NoSuchAlgorithmException e) {
706             Slog.e(TAG, "Device does not support MessageDigest algorithm", e);
707             return null;
708         }
709     }
710 
extractHashFromV2V3Signature( String split, String filePath, int types)711     private static Map<Integer, ApkChecksum> extractHashFromV2V3Signature(
712             String split, String filePath, int types) {
713         Map<Integer, byte[]> contentDigests = null;
714         final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
715         final ParseResult<ApkSignatureVerifier.SigningDetailsWithDigests> result =
716                 ApkSignatureVerifier.verifySignaturesInternal(input, filePath,
717                         SignatureSchemeVersion.SIGNING_BLOCK_V2, false /*verifyFull*/);
718         if (result.isError()) {
719             if (!(result.getException() instanceof SignatureNotFoundException)) {
720                 Slog.e(TAG, "Signature verification error", result.getException());
721             }
722         } else {
723             contentDigests = result.getResult().contentDigests;
724         }
725 
726         if (contentDigests == null) {
727             return null;
728         }
729 
730         Map<Integer, ApkChecksum> checksums = new ArrayMap<>();
731         if ((types & TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256) != 0) {
732             byte[] hash = contentDigests.getOrDefault(CONTENT_DIGEST_CHUNKED_SHA256, null);
733             if (hash != null) {
734                 checksums.put(TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256,
735                         new ApkChecksum(split, TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256, hash));
736             }
737         }
738         if ((types & TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512) != 0) {
739             byte[] hash = contentDigests.getOrDefault(CONTENT_DIGEST_CHUNKED_SHA512, null);
740             if (hash != null) {
741                 checksums.put(TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512,
742                         new ApkChecksum(split, TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512, hash));
743             }
744         }
745         return checksums;
746     }
747 
getMessageDigestAlgoForChecksumKind(int type)748     private static String getMessageDigestAlgoForChecksumKind(int type)
749             throws NoSuchAlgorithmException {
750         switch (type) {
751             case TYPE_WHOLE_MD5:
752                 return ALGO_MD5;
753             case TYPE_WHOLE_SHA1:
754                 return ALGO_SHA1;
755             case TYPE_WHOLE_SHA256:
756                 return ALGO_SHA256;
757             case TYPE_WHOLE_SHA512:
758                 return ALGO_SHA512;
759             default:
760                 throw new NoSuchAlgorithmException("Invalid checksum type: " + type);
761         }
762     }
763 
calculateChecksumIfRequested(Map<Integer, ApkChecksum> checksums, String split, File file, int required, int type)764     private static void calculateChecksumIfRequested(Map<Integer, ApkChecksum> checksums,
765             String split, File file, int required, int type) {
766         if ((required & type) != 0 && !checksums.containsKey(type)) {
767             final byte[] checksum = getApkChecksum(file, type);
768             if (checksum != null) {
769                 checksums.put(type, new ApkChecksum(split, type, checksum));
770             }
771         }
772     }
773 
774     static final int MIN_BUFFER_SIZE = 4 * 1024;
775     static final int MAX_BUFFER_SIZE = 128 * 1024;
776 
getApkChecksum(File file, int type)777     private static byte[] getApkChecksum(File file, int type) {
778         final int bufferSize = (int) Math.max(MIN_BUFFER_SIZE,
779                 Math.min(MAX_BUFFER_SIZE, file.length()));
780         try (FileInputStream fis = new FileInputStream(file)) {
781             final byte[] buffer = new byte[bufferSize];
782             int nread = 0;
783 
784             final String algo = getMessageDigestAlgoForChecksumKind(type);
785             MessageDigest md = MessageDigest.getInstance(algo);
786             while ((nread = fis.read(buffer)) != -1) {
787                 md.update(buffer, 0, nread);
788             }
789 
790             return md.digest();
791         } catch (IOException e) {
792             Slog.e(TAG, "Error reading " + file.getAbsolutePath() + " to compute hash.", e);
793             return null;
794         } catch (NoSuchAlgorithmException e) {
795             Slog.e(TAG, "Device does not support MessageDigest algorithm", e);
796             return null;
797         }
798     }
799 
getContentDigestAlgos(boolean needSignatureSha256, boolean needSignatureSha512)800     private static int[] getContentDigestAlgos(boolean needSignatureSha256,
801             boolean needSignatureSha512) {
802         if (needSignatureSha256 && needSignatureSha512) {
803             // Signature block present, but no digests???
804             return new int[]{CONTENT_DIGEST_CHUNKED_SHA256, CONTENT_DIGEST_CHUNKED_SHA512};
805         } else if (needSignatureSha256) {
806             return new int[]{CONTENT_DIGEST_CHUNKED_SHA256};
807         } else {
808             return new int[]{CONTENT_DIGEST_CHUNKED_SHA512};
809         }
810     }
811 
getChecksumKindForContentDigestAlgo(int contentDigestAlgo)812     private static int getChecksumKindForContentDigestAlgo(int contentDigestAlgo) {
813         switch (contentDigestAlgo) {
814             case CONTENT_DIGEST_CHUNKED_SHA256:
815                 return TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256;
816             case CONTENT_DIGEST_CHUNKED_SHA512:
817                 return TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512;
818             default:
819                 return -1;
820         }
821     }
822 
calculatePartialChecksumsIfRequested(Map<Integer, ApkChecksum> checksums, String split, File file, int required)823     private static void calculatePartialChecksumsIfRequested(Map<Integer, ApkChecksum> checksums,
824             String split, File file, int required) {
825         boolean needSignatureSha256 =
826                 (required & TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256) != 0 && !checksums.containsKey(
827                         TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
828         boolean needSignatureSha512 =
829                 (required & TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512) != 0 && !checksums.containsKey(
830                         TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512);
831         if (!needSignatureSha256 && !needSignatureSha512) {
832             return;
833         }
834 
835         try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
836             SignatureInfo signatureInfo = null;
837             try {
838                 signatureInfo = ApkSignatureSchemeV3Verifier.findSignature(raf);
839             } catch (SignatureNotFoundException e) {
840                 try {
841                     signatureInfo = ApkSignatureSchemeV2Verifier.findSignature(raf);
842                 } catch (SignatureNotFoundException ee) {
843                 }
844             }
845             if (signatureInfo == null) {
846                 Slog.e(TAG, "V2/V3 signatures not found in " + file.getAbsolutePath());
847                 return;
848             }
849 
850             final int[] digestAlgos = getContentDigestAlgos(needSignatureSha256,
851                     needSignatureSha512);
852             byte[][] digests = ApkSigningBlockUtils.computeContentDigestsPer1MbChunk(digestAlgos,
853                     raf.getFD(), signatureInfo);
854             for (int i = 0, size = digestAlgos.length; i < size; ++i) {
855                 int checksumKind = getChecksumKindForContentDigestAlgo(digestAlgos[i]);
856                 if (checksumKind != -1) {
857                     checksums.put(checksumKind, new ApkChecksum(split, checksumKind, digests[i]));
858                 }
859             }
860         } catch (IOException | DigestException e) {
861             Slog.e(TAG, "Error computing hash.", e);
862         }
863     }
864 }
865