Loading core/java/android/content/pm/PackageParser.java +13 −4 Original line number Diff line number Diff line Loading @@ -1558,14 +1558,19 @@ 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 Loading @@ -1574,6 +1579,10 @@ public class PackageParser { "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; Loading core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java +33 −12 Original line number Diff line number Diff line Loading @@ -97,11 +97,29 @@ public class ApkSignatureSchemeV2Verifier { */ public static X509Certificate[][] verify(String apkFile) throws SignatureNotFoundException, SecurityException, IOException { try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { return verify(apk); 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, verifyIntegrity); } } /** * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates * associated with each signer. Loading @@ -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); } /** Loading Loading @@ -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<>(); Loading Loading @@ -198,6 +217,7 @@ public class ApkSignatureSchemeV2Verifier { throw new SecurityException("No content digests found"); } if (doVerifyIntegrity) { verifyIntegrity( contentDigests, apkFileDescriptor, Loading @@ -205,6 +225,7 @@ public class ApkSignatureSchemeV2Verifier { signatureInfo.centralDirOffset, signatureInfo.eocdOffset, signatureInfo.eocd); } return signerCerts.toArray(new X509Certificate[signerCerts.size()][]); } Loading core/java/android/util/apk/ApkSignatureVerifier.java +60 −33 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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, Loading @@ -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; Loading @@ -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) { Loading @@ -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(); Loading @@ -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)) { Loading Loading @@ -244,6 +234,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. */ Loading Loading
core/java/android/content/pm/PackageParser.java +13 −4 Original line number Diff line number Diff line Loading @@ -1558,14 +1558,19 @@ 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 Loading @@ -1574,6 +1579,10 @@ public class PackageParser { "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; Loading
core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java +33 −12 Original line number Diff line number Diff line Loading @@ -97,11 +97,29 @@ public class ApkSignatureSchemeV2Verifier { */ public static X509Certificate[][] verify(String apkFile) throws SignatureNotFoundException, SecurityException, IOException { try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { return verify(apk); 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, verifyIntegrity); } } /** * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates * associated with each signer. Loading @@ -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); } /** Loading Loading @@ -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<>(); Loading Loading @@ -198,6 +217,7 @@ public class ApkSignatureSchemeV2Verifier { throw new SecurityException("No content digests found"); } if (doVerifyIntegrity) { verifyIntegrity( contentDigests, apkFileDescriptor, Loading @@ -205,6 +225,7 @@ public class ApkSignatureSchemeV2Verifier { signatureInfo.centralDirOffset, signatureInfo.eocdOffset, signatureInfo.eocd); } return signerCerts.toArray(new X509Certificate[signerCerts.size()][]); } Loading
core/java/android/util/apk/ApkSignatureVerifier.java +60 −33 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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, Loading @@ -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; Loading @@ -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) { Loading @@ -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(); Loading @@ -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)) { Loading Loading @@ -244,6 +234,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. */ Loading