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