Support 4k chunk based signature algorithms

This change makes APK signature verifier accept the 4k-based signature
algorithms.

Test: build, install apk with such algorithm by apksig
Bug: 30972906

Change-Id: I90f32a6779f258605668e44f0d66f53e6890cfa7
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index 530937e..ce8998f 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -23,6 +23,9 @@
 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512;
 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA256;
 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_DSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_ECDSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256;
 import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
 import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
 import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
@@ -35,7 +38,6 @@
 import android.util.Pair;
 
 import java.io.ByteArrayInputStream;
-import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.RandomAccessFile;
 import java.nio.BufferUnderflowException;
@@ -139,7 +141,7 @@
     private static X509Certificate[][] verify(RandomAccessFile apk, boolean verifyIntegrity)
             throws SignatureNotFoundException, SecurityException, IOException {
         SignatureInfo signatureInfo = findSignature(apk);
-        return verify(apk.getFD(), signatureInfo, verifyIntegrity);
+        return verify(apk, signatureInfo, verifyIntegrity);
     }
 
     /**
@@ -162,9 +164,9 @@
      *        against the APK file.
      */
     private static X509Certificate[][] verify(
-            FileDescriptor apkFileDescriptor,
+            RandomAccessFile apk,
             SignatureInfo signatureInfo,
-            boolean doVerifyIntegrity) throws SecurityException {
+            boolean doVerifyIntegrity) throws SecurityException, IOException {
         int signerCount = 0;
         Map<Integer, byte[]> contentDigests = new ArrayMap<>();
         List<X509Certificate[]> signerCerts = new ArrayList<>();
@@ -202,13 +204,7 @@
         }
 
         if (doVerifyIntegrity) {
-            ApkSigningBlockUtils.verifyIntegrity(
-                    contentDigests,
-                    apkFileDescriptor,
-                    signatureInfo.apkSigningBlockOffset,
-                    signatureInfo.centralDirOffset,
-                    signatureInfo.eocdOffset,
-                    signatureInfo.eocd);
+            ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo);
         }
 
         return signerCerts.toArray(new X509Certificate[signerCerts.size()][]);
@@ -386,6 +382,7 @@
         }
         return;
     }
+
     private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
         switch (sigAlgorithm) {
             case SIGNATURE_RSA_PSS_WITH_SHA256:
@@ -395,6 +392,9 @@
             case SIGNATURE_ECDSA_WITH_SHA256:
             case SIGNATURE_ECDSA_WITH_SHA512:
             case SIGNATURE_DSA_WITH_SHA256:
+            case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
+            case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
+            case SIGNATURE_VERITY_DSA_WITH_SHA256:
                 return true;
             default:
                 return false;
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
index e43dee3..c9e67fe 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
@@ -23,6 +23,9 @@
 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512;
 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA256;
 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_DSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_ECDSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256;
 import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
 import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
 import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
@@ -36,7 +39,6 @@
 import android.util.Pair;
 
 import java.io.ByteArrayInputStream;
-import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.RandomAccessFile;
 import java.nio.BufferUnderflowException;
@@ -136,7 +138,7 @@
     private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity)
             throws SignatureNotFoundException, SecurityException, IOException {
         SignatureInfo signatureInfo = findSignature(apk);
-        return verify(apk.getFD(), signatureInfo, verifyIntegrity);
+        return verify(apk, signatureInfo, verifyIntegrity);
     }
 
     /**
@@ -159,7 +161,7 @@
      *        against the APK file.
      */
     private static VerifiedSigner verify(
-            FileDescriptor apkFileDescriptor,
+            RandomAccessFile apk,
             SignatureInfo signatureInfo,
             boolean doVerifyIntegrity) throws SecurityException {
         int signerCount = 0;
@@ -206,13 +208,7 @@
         }
 
         if (doVerifyIntegrity) {
-            ApkSigningBlockUtils.verifyIntegrity(
-                    contentDigests,
-                    apkFileDescriptor,
-                    signatureInfo.apkSigningBlockOffset,
-                    signatureInfo.centralDirOffset,
-                    signatureInfo.eocdOffset,
-                    signatureInfo.eocd);
+            ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo);
         }
 
         return result;
@@ -512,6 +508,9 @@
             case SIGNATURE_ECDSA_WITH_SHA256:
             case SIGNATURE_ECDSA_WITH_SHA512:
             case SIGNATURE_DSA_WITH_SHA256:
+            case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
+            case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
+            case SIGNATURE_VERITY_DSA_WITH_SHA256:
                 return true;
             default:
                 return false;
diff --git a/core/java/android/util/apk/ApkSigningBlockUtils.java b/core/java/android/util/apk/ApkSigningBlockUtils.java
index 9279510..9d53847 100644
--- a/core/java/android/util/apk/ApkSigningBlockUtils.java
+++ b/core/java/android/util/apk/ApkSigningBlockUtils.java
@@ -16,6 +16,7 @@
 
 package android.util.apk;
 
+import android.util.ArrayMap;
 import android.util.Pair;
 
 import java.io.FileDescriptor;
@@ -30,6 +31,7 @@
 import java.security.spec.AlgorithmParameterSpec;
 import java.security.spec.MGF1ParameterSpec;
 import java.security.spec.PSSParameterSpec;
+import java.util.Arrays;
 import java.util.Map;
 
 /**
@@ -84,16 +86,41 @@
 
     static void verifyIntegrity(
             Map<Integer, byte[]> expectedDigests,
-            FileDescriptor apkFileDescriptor,
-            long apkSigningBlockOffset,
-            long centralDirOffset,
-            long eocdOffset,
-            ByteBuffer eocdBuf) throws SecurityException {
-
+            RandomAccessFile apk,
+            SignatureInfo signatureInfo) throws SecurityException {
         if (expectedDigests.isEmpty()) {
             throw new SecurityException("No digests provided");
         }
 
+        Map<Integer, byte[]> expected1MbChunkDigests = new ArrayMap<>();
+        if (expectedDigests.containsKey(CONTENT_DIGEST_CHUNKED_SHA256)) {
+            expected1MbChunkDigests.put(CONTENT_DIGEST_CHUNKED_SHA256,
+                    expectedDigests.get(CONTENT_DIGEST_CHUNKED_SHA256));
+        }
+        if (expectedDigests.containsKey(CONTENT_DIGEST_CHUNKED_SHA512)) {
+            expected1MbChunkDigests.put(CONTENT_DIGEST_CHUNKED_SHA512,
+                    expectedDigests.get(CONTENT_DIGEST_CHUNKED_SHA512));
+        }
+
+        if (expectedDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) {
+            verifyIntegrityForVerityBasedAlgorithm(
+                    expectedDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256), apk, signatureInfo);
+        } else if (!expected1MbChunkDigests.isEmpty()) {
+            try {
+                verifyIntegrityFor1MbChunkBasedAlgorithm(expected1MbChunkDigests, apk.getFD(),
+                        signatureInfo);
+            } catch (IOException e) {
+                throw new SecurityException("Cannot get FD", e);
+            }
+        } else {
+            throw new SecurityException("No known digest exists for integrity check");
+        }
+    }
+
+    private static void verifyIntegrityFor1MbChunkBasedAlgorithm(
+            Map<Integer, byte[]> expectedDigests,
+            FileDescriptor apkFileDescriptor,
+            SignatureInfo signatureInfo) throws SecurityException {
         // We need to verify the integrity of the following three sections of the file:
         // 1. Everything up to the start of the APK Signing Block.
         // 2. ZIP Central Directory.
@@ -105,16 +132,18 @@
         // APK are already there in the OS's page cache and thus mmap does not use additional
         // physical memory.
         DataSource beforeApkSigningBlock =
-                new MemoryMappedFileDataSource(apkFileDescriptor, 0, apkSigningBlockOffset);
+                new MemoryMappedFileDataSource(apkFileDescriptor, 0,
+                        signatureInfo.apkSigningBlockOffset);
         DataSource centralDir =
                 new MemoryMappedFileDataSource(
-                        apkFileDescriptor, centralDirOffset, eocdOffset - centralDirOffset);
+                        apkFileDescriptor, signatureInfo.centralDirOffset,
+                        signatureInfo.eocdOffset - signatureInfo.centralDirOffset);
 
         // For the purposes of integrity verification, ZIP End of Central Directory's field Start of
         // Central Directory must be considered to point to the offset of the APK Signing Block.
-        eocdBuf = eocdBuf.duplicate();
+        ByteBuffer eocdBuf = signatureInfo.eocd.duplicate();
         eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
-        ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, apkSigningBlockOffset);
+        ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, signatureInfo.apkSigningBlockOffset);
         DataSource eocd = new ByteBufferDataSource(eocdBuf);
 
         int[] digestAlgorithms = new int[expectedDigests.size()];
@@ -126,7 +155,7 @@
         byte[][] actualDigests;
         try {
             actualDigests =
-                    computeContentDigests(
+                    computeContentDigestsPer1MbChunk(
                             digestAlgorithms,
                             new DataSource[] {beforeApkSigningBlock, centralDir, eocd});
         } catch (DigestException e) {
@@ -144,7 +173,7 @@
         }
     }
 
-    private static byte[][] computeContentDigests(
+    private static byte[][] computeContentDigestsPer1MbChunk(
             int[] digestAlgorithms,
             DataSource[] contents) throws DigestException {
         // For each digest algorithm the result is computed as follows:
@@ -256,6 +285,26 @@
         return result;
     }
 
+    private static void verifyIntegrityForVerityBasedAlgorithm(
+            byte[] expectedRootHash,
+            RandomAccessFile apk,
+            SignatureInfo signatureInfo) throws SecurityException {
+        try {
+            ApkVerityBuilder.ApkVerityResult verity = ApkVerityBuilder.generateApkVerity(apk,
+                    signatureInfo, new ByteBufferFactory() {
+                        @Override
+                        public ByteBuffer create(int capacity) {
+                            return ByteBuffer.allocate(capacity);
+                        }
+                    });
+            if (!Arrays.equals(expectedRootHash, verity.rootHash)) {
+                throw new SecurityException("APK verity digest of contents did not verify");
+            }
+        } catch (DigestException | IOException | NoSuchAlgorithmException e) {
+            throw new SecurityException("Error during verification", e);
+        }
+    }
+
     /**
      * Returns the ZIP End of Central Directory (EoCD) and its offset in the file.
      *
@@ -304,9 +353,13 @@
     static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201;
     static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202;
     static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301;
+    static final int SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0401;
+    static final int SIGNATURE_VERITY_ECDSA_WITH_SHA256 = 0x0403;
+    static final int SIGNATURE_VERITY_DSA_WITH_SHA256 = 0x0405;
 
     static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
     static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
+    static final int CONTENT_DIGEST_VERITY_CHUNKED_SHA256 = 3;
 
     static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) {
         int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1);
@@ -321,6 +374,7 @@
                     case CONTENT_DIGEST_CHUNKED_SHA256:
                         return 0;
                     case CONTENT_DIGEST_CHUNKED_SHA512:
+                    case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
                         return -1;
                     default:
                         throw new IllegalArgumentException(
@@ -329,6 +383,7 @@
             case CONTENT_DIGEST_CHUNKED_SHA512:
                 switch (digestAlgorithm2) {
                     case CONTENT_DIGEST_CHUNKED_SHA256:
+                    case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
                         return 1;
                     case CONTENT_DIGEST_CHUNKED_SHA512:
                         return 0;
@@ -336,6 +391,18 @@
                         throw new IllegalArgumentException(
                                 "Unknown digestAlgorithm2: " + digestAlgorithm2);
                 }
+            case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+                switch (digestAlgorithm2) {
+                    case CONTENT_DIGEST_CHUNKED_SHA512:
+                        return -1;
+                    case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+                        return 0;
+                    case CONTENT_DIGEST_CHUNKED_SHA256:
+                        return 1;
+                    default:
+                        throw new IllegalArgumentException(
+                                "Unknown digestAlgorithm2: " + digestAlgorithm2);
+                }
             default:
                 throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1);
         }
@@ -352,6 +419,10 @@
             case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
             case SIGNATURE_ECDSA_WITH_SHA512:
                 return CONTENT_DIGEST_CHUNKED_SHA512;
+            case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
+            case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
+            case SIGNATURE_VERITY_DSA_WITH_SHA256:
+                return CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
             default:
                 throw new IllegalArgumentException(
                         "Unknown signature algorithm: 0x"
@@ -362,6 +433,7 @@
     static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) {
         switch (digestAlgorithm) {
             case CONTENT_DIGEST_CHUNKED_SHA256:
+            case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
                 return "SHA-256";
             case CONTENT_DIGEST_CHUNKED_SHA512:
                 return "SHA-512";
@@ -374,6 +446,7 @@
     private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) {
         switch (digestAlgorithm) {
             case CONTENT_DIGEST_CHUNKED_SHA256:
+            case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
                 return 256 / 8;
             case CONTENT_DIGEST_CHUNKED_SHA512:
                 return 512 / 8;
@@ -389,11 +462,14 @@
             case SIGNATURE_RSA_PSS_WITH_SHA512:
             case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
             case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+            case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
                 return "RSA";
             case SIGNATURE_ECDSA_WITH_SHA256:
             case SIGNATURE_ECDSA_WITH_SHA512:
+            case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
                 return "EC";
             case SIGNATURE_DSA_WITH_SHA256:
+            case SIGNATURE_VERITY_DSA_WITH_SHA256:
                 return "DSA";
             default:
                 throw new IllegalArgumentException(
@@ -416,14 +492,17 @@
                         new PSSParameterSpec(
                                 "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1));
             case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+            case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
                 return Pair.create("SHA256withRSA", null);
             case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
                 return Pair.create("SHA512withRSA", null);
             case SIGNATURE_ECDSA_WITH_SHA256:
+            case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
                 return Pair.create("SHA256withECDSA", null);
             case SIGNATURE_ECDSA_WITH_SHA512:
                 return Pair.create("SHA512withECDSA", null);
             case SIGNATURE_DSA_WITH_SHA256:
+            case SIGNATURE_VERITY_DSA_WITH_SHA256:
                 return Pair.create("SHA256withDSA", null);
             default:
                 throw new IllegalArgumentException(