diff options
| -rw-r--r-- | core/java/android/content/pm/PackageParser.java | 17 | ||||
| -rw-r--r-- | core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java | 45 | ||||
| -rw-r--r-- | core/java/android/util/apk/ApkSignatureVerifier.java | 93 |
3 files changed, 106 insertions, 49 deletions
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 21e203bc50d7..77eb57f25613 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1558,22 +1558,31 @@ public class PackageParser { throws PackageParserException { final String apkPath = apkFile.getAbsolutePath(); - boolean systemDir = (parseFlags & PARSE_IS_SYSTEM_DIR) != 0; int minSignatureScheme = ApkSignatureVerifier.VERSION_JAR_SIGNATURE_SCHEME; if (pkg.applicationInfo.isStaticSharedLibrary()) { // must use v2 signing scheme minSignatureScheme = ApkSignatureVerifier.VERSION_APK_SIGNATURE_SCHEME_V2; } - ApkSignatureVerifier.Result verified = - ApkSignatureVerifier.verify(apkPath, minSignatureScheme, systemDir); + ApkSignatureVerifier.Result verified; + if ((parseFlags & PARSE_IS_SYSTEM_DIR) != 0) { + // systemDir APKs are already trusted, save time by not verifying + verified = ApkSignatureVerifier.plsCertsNoVerifyOnlyCerts( + apkPath, minSignatureScheme); + } else { + verified = ApkSignatureVerifier.verify(apkPath, minSignatureScheme); + } if (verified.signatureSchemeVersion < ApkSignatureVerifier.VERSION_APK_SIGNATURE_SCHEME_V2) { // TODO (b/68860689): move this logic to packagemanagerserivce if ((parseFlags & PARSE_IS_EPHEMERAL) != 0) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, - "No APK Signature Scheme v2 signature in ephemeral package " + apkPath); + "No APK Signature Scheme v2 signature in ephemeral package " + apkPath); } } + + // Verify that entries are signed consistently with the first pkg + // we encountered. Note that for splits, certificates may have + // already been populated during an earlier parse of a base APK. if (pkg.mCertificates == null) { pkg.mCertificates = verified.certs; pkg.mSignatures = verified.sigs; diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java index a74a8821eba6..0a54f3a59a7d 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java @@ -97,11 +97,29 @@ public class ApkSignatureSchemeV2Verifier { */ public static X509Certificate[][] verify(String apkFile) throws SignatureNotFoundException, SecurityException, IOException { + return verify(apkFile, true); + } + + /** + * Returns the certificates associated with each signer for the given APK without verification. + * This method is dangerous and should not be used, unless the caller is absolutely certain the + * APK is trusted. Specifically, verification is only done for the APK Signature Scheme V2 + * Block while gathering signer information. The APK contents are not verified. + * + * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2. + * @throws IOException if an I/O error occurs while reading the APK file. + */ + public static X509Certificate[][] plsCertsNoVerifyOnlyCerts(String apkFile) + throws SignatureNotFoundException, SecurityException, IOException { + return verify(apkFile, false); + } + + private static X509Certificate[][] verify(String apkFile, boolean verifyIntegrity) + throws SignatureNotFoundException, SecurityException, IOException { try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { - return verify(apk); + return verify(apk, verifyIntegrity); } } - /** * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates * associated with each signer. @@ -111,10 +129,10 @@ public class ApkSignatureSchemeV2Verifier { * verify. * @throws IOException if an I/O error occurs while reading the APK file. */ - private static X509Certificate[][] verify(RandomAccessFile apk) + private static X509Certificate[][] verify(RandomAccessFile apk, boolean verifyIntegrity) throws SignatureNotFoundException, SecurityException, IOException { SignatureInfo signatureInfo = findSignature(apk); - return verify(apk.getFD(), signatureInfo); + return verify(apk.getFD(), signatureInfo, verifyIntegrity); } /** @@ -161,7 +179,8 @@ public class ApkSignatureSchemeV2Verifier { */ private static X509Certificate[][] verify( FileDescriptor apkFileDescriptor, - SignatureInfo signatureInfo) throws SecurityException { + SignatureInfo signatureInfo, + boolean doVerifyIntegrity) throws SecurityException { int signerCount = 0; Map<Integer, byte[]> contentDigests = new ArrayMap<>(); List<X509Certificate[]> signerCerts = new ArrayList<>(); @@ -198,13 +217,15 @@ public class ApkSignatureSchemeV2Verifier { throw new SecurityException("No content digests found"); } - verifyIntegrity( - contentDigests, - apkFileDescriptor, - signatureInfo.apkSigningBlockOffset, - signatureInfo.centralDirOffset, - signatureInfo.eocdOffset, - signatureInfo.eocd); + if (doVerifyIntegrity) { + verifyIntegrity( + contentDigests, + apkFileDescriptor, + signatureInfo.apkSigningBlockOffset, + signatureInfo.centralDirOffset, + signatureInfo.eocdOffset, + signatureInfo.eocd); + } return signerCerts.toArray(new X509Certificate[signerCerts.size()][]); } diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java index 75c1000ba41d..17b11a9b5170 100644 --- a/core/java/android/util/apk/ApkSignatureVerifier.java +++ b/core/java/android/util/apk/ApkSignatureVerifier.java @@ -58,37 +58,19 @@ public class ApkSignatureVerifier { private static final AtomicReference<byte[]> sBuffer = new AtomicReference<>(); /** - * Verifies the provided APK and returns the certificates associated with each signer. Also - * ensures that the provided APK contains an AndroidManifest.xml file. - * - * @param systemDir systemDir apk contents are already trusted, so we don't need to enforce - * v2 stripping rollback protection, or verify integrity of the APK. + * Verifies the provided APK and returns the certificates associated with each signer. * * @throws PackageParserException if the APK's signature failed to verify. */ - public static Result verify(String apkPath, int minSignatureSchemeVersion, boolean systemDir) + public static Result verify(String apkPath, int minSignatureSchemeVersion) throws PackageParserException { // first try v2 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2"); try { - Certificate[][] signerCerts; - signerCerts = ApkSignatureSchemeV2Verifier.verify(apkPath); + Certificate[][] signerCerts = ApkSignatureSchemeV2Verifier.verify(apkPath); Signature[] signerSigs = convertToSignatures(signerCerts); - // sanity check - must have an AndroidManifest file - StrictJarFile jarFile = null; - try { - jarFile = new StrictJarFile(apkPath, false, false); - final ZipEntry manifestEntry = - jarFile.findEntry(PackageParser.ANDROID_MANIFEST_FILENAME); - if (manifestEntry == null) { - throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, - "Package " + apkPath + " has no manifest"); - } - } finally { - closeQuietly(jarFile); - } return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V2); } catch (SignatureNotFoundException e) { // not signed with v2, try older if allowed @@ -96,9 +78,6 @@ public class ApkSignatureVerifier { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "No APK Signature Scheme v2 signature in package " + apkPath, e); } - } catch (PackageParserException e) { - // preserve any new exceptions explicitly thrown here - throw e; } catch (Exception e) { // APK Signature Scheme v2 signature found but did not verify throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, @@ -109,10 +88,17 @@ public class ApkSignatureVerifier { } // v2 didn't work, try jarsigner - return verifyV1Signature(apkPath, systemDir); + return verifyV1Signature(apkPath, true); } - private static Result verifyV1Signature(String apkPath, boolean systemDir) + /** + * Verifies the provided APK and returns the certificates associated with each signer. + * + * @param verifyFull whether to verify all contents of this APK or just collect certificates. + * + * @throws PackageParserException if there was a problem collecting certificates + */ + private static Result verifyV1Signature(String apkPath, boolean verifyFull) throws PackageParserException { StrictJarFile jarFile = null; @@ -121,10 +107,17 @@ public class ApkSignatureVerifier { final Signature[] lastSigs; Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor"); - jarFile = new StrictJarFile(apkPath, true, !systemDir); + + // we still pass verify = true to ctor to collect certs, even though we're not checking + // the whole jar. + jarFile = new StrictJarFile( + apkPath, + true, // collect certs + verifyFull); // whether to reject APK with stripped v2 signatures (b/27887819) final List<ZipEntry> toVerify = new ArrayList<>(); - // Always verify manifest, regardless of source + // Gather certs from AndroidManifest.xml, which every APK must have, as an optimization + // to not need to verify the whole APK when verifyFUll == false. final ZipEntry manifestEntry = jarFile.findEntry( PackageParser.ANDROID_MANIFEST_FILENAME); if (manifestEntry == null) { @@ -139,8 +132,8 @@ public class ApkSignatureVerifier { } lastSigs = convertToSignatures(lastCerts); - // don't waste time on already-trusted packages - if (!systemDir) { + // fully verify all contents, except for AndroidManifest.xml and the META-INF/ files. + if (verifyFull) { final Iterator<ZipEntry> i = jarFile.iterator(); while (i.hasNext()) { final ZipEntry entry = i.next(); @@ -153,9 +146,6 @@ public class ApkSignatureVerifier { toVerify.add(entry); } - // Verify that entries are signed consistently with the first entry - // we encountered. Note that for splits, certificates may have - // already been populated during an earlier parse of a base APK.; for (ZipEntry entry : toVerify) { final Certificate[][] entryCerts = loadCertificates(jarFile, entry); if (ArrayUtils.isEmpty(entryCerts)) { @@ -245,6 +235,43 @@ public class ApkSignatureVerifier { } /** + * Returns the certificates associated with each signer for the given APK without verification. + * This method is dangerous and should not be used, unless the caller is absolutely certain the + * APK is trusted. + * + * @throws PackageParserException if the APK's signature failed to verify. + * or greater is not found, except in the case of no JAR signature. + */ + public static Result plsCertsNoVerifyOnlyCerts(String apkPath, int minSignatureSchemeVersion) + throws PackageParserException { + + // first try v2 + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "certsOnlyV2"); + try { + Certificate[][] signerCerts = + ApkSignatureSchemeV2Verifier.plsCertsNoVerifyOnlyCerts(apkPath); + Signature[] signerSigs = convertToSignatures(signerCerts); + return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V2); + } catch (SignatureNotFoundException e) { + // not signed with v2, try older if allowed + if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V2) { + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "No APK Signature Scheme v2 signature in package " + apkPath, e); + } + } catch (Exception e) { + // APK Signature Scheme v2 signature found but did not verify + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "Failed to collect certificates from " + apkPath + + " using APK Signature Scheme v2", e); + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + + // v2 didn't work, try jarsigner + return verifyV1Signature(apkPath, false); + } + + /** * Result of a successful APK verification operation. */ public static class Result { |