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