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