summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/content/pm/PackageParser.java17
-rw-r--r--core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java45
-rw-r--r--core/java/android/util/apk/ApkSignatureVerifier.java93
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 {