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.