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