• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.text.TextUtils;
51 import android.util.ArrayMap;
52 import android.util.ArraySet;
53 import android.util.Pair;
54 import android.util.Slog;
55 import android.util.apk.ApkSignatureSchemeV2Verifier;
56 import android.util.apk.ApkSignatureSchemeV3Verifier;
57 import android.util.apk.ApkSignatureSchemeV4Verifier;
58 import android.util.apk.ApkSignatureVerifier;
59 import android.util.apk.ApkSigningBlockUtils;
60 import android.util.apk.ByteBufferFactory;
61 import android.util.apk.SignatureInfo;
62 import android.util.apk.SignatureNotFoundException;
63 import android.util.apk.VerityBuilder;
64 
65 import com.android.internal.annotations.VisibleForTesting;
66 import com.android.internal.security.VerityUtils;
67 import com.android.server.pm.parsing.pkg.AndroidPackage;
68 
69 import java.io.ByteArrayOutputStream;
70 import java.io.DataInputStream;
71 import java.io.DataOutputStream;
72 import java.io.EOFException;
73 import java.io.File;
74 import java.io.FileInputStream;
75 import java.io.IOException;
76 import java.io.InputStream;
77 import java.io.OutputStream;
78 import java.io.RandomAccessFile;
79 import java.nio.ByteBuffer;
80 import java.nio.ByteOrder;
81 import java.nio.file.Files;
82 import java.security.DigestException;
83 import java.security.InvalidParameterException;
84 import java.security.MessageDigest;
85 import java.security.NoSuchAlgorithmException;
86 import java.security.SignatureException;
87 import java.security.cert.Certificate;
88 import java.security.cert.CertificateEncodingException;
89 import java.security.cert.X509Certificate;
90 import java.util.ArrayList;
91 import java.util.Arrays;
92 import java.util.List;
93 import java.util.Map;
94 import java.util.Set;
95 
96 import sun.security.pkcs.PKCS7;
97 import sun.security.pkcs.SignerInfo;
98 
99 /**
100  * Provides checksums for APK.
101  */
102 public class ApkChecksums {
103     static final String TAG = "ApkChecksums";
104 
105     private static final String DIGESTS_FILE_EXTENSION = ".digests";
106     private static final String DIGESTS_SIGNATURE_FILE_EXTENSION = ".signature";
107 
108     // MessageDigest algorithms.
109     static final String ALGO_MD5 = "MD5";
110     static final String ALGO_SHA1 = "SHA1";
111     static final String ALGO_SHA256 = "SHA256";
112     static final String ALGO_SHA512 = "SHA512";
113 
114     private static final Certificate[] EMPTY_CERTIFICATE_ARRAY = {};
115 
116     /**
117      * Check back in 1 second after we detected we needed to wait for the APK to be fully available.
118      */
119     private static final long PROCESS_REQUIRED_CHECKSUMS_DELAY_MILLIS = 1000;
120 
121     /**
122      * 24 hours timeout to wait till all files are loaded.
123      */
124     private static final long PROCESS_REQUIRED_CHECKSUMS_TIMEOUT_MILLIS = 1000 * 3600 * 24;
125 
126     /**
127      * Unit tests will instantiate, extend and/or mock to mock dependencies / behaviors.
128      *
129      * NOTE: All getters should return the same instance for every call.
130      */
131     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
132     static class Injector {
133 
134         @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
135         interface Producer<T> {
136             /** Produce an instance of type {@link T} */
produce()137             T produce();
138         }
139 
140         private final Producer<Context> mContext;
141         private final Producer<Handler> mHandlerProducer;
142         private final Producer<IncrementalManager> mIncrementalManagerProducer;
143         private final Producer<PackageManagerInternal> mPackageManagerInternalProducer;
144 
Injector(Producer<Context> context, Producer<Handler> handlerProducer, Producer<IncrementalManager> incrementalManagerProducer, Producer<PackageManagerInternal> packageManagerInternalProducer)145         Injector(Producer<Context> context, Producer<Handler> handlerProducer,
146                 Producer<IncrementalManager> incrementalManagerProducer,
147                 Producer<PackageManagerInternal> packageManagerInternalProducer) {
148             mContext = context;
149             mHandlerProducer = handlerProducer;
150             mIncrementalManagerProducer = incrementalManagerProducer;
151             mPackageManagerInternalProducer = packageManagerInternalProducer;
152         }
153 
getContext()154         public Context getContext() {
155             return mContext.produce();
156         }
157 
getHandler()158         public Handler getHandler() {
159             return mHandlerProducer.produce();
160         }
161 
getIncrementalManager()162         public IncrementalManager getIncrementalManager() {
163             return mIncrementalManagerProducer.produce();
164         }
165 
getPackageManagerInternal()166         public PackageManagerInternal getPackageManagerInternal() {
167             return mPackageManagerInternalProducer.produce();
168         }
169     }
170 
171     /**
172      * Return the digests path associated with the given code path
173      * (replaces '.apk' extension with '.digests')
174      *
175      * @throws IllegalArgumentException if the code path is not an .apk.
176      */
buildDigestsPathForApk(String codePath)177     public static String buildDigestsPathForApk(String codePath) {
178         if (!ApkLiteParseUtils.isApkPath(codePath)) {
179             throw new IllegalStateException("Code path is not an apk " + codePath);
180         }
181         return codePath.substring(0, codePath.length() - APK_FILE_EXTENSION.length())
182                 + DIGESTS_FILE_EXTENSION;
183     }
184 
185     /**
186      * Return the signature path associated with the given digests path.
187      * (appends '.signature' to the end)
188      */
buildSignaturePathForDigests(String digestsPath)189     public static String buildSignaturePathForDigests(String digestsPath) {
190         return digestsPath + DIGESTS_SIGNATURE_FILE_EXTENSION;
191     }
192 
193     /** Returns true if the given file looks like containing digests or digests' signature. */
isDigestOrDigestSignatureFile(File file)194     public static boolean isDigestOrDigestSignatureFile(File file) {
195         final String name = file.getName();
196         return name.endsWith(DIGESTS_FILE_EXTENSION) || name.endsWith(
197                 DIGESTS_SIGNATURE_FILE_EXTENSION);
198     }
199 
200     /**
201      * Search for the digests file associated with the given target file.
202      * If it exists, the method returns the digests file; otherwise it returns null.
203      */
findDigestsForFile(File targetFile)204     public static File findDigestsForFile(File targetFile) {
205         String digestsPath = buildDigestsPathForApk(targetFile.getAbsolutePath());
206         File digestsFile = new File(digestsPath);
207         return digestsFile.exists() ? digestsFile : null;
208     }
209 
210     /**
211      * Search for the signature file associated with the given digests file.
212      * If it exists, the method returns the signature file; otherwise it returns null.
213      */
findSignatureForDigests(File digestsFile)214     public static File findSignatureForDigests(File digestsFile) {
215         String signaturePath = buildSignaturePathForDigests(digestsFile.getAbsolutePath());
216         File signatureFile = new File(signaturePath);
217         return signatureFile.exists() ? signatureFile : null;
218     }
219 
220     /**
221      * Serialize checksums to the stream in binary format.
222      */
writeChecksums(OutputStream os, Checksum[] checksums)223     public static void writeChecksums(OutputStream os, Checksum[] checksums)
224             throws IOException {
225         try (DataOutputStream dos = new DataOutputStream(os)) {
226             for (Checksum checksum : checksums) {
227                 Checksum.writeToStream(dos, checksum);
228             }
229         }
230     }
231 
readChecksums(File file)232     private static Checksum[] readChecksums(File file) throws IOException {
233         try (InputStream is = new FileInputStream(file)) {
234             return readChecksums(is);
235         }
236     }
237 
238     /**
239      * Deserialize array of checksums previously stored in
240      * {@link #writeChecksums(OutputStream, Checksum[])}.
241      */
readChecksums(InputStream is)242     public static Checksum[] readChecksums(InputStream is) throws IOException {
243         try (DataInputStream dis = new DataInputStream(is)) {
244             ArrayList<Checksum> checksums = new ArrayList<>();
245             try {
246                 // 100 is an arbitrary very big number. We should stop at EOF.
247                 for (int i = 0; i < 100; ++i) {
248                     checksums.add(Checksum.readFromStream(dis));
249                 }
250             } catch (EOFException e) {
251                 // expected
252             }
253             return checksums.toArray(new Checksum[checksums.size()]);
254         }
255     }
256 
257     /**
258      * Verifies signature over binary serialized checksums.
259      * @param checksums array of checksums
260      * @param signature detached PKCS7 signature in DER format
261      * @return all certificates that passed verification
262      * @throws SignatureException if verification fails
263      */
verifySignature(Checksum[] checksums, byte[] signature)264     public static @NonNull Certificate[] verifySignature(Checksum[] checksums, byte[] signature)
265             throws NoSuchAlgorithmException, IOException, SignatureException {
266         final byte[] blob;
267         try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
268             writeChecksums(os, checksums);
269             blob = os.toByteArray();
270         }
271 
272         PKCS7 pkcs7 = new PKCS7(signature);
273 
274         final Certificate[] certs = pkcs7.getCertificates();
275         if (certs == null || certs.length == 0) {
276             throw new SignatureException("Signature missing certificates");
277         }
278 
279         final SignerInfo[] signerInfos = pkcs7.verify(blob);
280         if (signerInfos == null || signerInfos.length == 0) {
281             throw new SignatureException("Verification failed");
282         }
283 
284         ArrayList<Certificate> certificates = new ArrayList<>(signerInfos.length);
285         for (SignerInfo signerInfo : signerInfos) {
286             ArrayList<X509Certificate> chain = signerInfo.getCertificateChain(pkcs7);
287             if (chain == null) {
288                 throw new SignatureException(
289                         "Verification passed, but certification chain is empty.");
290             }
291             certificates.addAll(chain);
292         }
293 
294         return certificates.toArray(new Certificate[certificates.size()]);
295     }
296 
297     /**
298      * Fetch or calculate checksums for the collection of files.
299      *
300      * @param filesToChecksum          split name, null for base and File to fetch checksums for
301      * @param optional                 mask to fetch readily available checksums
302      * @param required                 mask to forcefully calculate if not available
303      * @param installerPackageName     package name of the installer of the packages
304      * @param trustedInstallers        array of certificate to trust, two specific cases:
305      *                                 null - trust anybody,
306      *                                 [] - trust nobody.
307      * @param onChecksumsReadyListener to receive the resulting checksums
308      */
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)309     public static void getChecksums(List<Pair<String, File>> filesToChecksum,
310             @Checksum.TypeMask int optional,
311             @Checksum.TypeMask int required,
312             @Nullable String installerPackageName,
313             @Nullable Certificate[] trustedInstallers,
314             @NonNull IOnChecksumsReadyListener onChecksumsReadyListener,
315             @NonNull Injector injector) {
316         List<Map<Integer, ApkChecksum>> result = new ArrayList<>(filesToChecksum.size());
317         for (int i = 0, size = filesToChecksum.size(); i < size; ++i) {
318             final String split = filesToChecksum.get(i).first;
319             final File file = filesToChecksum.get(i).second;
320             Map<Integer, ApkChecksum> checksums = new ArrayMap<>();
321             result.add(checksums);
322 
323             try {
324                 getAvailableApkChecksums(split, file, optional | required, installerPackageName,
325                         trustedInstallers, checksums, injector);
326             } catch (Throwable e) {
327                 Slog.e(TAG, "Preferred checksum calculation error", e);
328             }
329         }
330 
331         long startTime = SystemClock.uptimeMillis();
332         processRequiredChecksums(filesToChecksum, result, required, onChecksumsReadyListener,
333                 injector, startTime);
334     }
335 
processRequiredChecksums(List<Pair<String, File>> filesToChecksum, List<Map<Integer, ApkChecksum>> result, @Checksum.TypeMask int required, @NonNull IOnChecksumsReadyListener onChecksumsReadyListener, @NonNull Injector injector, long startTime)336     private static void processRequiredChecksums(List<Pair<String, File>> filesToChecksum,
337             List<Map<Integer, ApkChecksum>> result,
338             @Checksum.TypeMask int required,
339             @NonNull IOnChecksumsReadyListener onChecksumsReadyListener,
340             @NonNull Injector injector,
341             long startTime) {
342         final boolean timeout =
343                 SystemClock.uptimeMillis() - startTime >= PROCESS_REQUIRED_CHECKSUMS_TIMEOUT_MILLIS;
344         List<ApkChecksum> allChecksums = new ArrayList<>();
345         for (int i = 0, size = filesToChecksum.size(); i < size; ++i) {
346             final String split = filesToChecksum.get(i).first;
347             final File file = filesToChecksum.get(i).second;
348             Map<Integer, ApkChecksum> checksums = result.get(i);
349 
350             try {
351                 if (!timeout || required != 0) {
352                     if (needToWait(file, required, checksums, injector)) {
353                         // Not ready, come back later.
354                         injector.getHandler().postDelayed(() -> {
355                             processRequiredChecksums(filesToChecksum, result, required,
356                                     onChecksumsReadyListener, injector, startTime);
357                         }, PROCESS_REQUIRED_CHECKSUMS_DELAY_MILLIS);
358                         return;
359                     }
360 
361                     getRequiredApkChecksums(split, file, required, checksums);
362                 }
363                 allChecksums.addAll(checksums.values());
364             } catch (Throwable e) {
365                 Slog.e(TAG, "Required checksum calculation error", e);
366             }
367         }
368 
369         try {
370             onChecksumsReadyListener.onChecksumsReady(allChecksums);
371         } catch (RemoteException e) {
372             Slog.w(TAG, e);
373         }
374     }
375 
376     /**
377      * Fetch readily available checksums - enforced by kernel or provided by Installer.
378      *
379      * @param split                 split name, null for base
380      * @param file                  to fetch checksums for
381      * @param types                 mask to fetch checksums
382      * @param installerPackageName  package name of the installer of the packages
383      * @param trustedInstallers     array of certificate to trust, two specific cases:
384      *                              null - trust anybody,
385      *                              [] - trust nobody.
386      * @param checksums             resulting checksums
387      */
getAvailableApkChecksums(String split, File file, @Checksum.TypeMask int types, @Nullable String installerPackageName, @Nullable Certificate[] trustedInstallers, Map<Integer, ApkChecksum> checksums, @NonNull Injector injector)388     private static void getAvailableApkChecksums(String split, File file,
389             @Checksum.TypeMask int types,
390             @Nullable String installerPackageName,
391             @Nullable Certificate[] trustedInstallers,
392             Map<Integer, ApkChecksum> checksums,
393             @NonNull Injector injector) {
394         final String filePath = file.getAbsolutePath();
395 
396         // Always available: FSI or IncFs.
397         if (isRequired(TYPE_WHOLE_MERKLE_ROOT_4K_SHA256, types, checksums)) {
398             // Hashes in fs-verity and IncFS are always verified.
399             ApkChecksum checksum = extractHashFromFS(split, filePath);
400             if (checksum != null) {
401                 checksums.put(checksum.getType(), checksum);
402             }
403         }
404 
405         // System enforced: v2/v3.
406         if (isRequired(TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256, types, checksums) || isRequired(
407                 TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512, types, checksums)) {
408             Map<Integer, ApkChecksum> v2v3checksums = extractHashFromV2V3Signature(
409                     split, filePath, types);
410             if (v2v3checksums != null) {
411                 checksums.putAll(v2v3checksums);
412             }
413         }
414 
415         // Note: this compares installer and system digests internally and
416         // has to be called right after all system digests are populated.
417         getInstallerChecksums(split, file, types, installerPackageName, trustedInstallers,
418                 checksums, injector);
419     }
420 
getInstallerChecksums(String split, File file, @Checksum.TypeMask int types, @Nullable String installerPackageName, @Nullable Certificate[] trustedInstallers, Map<Integer, ApkChecksum> checksums, @NonNull Injector injector)421     private static void getInstallerChecksums(String split, File file,
422             @Checksum.TypeMask int types,
423             @Nullable String installerPackageName,
424             @Nullable Certificate[] trustedInstallers,
425             Map<Integer, ApkChecksum> checksums,
426             @NonNull Injector injector) {
427         if (TextUtils.isEmpty(installerPackageName)) {
428             return;
429         }
430         if (trustedInstallers != null && trustedInstallers.length == 0) {
431             return;
432         }
433 
434         final File digestsFile = findDigestsForFile(file);
435         if (digestsFile == null) {
436             return;
437         }
438         final File signatureFile = findSignatureForDigests(digestsFile);
439 
440         try {
441             final Checksum[] digests = readChecksums(digestsFile);
442             final Signature[] certs;
443             final Signature[] pastCerts;
444 
445             if (signatureFile != null) {
446                 final Certificate[] certificates = verifySignature(digests,
447                         Files.readAllBytes(signatureFile.toPath()));
448                 if (certificates == null || certificates.length == 0) {
449                     Slog.e(TAG, "Error validating signature");
450                     return;
451                 }
452 
453                 certs = new Signature[certificates.length];
454                 for (int i = 0, size = certificates.length; i < size; i++) {
455                     certs[i] = new Signature(certificates[i].getEncoded());
456                 }
457 
458                 pastCerts = null;
459             } else {
460                 final AndroidPackage installer = injector.getPackageManagerInternal().getPackage(
461                         installerPackageName);
462                 if (installer == null) {
463                     Slog.e(TAG, "Installer package not found.");
464                     return;
465                 }
466 
467                 // Obtaining array of certificates used for signing the installer package.
468                 certs = installer.getSigningDetails().getSignatures();
469                 pastCerts = installer.getSigningDetails().getPastSigningCertificates();
470             }
471             if (certs == null || certs.length == 0 || certs[0] == null) {
472                 Slog.e(TAG, "Can't obtain certificates.");
473                 return;
474             }
475 
476             // According to V2/V3 signing schema, the first certificate corresponds to the public
477             // key in the signing block.
478             byte[] trustedCertBytes = certs[0].toByteArray();
479 
480             final Set<Signature> trusted = convertToSet(trustedInstallers);
481 
482             if (trusted != null && !trusted.isEmpty()) {
483                 // Obtaining array of certificates used for signing the installer package.
484                 Signature trustedCert = isTrusted(certs, trusted);
485                 if (trustedCert == null) {
486                     trustedCert = isTrusted(pastCerts, trusted);
487                 }
488                 if (trustedCert == null) {
489                     return;
490                 }
491                 trustedCertBytes = trustedCert.toByteArray();
492             }
493 
494             // Compare OS-enforced digests.
495             for (Checksum digest : digests) {
496                 final ApkChecksum system = checksums.get(digest.getType());
497                 if (system != null && !Arrays.equals(system.getValue(), digest.getValue())) {
498                     throw new InvalidParameterException("System digest " + digest.getType()
499                             + " mismatch, can't bind installer-provided digests to the APK.");
500                 }
501             }
502 
503             // Append missing digests.
504             for (Checksum digest : digests) {
505                 if (isRequired(digest.getType(), types, checksums)) {
506                     checksums.put(digest.getType(),
507                             new ApkChecksum(split, digest, installerPackageName, trustedCertBytes));
508                 }
509             }
510         } catch (IOException e) {
511             Slog.e(TAG, "Error reading .digests or .signature", e);
512         } catch (NoSuchAlgorithmException | SignatureException | InvalidParameterException e) {
513             Slog.e(TAG, "Error validating digests. Invalid digests will be removed", e);
514             try {
515                 Files.deleteIfExists(digestsFile.toPath());
516                 if (signatureFile != null) {
517                     Files.deleteIfExists(signatureFile.toPath());
518                 }
519             } catch (IOException ignored) {
520             }
521         } catch (CertificateEncodingException e) {
522             Slog.e(TAG, "Error encoding trustedInstallers", e);
523         }
524     }
525 
526     /**
527      * Whether the file is available for checksumming or we need to wait.
528      */
needToWait(File file, @Checksum.TypeMask int types, Map<Integer, ApkChecksum> checksums, @NonNull Injector injector)529     private static boolean needToWait(File file,
530             @Checksum.TypeMask int types,
531             Map<Integer, ApkChecksum> checksums,
532             @NonNull Injector injector) throws IOException {
533         if (!isRequired(TYPE_WHOLE_MERKLE_ROOT_4K_SHA256, types, checksums)
534                 && !isRequired(TYPE_WHOLE_MD5, types, checksums)
535                 && !isRequired(TYPE_WHOLE_SHA1, types, checksums)
536                 && !isRequired(TYPE_WHOLE_SHA256, types, checksums)
537                 && !isRequired(TYPE_WHOLE_SHA512, types, checksums)
538                 && !isRequired(TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256, types, checksums)
539                 && !isRequired(TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512, types, checksums)) {
540             return false;
541         }
542 
543         final String filePath = file.getAbsolutePath();
544         if (!IncrementalManager.isIncrementalPath(filePath)) {
545             return false;
546         }
547 
548         IncrementalManager manager = injector.getIncrementalManager();
549         if (manager == null) {
550             Slog.e(TAG, "IncrementalManager is missing.");
551             return false;
552         }
553         IncrementalStorage storage = manager.openStorage(filePath);
554         if (storage == null) {
555             Slog.e(TAG, "IncrementalStorage is missing for a path on IncFs: " + filePath);
556             return false;
557         }
558 
559         return !storage.isFileFullyLoaded(filePath);
560     }
561 
562     /**
563      * Fetch or calculate checksums for the specific file.
564      *
565      * @param split     split name, null for base
566      * @param file      to fetch checksums for
567      * @param types     mask to forcefully calculate if not available
568      * @param checksums resulting checksums
569      */
getRequiredApkChecksums(String split, File file, @Checksum.TypeMask int types, Map<Integer, ApkChecksum> checksums)570     private static void getRequiredApkChecksums(String split, File file,
571             @Checksum.TypeMask int types,
572             Map<Integer, ApkChecksum> checksums) {
573         final String filePath = file.getAbsolutePath();
574 
575         // Manually calculating required checksums if not readily available.
576         if (isRequired(TYPE_WHOLE_MERKLE_ROOT_4K_SHA256, types, checksums)) {
577             try {
578                 byte[] generatedRootHash = VerityBuilder.generateFsVerityRootHash(
579                         filePath, /*salt=*/null,
580                         new ByteBufferFactory() {
581                             @Override
582                             public ByteBuffer create(int capacity) {
583                                 return ByteBuffer.allocate(capacity);
584                             }
585                         });
586                 checksums.put(TYPE_WHOLE_MERKLE_ROOT_4K_SHA256,
587                         new ApkChecksum(split, TYPE_WHOLE_MERKLE_ROOT_4K_SHA256,
588                                 verityHashForFile(file, generatedRootHash)));
589             } catch (IOException | NoSuchAlgorithmException | DigestException e) {
590                 Slog.e(TAG, "Error calculating WHOLE_MERKLE_ROOT_4K_SHA256", e);
591             }
592         }
593 
594         calculateChecksumIfRequested(checksums, split, file, types, TYPE_WHOLE_MD5);
595         calculateChecksumIfRequested(checksums, split, file, types, TYPE_WHOLE_SHA1);
596         calculateChecksumIfRequested(checksums, split, file, types, TYPE_WHOLE_SHA256);
597         calculateChecksumIfRequested(checksums, split, file, types, TYPE_WHOLE_SHA512);
598 
599         calculatePartialChecksumsIfRequested(checksums, split, file, types);
600     }
601 
isRequired(@hecksum.Type int type, @Checksum.TypeMask int types, Map<Integer, ApkChecksum> checksums)602     private static boolean isRequired(@Checksum.Type int type,
603             @Checksum.TypeMask int types, Map<Integer, ApkChecksum> checksums) {
604         if ((types & type) == 0) {
605             return false;
606         }
607         if (checksums.containsKey(type)) {
608             return false;
609         }
610         return true;
611     }
612 
613     /**
614      * Signature class provides a fast way to compare certificates using their hashes.
615      * The hash is exactly the same as in X509/Certificate.
616      */
convertToSet(@ullable Certificate[] array)617     private static Set<Signature> convertToSet(@Nullable Certificate[] array) throws
618             CertificateEncodingException {
619         if (array == null) {
620             return null;
621         }
622         final Set<Signature> set = new ArraySet<>(array.length);
623         for (Certificate item : array) {
624             set.add(new Signature(item.getEncoded()));
625         }
626         return set;
627     }
628 
isTrusted(Signature[] signatures, Set<Signature> trusted)629     private static Signature isTrusted(Signature[] signatures, Set<Signature> trusted) {
630         if (signatures == null) {
631             return null;
632         }
633         for (Signature signature : signatures) {
634             if (trusted.contains(signature)) {
635                 return signature;
636             }
637         }
638         return null;
639     }
640 
containsFile(File dir, String filePath)641     private static boolean containsFile(File dir, String filePath) {
642         if (dir == null) {
643             return false;
644         }
645         return FileUtils.contains(dir.getAbsolutePath(), filePath);
646     }
647 
extractHashFromFS(String split, String filePath)648     private static ApkChecksum extractHashFromFS(String split, String filePath) {
649         // verity first
650         // Skip /product folder.
651         // TODO(b/231354111): remove this hack once we are allowed to change SELinux rules.
652         if (!containsFile(Environment.getProductDirectory(), filePath)) {
653             byte[] verityHash = VerityUtils.getFsverityRootHash(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 (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