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