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