Merge "v4 signing schema parsing and verification."
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index ef3b0c8..637e64d 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -61,7 +61,6 @@
import android.content.pm.parsing.AndroidPackage;
import android.content.pm.parsing.ApkParseUtils;
import android.content.pm.parsing.PackageImpl;
-import android.content.pm.parsing.PackageInfoUtils;
import android.content.pm.parsing.ParsedPackage;
import android.content.pm.permission.SplitPermissionInfoParcelable;
import android.content.pm.split.DefaultSplitAssetLoader;
@@ -5967,12 +5966,14 @@
@IntDef({SigningDetails.SignatureSchemeVersion.UNKNOWN,
SigningDetails.SignatureSchemeVersion.JAR,
SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2,
- SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3})
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4})
public @interface SignatureSchemeVersion {
int UNKNOWN = 0;
int JAR = 1;
int SIGNING_BLOCK_V2 = 2;
int SIGNING_BLOCK_V3 = 3;
+ int SIGNING_BLOCK_V4 = 4;
}
@Nullable
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java
new file mode 100644
index 0000000..b6b8089
--- /dev/null
+++ b/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2020 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 android.os.incremental.IncrementalManager;
+
+import java.io.File;
+import java.security.cert.Certificate;
+
+import sun.security.pkcs.PKCS7;
+import sun.security.pkcs.ParsingException;
+
+/**
+ * APK Signature Scheme v4 verifier.
+ *
+ * @hide for internal use only.
+ */
+public class ApkSignatureSchemeV4Verifier {
+
+ /**
+ * Extracts APK Signature Scheme v4 signatures of the provided APK and returns the certificates
+ * associated with each signer.
+ */
+ public static Certificate[] extractCertificates(String apkFile)
+ throws SignatureNotFoundException, SecurityException {
+ final byte[] rawSignature = IncrementalManager.unsafeGetFileSignature(
+ new File(apkFile).getAbsolutePath());
+ if (rawSignature == null || rawSignature.length == 0) {
+ throw new SignatureNotFoundException("Failed to obtain raw signature from IncFS.");
+ }
+
+ try {
+ PKCS7 pkcs7 = new PKCS7(rawSignature);
+ return pkcs7.getCertificates();
+ } catch (ParsingException e) {
+ throw new SecurityException("Failed to parse signature and extract certificates", e);
+ }
+ }
+}
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index 1d3e6d0..f325c21 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -92,6 +92,24 @@
@SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
throws PackageParserException {
+ if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V4) {
+ // V3 and before are older than the requested minimum signing version
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "No signature found in package of version " + minSignatureSchemeVersion
+ + " or newer for package " + apkPath);
+ }
+
+ // first try v4
+ try {
+ return verifyV4Signature(apkPath, minSignatureSchemeVersion, verifyFull);
+ } catch (SignatureNotFoundException e) {
+ // not signed with v4, try older if allowed
+ if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V4) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "No APK Signature Scheme v4 signature in package " + apkPath, e);
+ }
+ }
+
if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V3) {
// V3 and before are older than the requested minimum signing version
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
@@ -99,7 +117,13 @@
+ " or newer for package " + apkPath);
}
- // first try v3
+ return verifyV3AndBelowSignatures(apkPath, minSignatureSchemeVersion, verifyFull);
+ }
+
+ private static PackageParser.SigningDetails verifyV3AndBelowSignatures(String apkPath,
+ @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
+ throws PackageParserException {
+ // try v3
try {
return verifyV3Signature(apkPath, verifyFull);
} catch (SignatureNotFoundException e) {
@@ -142,6 +166,59 @@
}
/**
+ * Verifies the provided APK using V4 schema.
+ *
+ * @param verifyFull whether to verify all contents of this APK or just collect certificates.
+ * @return the certificates associated with each signer.
+ * @throws SignatureNotFoundException if there are no V4 signatures in the APK
+ * @throws PackageParserException if there was a problem collecting certificates
+ */
+ private static PackageParser.SigningDetails verifyV4Signature(String apkPath,
+ @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
+ throws SignatureNotFoundException, PackageParserException {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV4" : "certsOnlyV4");
+ try {
+ Certificate[] certs = ApkSignatureSchemeV4Verifier.extractCertificates(apkPath);
+ Certificate[][] signerCerts = new Certificate[][]{certs};
+ Signature[] signerSigs = convertToSignatures(signerCerts);
+
+ if (verifyFull) {
+ // v4 is an add-on and requires v2/v3 signature to validate against its certificates
+ final PackageParser.SigningDetails nonstreaming = verifyV3AndBelowSignatures(
+ apkPath, minSignatureSchemeVersion, false);
+ if (nonstreaming.signatureSchemeVersion <= SignatureSchemeVersion.JAR) {
+ throw new SecurityException(
+ "V4 signing block can only be verified along with V2 and above.");
+ }
+ if (nonstreaming.signatures.length == 0
+ || nonstreaming.signatures.length != signerSigs.length) {
+ throw new SecurityException("Invalid number of signatures in "
+ + nonstreaming.signatureSchemeVersion);
+ }
+
+ for (int i = 0, size = signerSigs.length; i < size; ++i) {
+ if (!nonstreaming.signatures[i].equals(signerSigs[i])) {
+ throw new SecurityException("V4 signature certificate does not match "
+ + nonstreaming.signatureSchemeVersion);
+ }
+ }
+ }
+
+ return new PackageParser.SigningDetails(signerSigs,
+ SignatureSchemeVersion.SIGNING_BLOCK_V4);
+ } catch (SignatureNotFoundException e) {
+ throw e;
+ } catch (Exception e) {
+ // APK Signature Scheme v4 signature found but did not verify
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "Failed to collect certificates from " + apkPath
+ + " using APK Signature Scheme v4", e);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+ }
+
+ /**
* Verifies the provided APK using V3 schema.
*
* @param verifyFull whether to verify all contents of this APK or just collect certificates.