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 // We need to always run v2/v3 verifier to figure out which block they use so we can 264 // return the past signers as well as the current one - the rotation chain is important 265 // for many callers who verify the signature origin as well as the apk integrity. 266 if (android.content.pm.Flags.alwaysLoadPastCertsV4() 267 || verifyFull || signingInfos.signingInfoBlocks.length > 0) { 268 try { 269 // v4 is an add-on and requires v2 or v3 signature to validate against its 270 // certificate and digest 271 ApkSignatureSchemeV3Verifier.VerifiedSigner v3Signer = 272 ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath); 273 nonstreamingDigests = v3Signer.contentDigests; 274 nonstreamingCerts = new Certificate[][]{v3Signer.certs}; 275 if (v3Signer.por != null) { 276 // populate proof-of-rotation information 277 pastSignerSigs = new Signature[v3Signer.por.certs.size()]; 278 for (int i = 0; i < pastSignerSigs.length; i++) { 279 pastSignerSigs[i] = new Signature( 280 v3Signer.por.certs.get(i).getEncoded()); 281 pastSignerSigs[i].setFlags(v3Signer.por.flagsList.get(i)); 282 } 283 } 284 v3BlockId = v3Signer.blockId; 285 } catch (SignatureNotFoundException e) { 286 try { 287 ApkSignatureSchemeV2Verifier.VerifiedSigner v2Signer = 288 ApkSignatureSchemeV2Verifier.verify(apkPath, false); 289 nonstreamingDigests = v2Signer.contentDigests; 290 nonstreamingCerts = v2Signer.certs; 291 } catch (SignatureNotFoundException ee) { 292 throw new SecurityException( 293 "V4 verification failed to collect V2/V3 certificates from : " 294 + apkPath, ee); 295 } 296 } 297 } 298 299 ApkSignatureSchemeV4Verifier.VerifiedSigner vSigner = 300 ApkSignatureSchemeV4Verifier.verify(apkPath, hashingInfo, signingInfos, 301 v3BlockId); 302 Certificate[][] signerCerts = new Certificate[][]{vSigner.certs}; 303 Signature[] signerSigs = convertToSignatures(signerCerts); 304 305 if (verifyFull) { 306 Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts); 307 if (nonstreamingSigs.length != signerSigs.length) { 308 throw new SecurityException( 309 "Invalid number of certificates: " + nonstreamingSigs.length); 310 } 311 312 for (int i = 0, size = signerSigs.length; i < size; ++i) { 313 if (!nonstreamingSigs[i].equals(signerSigs[i])) { 314 throw new SecurityException( 315 "V4 signature certificate does not match V2/V3"); 316 } 317 } 318 319 boolean found = false; 320 for (byte[] nonstreamingDigest : nonstreamingDigests.values()) { 321 if (ArrayUtils.equals(vSigner.apkDigest, nonstreamingDigest, 322 vSigner.apkDigest.length)) { 323 found = true; 324 break; 325 } 326 } 327 if (!found) { 328 throw new SecurityException("APK digest in V4 signature does not match V2/V3"); 329 } 330 } 331 332 return input.success(new SigningDetailsWithDigests(new SigningDetails(signerSigs, 333 SignatureSchemeVersion.SIGNING_BLOCK_V4, pastSignerSigs), 334 vSigner.contentDigests)); 335 } catch (SignatureNotFoundException e) { 336 throw e; 337 } catch (Exception e) { 338 // APK Signature Scheme v4 signature found but did not verify. 339 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 340 "Failed to collect certificates from " + apkPath 341 + " using APK Signature Scheme v4", e); 342 } finally { 343 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); 344 } 345 } 346 347 /** 348 * Verifies the provided APK using V3 schema. 349 * 350 * @param verifyFull whether to verify all contents of this APK or just collect certificates. 351 * @return the certificates associated with each signer. 352 * @throws SignatureNotFoundException if there are no V3 signatures in the APK 353 */ verifyV3Signature(ParseInput input, String apkPath, boolean verifyFull)354 private static ParseResult<SigningDetailsWithDigests> verifyV3Signature(ParseInput input, 355 String apkPath, boolean verifyFull) throws SignatureNotFoundException { 356 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV3" : "certsOnlyV3"); 357 try { 358 ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner = 359 verifyFull ? ApkSignatureSchemeV3Verifier.verify(apkPath) 360 : ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification( 361 apkPath); 362 Certificate[][] signerCerts = new Certificate[][]{vSigner.certs}; 363 Signature[] signerSigs = convertToSignatures(signerCerts); 364 Signature[] pastSignerSigs = null; 365 if (vSigner.por != null) { 366 // populate proof-of-rotation information 367 pastSignerSigs = new Signature[vSigner.por.certs.size()]; 368 for (int i = 0; i < pastSignerSigs.length; i++) { 369 pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded()); 370 pastSignerSigs[i].setFlags(vSigner.por.flagsList.get(i)); 371 } 372 } 373 return input.success(new SigningDetailsWithDigests(new SigningDetails(signerSigs, 374 SignatureSchemeVersion.SIGNING_BLOCK_V3, pastSignerSigs), 375 vSigner.contentDigests)); 376 } catch (SignatureNotFoundException e) { 377 throw e; 378 } catch (Exception e) { 379 // APK Signature Scheme v3 signature found but did not verify 380 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 381 "Failed to collect certificates from " + apkPath 382 + " using APK Signature Scheme v3", e); 383 } finally { 384 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); 385 } 386 } 387 388 /** 389 * Verifies the provided APK using V2 schema. 390 * 391 * @param verifyFull whether to verify all contents of this APK or just collect certificates. 392 * @return the certificates associated with each signer. 393 * @throws SignatureNotFoundException if there are no V2 signatures in the APK 394 */ verifyV2Signature(ParseInput input, String apkPath, boolean verifyFull)395 private static ParseResult<SigningDetailsWithDigests> verifyV2Signature(ParseInput input, 396 String apkPath, boolean verifyFull) throws SignatureNotFoundException { 397 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV2" : "certsOnlyV2"); 398 try { 399 ApkSignatureSchemeV2Verifier.VerifiedSigner vSigner = 400 ApkSignatureSchemeV2Verifier.verify(apkPath, verifyFull); 401 Certificate[][] signerCerts = vSigner.certs; 402 Signature[] signerSigs = convertToSignatures(signerCerts); 403 return input.success(new SigningDetailsWithDigests(new SigningDetails(signerSigs, 404 SignatureSchemeVersion.SIGNING_BLOCK_V2), vSigner.contentDigests)); 405 } catch (SignatureNotFoundException e) { 406 throw e; 407 } catch (Exception e) { 408 // APK Signature Scheme v2 signature found but did not verify 409 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 410 "Failed to collect certificates from " + apkPath 411 + " using APK Signature Scheme v2", e); 412 } finally { 413 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); 414 } 415 } 416 417 /** 418 * Verifies the provided APK using JAR schema. 419 * @return the certificates associated with each signer. 420 * @param verifyFull whether to verify all contents of this APK or just collect certificates. 421 */ verifyV1Signature(ParseInput input, String apkPath, boolean verifyFull)422 private static ParseResult<SigningDetailsWithDigests> verifyV1Signature(ParseInput input, 423 String apkPath, boolean verifyFull) { 424 StrictJarFile jarFile = null; 425 426 try { 427 final Certificate[][] lastCerts; 428 final Signature[] lastSigs; 429 430 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor"); 431 432 // we still pass verify = true to ctor to collect certs, even though we're not checking 433 // the whole jar. 434 jarFile = new StrictJarFile( 435 apkPath, 436 true, // collect certs 437 verifyFull); // whether to reject APK with stripped v2 signatures (b/27887819) 438 final List<ZipEntry> toVerify = new ArrayList<>(); 439 440 // Gather certs from AndroidManifest.xml, which every APK must have, as an optimization 441 // to not need to verify the whole APK when verifyFUll == false. 442 final ZipEntry manifestEntry = jarFile.findEntry( 443 ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME); 444 if (manifestEntry == null) { 445 return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST, 446 "Package " + apkPath + " has no manifest"); 447 } 448 final ParseResult<Certificate[][]> result = 449 loadCertificates(input, jarFile, manifestEntry); 450 if (result.isError()) { 451 return input.error(result); 452 } 453 lastCerts = result.getResult(); 454 if (ArrayUtils.isEmpty(lastCerts)) { 455 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Package " 456 + apkPath + " has no certificates at entry " 457 + ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME); 458 } 459 lastSigs = convertToSignatures(lastCerts); 460 461 // fully verify all contents, except for AndroidManifest.xml and the META-INF/ files. 462 if (verifyFull) { 463 final Iterator<ZipEntry> i = jarFile.iterator(); 464 while (i.hasNext()) { 465 final ZipEntry entry = i.next(); 466 if (entry.isDirectory()) continue; 467 468 final String entryName = entry.getName(); 469 if (entryName.startsWith("META-INF/")) continue; 470 if (entryName.equals(ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME)) continue; 471 472 toVerify.add(entry); 473 } 474 475 for (ZipEntry entry : toVerify) { 476 final Certificate[][] entryCerts; 477 final ParseResult<Certificate[][]> ret = 478 loadCertificates(input, jarFile, entry); 479 if (ret.isError()) { 480 return input.error(ret); 481 } 482 entryCerts = ret.getResult(); 483 if (ArrayUtils.isEmpty(entryCerts)) { 484 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 485 "Package " + apkPath + " has no certificates at entry " 486 + entry.getName()); 487 } 488 489 // make sure all entries use the same signing certs 490 final Signature[] entrySigs = convertToSignatures(entryCerts); 491 if (!Arrays.equals(lastSigs, entrySigs)) { 492 return input.error( 493 INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, 494 "Package " + apkPath + " has mismatched certificates at entry " 495 + entry.getName()); 496 } 497 } 498 } 499 return input.success(new SigningDetailsWithDigests( 500 new SigningDetails(lastSigs, SignatureSchemeVersion.JAR), null)); 501 } catch (GeneralSecurityException e) { 502 return input.error(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING, 503 "Failed to collect certificates from " + apkPath, e); 504 } catch (IOException | RuntimeException e) { 505 return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 506 "Failed to collect certificates from " + apkPath, e); 507 } finally { 508 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); 509 closeQuietly(jarFile); 510 } 511 } 512 loadCertificates(ParseInput input, StrictJarFile jarFile, ZipEntry entry)513 private static ParseResult<Certificate[][]> loadCertificates(ParseInput input, 514 StrictJarFile jarFile, ZipEntry entry) { 515 InputStream is = null; 516 try { 517 // We must read the stream for the JarEntry to retrieve 518 // its certificates. 519 is = jarFile.getInputStream(entry); 520 readFullyIgnoringContents(is); 521 return input.success(jarFile.getCertificateChains(entry)); 522 } catch (IOException | RuntimeException e) { 523 return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, 524 "Failed reading " + entry.getName() + " in " + jarFile, e); 525 } finally { 526 IoUtils.closeQuietly(is); 527 } 528 } 529 readFullyIgnoringContents(InputStream in)530 private static void readFullyIgnoringContents(InputStream in) throws IOException { 531 byte[] buffer = sBuffer.getAndSet(null); 532 if (buffer == null) { 533 buffer = new byte[4096]; 534 } 535 536 int n = 0; 537 int count = 0; 538 while ((n = in.read(buffer, 0, buffer.length)) != -1) { 539 count += n; 540 } 541 542 sBuffer.set(buffer); 543 return; 544 } 545 546 /** 547 * Converts an array of certificate chains into the {@code Signature} equivalent used by the 548 * PackageManager. 549 * 550 * @throws CertificateEncodingException if it is unable to create a Signature object. 551 */ convertToSignatures(Certificate[][] certs)552 private static Signature[] convertToSignatures(Certificate[][] certs) 553 throws CertificateEncodingException { 554 final Signature[] res = new Signature[certs.length]; 555 for (int i = 0; i < certs.length; i++) { 556 res[i] = new Signature(certs[i]); 557 } 558 return res; 559 } 560 closeQuietly(StrictJarFile jarFile)561 private static void closeQuietly(StrictJarFile jarFile) { 562 if (jarFile != null) { 563 try { 564 jarFile.close(); 565 } catch (Exception ignored) { 566 } 567 } 568 } 569 570 /** 571 * Returns the minimum signature scheme version required for an app targeting the specified 572 * {@code targetSdk}. 573 */ getMinimumSignatureSchemeVersionForTargetSdk(int targetSdk)574 public static int getMinimumSignatureSchemeVersionForTargetSdk(int targetSdk) { 575 if (targetSdk >= Build.VERSION_CODES.R) { 576 return SignatureSchemeVersion.SIGNING_BLOCK_V2; 577 } 578 return SignatureSchemeVersion.JAR; 579 } 580 581 /** 582 * Result of a successful APK verification operation. 583 */ 584 public static class Result { 585 public final Certificate[][] certs; 586 public final Signature[] sigs; 587 public final int signatureSchemeVersion; 588 Result(Certificate[][] certs, Signature[] sigs, int signingVersion)589 public Result(Certificate[][] certs, Signature[] sigs, int signingVersion) { 590 this.certs = certs; 591 this.sigs = sigs; 592 this.signatureSchemeVersion = signingVersion; 593 } 594 } 595 596 /** 597 * @return the verity root hash in the Signing Block. 598 */ getVerityRootHash(String apkPath)599 public static byte[] getVerityRootHash(String apkPath) throws IOException, SecurityException { 600 // first try v3 601 try { 602 return ApkSignatureSchemeV3Verifier.getVerityRootHash(apkPath); 603 } catch (SignatureNotFoundException e) { 604 // try older version 605 } 606 try { 607 return ApkSignatureSchemeV2Verifier.getVerityRootHash(apkPath); 608 } catch (SignatureNotFoundException e) { 609 return null; 610 } 611 } 612 613 /** 614 * Generates the Merkle tree and verity metadata to the buffer allocated by the {@code 615 * ByteBufferFactory}. 616 * 617 * @return the verity root hash of the generated Merkle tree. 618 */ generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)619 public static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory) 620 throws IOException, SignatureNotFoundException, SecurityException, DigestException, 621 NoSuchAlgorithmException { 622 // first try v3 623 try { 624 return ApkSignatureSchemeV3Verifier.generateApkVerity(apkPath, bufferFactory); 625 } catch (SignatureNotFoundException e) { 626 // try older version 627 } 628 return ApkSignatureSchemeV2Verifier.generateApkVerity(apkPath, bufferFactory); 629 } 630 631 /** 632 * Extended signing details. 633 * @hide for internal use only. 634 */ 635 public static class SigningDetailsWithDigests { 636 public final SigningDetails signingDetails; 637 638 /** 639 * APK Signature Schemes v2/v3/v4 might contain multiple content digests. 640 * SignatureVerifier usually chooses one of them to verify. 641 * For certain signature schemes, e.g. v4, this digest is verified continuously. 642 * For others, e.g. v2, the caller has to specify if they want to verify. 643 * Please refer to documentation for more details. 644 */ 645 public final Map<Integer, byte[]> contentDigests; 646 SigningDetailsWithDigests(SigningDetails signingDetails, Map<Integer, byte[]> contentDigests)647 SigningDetailsWithDigests(SigningDetails signingDetails, 648 Map<Integer, byte[]> contentDigests) { 649 this.signingDetails = signingDetails; 650 this.contentDigests = contentDigests; 651 } 652 } 653 } 654