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