From e92f8428ddb88574b84c9bc9137a3c7984285566 Mon Sep 17 00:00:00 2001 From: Dan Cashman Date: Fri, 8 Dec 2017 14:02:51 -0800 Subject: [PATCH] Refactor PackageParser.collectCerts() to hide signature scheme. PackageParser shoudln't really need to know the gory details of APK verification, it should just get back the blobs it needs to do its job. Move the package verification into its own class which is *almost* exclusively responsible for verifying app signatures. This is in preparation for adding APK signature scheme v3, which will add yet another way to do this. Bug: 64686581 Test: Builds 'n' boots without issue. Test: android.appsecurity.cts.PkgInstallSignatureVerificationTest passes. Change-Id: Ieb76b2353bd44ffdb83e7b894e5ad720d1697dc7 --- .../android/content/pm/PackageParser.java | 231 +++------------ .../apk/ApkSignatureSchemeV2Verifier.java | 12 - .../util/apk/ApkSignatureVerifier.java | 263 ++++++++++++++++++ .../util/apk/SignatureNotFoundException.java | 34 +++ 4 files changed, 333 insertions(+), 207 deletions(-) create mode 100644 core/java/android/util/apk/ApkSignatureVerifier.java create mode 100644 core/java/android/util/apk/SignatureNotFoundException.java diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 97c2b7d11354a..142bb39fe8926 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -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; @@ -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; @@ -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; @@ -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 @@ -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/"; @@ -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; @@ -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, @@ -1580,157 +1559,46 @@ 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, + 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; + } + try { + ApkSignatureVerifier.Result verified = + ApkSignatureVerifier.verify(apkPath, minSignatureScheme, untrusted); + if (pkg.mCertificates == null) { + 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, verified.sigs)) { + throw new PackageParserException( + INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, + apkPath + " has mismatched certificates"); + } + } + } catch (SignatureNotFoundException e) { + 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); - } - // 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 + } + if (pkg.applicationInfo.isStaticSharedLibrary()) { 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); + "Static shared libs must use v2 signature scheme " + apkPath); } - - if (verified) { - 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]; - Certificate signerCert = signerCerts[0]; - pkg.mSigningKeys.add(signerCert.getPublicKey()); - } - } else { - if (!Signature.areExactMatch(pkg.mSignatures, signatures)) { - 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 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 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)) { - 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(); - 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()); - } - } - } - 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) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, - "Failed to collect certificates from " + apkPath, e); - } finally { - closeQuietly(jarFile); + "No APK Signature Scheme v2 signature in package " + apkPath, e); } } - 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]); - } - return res; - } - private static AssetManager newConfiguredAssetManager() { AssetManager assetManager = new AssetManager(); assetManager.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -7647,33 +7515,6 @@ public class PackageParser { sCompatibilityModeEnabled = compatibilityModeEnabled; } - private static AtomicReference sBuffer = new AtomicReference(); - - 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; diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java index 180812340ba81..a74a8821eba61 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java @@ -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. */ diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java new file mode 100644 index 0000000000000..73a9478509e69 --- /dev/null +++ b/core/java/android/util/apk/ApkSignatureVerifier.java @@ -0,0 +1,263 @@ +/* + * 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 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 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 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; + } + } +} diff --git a/core/java/android/util/apk/SignatureNotFoundException.java b/core/java/android/util/apk/SignatureNotFoundException.java new file mode 100644 index 0000000000000..9c7c7600cf732 --- /dev/null +++ b/core/java/android/util/apk/SignatureNotFoundException.java @@ -0,0 +1,34 @@ +/* + * 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; + +/** + * Indicates that the APK is missing a signature. + * + * @hide + */ +public 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); + } +}