1 /* 2 * Copyright (C) 2017 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 android.util.apk; 18 19 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; 20 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING; 21 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; 22 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES; 23 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION; 24 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; 25 import static android.util.apk.ApkSignatureSchemeV4Verifier.APK_SIGNATURE_SCHEME_DEFAULT; 26 27 import android.annotation.NonNull; 28 import android.content.pm.Signature; 29 import android.content.pm.SigningDetails; 30 import android.content.pm.SigningDetails.SignatureSchemeVersion; 31 import android.content.pm.parsing.ApkLiteParseUtils; 32 import android.content.pm.parsing.result.ParseInput; 33 import android.content.pm.parsing.result.ParseResult; 34 import android.os.Build; 35 import android.os.Trace; 36 import android.os.incremental.V4Signature; 37 import android.util.ArrayMap; 38 import android.util.Pair; 39 import android.util.Slog; 40 import android.util.jar.StrictJarFile; 41 42 import com.android.internal.annotations.GuardedBy; 43 import com.android.internal.util.ArrayUtils; 44 45 import libcore.io.IoUtils; 46 47 import java.io.IOException; 48 import java.io.InputStream; 49 import java.security.DigestException; 50 import java.security.GeneralSecurityException; 51 import java.security.NoSuchAlgorithmException; 52 import java.security.cert.Certificate; 53 import java.security.cert.CertificateEncodingException; 54 import java.util.ArrayList; 55 import java.util.Arrays; 56 import java.util.Iterator; 57 import java.util.List; 58 import java.util.Map; 59 import java.util.concurrent.atomic.AtomicReference; 60 import java.util.zip.ZipEntry; 61 62 /** 63 * Facade class that takes care of the details of APK verification on 64 * behalf of ParsingPackageUtils. 65 * 66 * @hide for internal use only. 67 */ 68 public class ApkSignatureVerifier { 69 70 private static final String LOG_TAG = "ApkSignatureVerifier"; 71 72 private static final AtomicReference<byte[]> sBuffer = new AtomicReference<>(); 73 74 @GuardedBy("sOverrideSigningDetails") 75 private static final ArrayMap<SigningDetails, SigningDetails> sOverrideSigningDetails = 76 new ArrayMap<>(); 77 78 /** 79 * Verifies the provided APK and returns the certificates associated with each signer. 80 */ verify(ParseInput input, String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion)81 public static ParseResult<SigningDetails> verify(ParseInput input, String apkPath, 82 @SignatureSchemeVersion int minSignatureSchemeVersion) { 83 return verifySignatures(input, apkPath, minSignatureSchemeVersion, true /* verifyFull */); 84 } 85 86 /** 87 * Returns the certificates associated with each signer for the given APK without verification. 88 * This method is dangerous and should not be used, unless the caller is absolutely certain the 89 * APK is trusted. 90 */ unsafeGetCertsWithoutVerification( ParseInput input, String apkPath, int minSignatureSchemeVersion)91 public static ParseResult<SigningDetails> unsafeGetCertsWithoutVerification( 92 ParseInput input, String apkPath, int minSignatureSchemeVersion) { 93 return verifySignatures(input, apkPath, minSignatureSchemeVersion, false /* verifyFull */); 94 } 95 96 /** 97 * Verifies the provided APK using all allowed signing schemas. 98 * @return the certificates associated with each signer. 99 * @param verifyFull whether to verify all contents of this APK or just collect certificates. 100 */ verifySignatures(ParseInput input, String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)101 private static ParseResult<SigningDetails> verifySignatures(ParseInput input, String apkPath, 102 @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull) { 103 final ParseResult<SigningDetailsWithDigests> result = 104 verifySignaturesInternal(input, apkPath, minSignatureSchemeVersion, verifyFull); 105 if (result.isError()) { 106 return input.error(result); 107 } 108 SigningDetails signingDetails = result.getResult().signingDetails; 109 if (Build.isDebuggable()) { 110 SigningDetails overrideSigningDetails; 111 synchronized (sOverrideSigningDetails) { 112 overrideSigningDetails = sOverrideSigningDetails.get(signingDetails); 113 } 114 if (overrideSigningDetails != null) { 115 Slog.i(LOG_TAG, "Applying override signing details for APK " + apkPath); 116 signingDetails = overrideSigningDetails; 117 } 118 } 119 return input.success(signingDetails); 120 } 121 122 /** 123 * Add a pair of signing details so that packages signed with {@code oldSigningDetails} will 124 * behave as if they are signed by the {@code newSigningDetails}. 125 * 126 * @param oldSigningDetails the original signing detail of the package 127 * @param newSigningDetails the new signing detail that will replace the original one 128 */ addOverrideSigningDetails(@onNull SigningDetails oldSigningDetails, @NonNull SigningDetails newSigningDetails)129 public static void addOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails, 130 @NonNull SigningDetails newSigningDetails) { 131 synchronized (sOverrideSigningDetails) { 132 sOverrideSigningDetails.put(oldSigningDetails, newSigningDetails); 133 } 134 } 135 136 /** 137 * Remove a pair of signing details previously added via {@link #addOverrideSigningDetails} by 138 * the old signing details. 139 * 140 * @param oldSigningDetails the original signing detail of the package 141 * @throws SecurityException if the build is not debuggable 142 */ removeOverrideSigningDetails(@onNull SigningDetails oldSigningDetails)143 public static void removeOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails) { 144 synchronized (sOverrideSigningDetails) { 145 sOverrideSigningDetails.remove(oldSigningDetails); 146 } 147 } 148 149 /** 150 * Clear all pairs of signing details previously added via {@link #addOverrideSigningDetails}. 151 */ clearOverrideSigningDetails()152 public static void clearOverrideSigningDetails() { 153 synchronized (sOverrideSigningDetails) { 154 sOverrideSigningDetails.clear(); 155 } 156 } 157 158 /** 159 * Verifies the provided APK using all allowed signing schemas. 160 * @return the certificates associated with each signer and content digests. 161 * @param verifyFull whether to verify all contents of this APK or just collect certificates. 162 * @hide 163 */ verifySignaturesInternal(ParseInput input, String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)164 public static ParseResult<SigningDetailsWithDigests> verifySignaturesInternal(ParseInput input, 165 String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion, 166 boolean verifyFull) { 167 168 if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V4) { 169 // V4 and before are older than the requested minimum signing version 170 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 171 "No signature found in package of version " + minSignatureSchemeVersion 172 + " or newer for package " + apkPath); 173 } 174 175 // first try v4 176 try { 177 return verifyV4Signature(input, apkPath, minSignatureSchemeVersion, verifyFull); 178 } catch (SignatureNotFoundException e) { 179 // not signed with v4, try older if allowed 180 if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V4) { 181 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 182 "No APK Signature Scheme v4 signature in package " + apkPath, e); 183 } 184 } 185 186 if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V3) { 187 // V3 and before are older than the requested minimum signing version 188 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 189 "No signature found in package of version " + minSignatureSchemeVersion 190 + " or newer for package " + apkPath); 191 } 192 193 return verifyV3AndBelowSignatures(input, apkPath, minSignatureSchemeVersion, verifyFull); 194 } 195 verifyV3AndBelowSignatures( ParseInput input, String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)196 private static ParseResult<SigningDetailsWithDigests> verifyV3AndBelowSignatures( 197 ParseInput input, String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion, 198 boolean verifyFull) { 199 // try v3 200 try { 201 return verifyV3Signature(input, apkPath, verifyFull); 202 } catch (SignatureNotFoundException e) { 203 // not signed with v3, try older if allowed 204 if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) { 205 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 206 "No APK Signature Scheme v3 signature in package " + apkPath, e); 207 } 208 } 209 210 // redundant, protective version check 211 if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V2) { 212 // V2 and before are older than the requested minimum signing version 213 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 214 "No signature found in package of version " + minSignatureSchemeVersion 215 + " or newer for package " + apkPath); 216 } 217 218 // try v2 219 try { 220 return verifyV2Signature(input, apkPath, verifyFull); 221 } catch (SignatureNotFoundException e) { 222 // not signed with v2, try older if allowed 223 if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V2) { 224 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 225 "No APK Signature Scheme v2 signature in package " + apkPath, e); 226 } 227 } 228 229 // redundant, protective version check 230 if (minSignatureSchemeVersion > SignatureSchemeVersion.JAR) { 231 // V1 and is older than the requested minimum signing version 232 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 233 "No signature found in package of version " + minSignatureSchemeVersion 234 + " or newer for package " + apkPath); 235 } 236 237 // v2 didn't work, try jarsigner 238 return verifyV1Signature(input, apkPath, verifyFull); 239 } 240 241 /** 242 * Verifies the provided APK using V4 schema. 243 * 244 * @param verifyFull whether to verify (V4 vs V3) or just collect certificates. 245 * @return the certificates associated with each signer. 246 * @throws SignatureNotFoundException if there are no V4 signatures in the APK 247 */ verifyV4Signature(ParseInput input, String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)248 private static ParseResult<SigningDetailsWithDigests> verifyV4Signature(ParseInput input, 249 String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion, 250 boolean verifyFull) throws SignatureNotFoundException { 251 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV4" : "certsOnlyV4"); 252 try { 253 final Pair<V4Signature.HashingInfo, V4Signature.SigningInfos> v4Pair = 254 ApkSignatureSchemeV4Verifier.extractSignature(apkPath); 255 final V4Signature.HashingInfo hashingInfo = v4Pair.first; 256 final V4Signature.SigningInfos signingInfos = v4Pair.second; 257 258 Signature[] pastSignerSigs = null; 259 Map<Integer, byte[]> nonstreamingDigests = null; 260 Certificate[][] nonstreamingCerts = null; 261 262 int v3BlockId = APK_SIGNATURE_SCHEME_DEFAULT; 263 // If V4 contains additional signing blocks then we need to always run v2/v3 verifier 264 // to figure out which block they use. 265 if (verifyFull || signingInfos.signingInfoBlocks.length > 0) { 266 try { 267 // v4 is an add-on and requires v2 or v3 signature to validate against its 268 // certificate and digest 269 ApkSignatureSchemeV3Verifier.VerifiedSigner v3Signer = 270 ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath); 271 nonstreamingDigests = v3Signer.contentDigests; 272 nonstreamingCerts = new Certificate[][]{v3Signer.certs}; 273 if (v3Signer.por != null) { 274 // populate proof-of-rotation information 275 pastSignerSigs = new Signature[v3Signer.por.certs.size()]; 276 for (int i = 0; i < pastSignerSigs.length; i++) { 277 pastSignerSigs[i] = new Signature( 278 v3Signer.por.certs.get(i).getEncoded()); 279 pastSignerSigs[i].setFlags(v3Signer.por.flagsList.get(i)); 280 } 281 } 282 v3BlockId = v3Signer.blockId; 283 } catch (SignatureNotFoundException e) { 284 try { 285 ApkSignatureSchemeV2Verifier.VerifiedSigner v2Signer = 286 ApkSignatureSchemeV2Verifier.verify(apkPath, false); 287 nonstreamingDigests = v2Signer.contentDigests; 288 nonstreamingCerts = v2Signer.certs; 289 } catch (SignatureNotFoundException ee) { 290 throw new SecurityException( 291 "V4 verification failed to collect V2/V3 certificates from : " 292 + apkPath, ee); 293 } 294 } 295 } 296 297 ApkSignatureSchemeV4Verifier.VerifiedSigner vSigner = 298 ApkSignatureSchemeV4Verifier.verify(apkPath, hashingInfo, signingInfos, 299 v3BlockId); 300 Certificate[][] signerCerts = new Certificate[][]{vSigner.certs}; 301 Signature[] signerSigs = convertToSignatures(signerCerts); 302 303 if (verifyFull) { 304 Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts); 305 if (nonstreamingSigs.length != signerSigs.length) { 306 throw new SecurityException( 307 "Invalid number of certificates: " + nonstreamingSigs.length); 308 } 309 310 for (int i = 0, size = signerSigs.length; i < size; ++i) { 311 if (!nonstreamingSigs[i].equals(signerSigs[i])) { 312 throw new SecurityException( 313 "V4 signature certificate does not match V2/V3"); 314 } 315 } 316 317 boolean found = false; 318 for (byte[] nonstreamingDigest : nonstreamingDigests.values()) { 319 if (ArrayUtils.equals(vSigner.apkDigest, nonstreamingDigest, 320 vSigner.apkDigest.length)) { 321 found = true; 322 break; 323 } 324 } 325 if (!found) { 326 throw new SecurityException("APK digest in V4 signature does not match V2/V3"); 327 } 328 } 329 330 return input.success(new SigningDetailsWithDigests(new SigningDetails(signerSigs, 331 SignatureSchemeVersion.SIGNING_BLOCK_V4, pastSignerSigs), 332 vSigner.contentDigests)); 333 } catch (SignatureNotFoundException e) { 334 throw e; 335 } catch (Exception e) { 336 // APK Signature Scheme v4 signature found but did not verify. 337 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 338 "Failed to collect certificates from " + apkPath 339 + " using APK Signature Scheme v4", e); 340 } finally { 341 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); 342 } 343 } 344 345 /** 346 * Verifies the provided APK using V3 schema. 347 * 348 * @param verifyFull whether to verify all contents of this APK or just collect certificates. 349 * @return the certificates associated with each signer. 350 * @throws SignatureNotFoundException if there are no V3 signatures in the APK 351 */ verifyV3Signature(ParseInput input, String apkPath, boolean verifyFull)352 private static ParseResult<SigningDetailsWithDigests> verifyV3Signature(ParseInput input, 353 String apkPath, boolean verifyFull) throws SignatureNotFoundException { 354 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV3" : "certsOnlyV3"); 355 try { 356 ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner = 357 verifyFull ? ApkSignatureSchemeV3Verifier.verify(apkPath) 358 : ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification( 359 apkPath); 360 Certificate[][] signerCerts = new Certificate[][]{vSigner.certs}; 361 Signature[] signerSigs = convertToSignatures(signerCerts); 362 Signature[] pastSignerSigs = null; 363 if (vSigner.por != null) { 364 // populate proof-of-rotation information 365 pastSignerSigs = new Signature[vSigner.por.certs.size()]; 366 for (int i = 0; i < pastSignerSigs.length; i++) { 367 pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded()); 368 pastSignerSigs[i].setFlags(vSigner.por.flagsList.get(i)); 369 } 370 } 371 return input.success(new SigningDetailsWithDigests(new SigningDetails(signerSigs, 372 SignatureSchemeVersion.SIGNING_BLOCK_V3, pastSignerSigs), 373 vSigner.contentDigests)); 374 } catch (SignatureNotFoundException e) { 375 throw e; 376 } catch (Exception e) { 377 // APK Signature Scheme v3 signature found but did not verify 378 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 379 "Failed to collect certificates from " + apkPath 380 + " using APK Signature Scheme v3", e); 381 } finally { 382 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); 383 } 384 } 385 386 /** 387 * Verifies the provided APK using V2 schema. 388 * 389 * @param verifyFull whether to verify all contents of this APK or just collect certificates. 390 * @return the certificates associated with each signer. 391 * @throws SignatureNotFoundException if there are no V2 signatures in the APK 392 */ verifyV2Signature(ParseInput input, String apkPath, boolean verifyFull)393 private static ParseResult<SigningDetailsWithDigests> verifyV2Signature(ParseInput input, 394 String apkPath, boolean verifyFull) throws SignatureNotFoundException { 395 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV2" : "certsOnlyV2"); 396 try { 397 ApkSignatureSchemeV2Verifier.VerifiedSigner vSigner = 398 ApkSignatureSchemeV2Verifier.verify(apkPath, verifyFull); 399 Certificate[][] signerCerts = vSigner.certs; 400 Signature[] signerSigs = convertToSignatures(signerCerts); 401 return input.success(new SigningDetailsWithDigests(new SigningDetails(signerSigs, 402 SignatureSchemeVersion.SIGNING_BLOCK_V2), vSigner.contentDigests)); 403 } catch (SignatureNotFoundException e) { 404 throw e; 405 } catch (Exception e) { 406 // APK Signature Scheme v2 signature found but did not verify 407 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 408 "Failed to collect certificates from " + apkPath 409 + " using APK Signature Scheme v2", e); 410 } finally { 411 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); 412 } 413 } 414 415 /** 416 * Verifies the provided APK using JAR schema. 417 * @return the certificates associated with each signer. 418 * @param verifyFull whether to verify all contents of this APK or just collect certificates. 419 */ verifyV1Signature(ParseInput input, String apkPath, boolean verifyFull)420 private static ParseResult<SigningDetailsWithDigests> verifyV1Signature(ParseInput input, 421 String apkPath, boolean verifyFull) { 422 StrictJarFile jarFile = null; 423 424 try { 425 final Certificate[][] lastCerts; 426 final Signature[] lastSigs; 427 428 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor"); 429 430 // we still pass verify = true to ctor to collect certs, even though we're not checking 431 // the whole jar. 432 jarFile = new StrictJarFile( 433 apkPath, 434 true, // collect certs 435 verifyFull); // whether to reject APK with stripped v2 signatures (b/27887819) 436 final List<ZipEntry> toVerify = new ArrayList<>(); 437 438 // Gather certs from AndroidManifest.xml, which every APK must have, as an optimization 439 // to not need to verify the whole APK when verifyFUll == false. 440 final ZipEntry manifestEntry = jarFile.findEntry( 441 ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME); 442 if (manifestEntry == null) { 443 return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST, 444 "Package " + apkPath + " has no manifest"); 445 } 446 final ParseResult<Certificate[][]> result = 447 loadCertificates(input, jarFile, manifestEntry); 448 if (result.isError()) { 449 return input.error(result); 450 } 451 lastCerts = result.getResult(); 452 if (ArrayUtils.isEmpty(lastCerts)) { 453 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Package " 454 + apkPath + " has no certificates at entry " 455 + ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME); 456 } 457 lastSigs = convertToSignatures(lastCerts); 458 459 // fully verify all contents, except for AndroidManifest.xml and the META-INF/ files. 460 if (verifyFull) { 461 final Iterator<ZipEntry> i = jarFile.iterator(); 462 while (i.hasNext()) { 463 final ZipEntry entry = i.next(); 464 if (entry.isDirectory()) continue; 465 466 final String entryName = entry.getName(); 467 if (entryName.startsWith("META-INF/")) continue; 468 if (entryName.equals(ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME)) continue; 469 470 toVerify.add(entry); 471 } 472 473 for (ZipEntry entry : toVerify) { 474 final Certificate[][] entryCerts; 475 final ParseResult<Certificate[][]> ret = 476 loadCertificates(input, jarFile, entry); 477 if (ret.isError()) { 478 return input.error(ret); 479 } 480 entryCerts = ret.getResult(); 481 if (ArrayUtils.isEmpty(entryCerts)) { 482 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 483 "Package " + apkPath + " has no certificates at entry " 484 + entry.getName()); 485 } 486 487 // make sure all entries use the same signing certs 488 final Signature[] entrySigs = convertToSignatures(entryCerts); 489 if (!Arrays.equals(lastSigs, entrySigs)) { 490 return input.error( 491 INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, 492 "Package " + apkPath + " has mismatched certificates at entry " 493 + entry.getName()); 494 } 495 } 496 } 497 return input.success(new SigningDetailsWithDigests( 498 new SigningDetails(lastSigs, SignatureSchemeVersion.JAR), null)); 499 } catch (GeneralSecurityException e) { 500 return input.error(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING, 501 "Failed to collect certificates from " + apkPath, e); 502 } catch (IOException | RuntimeException e) { 503 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 504 "Failed to collect certificates from " + apkPath, e); 505 } finally { 506 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); 507 closeQuietly(jarFile); 508 } 509 } 510 loadCertificates(ParseInput input, StrictJarFile jarFile, ZipEntry entry)511 private static ParseResult<Certificate[][]> loadCertificates(ParseInput input, 512 StrictJarFile jarFile, ZipEntry entry) { 513 InputStream is = null; 514 try { 515 // We must read the stream for the JarEntry to retrieve 516 // its certificates. 517 is = jarFile.getInputStream(entry); 518 readFullyIgnoringContents(is); 519 return input.success(jarFile.getCertificateChains(entry)); 520 } catch (IOException | RuntimeException e) { 521 return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, 522 "Failed reading " + entry.getName() + " in " + jarFile, e); 523 } finally { 524 IoUtils.closeQuietly(is); 525 } 526 } 527 readFullyIgnoringContents(InputStream in)528 private static void readFullyIgnoringContents(InputStream in) throws IOException { 529 byte[] buffer = sBuffer.getAndSet(null); 530 if (buffer == null) { 531 buffer = new byte[4096]; 532 } 533 534 int n = 0; 535 int count = 0; 536 while ((n = in.read(buffer, 0, buffer.length)) != -1) { 537 count += n; 538 } 539 540 sBuffer.set(buffer); 541 return; 542 } 543 544 /** 545 * Converts an array of certificate chains into the {@code Signature} equivalent used by the 546 * PackageManager. 547 * 548 * @throws CertificateEncodingException if it is unable to create a Signature object. 549 */ convertToSignatures(Certificate[][] certs)550 private static Signature[] convertToSignatures(Certificate[][] certs) 551 throws CertificateEncodingException { 552 final Signature[] res = new Signature[certs.length]; 553 for (int i = 0; i < certs.length; i++) { 554 res[i] = new Signature(certs[i]); 555 } 556 return res; 557 } 558 closeQuietly(StrictJarFile jarFile)559 private static void closeQuietly(StrictJarFile jarFile) { 560 if (jarFile != null) { 561 try { 562 jarFile.close(); 563 } catch (Exception ignored) { 564 } 565 } 566 } 567 568 /** 569 * Returns the minimum signature scheme version required for an app targeting the specified 570 * {@code targetSdk}. 571 */ getMinimumSignatureSchemeVersionForTargetSdk(int targetSdk)572 public static int getMinimumSignatureSchemeVersionForTargetSdk(int targetSdk) { 573 if (targetSdk >= Build.VERSION_CODES.R) { 574 return SignatureSchemeVersion.SIGNING_BLOCK_V2; 575 } 576 return SignatureSchemeVersion.JAR; 577 } 578 579 /** 580 * Result of a successful APK verification operation. 581 */ 582 public static class Result { 583 public final Certificate[][] certs; 584 public final Signature[] sigs; 585 public final int signatureSchemeVersion; 586 Result(Certificate[][] certs, Signature[] sigs, int signingVersion)587 public Result(Certificate[][] certs, Signature[] sigs, int signingVersion) { 588 this.certs = certs; 589 this.sigs = sigs; 590 this.signatureSchemeVersion = signingVersion; 591 } 592 } 593 594 /** 595 * @return the verity root hash in the Signing Block. 596 */ getVerityRootHash(String apkPath)597 public static byte[] getVerityRootHash(String apkPath) throws IOException, SecurityException { 598 // first try v3 599 try { 600 return ApkSignatureSchemeV3Verifier.getVerityRootHash(apkPath); 601 } catch (SignatureNotFoundException e) { 602 // try older version 603 } 604 try { 605 return ApkSignatureSchemeV2Verifier.getVerityRootHash(apkPath); 606 } catch (SignatureNotFoundException e) { 607 return null; 608 } 609 } 610 611 /** 612 * Generates the Merkle tree and verity metadata to the buffer allocated by the {@code 613 * ByteBufferFactory}. 614 * 615 * @return the verity root hash of the generated Merkle tree. 616 */ generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)617 public static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory) 618 throws IOException, SignatureNotFoundException, SecurityException, DigestException, 619 NoSuchAlgorithmException { 620 // first try v3 621 try { 622 return ApkSignatureSchemeV3Verifier.generateApkVerity(apkPath, bufferFactory); 623 } catch (SignatureNotFoundException e) { 624 // try older version 625 } 626 return ApkSignatureSchemeV2Verifier.generateApkVerity(apkPath, bufferFactory); 627 } 628 629 /** 630 * Extended signing details. 631 * @hide for internal use only. 632 */ 633 public static class SigningDetailsWithDigests { 634 public final SigningDetails signingDetails; 635 636 /** 637 * APK Signature Schemes v2/v3/v4 might contain multiple content digests. 638 * SignatureVerifier usually chooses one of them to verify. 639 * For certain signature schemes, e.g. v4, this digest is verified continuously. 640 * For others, e.g. v2, the caller has to specify if they want to verify. 641 * Please refer to documentation for more details. 642 */ 643 public final Map<Integer, byte[]> contentDigests; 644 SigningDetailsWithDigests(SigningDetails signingDetails, Map<Integer, byte[]> contentDigests)645 SigningDetailsWithDigests(SigningDetails signingDetails, 646 Map<Integer, byte[]> contentDigests) { 647 this.signingDetails = signingDetails; 648 this.contentDigests = contentDigests; 649 } 650 } 651 } 652