diff options
| author | 2017-12-18 10:38:20 -0800 | |
|---|---|---|
| committer | 2017-12-20 16:02:51 -0800 | |
| commit | 636ea5e888197c0705c440bccff81d761ddf354a (patch) | |
| tree | 081aa81ccdd7109c5f8ba565c0df0cdd84c0b4e9 | |
| parent | 6b93e3931e4ae002f8465139598a1104c4c5ae2f (diff) | |
Add plsCertsNoVerifyOnlyCerts to ApkSignatureVerifier.
There are currently two conceptual operations performed by PackageParser
while parsing APKs: collecting certificates and verifying them.
ApkSignatureVerifier relies on the systemDir flag to indicate whether or
not it should do a full verification of a package, but this only applies
when verifying V1 (jar signed) APKs. This distinction should be explicitly
made. This creates cleaner code and also saves time when verifying V2
signed systemDir APKs.
Bug: 64686581
Test: Builds, boots, passes
android.appsecurity.cts.PkgInstallSignatureVerificationTest.
Change-Id: Ie8a0f8cad3dd8f70da791f2f1f4516e84e2ae4d0
| -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 { |