Merge "Add option to link static lite protobuf library."
diff --git a/core/Makefile b/core/Makefile
index 79a0dc3..064af66 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -518,7 +518,7 @@
 ifndef BOARD_BVB_KEY_PATH
 # If key path isn't specified, use the 4096-bit test key.
 INTERNAL_BVBTOOL_SIGN_BOOT_IMAGE_ARGS := --algorithm SHA256_RSA4096 \
-	--key system/bvb/test/testkey_rsa4096.pem
+	--key external/bvb/test/testkey_rsa4096.pem
 else
 INTERNAL_BVBTOOL_SIGN_BOOT_IMAGE_ARGS := \
 	--algorithm $(BOARD_BVB_ALGORITHM) --key $(BOARD_BVB_KEY_PATH)
diff --git a/core/binary.mk b/core/binary.mk
index 731fc4f..0c57030 100644
--- a/core/binary.mk
+++ b/core/binary.mk
@@ -1135,6 +1135,34 @@
 endif
 
 
+####################################################
+## For NDK-built libraries, move LOCAL_SHARED_LIBRARY
+## references to my_ldlibs, so that we use the NDK
+## prebuilt library and headers for linking.
+####################################################
+ifndef LOCAL_IS_HOST_MODULE
+my_allowed_ldlibs :=
+ifdef LOCAL_SDK_VERSION
+  my_ndk_shared_libraries := $(filter $(addprefix lib,$(NDK_PREBUILT_SHARED_LIBRARIES)),$(my_shared_libraries))
+  my_shared_libraries := $(filter-out $(my_ndk_shared_libraries),$(my_shared_libraries))
+  my_ldlibs += $(patsubst lib%,-l%,$(my_ndk_shared_libraries))
+  my_ndk_shared_libraries :=
+  my_allowed_ldlibs := $(addprefix -l,$(NDK_PREBUILT_SHARED_LIBRARIES))
+endif
+
+# Sort ldlibs and ldflags between -l and other linker flags
+# We'll do this again later, since there are still changes happening, but that's fine.
+my_ldlib_flags := $(my_ldflags) $(my_ldlibs)
+my_ldlibs := $(filter -l%,$(my_ldlib_flags))
+my_ldflags := $(filter-out -l%,$(my_ldlib_flags))
+my_ldlib_flags :=
+
+# Move other ldlibs back to shared libraries
+my_shared_libraries += $(patsubst -l%,lib%,$(filter-out $(my_allowed_ldlibs),$(my_ldlibs)))
+my_ldlibs := $(filter $(my_allowed_ldlibs),$(my_ldlibs))
+endif
+
+
 ##########################################################
 ## Set up installed module dependency
 ## We cannot compute the full path of the LOCAL_SHARED_LIBRARIES for
@@ -1433,6 +1461,24 @@
   endif
 endif
 
+# Move -l* entries from ldflags to ldlibs, and everything else to ldflags
+my_ldlib_flags := $(my_ldflags) $(my_ldlibs)
+my_ldlibs := $(filter -l%,$(my_ldlib_flags))
+my_ldflags := $(filter-out -l%,$(my_ldlib_flags))
+
+# One last verification check for ldlibs
+ifndef LOCAL_IS_HOST_MODULE
+my_allowed_ldlibs :=
+ifdef LOCAL_SDK_VERSION
+  my_allowed_ldlibs := $(addprefix -l,$(NDK_PREBUILT_SHARED_LIBRARIES))
+endif
+
+my_bad_ldlibs := $(filter-out $(my_allowed_ldlibs),$(my_ldlibs))
+ifneq ($(my_bad_ldlibs),)
+  $(error $(LOCAL_MODULE_MAKEFILE): $(LOCAL_MODULE): Bad LOCAL_LDLIBS entries: $(my_bad_ldlibs))
+endif
+endif
+
 $(LOCAL_INTERMEDIATE_TARGETS): PRIVATE_YACCFLAGS := $(LOCAL_YACCFLAGS)
 $(LOCAL_INTERMEDIATE_TARGETS): PRIVATE_ASFLAGS := $(my_asflags)
 $(LOCAL_INTERMEDIATE_TARGETS): PRIVATE_CONLYFLAGS := $(my_conlyflags)
diff --git a/core/config.mk b/core/config.mk
index 727a5a8..ba9f0cd 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -571,8 +571,6 @@
 JACK_DEFAULT_ARGS := $(BUILD_SYSTEM)/jack-default.args
 DEFAULT_JACK_EXTRA_ARGS := @$(JACK_DEFAULT_ARGS)
 endif
-# Turn off jack warnings by default.
-DEFAULT_JACK_EXTRA_ARGS += --verbose error
 
 PROGUARD := external/proguard/bin/proguard.sh
 JAVATAGS := build/tools/java-event-log-tags.py
@@ -781,6 +779,8 @@
 RSCOMPAT_32BIT_ONLY_API_LEVELS := 8 9 10 11 12 13 14 15 16 17 18 19 20
 RSCOMPAT_NO_USAGEIO_API_LEVELS := 8 9 10 11 12 13
 
+NDK_PREBUILT_SHARED_LIBRARIES := android c dl EGL GLESv1_CM GLESv2 GLESv3 jnigraphics log mediandk m OpenMAXAL OpenSLES stdc++ vulkan z
+
 ifeq ($(JAVA_NOT_REQUIRED),true)
 # Remove java and tools from our path so that we make sure nobody uses them.
 unexport ANDROID_JAVA_HOME
diff --git a/core/jack-default.args b/core/jack-default.args
index 8d70a82..bcc3235 100644
--- a/core/jack-default.args
+++ b/core/jack-default.args
@@ -2,4 +2,5 @@
 -D sched.runner.thread.kind=fixed
 -D sched.runner.thread.fixed.count=4
 --sanity-checks off
--D jack.reporter.level.file=error=--,warning=-
\ No newline at end of file
+-D jack.reporter.level.file=error=--,warning=-
+--verbose error
\ No newline at end of file
diff --git a/core/product.mk b/core/product.mk
index 7043cff..4f780f9 100644
--- a/core/product.mk
+++ b/core/product.mk
@@ -120,7 +120,7 @@
     PRODUCT_DEX_PREOPT_MODULE_CONFIGS \
     PRODUCT_DEX_PREOPT_DEFAULT_FLAGS \
     PRODUCT_DEX_PREOPT_BOOT_FLAGS \
-
+    PRODUCT_SHIPPING_API_LEVEL \
 
 define dump-product
 $(info ==== $(1) ====)\
diff --git a/core/product_config.mk b/core/product_config.mk
index 6aefa8d..a4063f1 100644
--- a/core/product_config.mk
+++ b/core/product_config.mk
@@ -355,6 +355,12 @@
 PRODUCT_DEFAULT_PROPERTY_OVERRIDES := \
     $(strip $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_DEFAULT_PROPERTY_OVERRIDES))
 
+PRODUCT_SHIPPING_API_LEVEL := $(strip $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SHIPPING_API_LEVEL))
+ifdef PRODUCT_SHIPPING_API_LEVEL
+ADDITIONAL_BUILD_PROPERTIES += \
+    ro.product.first_api_level=$(PRODUCT_SHIPPING_API_LEVEL)
+endif
+
 # Should we use the default resources or add any product specific overlays
 PRODUCT_PACKAGE_OVERLAYS := \
     $(strip $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGE_OVERLAYS))
diff --git a/target/board/generic/sepolicy/goldfish_setup.te b/target/board/generic/sepolicy/goldfish_setup.te
index bce196a..b8f121c 100644
--- a/target/board/generic/sepolicy/goldfish_setup.te
+++ b/target/board/generic/sepolicy/goldfish_setup.te
@@ -5,7 +5,7 @@
 init_daemon_domain(goldfish_setup)
 
 # Inherit open file to shell (interpreter) for script.
-allow goldfish_setup shell_exec:file read;
+allow goldfish_setup shell_exec:file rx_file_perms;
 
 # Run ifconfig, route commands to configure interfaces and routes.
 allow goldfish_setup system_file:file execute_no_trans;
diff --git a/target/product/product_launched_with_l.mk b/target/product/product_launched_with_l.mk
new file mode 100644
index 0000000..8127bc3
--- /dev/null
+++ b/target/product/product_launched_with_l.mk
@@ -0,0 +1,3 @@
+#PRODUCT_SHIPPING_API_LEVEL indicates the first api level, device has been commercially launced on.
+PRODUCT_SHIPPING_API_LEVEL := 21
+
diff --git a/target/product/product_launched_with_l_mr1.mk b/target/product/product_launched_with_l_mr1.mk
new file mode 100644
index 0000000..7a5bd0f
--- /dev/null
+++ b/target/product/product_launched_with_l_mr1.mk
@@ -0,0 +1,2 @@
+#PRODUCT_SHIPPING_API_LEVEL indicates the first api level, device has been commercially launced on.
+PRODUCT_SHIPPING_API_LEVEL := 22
diff --git a/target/product/product_launched_with_m.mk b/target/product/product_launched_with_m.mk
new file mode 100644
index 0000000..afd8647
--- /dev/null
+++ b/target/product/product_launched_with_m.mk
@@ -0,0 +1,2 @@
+#PRODUCT_SHIPPING_API_LEVEL indicates the first api level, device has been commercially launced on.
+PRODUCT_SHIPPING_API_LEVEL := 23
diff --git a/target/product/product_launched_with_n.mk b/target/product/product_launched_with_n.mk
new file mode 100644
index 0000000..f7c5d79
--- /dev/null
+++ b/target/product/product_launched_with_n.mk
@@ -0,0 +1,2 @@
+#PRODUCT_SHIPPING_API_LEVEL indicates the first api level, device has been commercially launced on.
+PRODUCT_SHIPPING_API_LEVEL := 24
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/ApkVerifier.java b/tools/apksigner/core/src/com/android/apksigner/core/ApkVerifier.java
new file mode 100644
index 0000000..931c7b2
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/ApkVerifier.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2016 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 com.android.apksigner.core;
+
+import com.android.apksigner.core.apk.ApkUtils;
+import com.android.apksigner.core.internal.apk.v2.ContentDigestAlgorithm;
+import com.android.apksigner.core.internal.apk.v2.SignatureAlgorithm;
+import com.android.apksigner.core.internal.apk.v2.V2SchemeVerifier;
+import com.android.apksigner.core.util.DataSource;
+import com.android.apksigner.core.zip.ZipFormatException;
+
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * APK signature verifier which mimics the behavior of the Android platform.
+ *
+ * <p>The verifier is designed to closely mimic the behavior of Android platforms. This is to enable
+ * the verifier to be used for checking whether an APK's signatures will verify on Android.
+ */
+public class ApkVerifier {
+
+    /**
+     * Verifies the APK's signatures and returns the result of verification. The APK can be
+     * considered verified iff the result's {@link Result#isVerified()} returns {@code true}.
+     * The verification result also includes errors, warnings, and information about signers.
+     *
+     * @param apk APK file contents
+     * @param minSdkVersion API Level of the oldest Android platform on which the APK's signatures
+     *        may need to be verified
+     *
+     * @throws IOException if an I/O error is encountered while reading the APK
+     * @throws ZipFormatException if the APK is malformed at ZIP format level
+     */
+    public Result verify(DataSource apk, int minSdkVersion) throws IOException, ZipFormatException {
+        ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(apk);
+
+        // Attempt to verify the APK using APK Signature Scheme v2
+        Result result = new Result();
+        try {
+            V2SchemeVerifier.Result v2Result = V2SchemeVerifier.verify(apk, zipSections);
+            result.mergeFrom(v2Result);
+        } catch (V2SchemeVerifier.SignatureNotFoundException ignored) {}
+        if (result.containsErrors()) {
+            return result;
+        }
+
+        // TODO: Verify JAR signature if necessary
+        if (!result.isVerifiedUsingV2Scheme()) {
+            return result;
+        }
+
+        // Verified
+        result.setVerified();
+        for (Result.V2SchemeSignerInfo signerInfo : result.getV2SchemeSigners()) {
+            result.addSignerCertificate(signerInfo.getCertificate());
+        }
+
+        return result;
+    }
+
+    /**
+     * Result of verifying an APKs signatures. The APK can be considered verified iff
+     * {@link #isVerified()} returns {@code true}.
+     */
+    public static class Result {
+        private final List<IssueWithParams> mErrors = new ArrayList<>();
+        private final List<IssueWithParams> mWarnings = new ArrayList<>();
+        private final List<X509Certificate> mSignerCerts = new ArrayList<>();
+        private final List<V2SchemeSignerInfo> mV2SchemeSigners = new ArrayList<>();
+
+        private boolean mVerified;
+        private boolean mVerifiedUsingV2Scheme;
+
+        /**
+         * Returns {@code true} if the APK's signatures verified.
+         */
+        public boolean isVerified() {
+            return mVerified;
+        }
+
+        private void setVerified() {
+            mVerified = true;
+        }
+
+        /**
+         * Returns {@code true} if the APK's APK Signature Scheme v2 signatures verified.
+         */
+        public boolean isVerifiedUsingV2Scheme() {
+            return mVerifiedUsingV2Scheme;
+        }
+
+        /**
+         * Returns the verified signers' certificates, one per signer.
+         */
+        public List<X509Certificate> getSignerCertificates() {
+            return mSignerCerts;
+        }
+
+        private void addSignerCertificate(X509Certificate cert) {
+            mSignerCerts.add(cert);
+        }
+
+        /**
+         * Returns information about APK Signature Scheme v2 signers associated with the APK's
+         * signature.
+         */
+        public List<V2SchemeSignerInfo> getV2SchemeSigners() {
+            return mV2SchemeSigners;
+        }
+
+        /**
+         * Returns errors encountered while verifying the APK's signatures.
+         */
+        public List<IssueWithParams> getErrors() {
+            return mErrors;
+        }
+
+        /**
+         * Returns warnings encountered while verifying the APK's signatures.
+         */
+        public List<IssueWithParams> getWarnings() {
+            return mWarnings;
+        }
+
+        private void mergeFrom(V2SchemeVerifier.Result source) {
+            mVerifiedUsingV2Scheme = source.verified;
+            mErrors.addAll(source.getErrors());
+            mWarnings.addAll(source.getWarnings());
+            for (V2SchemeVerifier.Result.SignerInfo signer : source.signers) {
+                mV2SchemeSigners.add(new V2SchemeSignerInfo(signer));
+            }
+        }
+
+        /**
+         * Returns {@code true} if an error was encountered while verifying the APK. Any error
+         * prevents the APK from being considered verified.
+         */
+        public boolean containsErrors() {
+            if (!mErrors.isEmpty()) {
+                return true;
+            }
+            if (!mV2SchemeSigners.isEmpty()) {
+                for (V2SchemeSignerInfo signer : mV2SchemeSigners) {
+                    if (signer.containsErrors()) {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        /**
+         * Information about an APK Signature Scheme v2 signer associated with the APK's signature.
+         */
+        public static class V2SchemeSignerInfo {
+            private final int mIndex;
+            private final List<X509Certificate> mCerts;
+
+            private final List<IssueWithParams> mErrors;
+            private final List<IssueWithParams> mWarnings;
+
+            private V2SchemeSignerInfo(V2SchemeVerifier.Result.SignerInfo result) {
+                mIndex = result.index;
+                mCerts = result.certs;
+                mErrors = result.getErrors();
+                mWarnings = result.getWarnings();
+            }
+
+            /**
+             * Returns this signer's {@code 0}-based index in the list of signers contained in the
+             * APK's APK Signature Scheme v2 signature.
+             */
+            public int getIndex() {
+                return mIndex;
+            }
+
+            /**
+             * Returns this signer's signing certificate or {@code null} if not available. The
+             * certificate is guaranteed to be available if no errors were encountered during
+             * verification (see {@link #containsErrors()}.
+             *
+             * <p>This certificate contains the signer's public key.
+             */
+            public X509Certificate getCertificate() {
+                return mCerts.isEmpty() ? null : mCerts.get(0);
+            }
+
+            /**
+             * Returns this signer's certificates. The first certificate is for the signer's public
+             * key. An empty list may be returned if an error was encountered during verification
+             * (see {@link #containsErrors()}).
+             */
+            public List<X509Certificate> getCertificates() {
+                return mCerts;
+            }
+
+            public boolean containsErrors() {
+                return !mErrors.isEmpty();
+            }
+
+            public List<IssueWithParams> getErrors() {
+                return mErrors;
+            }
+
+            public List<IssueWithParams> getWarnings() {
+                return mWarnings;
+            }
+        }
+    }
+
+    /**
+     * Error or warning encountered while verifying an APK's signatures.
+     */
+    public static enum Issue {
+
+        /**
+         * Failed to parse the list of signers contained in the APK Signature Scheme v2 signature.
+         */
+        V2_SIG_MALFORMED_SIGNERS("Malformed list of signers"),
+
+        /**
+         * Failed to parse this signer's signer block contained in the APK Signature Scheme v2
+         * signature.
+         */
+        V2_SIG_MALFORMED_SIGNER("Malformed signer block"),
+
+        /**
+         * Public key embedded in the APK Signature Scheme v2 signature of this signer could not be
+         * parsed.
+         *
+         * <ul>
+         * <li>Parameter 1: error details ({@code Throwable})</li>
+         * </ul>
+         */
+        V2_SIG_MALFORMED_PUBLIC_KEY("Malformed public key: %1$s"),
+
+        /**
+         * This APK Signature Scheme v2 signer's certificate could not be parsed.
+         *
+         * <ul>
+         * <li>Parameter 1: index ({@code 0}-based) of the certificate in the signer's list of
+         *     certificates ({@code Integer})</li>
+         * <li>Parameter 2: sequence number ({@code 1}-based) of the certificate in the signer's
+         *     list of certificates ({@code Integer})</li>
+         * <li>Parameter 3: error details ({@code Throwable})</li>
+         * </ul>
+         */
+        V2_SIG_MALFORMED_CERTIFICATE("Malformed certificate #%2$d: %3$s"),
+
+        /**
+         * Failed to parse this signer's signature record contained in the APK Signature Scheme v2
+         * signature.
+         *
+         * <ul>
+         * <li>Parameter 1: record number (first record is {@code 1}) ({@code Integer})</li>
+         * </ul>
+         */
+        V2_SIG_MALFORMED_SIGNATURE("Malformed APK Signature Scheme v2 signature record #%1$d"),
+
+        /**
+         * Failed to parse this signer's digest record contained in the APK Signature Scheme v2
+         * signature.
+         *
+         * <ul>
+         * <li>Parameter 1: record number (first record is {@code 1}) ({@code Integer})</li>
+         * </ul>
+         */
+        V2_SIG_MALFORMED_DIGEST("Malformed APK Signature Scheme v2 digest record #%1$d"),
+
+        /**
+         * This APK Signature Scheme v2 signer contains a malformed additional attribute.
+         *
+         * <ul>
+         * <li>Parameter 1: attribute number (first attribute is {@code 1}) {@code Integer})</li>
+         * </ul>
+         */
+        V2_SIG_MALFORMED_ADDITIONAL_ATTRIBUTE("Malformed additional attribute #%1$d"),
+
+        /**
+         * APK Signature Scheme v2 signature contains no signers.
+         */
+        V2_SIG_NO_SIGNERS("No signers in APK Signature Scheme v2 signature"),
+
+        /**
+         * This APK Signature Scheme v2 signer contains a signature produced using an unknown
+         * algorithm.
+         *
+         * <ul>
+         * <li>Parameter 1: algorithm ID ({@code Integer})</li>
+         * </ul>
+         */
+        V2_SIG_UNKNOWN_SIG_ALGORITHM("Unknown signature algorithm: %1$#x"),
+
+        /**
+         * This APK Signature Scheme v2 signer contains an unknown additional attribute.
+         *
+         * <ul>
+         * <li>Parameter 1: attribute ID ({@code Integer})</li>
+         * </ul>
+         */
+        V2_SIG_UNKNOWN_ADDITIONAL_ATTRIBUTE("Unknown additional attribute: ID %1$#x"),
+
+        /**
+         * An exception was encountered while verifying APK Signature Scheme v2 signature of this
+         * signer.
+         *
+         * <ul>
+         * <li>Parameter 1: signature algorithm ({@link SignatureAlgorithm})</li>
+         * <li>Parameter 2: exception ({@code Throwable})</li>
+         * </ul>
+         */
+        V2_SIG_VERIFY_EXCEPTION("Failed to verify %1$s signature: %2$s"),
+
+        /**
+         * APK Signature Scheme v2 signature over this signer's signed-data block did not verify.
+         *
+         * <ul>
+         * <li>Parameter 1: signature algorithm ({@link SignatureAlgorithm})</li>
+         * </ul>
+         */
+        V2_SIG_DID_NOT_VERIFY("%1$s signature over signed-data did not verify"),
+
+        /**
+         * This APK Signature Scheme v2 signer offers no signatures.
+         */
+        V2_SIG_NO_SIGNATURES("No signatures"),
+
+        /**
+         * This APK Signature Scheme v2 signer offers signatures but none of them are supported.
+         */
+        V2_SIG_NO_SUPPORTED_SIGNATURES("No supported signatures"),
+
+        /**
+         * This APK Signature Scheme v2 signer offers no certificates.
+         */
+        V2_SIG_NO_CERTIFICATES("No certificates"),
+
+        /**
+         * This APK Signature Scheme v2 signer's public key listed in the signer's certificate does
+         * not match the public key listed in the signatures record.
+         *
+         * <ul>
+         * <li>Parameter 1: hex-encoded public key from certificate ({@code String})</li>
+         * <li>Parameter 2: hex-encoded public key from signatures record ({@code String})</li>
+         * </ul>
+         */
+        V2_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD(
+                "Public key mismatch between certificate and signature record: <%1$s> vs <%2$s>"),
+
+        /**
+         * This APK Signature Scheme v2 signer's signature algorithms listed in the signatures
+         * record do not match the signature algorithms listed in the signatures record.
+         *
+         * <ul>
+         * <li>Parameter 1: signature algorithms from signatures record ({@code List<Integer>})</li>
+         * <li>Parameter 2: signature algorithms from digests record ({@code List<Integer>})</li>
+         * </ul>
+         */
+        V2_SIG_SIG_ALG_MISMATCH_BETWEEN_SIGNATURES_AND_DIGESTS_RECORDS(
+                "Signature algorithms mismatch between signatures and digests records"
+                        + ": %1$s vs %2$s"),
+
+        /**
+         * The APK's digest does not match the digest contained in the APK Signature Scheme v2
+         * signature.
+         *
+         * <ul>
+         * <li>Parameter 1: content digest algorithm ({@link ContentDigestAlgorithm})</li>
+         * <li>Parameter 2: hex-encoded expected digest of the APK ({@code String})</li>
+         * <li>Parameter 3: hex-encoded actual digest of the APK ({@code String})</li>
+         * </ul>
+         */
+        V2_SIG_APK_DIGEST_DID_NOT_VERIFY(
+                "APK integrity check failed. %1$s digest mismatch."
+                        + " Expected: <%2$s>, actual: <%3$s>"),
+
+        /**
+         * APK Signing Block contains an unknown entry.
+         *
+         * <ul>
+         * <li>Parameter 1: entry ID ({@code Integer})</li>
+         * </ul>
+         */
+        APK_SIG_BLOCK_UNKNOWN_ENTRY_ID("APK Signing Block contains unknown entry: ID %1$#x");
+
+        private final String mFormat;
+
+        private Issue(String format) {
+            mFormat = format;
+        }
+
+        /**
+         * Returns the format string suitable for combining the parameters of this issue into a
+         * readable string. See {@link java.util.Formatter} for format.
+         */
+        private String getFormat() {
+            return mFormat;
+        }
+    }
+
+    /**
+     * {@link Issue} with associated parameters. {@link #toString()} produces a readable formatted
+     * form.
+     */
+    public static class IssueWithParams {
+        private final Issue mIssue;
+        private final Object[] mParams;
+
+        /**
+         * Constructs a new {@code IssueWithParams} of the specified type and with provided
+         * parameters.
+         */
+        public IssueWithParams(Issue issue, Object[] params) {
+            mIssue = issue;
+            mParams = params;
+        }
+
+        /**
+         * Returns the type of this issue.
+         */
+        public Issue getIssue() {
+            return mIssue;
+        }
+
+        /**
+         * Returns the parameters of this issue.
+         */
+        public Object[] getParams() {
+            return mParams.clone();
+        }
+
+        /**
+         * Returns a readable form of this issue.
+         */
+        @Override
+        public String toString() {
+            return String.format(mIssue.getFormat(), mParams);
+        }
+    }
+}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/DefaultApkSignerEngine.java b/tools/apksigner/core/src/com/android/apksigner/core/DefaultApkSignerEngine.java
index dae3c5e..1c6b622 100644
--- a/tools/apksigner/core/src/com/android/apksigner/core/DefaultApkSignerEngine.java
+++ b/tools/apksigner/core/src/com/android/apksigner/core/DefaultApkSignerEngine.java
@@ -18,13 +18,14 @@
 
 import com.android.apksigner.core.internal.apk.v1.DigestAlgorithm;
 import com.android.apksigner.core.internal.apk.v1.V1SchemeSigner;
-import com.android.apksigner.core.internal.apk.v2.MessageDigestSink;
 import com.android.apksigner.core.internal.apk.v2.V2SchemeSigner;
-import com.android.apksigner.core.internal.util.ByteArrayOutputStreamSink;
+import com.android.apksigner.core.internal.util.MessageDigestSink;
 import com.android.apksigner.core.internal.util.Pair;
 import com.android.apksigner.core.util.DataSink;
+import com.android.apksigner.core.util.DataSinks;
 import com.android.apksigner.core.util.DataSource;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.security.InvalidKeyException;
 import java.security.MessageDigest;
@@ -593,7 +594,8 @@
         private final Object mLock = new Object();
 
         private boolean mDone;
-        private ByteArrayOutputStreamSink mBuf;
+        private DataSink mDataSink;
+        private ByteArrayOutputStream mDataSinkBuf;
 
         private GetJarEntryDataRequest(String entryName) {
             mEntryName = entryName;
@@ -608,10 +610,13 @@
         public DataSink getDataSink() {
             synchronized (mLock) {
                 checkNotDone();
-                if (mBuf == null) {
-                    mBuf = new ByteArrayOutputStreamSink();
+                if (mDataSinkBuf == null) {
+                    mDataSinkBuf = new ByteArrayOutputStream();
                 }
-                return mBuf;
+                if (mDataSink == null) {
+                    mDataSink = DataSinks.asDataSink(mDataSinkBuf);
+                }
+                return mDataSink;
             }
         }
 
@@ -644,7 +649,7 @@
                 if (!mDone) {
                     throw new IllegalStateException("Not yet done");
                 }
-                return (mBuf != null) ? mBuf.getData() : new byte[0];
+                return (mDataSinkBuf != null) ? mDataSinkBuf.toByteArray() : new byte[0];
             }
         }
     }
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeSigner.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeSigner.java
index 8b59b8e..9f4ccce 100644
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeSigner.java
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeSigner.java
@@ -41,13 +41,20 @@
 import java.util.jar.Manifest;
 
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERNull;
 import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
 import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
 import org.bouncycastle.cms.CMSException;
 import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignatureEncryptionAlgorithmFinder;
 import org.bouncycastle.cms.CMSSignedData;
 import org.bouncycastle.cms.CMSSignedDataGenerator;
-import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
+import org.bouncycastle.cms.DefaultCMSSignatureEncryptionAlgorithmFinder;
+import org.bouncycastle.cms.SignerInfoGeneratorBuilder;
 import org.bouncycastle.operator.ContentSigner;
 import org.bouncycastle.operator.OperatorCreationException;
 import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
@@ -462,10 +469,11 @@
                     .build(signerConfig.privateKey);
             CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
             gen.addSignerInfoGenerator(
-                    new JcaSignerInfoGeneratorBuilder(
-                            new JcaDigestCalculatorProviderBuilder().build())
-                    .setDirectSignature(true)
-                    .build(signer, signerCert));
+                    new SignerInfoGeneratorBuilder(
+                            new JcaDigestCalculatorProviderBuilder().build(),
+                            SignerInfoSignatureAlgorithmFinder.INSTANCE)
+                            .setDirectSignature(true)
+                            .build(signer, new JcaX509CertificateHolder(signerCert)));
             gen.addCertificates(certs);
 
             CMSSignedData sigData =
@@ -482,6 +490,37 @@
         }
     }
 
+    /**
+     * Chooser of SignatureAlgorithm for PKCS #7 CMS SignerInfo.
+     */
+    private static class SignerInfoSignatureAlgorithmFinder
+            implements CMSSignatureEncryptionAlgorithmFinder {
+        private static final SignerInfoSignatureAlgorithmFinder INSTANCE =
+                new SignerInfoSignatureAlgorithmFinder();
+
+        private static final AlgorithmIdentifier DSA =
+                new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, DERNull.INSTANCE);
+
+        private final CMSSignatureEncryptionAlgorithmFinder mDefault =
+                new DefaultCMSSignatureEncryptionAlgorithmFinder();
+
+        @Override
+        public AlgorithmIdentifier findEncryptionAlgorithm(AlgorithmIdentifier id) {
+            // Use the default chooser, but replace dsaWithSha1 with dsa. This is because "dsa" is
+            // accepted by any Android platform whereas "dsaWithSha1" is accepted only since
+            // API Level 9.
+            id = mDefault.findEncryptionAlgorithm(id);
+            if (id != null) {
+                ASN1ObjectIdentifier oid = id.getAlgorithm();
+                if (X9ObjectIdentifiers.id_dsa_with_sha1.equals(oid)) {
+                    return DSA;
+                }
+            }
+
+            return id;
+        }
+    }
+
     private static String getEntryDigestAttributeName(DigestAlgorithm digestAlgorithm) {
         switch (digestAlgorithm) {
             case SHA1:
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/ContentDigestAlgorithm.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/ContentDigestAlgorithm.java
index cb0f84a..7c136be 100644
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/ContentDigestAlgorithm.java
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/ContentDigestAlgorithm.java
@@ -19,7 +19,7 @@
 /**
  * APK Signature Scheme v2 content digest algorithm.
  */
-enum ContentDigestAlgorithm {
+public enum ContentDigestAlgorithm {
     /** SHA2-256 over 1 MB chunks. */
     CHUNKED_SHA256("SHA-256", 256 / 8),
 
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/SignatureAlgorithm.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/SignatureAlgorithm.java
index 3c7b5f0..20f890d 100644
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/SignatureAlgorithm.java
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/SignatureAlgorithm.java
@@ -23,7 +23,7 @@
 import java.security.spec.PSSParameterSpec;
 
 /**
- * APK Signature Scheme v2 content digest algorithm.
+ * APK Signature Scheme v2 signature algorithm.
  */
 public enum SignatureAlgorithm {
     /**
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeSigner.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeSigner.java
index 103a0ec..aba390b 100644
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeSigner.java
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeSigner.java
@@ -16,6 +16,7 @@
 
 package com.android.apksigner.core.internal.apk.v2;
 
+import com.android.apksigner.core.internal.util.MessageDigestSink;
 import com.android.apksigner.core.internal.util.Pair;
 import com.android.apksigner.core.internal.zip.ZipUtils;
 import com.android.apksigner.core.util.DataSource;
@@ -216,7 +217,7 @@
         return generateApkSigningBlock(signerConfigs, contentDigests);
     }
 
-    private static Map<ContentDigestAlgorithm, byte[]> computeContentDigests(
+    static Map<ContentDigestAlgorithm, byte[]> computeContentDigests(
             Set<ContentDigestAlgorithm> digestAlgorithms,
             DataSource[] contents) throws IOException, DigestException {
         // For each digest algorithm the result is computed as follows:
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeVerifier.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeVerifier.java
new file mode 100644
index 0000000..efefb00
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeVerifier.java
@@ -0,0 +1,939 @@
+/*
+ * Copyright (C) 2016 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 com.android.apksigner.core.internal.apk.v2;
+
+import com.android.apksigner.core.ApkVerifier.Issue;
+import com.android.apksigner.core.ApkVerifier.IssueWithParams;
+import com.android.apksigner.core.apk.ApkUtils;
+import com.android.apksigner.core.internal.util.ByteBufferDataSource;
+import com.android.apksigner.core.internal.util.DelegatingX509Certificate;
+import com.android.apksigner.core.internal.util.Pair;
+import com.android.apksigner.core.internal.zip.ZipUtils;
+import com.android.apksigner.core.util.DataSource;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.DigestException;
+import java.security.KeyFactory;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * APK Signature Scheme v2 verifier.
+ *
+ * <p>APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single
+ * bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and
+ * uncompressed contents of ZIP entries.
+ *
+ * <p>TODO: Link to APK Signature Scheme v2 documentation once it's available.
+ */
+public abstract class V2SchemeVerifier {
+
+    private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L;
+    private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L;
+    private static final int APK_SIG_BLOCK_MIN_SIZE = 32;
+
+    private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
+
+    /** Hidden constructor to prevent instantiation. */
+    private V2SchemeVerifier() {}
+
+    /**
+     * Verifies the provided APK's APK Signature Scheme v2 signatures and returns the result of
+     * verification. APK is considered verified only if {@link Result#verified} is {@code true}. If
+     * verification fails, the result will contain errors -- see {@link Result#getErrors()}.
+     *
+     * @throws SignatureNotFoundException if no APK Signature Scheme v2 signatures are found
+     * @throws IOException if an I/O error occurs when reading the APK
+     */
+    public static Result verify(DataSource apk, ApkUtils.ZipSections zipSections)
+            throws IOException, SignatureNotFoundException {
+        Result result = new Result();
+        SignatureInfo signatureInfo = findSignature(apk, zipSections, result);
+
+        DataSource beforeApkSigningBlock = apk.slice(0, signatureInfo.apkSigningBlockOffset);
+        DataSource centralDir =
+                apk.slice(
+                        signatureInfo.centralDirOffset,
+                        signatureInfo.eocdOffset - signatureInfo.centralDirOffset);
+        ByteBuffer eocd = signatureInfo.eocd;
+
+        verify(beforeApkSigningBlock,
+                signatureInfo.signatureBlock,
+                centralDir,
+                eocd,
+                result);
+        return result;
+    }
+
+    /**
+     * Verifies the provided APK's v2 signatures and outputs the results into the provided
+     * {@code result}. APK is considered verified only if there are no errors reported in the
+     * {@code result}.
+     */
+    private static void verify(
+            DataSource beforeApkSigningBlock,
+            ByteBuffer apkSignatureSchemeV2Block,
+            DataSource centralDir,
+            ByteBuffer eocd,
+            Result result) throws IOException {
+        Set<ContentDigestAlgorithm> contentDigestsToVerify = new HashSet<>(1);
+        parseSigners(apkSignatureSchemeV2Block, contentDigestsToVerify, result);
+        if (result.containsErrors()) {
+            return;
+        }
+        verifyIntegrity(
+                beforeApkSigningBlock, centralDir, eocd, contentDigestsToVerify, result);
+        if (!result.containsErrors()) {
+            result.verified = true;
+        }
+    }
+
+    /**
+     * Parses each signer in the provided APK Signature Scheme v2 block and populates
+     * {@code signerInfos} of the provided {@code result}.
+     *
+     * <p>This verifies signatures over {@code signed-data} block contained in each signer block.
+     * However, this does not verify the integrity of the rest of the APK but rather simply reports
+     * the expected digests of the rest of the APK (see {@code contentDigestsToVerify}).
+     */
+    private static void parseSigners(
+            ByteBuffer apkSignatureSchemeV2Block,
+            Set<ContentDigestAlgorithm> contentDigestsToVerify,
+            Result result) {
+        ByteBuffer signers;
+        try {
+            signers = getLengthPrefixedSlice(apkSignatureSchemeV2Block);
+        } catch (IOException e) {
+            result.addError(Issue.V2_SIG_MALFORMED_SIGNERS);
+            return;
+        }
+        if (!signers.hasRemaining()) {
+            result.addError(Issue.V2_SIG_NO_SIGNERS);
+            return;
+        }
+
+        CertificateFactory certFactory;
+        try {
+            certFactory = CertificateFactory.getInstance("X.509");
+        } catch (CertificateException e) {
+            throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
+        }
+        int signerCount = 0;
+        while (signers.hasRemaining()) {
+            int signerIndex = signerCount;
+            signerCount++;
+            Result.SignerInfo signerInfo = new Result.SignerInfo();
+            signerInfo.index = signerIndex;
+            result.signers.add(signerInfo);
+            try {
+                ByteBuffer signer = getLengthPrefixedSlice(signers);
+                parseSigner(signer, certFactory, signerInfo, contentDigestsToVerify);
+            } catch (IOException | BufferUnderflowException e) {
+                signerInfo.addError(Issue.V2_SIG_MALFORMED_SIGNER);
+                return;
+            }
+        }
+    }
+
+    /**
+     * Parses the provided signer block and populates the {@code result}.
+     *
+     * <p>This verifies signatures over {@code signed-data} contained in this block but does not
+     * verify the integrity of the rest of the APK. Rather, this method adds to the
+     * {@code contentDigestsToVerify}.
+     */
+    private static void parseSigner(
+            ByteBuffer signerBlock,
+            CertificateFactory certFactory,
+            Result.SignerInfo result,
+            Set<ContentDigestAlgorithm> contentDigestsToVerify) throws IOException {
+        ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
+        byte[] signedDataBytes = new byte[signedData.remaining()];
+        signedData.get(signedDataBytes);
+        signedData.flip();
+        result.signedData = signedDataBytes;
+
+        ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
+        byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);
+
+        // Parse the signatures block and identify supported signatures
+        int signatureCount = 0;
+        List<SupportedSignature> supportedSignatures = new ArrayList<>(1);
+        while (signatures.hasRemaining()) {
+            signatureCount++;
+            try {
+                ByteBuffer signature = getLengthPrefixedSlice(signatures);
+                int sigAlgorithmId = signature.getInt();
+                byte[] sigBytes = readLengthPrefixedByteArray(signature);
+                result.signatures.add(
+                        new Result.SignerInfo.Signature(sigAlgorithmId, sigBytes));
+                SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.findById(sigAlgorithmId);
+                if (signatureAlgorithm == null) {
+                    result.addWarning(Issue.V2_SIG_UNKNOWN_SIG_ALGORITHM, sigAlgorithmId);
+                    continue;
+                }
+                supportedSignatures.add(new SupportedSignature(signatureAlgorithm, sigBytes));
+            } catch (IOException | BufferUnderflowException e) {
+                result.addError(Issue.V2_SIG_MALFORMED_SIGNATURE, signatureCount);
+                return;
+            }
+        }
+        if (result.signatures.isEmpty()) {
+            result.addError(Issue.V2_SIG_NO_SIGNATURES);
+            return;
+        }
+
+        // Verify signatures over signed-data block using the public key
+        List<SupportedSignature> signaturesToVerify = getSignaturesToVerify(supportedSignatures);
+        if (signaturesToVerify.isEmpty()) {
+            result.addError(Issue.V2_SIG_NO_SUPPORTED_SIGNATURES);
+            return;
+        }
+        for (SupportedSignature signature : signaturesToVerify) {
+            SignatureAlgorithm signatureAlgorithm = signature.algorithm;
+            String jcaSignatureAlgorithm =
+                    signatureAlgorithm.getJcaSignatureAlgorithmAndParams().getFirst();
+            AlgorithmParameterSpec jcaSignatureAlgorithmParams =
+                    signatureAlgorithm.getJcaSignatureAlgorithmAndParams().getSecond();
+            String keyAlgorithm = signatureAlgorithm.getJcaKeyAlgorithm();
+            PublicKey publicKey;
+            try {
+                publicKey =
+                        KeyFactory.getInstance(keyAlgorithm).generatePublic(
+                                new X509EncodedKeySpec(publicKeyBytes));
+            } catch (Exception e) {
+                result.addError(Issue.V2_SIG_MALFORMED_PUBLIC_KEY, e);
+                return;
+            }
+            try {
+                Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
+                sig.initVerify(publicKey);
+                if (jcaSignatureAlgorithmParams != null) {
+                    sig.setParameter(jcaSignatureAlgorithmParams);
+                }
+                signedData.position(0);
+                sig.update(signedData);
+                byte[] sigBytes = signature.signature;
+                if (!sig.verify(sigBytes)) {
+                    result.addError(Issue.V2_SIG_DID_NOT_VERIFY, signatureAlgorithm);
+                    return;
+                }
+                result.verifiedSignatures.put(signatureAlgorithm, sigBytes);
+                contentDigestsToVerify.add(signatureAlgorithm.getContentDigestAlgorithm());
+            } catch (Exception e) {
+                result.addError(Issue.V2_SIG_VERIFY_EXCEPTION, signatureAlgorithm, e);
+                return;
+            }
+        }
+
+        // At least one signature over signedData has verified. We can now parse signed-data.
+        signedData.position(0);
+        ByteBuffer digests = getLengthPrefixedSlice(signedData);
+        ByteBuffer certificates = getLengthPrefixedSlice(signedData);
+        ByteBuffer additionalAttributes = getLengthPrefixedSlice(signedData);
+
+        // Parse the certificates block
+        int certificateIndex = -1;
+        while (certificates.hasRemaining()) {
+            certificateIndex++;
+            byte[] encodedCert = readLengthPrefixedByteArray(certificates);
+            X509Certificate certificate;
+            try {
+                certificate =
+                        (X509Certificate)
+                                certFactory.generateCertificate(
+                                        new ByteArrayInputStream(encodedCert));
+            } catch (CertificateException e) {
+                result.addError(
+                        Issue.V2_SIG_MALFORMED_CERTIFICATE,
+                        certificateIndex,
+                        certificateIndex + 1,
+                        e);
+                return;
+            }
+            // Wrap the cert so that the result's getEncoded returns exactly the original encoded
+            // form. Without this, getEncoded may return a different form from what was stored in
+            // the signature. This is becase some X509Certificate(Factory) implementations re-encode
+            // certificates.
+            certificate = new GuaranteedEncodedFormX509Certificate(certificate, encodedCert);
+            result.certs.add(certificate);
+        }
+
+        if (result.certs.isEmpty()) {
+            result.addError(Issue.V2_SIG_NO_CERTIFICATES);
+            return;
+        }
+        X509Certificate mainCertificate = result.certs.get(0);
+        byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
+        if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
+            result.addError(
+                    Issue.V2_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD,
+                    toHex(certificatePublicKeyBytes),
+                    toHex(publicKeyBytes));
+            return;
+        }
+
+        // Parse the digests block
+        int digestCount = 0;
+        while (digests.hasRemaining()) {
+            digestCount++;
+            try {
+                ByteBuffer digest = getLengthPrefixedSlice(digests);
+                int sigAlgorithmId = digest.getInt();
+                byte[] digestBytes = readLengthPrefixedByteArray(digest);
+                result.contentDigests.add(
+                        new Result.SignerInfo.ContentDigest(sigAlgorithmId, digestBytes));
+            } catch (IOException | BufferUnderflowException e) {
+                result.addError(Issue.V2_SIG_MALFORMED_DIGEST, digestCount);
+                return;
+            }
+        }
+
+        List<Integer> sigAlgsFromSignaturesRecord = new ArrayList<>(result.signatures.size());
+        for (Result.SignerInfo.Signature signature : result.signatures) {
+            sigAlgsFromSignaturesRecord.add(signature.getAlgorithmId());
+        }
+        List<Integer> sigAlgsFromDigestsRecord = new ArrayList<>(result.contentDigests.size());
+        for (Result.SignerInfo.ContentDigest digest : result.contentDigests) {
+            sigAlgsFromDigestsRecord.add(digest.getSignatureAlgorithmId());
+        }
+
+        if (!sigAlgsFromSignaturesRecord.equals(sigAlgsFromDigestsRecord)) {
+            result.addError(
+                    Issue.V2_SIG_SIG_ALG_MISMATCH_BETWEEN_SIGNATURES_AND_DIGESTS_RECORDS,
+                    sigAlgsFromSignaturesRecord,
+                    sigAlgsFromDigestsRecord);
+            return;
+        }
+
+        // Parse the additional attributes block.
+        int additionalAttributeCount = 0;
+        while (additionalAttributes.hasRemaining()) {
+            additionalAttributeCount++;
+            try {
+                ByteBuffer attribute = getLengthPrefixedSlice(additionalAttributes);
+                int id = attribute.getInt();
+                byte[] value = readLengthPrefixedByteArray(attribute);
+                result.additionalAttributes.add(
+                        new Result.SignerInfo.AdditionalAttribute(id, value));
+                result.addWarning(Issue.V2_SIG_UNKNOWN_ADDITIONAL_ATTRIBUTE, id);
+            } catch (IOException | BufferUnderflowException e) {
+                result.addError(
+                        Issue.V2_SIG_MALFORMED_ADDITIONAL_ATTRIBUTE, additionalAttributeCount);
+                return;
+            }
+        }
+    }
+
+    private static List<SupportedSignature> getSignaturesToVerify(
+            List<SupportedSignature> signatures) {
+        // Pick the signature with the strongest algorithm, to mimic Android's behavior.
+        SignatureAlgorithm bestSigAlgorithm = null;
+        byte[] bestSigAlgorithmSignatureBytes = null;
+        for (SupportedSignature sig : signatures) {
+            SignatureAlgorithm sigAlgorithm = sig.algorithm;
+            if ((bestSigAlgorithm == null)
+                    || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
+                bestSigAlgorithm = sigAlgorithm;
+                bestSigAlgorithmSignatureBytes = sig.signature;
+            }
+        }
+
+        if (bestSigAlgorithm == null) {
+            return Collections.emptyList();
+        } else {
+            return Collections.singletonList(
+                    new SupportedSignature(bestSigAlgorithm, bestSigAlgorithmSignatureBytes));
+        }
+    }
+
+    private static class SupportedSignature {
+        private final SignatureAlgorithm algorithm;
+        private final byte[] signature;
+
+        private SupportedSignature(SignatureAlgorithm algorithm, byte[] signature) {
+            this.algorithm = algorithm;
+            this.signature = signature;
+        }
+    }
+
+    /**
+     * Returns positive number if {@code alg1} is preferred over {@code alg2}, {@code -1} if
+     * {@code alg2} is preferred over {@code alg1}, and {@code 0} if there is no preference.
+     */
+    private static int compareSignatureAlgorithm(SignatureAlgorithm alg1, SignatureAlgorithm alg2) {
+        ContentDigestAlgorithm digestAlg1 = alg1.getContentDigestAlgorithm();
+        ContentDigestAlgorithm digestAlg2 = alg2.getContentDigestAlgorithm();
+        return compareContentDigestAlgorithm(digestAlg1, digestAlg2);
+    }
+
+    /**
+     * Returns positive number if {@code alg1} is preferred over {@code alg2}, {@code -1} if
+     * {@code alg2} is preferred over {@code alg1}, and {@code 0} if there is no preference.
+     */
+    private static int compareContentDigestAlgorithm(
+            ContentDigestAlgorithm alg1,
+            ContentDigestAlgorithm alg2) {
+        switch (alg1) {
+            case CHUNKED_SHA256:
+                switch (alg2) {
+                    case CHUNKED_SHA256:
+                        return 0;
+                    case CHUNKED_SHA512:
+                        return -1;
+                    default:
+                        throw new IllegalArgumentException("Unknown alg2: " + alg2);
+                }
+            case CHUNKED_SHA512:
+                switch (alg2) {
+                    case CHUNKED_SHA256:
+                        return 1;
+                    case CHUNKED_SHA512:
+                        return 0;
+                    default:
+                        throw new IllegalArgumentException("Unknown alg2: " + alg2);
+                }
+            default:
+                throw new IllegalArgumentException("Unknown alg1: " + alg1);
+        }
+    }
+
+    /**
+     * Verifies integrity of the APK outside of the APK Signing Block by computing digests of the
+     * APK and comparing them against the digests listed in APK Signing Block. The expected digests
+     * taken from {@code v2SchemeSignerInfos} of the provided {@code result}.
+     */
+    private static void verifyIntegrity(
+            DataSource beforeApkSigningBlock,
+            DataSource centralDir,
+            ByteBuffer eocd,
+            Set<ContentDigestAlgorithm> contentDigestAlgorithms,
+            Result result) throws IOException {
+        if (contentDigestAlgorithms.isEmpty()) {
+            // This should never occur because this method is invoked once at least one signature
+            // is verified, meaning at least one content digest is known.
+            throw new RuntimeException("No content digests found");
+        }
+
+        // For the purposes of verifying integrity, ZIP End of Central Directory (EoCD) must be
+        // treated as though its Central Directory offset points to the start of APK Signing Block.
+        // We thus modify the EoCD accordingly.
+        ByteBuffer modifiedEocd = ByteBuffer.allocate(eocd.remaining());
+        modifiedEocd.order(ByteOrder.LITTLE_ENDIAN);
+        modifiedEocd.put(eocd);
+        modifiedEocd.flip();
+        ZipUtils.setZipEocdCentralDirectoryOffset(modifiedEocd, beforeApkSigningBlock.size());
+        Map<ContentDigestAlgorithm, byte[]> actualContentDigests;
+        try {
+            actualContentDigests =
+                    V2SchemeSigner.computeContentDigests(
+                            contentDigestAlgorithms,
+                            new DataSource[] {
+                                    beforeApkSigningBlock,
+                                    centralDir,
+                                    new ByteBufferDataSource(modifiedEocd)
+                            });
+        } catch (DigestException e) {
+            throw new RuntimeException("Failed to compute content digests", e);
+        }
+        if (!contentDigestAlgorithms.equals(actualContentDigests.keySet())) {
+            throw new RuntimeException(
+                    "Mismatch between sets of requested and computed content digests"
+                            + " . Requested: " + contentDigestAlgorithms
+                            + ", computed: " + actualContentDigests.keySet());
+        }
+
+        // Compare digests computed over the rest of APK against the corresponding expected digests
+        // in signer blocks.
+        for (Result.SignerInfo signerInfo : result.signers) {
+            for (Result.SignerInfo.ContentDigest expected : signerInfo.contentDigests) {
+                SignatureAlgorithm signatureAlgorithm =
+                        SignatureAlgorithm.findById(expected.getSignatureAlgorithmId());
+                if (signatureAlgorithm == null) {
+                    continue;
+                }
+                ContentDigestAlgorithm contentDigestAlgorithm =
+                        signatureAlgorithm.getContentDigestAlgorithm();
+                byte[] expectedDigest = expected.getValue();
+                byte[] actualDigest = actualContentDigests.get(contentDigestAlgorithm);
+                if (!Arrays.equals(expectedDigest, actualDigest)) {
+                    signerInfo.addError(
+                            Issue.V2_SIG_APK_DIGEST_DID_NOT_VERIFY,
+                            contentDigestAlgorithm,
+                            toHex(expectedDigest),
+                            toHex(actualDigest));
+                    continue;
+                }
+                signerInfo.verifiedContentDigests.put(contentDigestAlgorithm, actualDigest);
+            }
+        }
+    }
+
+    /**
+     * APK Signature Scheme v2 block and additional information relevant to verifying the signatures
+     * contained in the block against the file.
+     */
+    private static class SignatureInfo {
+        /** Contents of APK Signature Scheme v2 block. */
+        private final ByteBuffer signatureBlock;
+
+        /** Position of the APK Signing Block in the file. */
+        private final long apkSigningBlockOffset;
+
+        /** Position of the ZIP Central Directory in the file. */
+        private final long centralDirOffset;
+
+        /** Position of the ZIP End of Central Directory (EoCD) in the file. */
+        private final long eocdOffset;
+
+        /** Contents of ZIP End of Central Directory (EoCD) of the file. */
+        private final ByteBuffer eocd;
+
+        private SignatureInfo(
+                ByteBuffer signatureBlock,
+                long apkSigningBlockOffset,
+                long centralDirOffset,
+                long eocdOffset,
+                ByteBuffer eocd) {
+            this.signatureBlock = signatureBlock;
+            this.apkSigningBlockOffset = apkSigningBlockOffset;
+            this.centralDirOffset = centralDirOffset;
+            this.eocdOffset = eocdOffset;
+            this.eocd = eocd;
+        }
+    }
+
+    /**
+     * Returns the APK Signature Scheme v2 block contained in the provided APK file and the
+     * additional information relevant for verifying the block against the file.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2
+     * @throws IOException if an I/O error occurs while reading the APK
+     */
+    private static SignatureInfo findSignature(
+            DataSource apk, ApkUtils.ZipSections zipSections, Result result)
+                    throws IOException, SignatureNotFoundException {
+        long centralDirStartOffset = zipSections.getZipCentralDirectoryOffset();
+        long centralDirEndOffset =
+                centralDirStartOffset + zipSections.getZipCentralDirectorySizeBytes();
+        long eocdStartOffset = zipSections.getZipEndOfCentralDirectoryOffset();
+        if (centralDirEndOffset != eocdStartOffset) {
+            throw new SignatureNotFoundException(
+                    "ZIP Central Directory is not immediately followed by End of Central Directory"
+                            + ". CD end: " + centralDirEndOffset
+                            + ", EoCD start: " + eocdStartOffset);
+        }
+
+        // Find the APK Signing Block. The block immediately precedes the Central Directory.
+        ByteBuffer eocd = zipSections.getZipEndOfCentralDirectory();
+        Pair<ByteBuffer, Long> apkSigningBlockAndOffset =
+                findApkSigningBlock(apk, centralDirStartOffset);
+        ByteBuffer apkSigningBlock = apkSigningBlockAndOffset.getFirst();
+        long apkSigningBlockOffset = apkSigningBlockAndOffset.getSecond();
+
+        // Find the APK Signature Scheme v2 Block inside the APK Signing Block.
+        ByteBuffer apkSignatureSchemeV2Block =
+                findApkSignatureSchemeV2Block(apkSigningBlock, result);
+
+        return new SignatureInfo(
+                apkSignatureSchemeV2Block,
+                apkSigningBlockOffset,
+                centralDirStartOffset,
+                eocdStartOffset,
+                eocd);
+    }
+
+    private static Pair<ByteBuffer, Long> findApkSigningBlock(
+            DataSource apk, long centralDirOffset) throws IOException, SignatureNotFoundException {
+        // FORMAT:
+        // OFFSET       DATA TYPE  DESCRIPTION
+        // * @+0  bytes uint64:    size in bytes (excluding this field)
+        // * @+8  bytes payload
+        // * @-24 bytes uint64:    size in bytes (same as the one above)
+        // * @-16 bytes uint128:   magic
+
+        if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) {
+            throw new SignatureNotFoundException(
+                    "APK too small for APK Signing Block. ZIP Central Directory offset: "
+                            + centralDirOffset);
+        }
+        // Read the magic and offset in file from the footer section of the block:
+        // * uint64:   size of block
+        // * 16 bytes: magic
+        ByteBuffer footer = apk.getByteBuffer(centralDirOffset - 24, 24);
+        footer.order(ByteOrder.LITTLE_ENDIAN);
+        if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO)
+                || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) {
+            throw new SignatureNotFoundException(
+                    "No APK Signing Block before ZIP Central Directory");
+        }
+        // Read and compare size fields
+        long apkSigBlockSizeInFooter = footer.getLong(0);
+        if ((apkSigBlockSizeInFooter < footer.capacity())
+                || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) {
+            throw new SignatureNotFoundException(
+                    "APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
+        }
+        int totalSize = (int) (apkSigBlockSizeInFooter + 8);
+        long apkSigBlockOffset = centralDirOffset - totalSize;
+        if (apkSigBlockOffset < 0) {
+            throw new SignatureNotFoundException(
+                    "APK Signing Block offset out of range: " + apkSigBlockOffset);
+        }
+        ByteBuffer apkSigBlock = apk.getByteBuffer(apkSigBlockOffset, totalSize);
+        apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
+        long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
+        if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
+            throw new SignatureNotFoundException(
+                    "APK Signing Block sizes in header and footer do not match: "
+                            + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
+        }
+        return Pair.of(apkSigBlock, apkSigBlockOffset);
+    }
+
+    private static ByteBuffer findApkSignatureSchemeV2Block(
+            ByteBuffer apkSigningBlock,
+            Result result) throws SignatureNotFoundException {
+        checkByteOrderLittleEndian(apkSigningBlock);
+        // FORMAT:
+        // OFFSET       DATA TYPE  DESCRIPTION
+        // * @+0  bytes uint64:    size in bytes (excluding this field)
+        // * @+8  bytes pairs
+        // * @-24 bytes uint64:    size in bytes (same as the one above)
+        // * @-16 bytes uint128:   magic
+        ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
+
+        int entryCount = 0;
+        while (pairs.hasRemaining()) {
+            entryCount++;
+            if (pairs.remaining() < 8) {
+                throw new SignatureNotFoundException(
+                        "Insufficient data to read size of APK Signing Block entry #" + entryCount);
+            }
+            long lenLong = pairs.getLong();
+            if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
+                throw new SignatureNotFoundException(
+                        "APK Signing Block entry #" + entryCount
+                                + " size out of range: " + lenLong);
+            }
+            int len = (int) lenLong;
+            int nextEntryPos = pairs.position() + len;
+            if (len > pairs.remaining()) {
+                throw new SignatureNotFoundException(
+                        "APK Signing Block entry #" + entryCount + " size out of range: " + len
+                                + ", available: " + pairs.remaining());
+            }
+            int id = pairs.getInt();
+            if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) {
+                return getByteBuffer(pairs, len - 4);
+            }
+            result.addWarning(Issue.APK_SIG_BLOCK_UNKNOWN_ENTRY_ID, id);
+            pairs.position(nextEntryPos);
+        }
+
+        throw new SignatureNotFoundException(
+                "No APK Signature Scheme v2 block in APK Signing Block");
+    }
+
+    private static void checkByteOrderLittleEndian(ByteBuffer buffer) {
+        if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
+            throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
+        }
+    }
+
+    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);
+        }
+    }
+
+    /**
+     * Returns new byte buffer whose content is a shared subsequence of this buffer's content
+     * between the specified start (inclusive) and end (exclusive) positions. As opposed to
+     * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source
+     * buffer's byte order.
+     */
+    private static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) {
+        if (start < 0) {
+            throw new IllegalArgumentException("start: " + start);
+        }
+        if (end < start) {
+            throw new IllegalArgumentException("end < start: " + end + " < " + start);
+        }
+        int capacity = source.capacity();
+        if (end > source.capacity()) {
+            throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
+        }
+        int originalLimit = source.limit();
+        int originalPosition = source.position();
+        try {
+            source.position(0);
+            source.limit(end);
+            source.position(start);
+            ByteBuffer result = source.slice();
+            result.order(source.order());
+            return result;
+        } finally {
+            source.position(0);
+            source.limit(originalLimit);
+            source.position(originalPosition);
+        }
+    }
+
+    /**
+     * Relative <em>get</em> method for reading {@code size} number of bytes from the current
+     * position of this buffer.
+     *
+     * <p>This method reads the next {@code size} bytes at this buffer's current position,
+     * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
+     * {@code size}, byte order set to this buffer's byte order; and then increments the position by
+     * {@code size}.
+     */
+    private static ByteBuffer getByteBuffer(ByteBuffer source, int size)
+            throws BufferUnderflowException {
+        if (size < 0) {
+            throw new IllegalArgumentException("size: " + size);
+        }
+        int originalLimit = source.limit();
+        int position = source.position();
+        int limit = position + size;
+        if ((limit < position) || (limit > originalLimit)) {
+            throw new BufferUnderflowException();
+        }
+        source.limit(limit);
+        try {
+            ByteBuffer result = source.slice();
+            result.order(source.order());
+            source.position(limit);
+            return result;
+        } finally {
+            source.limit(originalLimit);
+        }
+    }
+
+    private static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException {
+        if (source.remaining() < 4) {
+            throw new IOException(
+                    "Remaining buffer too short to contain length of length-prefixed field."
+                            + " Remaining: " + source.remaining());
+        }
+        int len = source.getInt();
+        if (len < 0) {
+            throw new IllegalArgumentException("Negative length");
+        } else if (len > source.remaining()) {
+            throw new IOException("Length-prefixed field longer than remaining buffer."
+                    + " Field length: " + len + ", remaining: " + source.remaining());
+        }
+        return getByteBuffer(source, len);
+    }
+
+    private static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException {
+        int len = buf.getInt();
+        if (len < 0) {
+            throw new IOException("Negative length");
+        } else if (len > buf.remaining()) {
+            throw new IOException("Underflow while reading length-prefixed value. Length: " + len
+                    + ", available: " + buf.remaining());
+        }
+        byte[] result = new byte[len];
+        buf.get(result);
+        return result;
+    }
+
+    /**
+     * {@link X509Certificate} whose {@link #getEncoded()} returns the data provided at construction
+     * time.
+     */
+    private static class GuaranteedEncodedFormX509Certificate extends DelegatingX509Certificate {
+        private byte[] mEncodedForm;
+
+        public GuaranteedEncodedFormX509Certificate(X509Certificate wrapped, byte[] encodedForm) {
+            super(wrapped);
+            this.mEncodedForm = (encodedForm != null) ? encodedForm.clone() : null;
+        }
+
+        @Override
+        public byte[] getEncoded() throws CertificateEncodingException {
+            return (mEncodedForm != null) ? mEncodedForm.clone() : null;
+        }
+    }
+
+    private static final char[] HEX_DIGITS = "01234567890abcdef".toCharArray();
+
+    private static String toHex(byte[] value) {
+        StringBuilder sb = new StringBuilder(value.length * 2);
+        int len = value.length;
+        for (int i = 0; i < len; i++) {
+            int hi = (value[i] & 0xff) >>> 4;
+            int lo = value[i] & 0x0f;
+            sb.append(HEX_DIGITS[hi]).append(HEX_DIGITS[lo]);
+        }
+        return sb.toString();
+    }
+
+    public static class Result {
+
+        /** Whether the APK's APK Signature Scheme v2 signature verifies. */
+        public boolean verified;
+
+        public final List<SignerInfo> signers = new ArrayList<>();
+        private final List<IssueWithParams> mWarnings = new ArrayList<>();
+        private final List<IssueWithParams> mErrors = new ArrayList<>();
+
+        public boolean containsErrors() {
+            if (!mErrors.isEmpty()) {
+                return true;
+            }
+            if (!signers.isEmpty()) {
+                for (SignerInfo signer : signers) {
+                    if (signer.containsErrors()) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+
+        public void addError(Issue msg, Object... parameters) {
+            mErrors.add(new IssueWithParams(msg, parameters));
+        }
+
+        public void addWarning(Issue msg, Object... parameters) {
+            mWarnings.add(new IssueWithParams(msg, parameters));
+        }
+
+        public List<IssueWithParams> getErrors() {
+            return mErrors;
+        }
+
+        public List<IssueWithParams> getWarnings() {
+            return mWarnings;
+        }
+
+        public static class SignerInfo {
+            public int index;
+            public List<X509Certificate> certs = new ArrayList<>();
+            public List<ContentDigest> contentDigests = new ArrayList<>();
+            public Map<ContentDigestAlgorithm, byte[]> verifiedContentDigests = new HashMap<>();
+            public List<Signature> signatures = new ArrayList<>();
+            public Map<SignatureAlgorithm, byte[]> verifiedSignatures = new HashMap<>();
+            public List<AdditionalAttribute> additionalAttributes = new ArrayList<>();
+            public byte[] signedData;
+
+            private final List<IssueWithParams> mWarnings = new ArrayList<>();
+            private final List<IssueWithParams> mErrors = new ArrayList<>();
+
+            public void addError(Issue msg, Object... parameters) {
+                mErrors.add(new IssueWithParams(msg, parameters));
+            }
+
+            public void addWarning(Issue msg, Object... parameters) {
+                mWarnings.add(new IssueWithParams(msg, parameters));
+            }
+
+            public boolean containsErrors() {
+                return !mErrors.isEmpty();
+            }
+
+            public List<IssueWithParams> getErrors() {
+                return mErrors;
+            }
+
+            public List<IssueWithParams> getWarnings() {
+                return mWarnings;
+            }
+
+            public static class ContentDigest {
+                private final int mSignatureAlgorithmId;
+                private final byte[] mValue;
+
+                public ContentDigest(int signatureAlgorithmId, byte[] value) {
+                    mSignatureAlgorithmId  = signatureAlgorithmId;
+                    mValue = value;
+                }
+
+                public int getSignatureAlgorithmId() {
+                    return mSignatureAlgorithmId;
+                }
+
+                public byte[] getValue() {
+                    return mValue;
+                }
+            }
+
+            public static class Signature {
+                private final int mAlgorithmId;
+                private final byte[] mValue;
+
+                public Signature(int algorithmId, byte[] value) {
+                    mAlgorithmId  = algorithmId;
+                    mValue = value;
+                }
+
+                public int getAlgorithmId() {
+                    return mAlgorithmId;
+                }
+
+                public byte[] getValue() {
+                    return mValue;
+                }
+            }
+
+            public static class AdditionalAttribute {
+                private final int mId;
+                private final byte[] mValue;
+
+                public AdditionalAttribute(int id, byte[] value) {
+                    mId  = id;
+                    mValue = value.clone();
+                }
+
+                public int getId() {
+                    return mId;
+                }
+
+                public byte[] getValue() {
+                    return mValue.clone();
+                }
+            }
+        }
+    }
+}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteArrayOutputStreamSink.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteArrayOutputStreamSink.java
deleted file mode 100644
index ca79df7..0000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteArrayOutputStreamSink.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2016 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 com.android.apksigner.core.internal.util;
-
-import com.android.apksigner.core.util.DataSink;
-
-import java.io.ByteArrayOutputStream;
-import java.nio.ByteBuffer;
-
-/**
- * Data sink which stores all input data into an internal {@link ByteArrayOutputStream}, thus
- * accepting an arbitrary amount of data.
- */
-public class ByteArrayOutputStreamSink implements DataSink {
-
-    private final ByteArrayOutputStream mBuf = new ByteArrayOutputStream();
-
-    @Override
-    public void consume(byte[] buf, int offset, int length) {
-        mBuf.write(buf, offset, length);
-    }
-
-    @Override
-    public void consume(ByteBuffer buf) {
-        if (!buf.hasRemaining()) {
-            return;
-        }
-
-        if (buf.hasArray()) {
-            mBuf.write(
-                    buf.array(),
-                    buf.arrayOffset() + buf.position(),
-                    buf.remaining());
-            buf.position(buf.limit());
-        } else {
-            byte[] tmp = new byte[buf.remaining()];
-            buf.get(tmp);
-            mBuf.write(tmp, 0, tmp.length);
-        }
-    }
-
-    /**
-     * Returns the data received so far.
-     */
-    public byte[] getData() {
-        return mBuf.toByteArray();
-    }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/DelegatingX509Certificate.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/DelegatingX509Certificate.java
new file mode 100644
index 0000000..936cfa9
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/DelegatingX509Certificate.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2016 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 com.android.apksigner.core.internal.util;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.Set;
+
+/**
+ * {@link X509Certificate} which delegates all method invocations to the provided delegate
+ * {@code X509Certificate}.
+ */
+public class DelegatingX509Certificate extends X509Certificate {
+    private final X509Certificate mDelegate;
+
+    public DelegatingX509Certificate(X509Certificate delegate) {
+        this.mDelegate = delegate;
+    }
+
+    @Override
+    public Set<String> getCriticalExtensionOIDs() {
+        return mDelegate.getCriticalExtensionOIDs();
+    }
+
+    @Override
+    public byte[] getExtensionValue(String oid) {
+        return mDelegate.getExtensionValue(oid);
+    }
+
+    @Override
+    public Set<String> getNonCriticalExtensionOIDs() {
+        return mDelegate.getNonCriticalExtensionOIDs();
+    }
+
+    @Override
+    public boolean hasUnsupportedCriticalExtension() {
+        return mDelegate.hasUnsupportedCriticalExtension();
+    }
+
+    @Override
+    public void checkValidity()
+            throws CertificateExpiredException, CertificateNotYetValidException {
+        mDelegate.checkValidity();
+    }
+
+    @Override
+    public void checkValidity(Date date)
+            throws CertificateExpiredException, CertificateNotYetValidException {
+        mDelegate.checkValidity(date);
+    }
+
+    @Override
+    public int getVersion() {
+        return mDelegate.getVersion();
+    }
+
+    @Override
+    public BigInteger getSerialNumber() {
+        return mDelegate.getSerialNumber();
+    }
+
+    @Override
+    public Principal getIssuerDN() {
+        return mDelegate.getIssuerDN();
+    }
+
+    @Override
+    public Principal getSubjectDN() {
+        return mDelegate.getSubjectDN();
+    }
+
+    @Override
+    public Date getNotBefore() {
+        return mDelegate.getNotBefore();
+    }
+
+    @Override
+    public Date getNotAfter() {
+        return mDelegate.getNotAfter();
+    }
+
+    @Override
+    public byte[] getTBSCertificate() throws CertificateEncodingException {
+        return mDelegate.getTBSCertificate();
+    }
+
+    @Override
+    public byte[] getSignature() {
+        return mDelegate.getSignature();
+    }
+
+    @Override
+    public String getSigAlgName() {
+        return mDelegate.getSigAlgName();
+    }
+
+    @Override
+    public String getSigAlgOID() {
+        return mDelegate.getSigAlgOID();
+    }
+
+    @Override
+    public byte[] getSigAlgParams() {
+        return mDelegate.getSigAlgParams();
+    }
+
+    @Override
+    public boolean[] getIssuerUniqueID() {
+        return mDelegate.getIssuerUniqueID();
+    }
+
+    @Override
+    public boolean[] getSubjectUniqueID() {
+        return mDelegate.getSubjectUniqueID();
+    }
+
+    @Override
+    public boolean[] getKeyUsage() {
+        return mDelegate.getKeyUsage();
+    }
+
+    @Override
+    public int getBasicConstraints() {
+        return mDelegate.getBasicConstraints();
+    }
+
+    @Override
+    public byte[] getEncoded() throws CertificateEncodingException {
+        return mDelegate.getEncoded();
+    }
+
+    @Override
+    public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
+            InvalidKeyException, NoSuchProviderException, SignatureException {
+        mDelegate.verify(key);
+    }
+
+    @Override
+    public void verify(PublicKey key, String sigProvider)
+            throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
+            NoSuchProviderException, SignatureException {
+        mDelegate.verify(key, sigProvider);
+    }
+
+    @Override
+    public String toString() {
+        return mDelegate.toString();
+    }
+
+    @Override
+    public PublicKey getPublicKey() {
+        return mDelegate.getPublicKey();
+    }
+}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/MessageDigestSink.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/MessageDigestSink.java
similarity index 96%
rename from tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/MessageDigestSink.java
rename to tools/apksigner/core/src/com/android/apksigner/core/internal/util/MessageDigestSink.java
index 9ef04bf..45bb30e 100644
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/MessageDigestSink.java
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/MessageDigestSink.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.apksigner.core.internal.apk.v2;
+package com.android.apksigner.core.internal.util;
 
 import com.android.apksigner.core.util.DataSink;
 
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/OutputStreamDataSink.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/OutputStreamDataSink.java
new file mode 100644
index 0000000..bf18ca0
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/OutputStreamDataSink.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 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 com.android.apksigner.core.internal.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+import com.android.apksigner.core.util.DataSink;
+
+/**
+ * {@link DataSink} which outputs received data into the associated {@link OutputStream}.
+ */
+public class OutputStreamDataSink implements DataSink {
+
+    private static final int MAX_READ_CHUNK_SIZE = 65536;
+
+    private final OutputStream mOut;
+
+    /**
+     * Constructs a new {@code OutputStreamDataSink} which outputs received data into the provided
+     * {@link OutputStream}.
+     */
+    public OutputStreamDataSink(OutputStream out) {
+        if (out == null) {
+            throw new NullPointerException("out == null");
+        }
+        mOut = out;
+    }
+
+    /**
+     * Returns {@link OutputStream} into which this data sink outputs received data.
+     */
+    public OutputStream getOutputStream() {
+        return mOut;
+    }
+
+    @Override
+    public void consume(byte[] buf, int offset, int length) throws IOException {
+        mOut.write(buf, offset, length);
+    }
+
+    @Override
+    public void consume(ByteBuffer buf) throws IOException {
+        if (!buf.hasRemaining()) {
+            return;
+        }
+
+        if (buf.hasArray()) {
+            mOut.write(
+                    buf.array(),
+                    buf.arrayOffset() + buf.position(),
+                    buf.remaining());
+            buf.position(buf.limit());
+        } else {
+            byte[] tmp = new byte[Math.min(buf.remaining(), MAX_READ_CHUNK_SIZE)];
+            while (buf.hasRemaining()) {
+                int chunkSize = Math.min(buf.remaining(), tmp.length);
+                buf.get(tmp, 0, chunkSize);
+                mOut.write(tmp, 0, chunkSize);
+            }
+        }
+    }
+}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/zip/ZipUtils.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/zip/ZipUtils.java
index 51110b6..5e724a2 100644
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/zip/ZipUtils.java
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/zip/ZipUtils.java
@@ -20,7 +20,6 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 
-import com.android.apksigner.core.internal.util.ByteBufferSink;
 import com.android.apksigner.core.internal.util.Pair;
 import com.android.apksigner.core.util.DataSource;
 
@@ -175,11 +174,10 @@
         // Lower maxCommentSize if the file is too small.
         maxCommentSize = (int) Math.min(maxCommentSize, fileSize - ZIP_EOCD_REC_MIN_SIZE);
 
-        ByteBuffer buf = ByteBuffer.allocate(ZIP_EOCD_REC_MIN_SIZE + maxCommentSize);
+        int maxEocdSize = ZIP_EOCD_REC_MIN_SIZE + maxCommentSize;
+        long bufOffsetInFile = fileSize - maxEocdSize;
+        ByteBuffer buf = zip.getByteBuffer(bufOffsetInFile, maxEocdSize);
         buf.order(ByteOrder.LITTLE_ENDIAN);
-        long bufOffsetInFile = fileSize - buf.capacity();
-        zip.feed(bufOffsetInFile, buf.remaining(), new ByteBufferSink(buf));
-        buf.flip();
         int eocdOffsetInBuf = findZipEndOfCentralDirectoryRecord(buf);
         if (eocdOffsetInBuf == -1) {
             // No EoCD record found in the buffer
@@ -252,10 +250,8 @@
             return false;
         }
 
-        ByteBuffer sig = ByteBuffer.allocate(4);
+        ByteBuffer sig = zip.getByteBuffer(locatorPosition, 4);
         sig.order(ByteOrder.LITTLE_ENDIAN);
-        zip.feed(locatorPosition, sig.remaining(), new ByteBufferSink(sig));
-        sig.flip();
         return sig.getInt(0) == ZIP64_EOCD_LOCATOR_SIG;
     }
 
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/util/DataSinks.java b/tools/apksigner/core/src/com/android/apksigner/core/util/DataSinks.java
new file mode 100644
index 0000000..8ee1f13
--- /dev/null
+++ b/tools/apksigner/core/src/com/android/apksigner/core/util/DataSinks.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 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 com.android.apksigner.core.util;
+
+import java.io.OutputStream;
+
+import com.android.apksigner.core.internal.util.OutputStreamDataSink;
+
+/**
+ * Utility methods for working with {@link DataSink} abstraction.
+ */
+public abstract class DataSinks {
+    private DataSinks() {}
+
+    /**
+     * Returns a {@link DataSink} which outputs received data into the provided
+     * {@link OutputStream}.
+     */
+    public static DataSink asDataSink(OutputStream out) {
+        return new OutputStreamDataSink(out);
+    }
+}
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 582412a..546c251 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -1153,6 +1153,9 @@
                                       source_file=None):
   """Generate an Android OTA package that has A/B update payload."""
 
+  # The place where the output from the subprocess should go.
+  log_file = sys.stdout if OPTIONS.verbose else subprocess.PIPE
+
   # Setup signing keys.
   if OPTIONS.package_key is None:
     OPTIONS.package_key = OPTIONS.info_dict.get(
@@ -1165,8 +1168,8 @@
          "-inform", "DER", "-nocrypt"]
   rsa_key = common.MakeTempFile(prefix="key-", suffix=".key")
   cmd.extend(["-out", rsa_key])
-  p1 = common.Run(cmd, stdout=subprocess.PIPE)
-  p1.wait()
+  p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
+  p1.communicate()
   assert p1.returncode == 0, "openssl pkcs8 failed"
 
   # Stage the output zip package for signing.
@@ -1204,8 +1207,8 @@
          "--target_image", target_file]
   if source_file is not None:
     cmd.extend(["--source_image", source_file])
-  p1 = common.Run(cmd, stdout=subprocess.PIPE)
-  p1.wait()
+  p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
+  p1.communicate()
   assert p1.returncode == 0, "brillo_update_payload generate failed"
 
   # 2. Generate hashes of the payload and metadata files.
@@ -1216,8 +1219,8 @@
          "--signature_size", "256",
          "--metadata_hash_file", metadata_sig_file,
          "--payload_hash_file", payload_sig_file]
-  p1 = common.Run(cmd, stdout=subprocess.PIPE)
-  p1.wait()
+  p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
+  p1.communicate()
   assert p1.returncode == 0, "brillo_update_payload hash failed"
 
   # 3. Sign the hashes and insert them back into the payload file.
@@ -1231,8 +1234,8 @@
          "-pkeyopt", "digest:sha256",
          "-in", payload_sig_file,
          "-out", signed_payload_sig_file]
-  p1 = common.Run(cmd, stdout=subprocess.PIPE)
-  p1.wait()
+  p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
+  p1.communicate()
   assert p1.returncode == 0, "openssl sign payload failed"
 
   # 3b. Sign the metadata hash.
@@ -1241,8 +1244,8 @@
          "-pkeyopt", "digest:sha256",
          "-in", metadata_sig_file,
          "-out", signed_metadata_sig_file]
-  p1 = common.Run(cmd, stdout=subprocess.PIPE)
-  p1.wait()
+  p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
+  p1.communicate()
   assert p1.returncode == 0, "openssl sign metadata failed"
 
   # 3c. Insert the signatures back into the payload file.
@@ -1254,8 +1257,8 @@
          "--signature_size", "256",
          "--metadata_signature_file", signed_metadata_sig_file,
          "--payload_signature_file", signed_payload_sig_file]
-  p1 = common.Run(cmd, stdout=subprocess.PIPE)
-  p1.wait()
+  p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
+  p1.communicate()
   assert p1.returncode == 0, "brillo_update_payload sign failed"
 
   # 4. Dump the signed payload properties.
@@ -1264,8 +1267,8 @@
   cmd = ["brillo_update_payload", "properties",
          "--payload", signed_payload_file,
          "--properties_file", properties_file]
-  p1 = common.Run(cmd, stdout=subprocess.PIPE)
-  p1.wait()
+  p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
+  p1.communicate()
   assert p1.returncode == 0, "brillo_update_payload properties failed"
 
   # Add the signed payload file and properties into the zip.
diff --git a/tools/signapk/src/com/android/signapk/SignApk.java b/tools/signapk/src/com/android/signapk/SignApk.java
index d84d070..5ba0666 100644
--- a/tools/signapk/src/com/android/signapk/SignApk.java
+++ b/tools/signapk/src/com/android/signapk/SignApk.java
@@ -445,7 +445,8 @@
             int extraPaddingSizeBytes = 0;
             if (alignment > 0) {
                 long paddingStartOffset = offset + ALIGNMENT_ZIP_EXTRA_DATA_FIELD_MIN_SIZE_BYTES;
-                extraPaddingSizeBytes = alignment - (int) (paddingStartOffset % alignment);
+                extraPaddingSizeBytes =
+                        (alignment - (int) (paddingStartOffset % alignment)) % alignment;
             }
             byte[] extra =
                     new byte[ALIGNMENT_ZIP_EXTRA_DATA_FIELD_MIN_SIZE_BYTES + extraPaddingSizeBytes];