Loading core/java/android/content/pm/PackageParser.java +34 −193 Original line number Diff line number Diff line Loading @@ -32,7 +32,6 @@ import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_ import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; Loading Loading @@ -87,7 +86,8 @@ import android.util.Slog; import android.util.SparseArray; import android.util.TypedValue; import android.util.apk.ApkSignatureSchemeV2Verifier; import android.util.jar.StrictJarFile; import android.util.apk.ApkSignatureVerifier; import android.util.apk.SignatureNotFoundException; import android.view.Gravity; import com.android.internal.R; Loading @@ -106,12 +106,10 @@ import java.io.File; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Constructor; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; Loading @@ -129,8 +127,6 @@ import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.zip.ZipEntry; /** * Parser for package files (APKs) on disk. This supports apps packaged either Loading Loading @@ -173,7 +169,7 @@ public class PackageParser { // TODO: refactor "codePath" to "apkPath" /** File name in an APK for the Android manifest. */ private static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml"; public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml"; /** Path prefix for apps on expanded storage */ private static final String MNT_EXPAND = "/mnt/expand/"; Loading Loading @@ -821,23 +817,6 @@ public class PackageParser { return pi; } private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry entry) throws PackageParserException { InputStream is = null; try { // We must read the stream for the JarEntry to retrieve // its certificates. is = jarFile.getInputStream(entry); readFullyIgnoringContents(is); return jarFile.getCertificateChains(entry); } catch (IOException | RuntimeException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, "Failed reading " + entry.getName() + " in " + jarFile, e); } finally { IoUtils.closeQuietly(is); } } public static final int PARSE_MUST_BE_APK = 1 << 0; public static final int PARSE_IGNORE_PROCESSES = 1 << 1; public static final int PARSE_FORWARD_LOCK = 1 << 2; Loading Loading @@ -1517,7 +1496,7 @@ public class PackageParser { pkg.mCertificates = certificates; try { pkg.mSignatures = convertToSignatures(certificates); pkg.mSignatures = ApkSignatureVerifier.convertToSignatures(certificates); } catch (CertificateEncodingException e) { // certificates weren't encoded properly; something went wrong throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, Loading Loading @@ -1580,155 +1559,44 @@ public class PackageParser { throws PackageParserException { final String apkPath = apkFile.getAbsolutePath(); // Try to verify the APK using APK Signature Scheme v2. boolean verified = false; { Certificate[][] allSignersCerts = null; Signature[] signatures = null; try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2"); allSignersCerts = ApkSignatureSchemeV2Verifier.verify(apkPath); signatures = convertToSignatures(allSignersCerts); // APK verified using APK Signature Scheme v2. verified = true; } catch (ApkSignatureSchemeV2Verifier.SignatureNotFoundException e) { // No APK Signature Scheme v2 signature found if ((parseFlags & PARSE_IS_EPHEMERAL) != 0) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "No APK Signature Scheme v2 signature in ephemeral package " + apkPath, e); boolean untrusted = (parseFlags & PARSE_IS_SYSTEM_DIR) == 0; int minSignatureScheme = ApkSignatureVerifier.VERSION_JAR_SIGNATURE_SCHEME; if ((parseFlags & PARSE_IS_EPHEMERAL) != 0 || pkg.applicationInfo.isStaticSharedLibrary()) { // must use v2 signing scheme minSignatureScheme = ApkSignatureVerifier.VERSION_APK_SIGNATURE_SCHEME_V2; } // Static shared libraries must use only the V2 signing scheme if (pkg.applicationInfo.isStaticSharedLibrary()) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Static shared libs must use v2 signature scheme " + apkPath); } } catch (Exception e) { // APK Signature Scheme v2 signature was 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); } if (verified) { try { ApkSignatureVerifier.Result verified = ApkSignatureVerifier.verify(apkPath, minSignatureScheme, untrusted); if (pkg.mCertificates == null) { pkg.mCertificates = allSignersCerts; pkg.mSignatures = signatures; pkg.mSigningKeys = new ArraySet<>(allSignersCerts.length); for (int i = 0; i < allSignersCerts.length; i++) { Certificate[] signerCerts = allSignersCerts[i]; pkg.mCertificates = verified.certs; pkg.mSignatures = verified.sigs; pkg.mSigningKeys = new ArraySet<>(verified.certs.length); for (int i = 0; i < verified.certs.length; i++) { Certificate[] signerCerts = verified.certs[i]; Certificate signerCert = signerCerts[0]; pkg.mSigningKeys.add(signerCert.getPublicKey()); } } else { if (!Signature.areExactMatch(pkg.mSignatures, signatures)) { if (!Signature.areExactMatch(pkg.mSignatures, verified.sigs)) { throw new PackageParserException( INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, apkPath + " has mismatched certificates"); } } // Not yet done, because we need to confirm that AndroidManifest.xml exists and, // if requested, that classes.dex exists. } } StrictJarFile jarFile = null; try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor"); // Ignore signature stripping protections when verifying APKs from system partition. // For those APKs we only care about extracting signer certificates, and don't care // about verifying integrity. boolean signatureSchemeRollbackProtectionsEnforced = (parseFlags & PARSE_IS_SYSTEM_DIR) == 0; jarFile = new StrictJarFile( apkPath, !verified, // whether to verify JAR signature signatureSchemeRollbackProtectionsEnforced); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); // Always verify manifest, regardless of source final ZipEntry manifestEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME); if (manifestEntry == null) { throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, "Package " + apkPath + " has no manifest"); } // Optimization: early termination when APK already verified if (verified) { return; } // APK's integrity needs to be verified using JAR signature scheme. Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV1"); final List<ZipEntry> toVerify = new ArrayList<>(); toVerify.add(manifestEntry); // If we're parsing an untrusted package, verify all contents if ((parseFlags & PARSE_IS_SYSTEM_DIR) == 0) { final Iterator<ZipEntry> i = jarFile.iterator(); while (i.hasNext()) { final ZipEntry entry = i.next(); if (entry.isDirectory()) continue; final String entryName = entry.getName(); if (entryName.startsWith("META-INF/")) continue; if (entryName.equals(ANDROID_MANIFEST_FILENAME)) continue; 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)) { } catch (SignatureNotFoundException e) { if ((parseFlags & PARSE_IS_EPHEMERAL) != 0) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Package " + apkPath + " has no certificates at entry " + entry.getName()); } final Signature[] entrySignatures = convertToSignatures(entryCerts); if (pkg.mCertificates == null) { pkg.mCertificates = entryCerts; pkg.mSignatures = entrySignatures; pkg.mSigningKeys = new ArraySet<PublicKey>(); for (int i=0; i < entryCerts.length; i++) { pkg.mSigningKeys.add(entryCerts[i][0].getPublicKey()); } } else { if (!Signature.areExactMatch(pkg.mSignatures, entrySignatures)) { throw new PackageParserException( INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, "Package " + apkPath + " has mismatched certificates at entry " + entry.getName()); } } "No APK Signature Scheme v2 signature in ephemeral package " + apkPath, e); } Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } catch (GeneralSecurityException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING, "Failed to collect certificates from " + apkPath, e); } catch (IOException | RuntimeException e) { if (pkg.applicationInfo.isStaticSharedLibrary()) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Failed to collect certificates from " + apkPath, e); } finally { closeQuietly(jarFile); } "Static shared libs must use v2 signature scheme " + apkPath); } private static Signature[] convertToSignatures(Certificate[][] certs) throws CertificateEncodingException { final Signature[] res = new Signature[certs.length]; for (int i = 0; i < certs.length; i++) { res[i] = new Signature(certs[i]); throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "No APK Signature Scheme v2 signature in package " + apkPath, e); } return res; } private static AssetManager newConfiguredAssetManager() { Loading Loading @@ -7647,33 +7515,6 @@ public class PackageParser { sCompatibilityModeEnabled = compatibilityModeEnabled; } private static AtomicReference<byte[]> sBuffer = new AtomicReference<byte[]>(); public static long readFullyIgnoringContents(InputStream in) throws IOException { byte[] buffer = sBuffer.getAndSet(null); if (buffer == null) { buffer = new byte[4096]; } int n = 0; int count = 0; while ((n = in.read(buffer, 0, buffer.length)) != -1) { count += n; } sBuffer.set(buffer); return count; } public static void closeQuietly(StrictJarFile jarFile) { if (jarFile != null) { try { jarFile.close(); } catch (Exception ignored) { } } } public static class PackageParserException extends Exception { public final int error; Loading core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java +0 −12 Original line number Diff line number Diff line Loading @@ -917,18 +917,6 @@ public class ApkSignatureSchemeV2Verifier { } } public static class SignatureNotFoundException extends Exception { private static final long serialVersionUID = 1L; public SignatureNotFoundException(String message) { super(message); } public SignatureNotFoundException(String message, Throwable cause) { super(message, cause); } } /** * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is feeded. */ Loading core/java/android/util/apk/ApkSignatureVerifier.java 0 → 100644 +263 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.util.apk; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.Signature; import android.os.Trace; import android.util.jar.StrictJarFile; import com.android.internal.util.ArrayUtils; import libcore.io.IoUtils; import java.io.IOException; import java.io.InputStream; import java.security.GeneralSecurityException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import java.util.zip.ZipEntry; /** * Facade class that takes care of the details of APK verification on * behalf of PackageParser. * * @hide for internal use only. */ public class ApkSignatureVerifier { public static final int VERSION_JAR_SIGNATURE_SCHEME = 1; public static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2; 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. * * @throws PackageParserException if the APK's signature failed to verify. * @throws SignatureNotFoundException if a signature corresponding to minLevel or greater * is not found, except in the case of no JAR signature. */ public static Result verify(String apkPath, int minSignatureSchemeVersion, boolean systemDir) throws PackageParserException, SignatureNotFoundException { boolean verified = false; Certificate[][] signerCerts; int level = VERSION_APK_SIGNATURE_SCHEME_V2; // first try v2 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2"); try { 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); } catch (SignatureNotFoundException e) { // not signed with v2, try older if allowed if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V2) { throw new SignatureNotFoundException( "No APK Signature Scheme v2 signature found for " + 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, "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, systemDir); } private static Result verifyV1Signature(String apkPath, boolean systemDir) throws PackageParserException { StrictJarFile jarFile = null; try { final Certificate[][] lastCerts; final Signature[] lastSigs; Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor"); jarFile = new StrictJarFile(apkPath, true, !systemDir); final List<ZipEntry> toVerify = new ArrayList<>(); // Always verify manifest, regardless of source 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"); } lastCerts = loadCertificates(jarFile, manifestEntry); if (ArrayUtils.isEmpty(lastCerts)) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Package " + apkPath + " has no certificates at entry " + PackageParser.ANDROID_MANIFEST_FILENAME); } lastSigs = convertToSignatures(lastCerts); // don't waste time on already-trusted packages if (!systemDir) { final Iterator<ZipEntry> i = jarFile.iterator(); while (i.hasNext()) { final ZipEntry entry = i.next(); if (entry.isDirectory()) continue; final String entryName = entry.getName(); if (entryName.startsWith("META-INF/")) continue; if (entryName.equals(PackageParser.ANDROID_MANIFEST_FILENAME)) continue; 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)) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Package " + apkPath + " has no certificates at entry " + entry.getName()); } // make sure all entries use the same signing certs final Signature[] entrySigs = convertToSignatures(entryCerts); if (!Signature.areExactMatch(lastSigs, entrySigs)) { throw new PackageParserException( INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, "Package " + apkPath + " has mismatched certificates at entry " + entry.getName()); } } } return new Result(lastCerts, lastSigs); } catch (GeneralSecurityException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING, "Failed to collect certificates from " + apkPath, e); } catch (IOException | RuntimeException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Failed to collect certificates from " + apkPath, e); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); closeQuietly(jarFile); } } private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry entry) throws PackageParserException { InputStream is = null; try { // We must read the stream for the JarEntry to retrieve // its certificates. is = jarFile.getInputStream(entry); readFullyIgnoringContents(is); return jarFile.getCertificateChains(entry); } catch (IOException | RuntimeException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, "Failed reading " + entry.getName() + " in " + jarFile, e); } finally { IoUtils.closeQuietly(is); } } private static void readFullyIgnoringContents(InputStream in) throws IOException { byte[] buffer = sBuffer.getAndSet(null); if (buffer == null) { buffer = new byte[4096]; } int n = 0; int count = 0; while ((n = in.read(buffer, 0, buffer.length)) != -1) { count += n; } sBuffer.set(buffer); return; } /** * Converts an array of certificate chains into the {@code Signature} equivalent used by the * PackageManager. * * @throws CertificateEncodingException if it is unable to create a Signature object. */ public static Signature[] convertToSignatures(Certificate[][] certs) throws CertificateEncodingException { final Signature[] res = new Signature[certs.length]; for (int i = 0; i < certs.length; i++) { res[i] = new Signature(certs[i]); } return res; } private static void closeQuietly(StrictJarFile jarFile) { if (jarFile != null) { try { jarFile.close(); } catch (Exception ignored) { } } } /** * Result of a successful APK verification operation. */ public static class Result { public final Certificate[][] certs; public final Signature[] sigs; public Result(Certificate[][] certs, Signature[] sigs) { this.certs = certs; this.sigs = sigs; } } } core/java/android/util/apk/SignatureNotFoundException.java 0 → 100644 +34 −0 File added.Preview size limit exceeded, changes collapsed. Show changes Loading
core/java/android/content/pm/PackageParser.java +34 −193 Original line number Diff line number Diff line Loading @@ -32,7 +32,6 @@ import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_ import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; Loading Loading @@ -87,7 +86,8 @@ import android.util.Slog; import android.util.SparseArray; import android.util.TypedValue; import android.util.apk.ApkSignatureSchemeV2Verifier; import android.util.jar.StrictJarFile; import android.util.apk.ApkSignatureVerifier; import android.util.apk.SignatureNotFoundException; import android.view.Gravity; import com.android.internal.R; Loading @@ -106,12 +106,10 @@ import java.io.File; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Constructor; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; Loading @@ -129,8 +127,6 @@ import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.zip.ZipEntry; /** * Parser for package files (APKs) on disk. This supports apps packaged either Loading Loading @@ -173,7 +169,7 @@ public class PackageParser { // TODO: refactor "codePath" to "apkPath" /** File name in an APK for the Android manifest. */ private static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml"; public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml"; /** Path prefix for apps on expanded storage */ private static final String MNT_EXPAND = "/mnt/expand/"; Loading Loading @@ -821,23 +817,6 @@ public class PackageParser { return pi; } private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry entry) throws PackageParserException { InputStream is = null; try { // We must read the stream for the JarEntry to retrieve // its certificates. is = jarFile.getInputStream(entry); readFullyIgnoringContents(is); return jarFile.getCertificateChains(entry); } catch (IOException | RuntimeException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, "Failed reading " + entry.getName() + " in " + jarFile, e); } finally { IoUtils.closeQuietly(is); } } public static final int PARSE_MUST_BE_APK = 1 << 0; public static final int PARSE_IGNORE_PROCESSES = 1 << 1; public static final int PARSE_FORWARD_LOCK = 1 << 2; Loading Loading @@ -1517,7 +1496,7 @@ public class PackageParser { pkg.mCertificates = certificates; try { pkg.mSignatures = convertToSignatures(certificates); pkg.mSignatures = ApkSignatureVerifier.convertToSignatures(certificates); } catch (CertificateEncodingException e) { // certificates weren't encoded properly; something went wrong throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, Loading Loading @@ -1580,155 +1559,44 @@ public class PackageParser { throws PackageParserException { final String apkPath = apkFile.getAbsolutePath(); // Try to verify the APK using APK Signature Scheme v2. boolean verified = false; { Certificate[][] allSignersCerts = null; Signature[] signatures = null; try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2"); allSignersCerts = ApkSignatureSchemeV2Verifier.verify(apkPath); signatures = convertToSignatures(allSignersCerts); // APK verified using APK Signature Scheme v2. verified = true; } catch (ApkSignatureSchemeV2Verifier.SignatureNotFoundException e) { // No APK Signature Scheme v2 signature found if ((parseFlags & PARSE_IS_EPHEMERAL) != 0) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "No APK Signature Scheme v2 signature in ephemeral package " + apkPath, e); boolean untrusted = (parseFlags & PARSE_IS_SYSTEM_DIR) == 0; int minSignatureScheme = ApkSignatureVerifier.VERSION_JAR_SIGNATURE_SCHEME; if ((parseFlags & PARSE_IS_EPHEMERAL) != 0 || pkg.applicationInfo.isStaticSharedLibrary()) { // must use v2 signing scheme minSignatureScheme = ApkSignatureVerifier.VERSION_APK_SIGNATURE_SCHEME_V2; } // Static shared libraries must use only the V2 signing scheme if (pkg.applicationInfo.isStaticSharedLibrary()) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Static shared libs must use v2 signature scheme " + apkPath); } } catch (Exception e) { // APK Signature Scheme v2 signature was 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); } if (verified) { try { ApkSignatureVerifier.Result verified = ApkSignatureVerifier.verify(apkPath, minSignatureScheme, untrusted); if (pkg.mCertificates == null) { pkg.mCertificates = allSignersCerts; pkg.mSignatures = signatures; pkg.mSigningKeys = new ArraySet<>(allSignersCerts.length); for (int i = 0; i < allSignersCerts.length; i++) { Certificate[] signerCerts = allSignersCerts[i]; pkg.mCertificates = verified.certs; pkg.mSignatures = verified.sigs; pkg.mSigningKeys = new ArraySet<>(verified.certs.length); for (int i = 0; i < verified.certs.length; i++) { Certificate[] signerCerts = verified.certs[i]; Certificate signerCert = signerCerts[0]; pkg.mSigningKeys.add(signerCert.getPublicKey()); } } else { if (!Signature.areExactMatch(pkg.mSignatures, signatures)) { if (!Signature.areExactMatch(pkg.mSignatures, verified.sigs)) { throw new PackageParserException( INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, apkPath + " has mismatched certificates"); } } // Not yet done, because we need to confirm that AndroidManifest.xml exists and, // if requested, that classes.dex exists. } } StrictJarFile jarFile = null; try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor"); // Ignore signature stripping protections when verifying APKs from system partition. // For those APKs we only care about extracting signer certificates, and don't care // about verifying integrity. boolean signatureSchemeRollbackProtectionsEnforced = (parseFlags & PARSE_IS_SYSTEM_DIR) == 0; jarFile = new StrictJarFile( apkPath, !verified, // whether to verify JAR signature signatureSchemeRollbackProtectionsEnforced); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); // Always verify manifest, regardless of source final ZipEntry manifestEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME); if (manifestEntry == null) { throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, "Package " + apkPath + " has no manifest"); } // Optimization: early termination when APK already verified if (verified) { return; } // APK's integrity needs to be verified using JAR signature scheme. Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV1"); final List<ZipEntry> toVerify = new ArrayList<>(); toVerify.add(manifestEntry); // If we're parsing an untrusted package, verify all contents if ((parseFlags & PARSE_IS_SYSTEM_DIR) == 0) { final Iterator<ZipEntry> i = jarFile.iterator(); while (i.hasNext()) { final ZipEntry entry = i.next(); if (entry.isDirectory()) continue; final String entryName = entry.getName(); if (entryName.startsWith("META-INF/")) continue; if (entryName.equals(ANDROID_MANIFEST_FILENAME)) continue; 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)) { } catch (SignatureNotFoundException e) { if ((parseFlags & PARSE_IS_EPHEMERAL) != 0) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Package " + apkPath + " has no certificates at entry " + entry.getName()); } final Signature[] entrySignatures = convertToSignatures(entryCerts); if (pkg.mCertificates == null) { pkg.mCertificates = entryCerts; pkg.mSignatures = entrySignatures; pkg.mSigningKeys = new ArraySet<PublicKey>(); for (int i=0; i < entryCerts.length; i++) { pkg.mSigningKeys.add(entryCerts[i][0].getPublicKey()); } } else { if (!Signature.areExactMatch(pkg.mSignatures, entrySignatures)) { throw new PackageParserException( INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, "Package " + apkPath + " has mismatched certificates at entry " + entry.getName()); } } "No APK Signature Scheme v2 signature in ephemeral package " + apkPath, e); } Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } catch (GeneralSecurityException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING, "Failed to collect certificates from " + apkPath, e); } catch (IOException | RuntimeException e) { if (pkg.applicationInfo.isStaticSharedLibrary()) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Failed to collect certificates from " + apkPath, e); } finally { closeQuietly(jarFile); } "Static shared libs must use v2 signature scheme " + apkPath); } private static Signature[] convertToSignatures(Certificate[][] certs) throws CertificateEncodingException { final Signature[] res = new Signature[certs.length]; for (int i = 0; i < certs.length; i++) { res[i] = new Signature(certs[i]); throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "No APK Signature Scheme v2 signature in package " + apkPath, e); } return res; } private static AssetManager newConfiguredAssetManager() { Loading Loading @@ -7647,33 +7515,6 @@ public class PackageParser { sCompatibilityModeEnabled = compatibilityModeEnabled; } private static AtomicReference<byte[]> sBuffer = new AtomicReference<byte[]>(); public static long readFullyIgnoringContents(InputStream in) throws IOException { byte[] buffer = sBuffer.getAndSet(null); if (buffer == null) { buffer = new byte[4096]; } int n = 0; int count = 0; while ((n = in.read(buffer, 0, buffer.length)) != -1) { count += n; } sBuffer.set(buffer); return count; } public static void closeQuietly(StrictJarFile jarFile) { if (jarFile != null) { try { jarFile.close(); } catch (Exception ignored) { } } } public static class PackageParserException extends Exception { public final int error; Loading
core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java +0 −12 Original line number Diff line number Diff line Loading @@ -917,18 +917,6 @@ public class ApkSignatureSchemeV2Verifier { } } public static class SignatureNotFoundException extends Exception { private static final long serialVersionUID = 1L; public SignatureNotFoundException(String message) { super(message); } public SignatureNotFoundException(String message, Throwable cause) { super(message, cause); } } /** * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is feeded. */ Loading
core/java/android/util/apk/ApkSignatureVerifier.java 0 → 100644 +263 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.util.apk; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.Signature; import android.os.Trace; import android.util.jar.StrictJarFile; import com.android.internal.util.ArrayUtils; import libcore.io.IoUtils; import java.io.IOException; import java.io.InputStream; import java.security.GeneralSecurityException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import java.util.zip.ZipEntry; /** * Facade class that takes care of the details of APK verification on * behalf of PackageParser. * * @hide for internal use only. */ public class ApkSignatureVerifier { public static final int VERSION_JAR_SIGNATURE_SCHEME = 1; public static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2; 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. * * @throws PackageParserException if the APK's signature failed to verify. * @throws SignatureNotFoundException if a signature corresponding to minLevel or greater * is not found, except in the case of no JAR signature. */ public static Result verify(String apkPath, int minSignatureSchemeVersion, boolean systemDir) throws PackageParserException, SignatureNotFoundException { boolean verified = false; Certificate[][] signerCerts; int level = VERSION_APK_SIGNATURE_SCHEME_V2; // first try v2 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2"); try { 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); } catch (SignatureNotFoundException e) { // not signed with v2, try older if allowed if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V2) { throw new SignatureNotFoundException( "No APK Signature Scheme v2 signature found for " + 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, "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, systemDir); } private static Result verifyV1Signature(String apkPath, boolean systemDir) throws PackageParserException { StrictJarFile jarFile = null; try { final Certificate[][] lastCerts; final Signature[] lastSigs; Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor"); jarFile = new StrictJarFile(apkPath, true, !systemDir); final List<ZipEntry> toVerify = new ArrayList<>(); // Always verify manifest, regardless of source 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"); } lastCerts = loadCertificates(jarFile, manifestEntry); if (ArrayUtils.isEmpty(lastCerts)) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Package " + apkPath + " has no certificates at entry " + PackageParser.ANDROID_MANIFEST_FILENAME); } lastSigs = convertToSignatures(lastCerts); // don't waste time on already-trusted packages if (!systemDir) { final Iterator<ZipEntry> i = jarFile.iterator(); while (i.hasNext()) { final ZipEntry entry = i.next(); if (entry.isDirectory()) continue; final String entryName = entry.getName(); if (entryName.startsWith("META-INF/")) continue; if (entryName.equals(PackageParser.ANDROID_MANIFEST_FILENAME)) continue; 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)) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Package " + apkPath + " has no certificates at entry " + entry.getName()); } // make sure all entries use the same signing certs final Signature[] entrySigs = convertToSignatures(entryCerts); if (!Signature.areExactMatch(lastSigs, entrySigs)) { throw new PackageParserException( INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, "Package " + apkPath + " has mismatched certificates at entry " + entry.getName()); } } } return new Result(lastCerts, lastSigs); } catch (GeneralSecurityException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING, "Failed to collect certificates from " + apkPath, e); } catch (IOException | RuntimeException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Failed to collect certificates from " + apkPath, e); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); closeQuietly(jarFile); } } private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry entry) throws PackageParserException { InputStream is = null; try { // We must read the stream for the JarEntry to retrieve // its certificates. is = jarFile.getInputStream(entry); readFullyIgnoringContents(is); return jarFile.getCertificateChains(entry); } catch (IOException | RuntimeException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, "Failed reading " + entry.getName() + " in " + jarFile, e); } finally { IoUtils.closeQuietly(is); } } private static void readFullyIgnoringContents(InputStream in) throws IOException { byte[] buffer = sBuffer.getAndSet(null); if (buffer == null) { buffer = new byte[4096]; } int n = 0; int count = 0; while ((n = in.read(buffer, 0, buffer.length)) != -1) { count += n; } sBuffer.set(buffer); return; } /** * Converts an array of certificate chains into the {@code Signature} equivalent used by the * PackageManager. * * @throws CertificateEncodingException if it is unable to create a Signature object. */ public static Signature[] convertToSignatures(Certificate[][] certs) throws CertificateEncodingException { final Signature[] res = new Signature[certs.length]; for (int i = 0; i < certs.length; i++) { res[i] = new Signature(certs[i]); } return res; } private static void closeQuietly(StrictJarFile jarFile) { if (jarFile != null) { try { jarFile.close(); } catch (Exception ignored) { } } } /** * Result of a successful APK verification operation. */ public static class Result { public final Certificate[][] certs; public final Signature[] sigs; public Result(Certificate[][] certs, Signature[] sigs) { this.certs = certs; this.sigs = sigs; } } }
core/java/android/util/apk/SignatureNotFoundException.java 0 → 100644 +34 −0 File added.Preview size limit exceeded, changes collapsed. Show changes