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 26 import android.content.pm.PackageParser; 27 import android.content.pm.PackageParser.PackageParserException; 28 import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion; 29 import android.content.pm.Signature; 30 import android.os.Build; 31 import android.os.Trace; 32 import android.util.jar.StrictJarFile; 33 34 import com.android.internal.util.ArrayUtils; 35 36 import libcore.io.IoUtils; 37 38 import java.io.IOException; 39 import java.io.InputStream; 40 import java.security.DigestException; 41 import java.security.GeneralSecurityException; 42 import java.security.NoSuchAlgorithmException; 43 import java.security.cert.Certificate; 44 import java.security.cert.CertificateEncodingException; 45 import java.util.ArrayList; 46 import java.util.Iterator; 47 import java.util.List; 48 import java.util.concurrent.atomic.AtomicReference; 49 import java.util.zip.ZipEntry; 50 51 /** 52 * Facade class that takes care of the details of APK verification on 53 * behalf of PackageParser. 54 * 55 * @hide for internal use only. 56 */ 57 public class ApkSignatureVerifier { 58 59 private static final AtomicReference<byte[]> sBuffer = new AtomicReference<>(); 60 61 /** 62 * Verifies the provided APK and returns the certificates associated with each signer. 63 * 64 * @throws PackageParserException if the APK's signature failed to verify. 65 */ verify(String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion)66 public static PackageParser.SigningDetails verify(String apkPath, 67 @SignatureSchemeVersion int minSignatureSchemeVersion) 68 throws PackageParserException { 69 return verifySignatures(apkPath, minSignatureSchemeVersion, true); 70 } 71 72 /** 73 * Returns the certificates associated with each signer for the given APK without verification. 74 * This method is dangerous and should not be used, unless the caller is absolutely certain the 75 * APK is trusted. 76 * 77 * @throws PackageParserException if there was a problem collecting certificates. 78 */ unsafeGetCertsWithoutVerification( String apkPath, int minSignatureSchemeVersion)79 public static PackageParser.SigningDetails unsafeGetCertsWithoutVerification( 80 String apkPath, int minSignatureSchemeVersion) 81 throws PackageParserException { 82 return verifySignatures(apkPath, minSignatureSchemeVersion, false); 83 } 84 85 /** 86 * Verifies the provided APK using all allowed signing schemas. 87 * @return the certificates associated with each signer. 88 * @param verifyFull whether to verify all contents of this APK or just collect certificates. 89 * @throws PackageParserException if there was a problem collecting certificates 90 */ verifySignatures(String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)91 private static PackageParser.SigningDetails verifySignatures(String apkPath, 92 @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull) 93 throws PackageParserException { 94 95 if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V4) { 96 // V3 and before are older than the requested minimum signing version 97 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 98 "No signature found in package of version " + minSignatureSchemeVersion 99 + " or newer for package " + apkPath); 100 } 101 102 // first try v4 103 try { 104 return verifyV4Signature(apkPath, minSignatureSchemeVersion, verifyFull); 105 } catch (SignatureNotFoundException e) { 106 // not signed with v4, try older if allowed 107 if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V4) { 108 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 109 "No APK Signature Scheme v4 signature in package " + apkPath, e); 110 } 111 } 112 113 if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V3) { 114 // V3 and before are older than the requested minimum signing version 115 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 116 "No signature found in package of version " + minSignatureSchemeVersion 117 + " or newer for package " + apkPath); 118 } 119 120 return verifyV3AndBelowSignatures(apkPath, minSignatureSchemeVersion, verifyFull); 121 } 122 verifyV3AndBelowSignatures(String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)123 private static PackageParser.SigningDetails verifyV3AndBelowSignatures(String apkPath, 124 @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull) 125 throws PackageParserException { 126 // try v3 127 try { 128 return verifyV3Signature(apkPath, verifyFull); 129 } catch (SignatureNotFoundException e) { 130 // not signed with v3, try older if allowed 131 if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) { 132 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 133 "No APK Signature Scheme v3 signature in package " + apkPath, e); 134 } 135 } 136 137 // redundant, protective version check 138 if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V2) { 139 // V2 and before are older than the requested minimum signing version 140 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 141 "No signature found in package of version " + minSignatureSchemeVersion 142 + " or newer for package " + apkPath); 143 } 144 145 // try v2 146 try { 147 return verifyV2Signature(apkPath, verifyFull); 148 } catch (SignatureNotFoundException e) { 149 // not signed with v2, try older if allowed 150 if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V2) { 151 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 152 "No APK Signature Scheme v2 signature in package " + apkPath, e); 153 } 154 } 155 156 // redundant, protective version check 157 if (minSignatureSchemeVersion > SignatureSchemeVersion.JAR) { 158 // V1 and is older than the requested minimum signing version 159 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 160 "No signature found in package of version " + minSignatureSchemeVersion 161 + " or newer for package " + apkPath); 162 } 163 164 // v2 didn't work, try jarsigner 165 return verifyV1Signature(apkPath, verifyFull); 166 } 167 168 /** 169 * Verifies the provided APK using V4 schema. 170 * 171 * @param verifyFull whether to verify (V4 vs V3) or just collect certificates. 172 * @return the certificates associated with each signer. 173 * @throws SignatureNotFoundException if there are no V4 signatures in the APK 174 * @throws PackageParserException if there was a problem collecting certificates 175 */ verifyV4Signature(String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)176 private static PackageParser.SigningDetails verifyV4Signature(String apkPath, 177 @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull) 178 throws SignatureNotFoundException, PackageParserException { 179 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV4" : "certsOnlyV4"); 180 try { 181 ApkSignatureSchemeV4Verifier.VerifiedSigner vSigner = 182 ApkSignatureSchemeV4Verifier.extractCertificates(apkPath); 183 Certificate[][] signerCerts = new Certificate[][]{vSigner.certs}; 184 Signature[] signerSigs = convertToSignatures(signerCerts); 185 186 if (verifyFull) { 187 byte[] nonstreamingDigest = null; 188 Certificate[][] nonstreamingCerts = null; 189 190 try { 191 // v4 is an add-on and requires v2 or v3 signature to validate against its 192 // certificate and digest 193 ApkSignatureSchemeV3Verifier.VerifiedSigner v3Signer = 194 ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath); 195 nonstreamingDigest = v3Signer.digest; 196 nonstreamingCerts = new Certificate[][]{v3Signer.certs}; 197 } catch (SignatureNotFoundException e) { 198 try { 199 ApkSignatureSchemeV2Verifier.VerifiedSigner v2Signer = 200 ApkSignatureSchemeV2Verifier.verify(apkPath, false); 201 nonstreamingDigest = v2Signer.digest; 202 nonstreamingCerts = v2Signer.certs; 203 } catch (SignatureNotFoundException ee) { 204 throw new SecurityException( 205 "V4 verification failed to collect V2/V3 certificates from : " 206 + apkPath, ee); 207 } 208 } 209 210 Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts); 211 if (nonstreamingSigs.length != signerSigs.length) { 212 throw new SecurityException( 213 "Invalid number of certificates: " + nonstreamingSigs.length); 214 } 215 216 for (int i = 0, size = signerSigs.length; i < size; ++i) { 217 if (!nonstreamingSigs[i].equals(signerSigs[i])) { 218 throw new SecurityException( 219 "V4 signature certificate does not match V2/V3"); 220 } 221 } 222 223 if (!ArrayUtils.equals(vSigner.apkDigest, nonstreamingDigest, 224 vSigner.apkDigest.length)) { 225 throw new SecurityException("APK digest in V4 signature does not match V2/V3"); 226 } 227 } 228 229 return new PackageParser.SigningDetails(signerSigs, 230 SignatureSchemeVersion.SIGNING_BLOCK_V4); 231 } catch (SignatureNotFoundException e) { 232 throw e; 233 } catch (Exception e) { 234 // APK Signature Scheme v4 signature found but did not verify 235 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 236 "Failed to collect certificates from " + apkPath 237 + " using APK Signature Scheme v4", e); 238 } finally { 239 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); 240 } 241 } 242 243 /** 244 * Verifies the provided APK using V3 schema. 245 * 246 * @param verifyFull whether to verify all contents of this APK or just collect certificates. 247 * @return the certificates associated with each signer. 248 * @throws SignatureNotFoundException if there are no V3 signatures in the APK 249 * @throws PackageParserException if there was a problem collecting certificates 250 */ verifyV3Signature(String apkPath, boolean verifyFull)251 private static PackageParser.SigningDetails verifyV3Signature(String apkPath, 252 boolean verifyFull) throws SignatureNotFoundException, PackageParserException { 253 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV3" : "certsOnlyV3"); 254 try { 255 ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner = 256 verifyFull ? ApkSignatureSchemeV3Verifier.verify(apkPath) 257 : ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification( 258 apkPath); 259 Certificate[][] signerCerts = new Certificate[][]{vSigner.certs}; 260 Signature[] signerSigs = convertToSignatures(signerCerts); 261 Signature[] pastSignerSigs = null; 262 if (vSigner.por != null) { 263 // populate proof-of-rotation information 264 pastSignerSigs = new Signature[vSigner.por.certs.size()]; 265 for (int i = 0; i < pastSignerSigs.length; i++) { 266 pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded()); 267 pastSignerSigs[i].setFlags(vSigner.por.flagsList.get(i)); 268 } 269 } 270 return new PackageParser.SigningDetails(signerSigs, 271 SignatureSchemeVersion.SIGNING_BLOCK_V3, pastSignerSigs); 272 } catch (SignatureNotFoundException e) { 273 throw e; 274 } catch (Exception e) { 275 // APK Signature Scheme v3 signature found but did not verify 276 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 277 "Failed to collect certificates from " + apkPath 278 + " using APK Signature Scheme v3", e); 279 } finally { 280 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); 281 } 282 } 283 284 /** 285 * Verifies the provided APK using V2 schema. 286 * 287 * @param verifyFull whether to verify all contents of this APK or just collect certificates. 288 * @return the certificates associated with each signer. 289 * @throws SignatureNotFoundException if there are no V2 signatures in the APK 290 * @throws PackageParserException if there was a problem collecting certificates 291 */ verifyV2Signature(String apkPath, boolean verifyFull)292 private static PackageParser.SigningDetails verifyV2Signature(String apkPath, 293 boolean verifyFull) throws SignatureNotFoundException, PackageParserException { 294 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV2" : "certsOnlyV2"); 295 try { 296 Certificate[][] signerCerts = verifyFull ? ApkSignatureSchemeV2Verifier.verify(apkPath) 297 : ApkSignatureSchemeV2Verifier.unsafeGetCertsWithoutVerification(apkPath); 298 Signature[] signerSigs = convertToSignatures(signerCerts); 299 return new PackageParser.SigningDetails(signerSigs, 300 SignatureSchemeVersion.SIGNING_BLOCK_V2); 301 } catch (SignatureNotFoundException e) { 302 throw e; 303 } catch (Exception e) { 304 // APK Signature Scheme v2 signature found but did not verify 305 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 306 "Failed to collect certificates from " + apkPath 307 + " using APK Signature Scheme v2", e); 308 } finally { 309 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); 310 } 311 } 312 313 /** 314 * Verifies the provided APK using JAR schema. 315 * @return the certificates associated with each signer. 316 * @param verifyFull whether to verify all contents of this APK or just collect certificates. 317 * @throws PackageParserException if there was a problem collecting certificates 318 */ verifyV1Signature( String apkPath, boolean verifyFull)319 private static PackageParser.SigningDetails verifyV1Signature( 320 String apkPath, boolean verifyFull) 321 throws PackageParserException { 322 StrictJarFile jarFile = null; 323 324 try { 325 final Certificate[][] lastCerts; 326 final Signature[] lastSigs; 327 328 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor"); 329 330 // we still pass verify = true to ctor to collect certs, even though we're not checking 331 // the whole jar. 332 jarFile = new StrictJarFile( 333 apkPath, 334 true, // collect certs 335 verifyFull); // whether to reject APK with stripped v2 signatures (b/27887819) 336 final List<ZipEntry> toVerify = new ArrayList<>(); 337 338 // Gather certs from AndroidManifest.xml, which every APK must have, as an optimization 339 // to not need to verify the whole APK when verifyFUll == false. 340 final ZipEntry manifestEntry = jarFile.findEntry( 341 PackageParser.ANDROID_MANIFEST_FILENAME); 342 if (manifestEntry == null) { 343 throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, 344 "Package " + apkPath + " has no manifest"); 345 } 346 lastCerts = loadCertificates(jarFile, manifestEntry); 347 if (ArrayUtils.isEmpty(lastCerts)) { 348 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Package " 349 + apkPath + " has no certificates at entry " 350 + PackageParser.ANDROID_MANIFEST_FILENAME); 351 } 352 lastSigs = convertToSignatures(lastCerts); 353 354 // fully verify all contents, except for AndroidManifest.xml and the META-INF/ files. 355 if (verifyFull) { 356 final Iterator<ZipEntry> i = jarFile.iterator(); 357 while (i.hasNext()) { 358 final ZipEntry entry = i.next(); 359 if (entry.isDirectory()) continue; 360 361 final String entryName = entry.getName(); 362 if (entryName.startsWith("META-INF/")) continue; 363 if (entryName.equals(PackageParser.ANDROID_MANIFEST_FILENAME)) continue; 364 365 toVerify.add(entry); 366 } 367 368 for (ZipEntry entry : toVerify) { 369 final Certificate[][] entryCerts = loadCertificates(jarFile, entry); 370 if (ArrayUtils.isEmpty(entryCerts)) { 371 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 372 "Package " + apkPath + " has no certificates at entry " 373 + entry.getName()); 374 } 375 376 // make sure all entries use the same signing certs 377 final Signature[] entrySigs = convertToSignatures(entryCerts); 378 if (!Signature.areExactMatch(lastSigs, entrySigs)) { 379 throw new PackageParserException( 380 INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, 381 "Package " + apkPath + " has mismatched certificates at entry " 382 + entry.getName()); 383 } 384 } 385 } 386 return new PackageParser.SigningDetails(lastSigs, SignatureSchemeVersion.JAR); 387 } catch (GeneralSecurityException e) { 388 throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING, 389 "Failed to collect certificates from " + apkPath, e); 390 } catch (IOException | RuntimeException e) { 391 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, 392 "Failed to collect certificates from " + apkPath, e); 393 } finally { 394 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); 395 closeQuietly(jarFile); 396 } 397 } 398 loadCertificates(StrictJarFile jarFile, ZipEntry entry)399 private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry entry) 400 throws PackageParserException { 401 InputStream is = null; 402 try { 403 // We must read the stream for the JarEntry to retrieve 404 // its certificates. 405 is = jarFile.getInputStream(entry); 406 readFullyIgnoringContents(is); 407 return jarFile.getCertificateChains(entry); 408 } catch (IOException | RuntimeException e) { 409 throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, 410 "Failed reading " + entry.getName() + " in " + jarFile, e); 411 } finally { 412 IoUtils.closeQuietly(is); 413 } 414 } 415 readFullyIgnoringContents(InputStream in)416 private static void readFullyIgnoringContents(InputStream in) throws IOException { 417 byte[] buffer = sBuffer.getAndSet(null); 418 if (buffer == null) { 419 buffer = new byte[4096]; 420 } 421 422 int n = 0; 423 int count = 0; 424 while ((n = in.read(buffer, 0, buffer.length)) != -1) { 425 count += n; 426 } 427 428 sBuffer.set(buffer); 429 return; 430 } 431 432 /** 433 * Converts an array of certificate chains into the {@code Signature} equivalent used by the 434 * PackageManager. 435 * 436 * @throws CertificateEncodingException if it is unable to create a Signature object. 437 */ convertToSignatures(Certificate[][] certs)438 private static Signature[] convertToSignatures(Certificate[][] certs) 439 throws CertificateEncodingException { 440 final Signature[] res = new Signature[certs.length]; 441 for (int i = 0; i < certs.length; i++) { 442 res[i] = new Signature(certs[i]); 443 } 444 return res; 445 } 446 closeQuietly(StrictJarFile jarFile)447 private static void closeQuietly(StrictJarFile jarFile) { 448 if (jarFile != null) { 449 try { 450 jarFile.close(); 451 } catch (Exception ignored) { 452 } 453 } 454 } 455 456 /** 457 * Returns the minimum signature scheme version required for an app targeting the specified 458 * {@code targetSdk}. 459 */ getMinimumSignatureSchemeVersionForTargetSdk(int targetSdk)460 public static int getMinimumSignatureSchemeVersionForTargetSdk(int targetSdk) { 461 if (targetSdk >= Build.VERSION_CODES.R) { 462 return SignatureSchemeVersion.SIGNING_BLOCK_V2; 463 } 464 return SignatureSchemeVersion.JAR; 465 } 466 467 /** 468 * Result of a successful APK verification operation. 469 */ 470 public static class Result { 471 public final Certificate[][] certs; 472 public final Signature[] sigs; 473 public final int signatureSchemeVersion; 474 Result(Certificate[][] certs, Signature[] sigs, int signingVersion)475 public Result(Certificate[][] certs, Signature[] sigs, int signingVersion) { 476 this.certs = certs; 477 this.sigs = sigs; 478 this.signatureSchemeVersion = signingVersion; 479 } 480 } 481 482 /** 483 * @return the verity root hash in the Signing Block. 484 */ getVerityRootHash(String apkPath)485 public static byte[] getVerityRootHash(String apkPath) throws IOException, SecurityException { 486 // first try v3 487 try { 488 return ApkSignatureSchemeV3Verifier.getVerityRootHash(apkPath); 489 } catch (SignatureNotFoundException e) { 490 // try older version 491 } 492 try { 493 return ApkSignatureSchemeV2Verifier.getVerityRootHash(apkPath); 494 } catch (SignatureNotFoundException e) { 495 return null; 496 } 497 } 498 499 /** 500 * Generates the Merkle tree and verity metadata to the buffer allocated by the {@code 501 * ByteBufferFactory}. 502 * 503 * @return the verity root hash of the generated Merkle tree. 504 */ generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)505 public static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory) 506 throws IOException, SignatureNotFoundException, SecurityException, DigestException, 507 NoSuchAlgorithmException { 508 // first try v3 509 try { 510 return ApkSignatureSchemeV3Verifier.generateApkVerity(apkPath, bufferFactory); 511 } catch (SignatureNotFoundException e) { 512 // try older version 513 } 514 return ApkSignatureSchemeV2Verifier.generateApkVerity(apkPath, bufferFactory); 515 } 516 517 /** 518 * Generates the FSVerity root hash from FSVerity header, extensions and Merkle tree root hash 519 * in Signing Block. 520 * 521 * @return FSverity root hash 522 */ generateApkVerityRootHash(String apkPath)523 public static byte[] generateApkVerityRootHash(String apkPath) 524 throws NoSuchAlgorithmException, DigestException, IOException { 525 // first try v3 526 try { 527 return ApkSignatureSchemeV3Verifier.generateApkVerityRootHash(apkPath); 528 } catch (SignatureNotFoundException e) { 529 // try older version 530 } 531 try { 532 return ApkSignatureSchemeV2Verifier.generateApkVerityRootHash(apkPath); 533 } catch (SignatureNotFoundException e) { 534 return null; 535 } 536 } 537 } 538