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