aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Groover <mpgroover@google.com>2018-07-06 18:55:19 -0700
committerMichael Groover <mpgroover@google.com>2018-07-06 18:55:19 -0700
commitdded3f8f4dc262b42d6b47f05262ac42806368e8 (patch)
tree42df71936ac221fecc812a5a7cb4d006ef002cae
parente74151cdd2c79d057be46be651943e9bfbd7ba94 (diff)
downloadapksig-android-o-mr1-iot-release-1.0.2.tar.gz
Add apksig Signature Scheme v3 testsandroid-p-preview-5android-o-mr1-iot-release-1.0.2
Change-Id: Ia8504c2bec9ace8856901d12d328371f0b560a5a Fixes: 111221005 Test: gradlew test
-rw-r--r--src/apksigner/java/com/android/apksigner/ApkSignerTool.java43
-rw-r--r--src/apksigner/java/com/android/apksigner/help_lineage.txt2
-rw-r--r--src/apksigner/java/com/android/apksigner/help_rotate.txt3
-rw-r--r--src/apksigner/java/com/android/apksigner/help_sign.txt3
-rw-r--r--src/main/java/com/android/apksig/SigningCertificateLineage.java127
-rw-r--r--src/main/java/com/android/apksig/internal/apk/v3/V3SchemeSigner.java2
-rw-r--r--src/test/java/com/android/apksig/AllTests.java1
-rw-r--r--src/test/java/com/android/apksig/ApkSignerTest.java481
-rw-r--r--src/test/java/com/android/apksig/ApkVerifierTest.java167
-rw-r--r--src/test/java/com/android/apksig/SigningCertificateLineageTest.java121
-rw-r--r--src/test/java/com/android/apksig/internal/util/Resources.java25
-rw-r--r--src/test/resources/com/android/apksig/golden-aligned-out.apkbin12865 -> 12865 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-aligned-v1-out.apkbin12865 -> 7707 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-aligned-v1v2-out.apkbin12865 -> 12865 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-aligned-v1v2v3-lineage-out.apkbin0 -> 16961 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-aligned-v1v2v3-out.apkbin0 -> 12865 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-aligned-v2-out.apkbin12666 -> 12666 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-aligned-v2v3-lineage-out.apkbin0 -> 16762 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-aligned-v2v3-out.apkbin0 -> 12666 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-aligned-v3-lineage-out.apkbin0 -> 12666 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-aligned-v3-out.apkbin0 -> 12666 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-legacy-aligned-out.apkbin12865 -> 12865 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-legacy-aligned-v1-out.apkbin12865 -> 7703 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2-out.apkbin12865 -> 12865 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2v3-lineage-out.apkbin0 -> 16961 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2v3-out.apkbin0 -> 12865 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-legacy-aligned-v2-out.apkbin12666 -> 12666 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-legacy-aligned-v2v3-lineage-out.apkbin0 -> 16762 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-legacy-aligned-v2v3-out.apkbin0 -> 12666 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-legacy-aligned-v3-lineage-out.apkbin0 -> 12666 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-legacy-aligned-v3-out.apkbin0 -> 12666 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-1-out.apkbin12695 -> 12695 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-18-out.apkbin12695 -> 12695 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-24-out.apkbin12695 -> 12695 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-rsa-out.apkbin12695 -> 12695 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-unaligned-out.apkbin12926 -> 12926 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-unaligned-v1-out.apkbin12926 -> 6177 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-unaligned-v1v2-out.apkbin12926 -> 12926 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-unaligned-v1v2v3-lineage-out.apkbin0 -> 17022 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-unaligned-v1v2v3-out.apkbin0 -> 12926 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-unaligned-v2-out.apkbin8631 -> 8631 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-unaligned-v2v3-lineage-out.apkbin0 -> 12727 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-unaligned-v2v3-out.apkbin0 -> 8631 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-unaligned-v3-lineage-out.apkbin0 -> 8631 bytes
-rw-r--r--src/test/resources/com/android/apksig/golden-unaligned-v3-out.apkbin0 -> 8631 bytes
-rw-r--r--src/test/resources/com/android/apksig/rsa-1024-lineage-2-signersbin0 -> 1210 bytes
-rw-r--r--src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers-invalid-lineage-attr.apkbin0 -> 16791 bytes
-rw-r--r--src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers-invalid-zip.apkbin0 -> 16791 bytes
-rw-r--r--src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers-no-sig-block.apkbin0 -> 16791 bytes
-rw-r--r--src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers.apkbin0 -> 16791 bytes
-rw-r--r--src/test/resources/com/android/apksig/v2v3-signed-v3-block-stripped.apkbin0 -> 28880 bytes
-rw-r--r--src/test/resources/com/android/apksig/v2v3-unknown-additional-attr.apkbin0 -> 16592 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-cert-and-public-key-mismatch.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-empty.apkbin0 -> 22 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-no-certs-in-sig.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-no-supported-sig-algs.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-signatures-and-digests-block-mismatch.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-unknown-additional-attr.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-unknown-pair-in-apk-sig-block.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-1024.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-2048-sig-does-not-verify.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-2048.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-3072-digest-mismatch.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-3072.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha256-p256.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha256-p384.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha256-p521.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p256.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p384-wrong-apk-sig-block-magic.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p384.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p521-sig-does-not-verify.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p521.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-ignorable-unsupported-sig-algs.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-1024.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-16384.apkbin0 -> 20688 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-2048.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-3072-sig-does-not-verify.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-3072.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-4096.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-8192.apkbin0 -> 16592 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-1024.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-16384.apkbin0 -> 20688 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-2048.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-3072.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-4096-apk-sig-block-size-mismatch.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-4096.apkbin0 -> 12496 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-8192-digest-mismatch.apkbin0 -> 16592 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-8192.apkbin0 -> 16592 bytes
-rw-r--r--src/test/resources/com/android/apksig/v3-stripped.apkbin0 -> 28880 bytes
89 files changed, 902 insertions, 73 deletions
diff --git a/src/apksigner/java/com/android/apksigner/ApkSignerTool.java b/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
index 4713bab..aeb02cd 100644
--- a/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
+++ b/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
@@ -20,7 +20,10 @@ import com.android.apksig.ApkSigner;
import com.android.apksig.ApkVerifier;
import com.android.apksig.SigningCertificateLineage;
import com.android.apksig.SigningCertificateLineage.SignerCapabilities;
+import com.android.apksig.apk.ApkFormatException;
import com.android.apksig.apk.MinSdkVersionException;
+import com.android.apksig.util.DataSource;
+import com.android.apksig.util.DataSources;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
@@ -31,6 +34,9 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -85,6 +91,8 @@ public class ApkSignerTool {
private static MessageDigest sha1 = null;
private static MessageDigest md5 = null;
+ public static final int ZIP_MAGIC = 0x04034b50;
+
public static void main(String[] params) throws Exception {
if ((params.length == 0) || ("--help".equals(params[0])) || ("-h".equals(params[0]))) {
printUsage(HELP_PAGE_GENERAL);
@@ -217,7 +225,7 @@ public class ApkSignerTool {
signerParams.certFile = optionsParser.getRequiredValue("Certificate file");
} else if ("lineage".equals(optionName)) {
File lineageFile = new File(optionsParser.getRequiredValue("Lineage File"));
- lineage = SigningCertificateLineage.readFromFile(lineageFile);
+ lineage = getLineageFromInputFile(lineageFile);
} else if (("v".equals(optionName)) || ("verbose".equals(optionName))) {
verbose = optionsParser.getOptionalBooleanValue(true);
} else if ("next-provider".equals(optionName)) {
@@ -651,7 +659,7 @@ public class ApkSignerTool {
SigningCertificateLineage lineage;
if (inputKeyLineage != null) {
// we already have history, add the new key to the end of it
- lineage = SigningCertificateLineage.readFromFile(inputKeyLineage);
+ lineage = getLineageFromInputFile(inputKeyLineage);
lineage.updateSignerCapabilities(oldSignerConfig,
oldSignerParams.signerCapabilitiesBuilder.build());
lineage = lineage.spawnDescendant(oldSignerConfig,
@@ -682,6 +690,7 @@ public class ApkSignerTool {
boolean verbose = false;
boolean printCerts = false;
boolean lineageUpdated = false;
+ File inputKeyLineage = null;
File outputKeyLineage = null;
String optionName;
OptionsParser optionsParser = new OptionsParser(params);
@@ -692,8 +701,7 @@ public class ApkSignerTool {
printUsage(HELP_PAGE_LINEAGE);
return;
} else if ("in".equals(optionName)) {
- File inputKeyLineage = new File(optionsParser.getRequiredValue("Input file name"));
- lineage = SigningCertificateLineage.readFromFile(inputKeyLineage);
+ inputKeyLineage = new File(optionsParser.getRequiredValue("Input file name"));
} else if ("out".equals(optionName)) {
outputKeyLineage = new File(optionsParser.getRequiredValue("Output file name"));
} else if ("signer".equals(optionName)) {
@@ -709,9 +717,11 @@ public class ApkSignerTool {
+ ". See --help for supported options.");
}
}
- if (lineage == null) {
+ if (inputKeyLineage == null) {
throw new ParameterException("Input lineage file parameter not present");
}
+ lineage = getLineageFromInputFile(inputKeyLineage);
+
try (PasswordRetriever passwordRetriever = new PasswordRetriever()) {
for (int i = 0; i < signers.size(); i++) {
SignerParams signerParams = signers.get(i);
@@ -775,6 +785,29 @@ public class ApkSignerTool {
}
}
+ /**
+ * Extracts the Signing Certificate Lineage from the provided lineage or APK file.
+ */
+ private static SigningCertificateLineage getLineageFromInputFile(File inputLineageFile)
+ throws ParameterException {
+ try (RandomAccessFile f = new RandomAccessFile(inputLineageFile, "r")) {
+ if (f.length() < 4) {
+ throw new ParameterException("The input file is not a valid lineage file.");
+ }
+ DataSource apk = DataSources.asDataSource(f);
+ int magicValue = apk.getByteBuffer(0, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();
+ if (magicValue == SigningCertificateLineage.MAGIC) {
+ return SigningCertificateLineage.readFromFile(inputLineageFile);
+ } else if (magicValue == ZIP_MAGIC) {
+ return SigningCertificateLineage.readFromApkFile(inputLineageFile);
+ } else {
+ throw new ParameterException("The input file is not a valid lineage file.");
+ }
+ } catch (IOException | ApkFormatException | IllegalArgumentException e) {
+ throw new ParameterException(e.getMessage());
+ }
+ }
+
private static SignerParams processSignerParams(OptionsParser optionsParser)
throws OptionsParser.OptionsException, ParameterException {
SignerParams signerParams = new SignerParams();
diff --git a/src/apksigner/java/com/android/apksigner/help_lineage.txt b/src/apksigner/java/com/android/apksigner/help_lineage.txt
index 0816176..3f4922d 100644
--- a/src/apksigner/java/com/android/apksigner/help_lineage.txt
+++ b/src/apksigner/java/com/android/apksigner/help_lineage.txt
@@ -10,6 +10,8 @@ has been migrated to the new signing certificate.
--in Input SigningCertificateLineage. This file contains a binary representation of
a SigningCertificateLineage object which contains the proof-of-rotation for
different signing certificates.
+ An APK previously signed with a SigningCertificateLineage can also be
+ specified; the lineage will then be read from the signed data in the APK.
--out File into which to put the binary representation of a
SigningCertificateLineage object.
diff --git a/src/apksigner/java/com/android/apksigner/help_rotate.txt b/src/apksigner/java/com/android/apksigner/help_rotate.txt
index cd569e7..ff58372 100644
--- a/src/apksigner/java/com/android/apksigner/help_rotate.txt
+++ b/src/apksigner/java/com/android/apksigner/help_rotate.txt
@@ -10,7 +10,8 @@ new, for use in a key rotation scenario using APK Signature Scheme v3.
a SigningCertificateLineage object, which contains the proof-of-rotation for
different signing certificates. This can be used with APK Signature Scheme v3
to rotate the signing certificate for an APK.
-
+ An APK previously signed with a SigningCertificateLineage can also be
+ specified; the lineage will then be read from the signed data in the APK.
--out File into which to put the binary representation of a
SigningCertificateLineage object.
diff --git a/src/apksigner/java/com/android/apksigner/help_sign.txt b/src/apksigner/java/com/android/apksigner/help_sign.txt
index 07d0f03..b9d0146 100644
--- a/src/apksigner/java/com/android/apksigner/help_sign.txt
+++ b/src/apksigner/java/com/android/apksigner/help_sign.txt
@@ -73,6 +73,9 @@ certificate.
points in the lineage and will be used on older platform
versions when the newest signer in the lineage is
unsupported.
+ An APK previously signed with a SigningCertificateLineage
+ can also be specified; the lineage will then be read from
+ the signed data in the APK.
-h, --help Show help about this command and exit
diff --git a/src/main/java/com/android/apksig/SigningCertificateLineage.java b/src/main/java/com/android/apksig/SigningCertificateLineage.java
index a27cbe9..54340d7 100644
--- a/src/main/java/com/android/apksig/SigningCertificateLineage.java
+++ b/src/main/java/com/android/apksig/SigningCertificateLineage.java
@@ -19,17 +19,21 @@ package com.android.apksig;
import static com.android.apksig.internal.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
import com.android.apksig.apk.ApkFormatException;
+import com.android.apksig.apk.ApkUtils;
import com.android.apksig.internal.apk.ApkSigningBlockUtils;
import com.android.apksig.internal.apk.SignatureAlgorithm;
+import com.android.apksig.internal.apk.SignatureInfo;
import com.android.apksig.internal.apk.v3.V3SchemeSigner;
import com.android.apksig.internal.apk.v3.V3SigningCertificateLineage;
import com.android.apksig.internal.apk.v3.V3SigningCertificateLineage.SigningCertificateNode;
import com.android.apksig.internal.util.AndroidSdkVersion;
+import com.android.apksig.internal.util.ByteBufferUtils;
import com.android.apksig.internal.util.Pair;
import com.android.apksig.internal.util.RandomAccessFileDataSink;
import com.android.apksig.util.DataSink;
import com.android.apksig.util.DataSource;
import com.android.apksig.util.DataSources;
+import com.android.apksig.zip.ZipFormatException;
import java.io.File;
import java.io.IOException;
@@ -66,7 +70,7 @@ import java.util.List;
*/
public class SigningCertificateLineage {
- private final static int MAGIC = 0x3eff39d1;
+ public final static int MAGIC = 0x3eff39d1;
private final static int FIRST_VERSION = 1;
@@ -157,8 +161,125 @@ public class SigningCertificateLineage {
return new SigningCertificateLineage(minSdkVersion, parsedLineage);
}
- public static SigningCertificateLineage readFromApkFile(File apkFile) {
- throw new UnsupportedOperationException("Not yet implemented");
+ /**
+ * Extracts a Signing Certificate Lineage from the proof-of-rotation attribute in the V3
+ * signature block of the provided APK File.
+ *
+ * @throws IllegalArgumentException if the provided APK does not contain a V3 signature block,
+ * or if the V3 signature block does not contain a valid lineage.
+ */
+ public static SigningCertificateLineage readFromApkFile(File apkFile)
+ throws IOException, ApkFormatException {
+ try (RandomAccessFile f = new RandomAccessFile(apkFile, "r")) {
+ DataSource apk = DataSources.asDataSource(f, 0, f.length());
+ return readFromApkDataSource(apk);
+ }
+ }
+
+ /**
+ * Extracts a Signing Certificate Lineage from the proof-of-rotation attribute in the V3
+ * signature block of the provided APK DataSource.
+ *
+ * @throws IllegalArgumentException if the provided APK does not contain a V3 signature block,
+ * or if the V3 signature block does not contain a valid lineage.
+ */
+ public static SigningCertificateLineage readFromApkDataSource(DataSource apk)
+ throws IOException, ApkFormatException {
+ SignatureInfo signatureInfo;
+ try {
+ ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(apk);
+ ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result(
+ ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3);
+ signatureInfo =
+ ApkSigningBlockUtils.findSignature(apk, zipSections,
+ V3SchemeSigner.APK_SIGNATURE_SCHEME_V3_BLOCK_ID, result);
+ } catch (ZipFormatException e) {
+ throw new ApkFormatException(e.getMessage());
+ } catch (ApkSigningBlockUtils.SignatureNotFoundException e) {
+ throw new IllegalArgumentException(
+ "The provided APK does not contain a valid V3 signature block.");
+ }
+
+ // FORMAT:
+ // * length-prefixed sequence of length-prefixed signers:
+ // * length-prefixed signed data
+ // * minSDK
+ // * maxSDK
+ // * length-prefixed sequence of length-prefixed signatures
+ // * length-prefixed public key
+ ByteBuffer signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
+ List<SigningCertificateLineage> lineages = new ArrayList<>(1);
+ while (signers.hasRemaining()) {
+ ByteBuffer signer = getLengthPrefixedSlice(signers);
+ ByteBuffer signedData = getLengthPrefixedSlice(signer);
+ try {
+ SigningCertificateLineage lineage = readFromSignedData(signedData);
+ lineages.add(lineage);
+ } catch (IllegalArgumentException ignored) {
+ // The current signer block does not contain a valid lineage, but it is possible
+ // another block will.
+ }
+ }
+ SigningCertificateLineage result;
+ if (lineages.isEmpty()) {
+ throw new IllegalArgumentException(
+ "The provided APK does not contain a valid lineage.");
+ } else if (lineages.size() > 1) {
+ result = consolidateLineages(lineages);
+ } else {
+ result = lineages.get(0);
+ }
+ return result;
+ }
+
+ /**
+ * Extracts a Signing Certificate Lineage from the proof-of-rotation attribute in the provided
+ * signed data portion of a signer in a V3 signature block.
+ *
+ * @throws IllegalArgumentException if the provided signed data does not contain a valid
+ * lineage.
+ */
+ public static SigningCertificateLineage readFromSignedData(ByteBuffer signedData)
+ throws IOException, ApkFormatException {
+ // FORMAT:
+ // * length-prefixed sequence of length-prefixed digests:
+ // * length-prefixed sequence of certificates:
+ // * length-prefixed bytes: X.509 certificate (ASN.1 DER encoded).
+ // * uint-32: minSdkVersion
+ // * uint-32: maxSdkVersion
+ // * length-prefixed sequence of length-prefixed additional attributes:
+ // * uint32: ID
+ // * (length - 4) bytes: value
+ // * uint32: Proof-of-rotation ID: 0x3ba06f8c
+ // * length-prefixed proof-of-rotation structure
+ // consume the digests through the maxSdkVersion to reach the lineage in the attributes
+ getLengthPrefixedSlice(signedData);
+ getLengthPrefixedSlice(signedData);
+ signedData.getInt();
+ signedData.getInt();
+ // iterate over the additional attributes adding any lineages to the List
+ ByteBuffer additionalAttributes = getLengthPrefixedSlice(signedData);
+ List<SigningCertificateLineage> lineages = new ArrayList<>(1);
+ while (additionalAttributes.hasRemaining()) {
+ ByteBuffer attribute = getLengthPrefixedSlice(additionalAttributes);
+ int id = attribute.getInt();
+ if (id == V3SchemeSigner.PROOF_OF_ROTATION_ATTR_ID) {
+ byte[] value = ByteBufferUtils.toByteArray(attribute);
+ SigningCertificateLineage lineage = readFromV3AttributeValue(value);
+ lineages.add(lineage);
+ }
+ }
+ SigningCertificateLineage result;
+ // There should only be a single attribute with the lineage, but if there are multiple then
+ // attempt to consolidate the lineages.
+ if (lineages.isEmpty()) {
+ throw new IllegalArgumentException("The signed data does not contain a valid lineage.");
+ } else if (lineages.size() > 1) {
+ result = consolidateLineages(lineages);
+ } else {
+ result = lineages.get(0);
+ }
+ return result;
}
public void writeToFile(File file) throws IOException {
diff --git a/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeSigner.java b/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeSigner.java
index 5545d6c..fc70a0a 100644
--- a/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeSigner.java
+++ b/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeSigner.java
@@ -59,7 +59,7 @@ import java.util.Map;
*/
public abstract class V3SchemeSigner {
- private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
+ public static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
/** Hidden constructor to prevent instantiation. */
private V3SchemeSigner() {}
diff --git a/src/test/java/com/android/apksig/AllTests.java b/src/test/java/com/android/apksig/AllTests.java
index cd5c909..4a9243d 100644
--- a/src/test/java/com/android/apksig/AllTests.java
+++ b/src/test/java/com/android/apksig/AllTests.java
@@ -23,6 +23,7 @@ import org.junit.runners.Suite;
@Suite.SuiteClasses({
ApkSignerTest.class,
ApkVerifierTest.class,
+ SigningCertificateLineageTest.class,
com.android.apksig.apk.AllTests.class,
com.android.apksig.internal.AllTests.class,
com.android.apksig.util.AllTests.class,
diff --git a/src/test/java/com/android/apksig/ApkSignerTest.java b/src/test/java/com/android/apksig/ApkSignerTest.java
index 80f35ba..1434017 100644
--- a/src/test/java/com/android/apksig/ApkSignerTest.java
+++ b/src/test/java/com/android/apksig/ApkSignerTest.java
@@ -16,10 +16,12 @@
package com.android.apksig;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.apksig.ApkVerifier.Issue;
import com.android.apksig.apk.ApkFormatException;
+import com.android.apksig.internal.util.ByteBufferDataSource;
import com.android.apksig.internal.util.Resources;
import com.android.apksig.util.DataSinks;
import com.android.apksig.util.DataSource;
@@ -35,6 +37,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SignatureException;
import java.security.cert.X509Certificate;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
@@ -50,6 +53,15 @@ public class ApkSignerTest {
*/
private static final boolean KEEP_FAILING_OUTPUT_AS_FILES = false;
+ // All signers with the same prefix and an _X suffix were signed with the private key of the
+ // (X-1) signer.
+ private static final String FIRST_RSA_2048_SIGNER_RESOURCE_NAME = "rsa-2048";
+ private static final String SECOND_RSA_2048_SIGNER_RESOURCE_NAME = "rsa-2048_2";
+ private static final String THIRD_RSA_2048_SIGNER_RESOURCE_NAME = "rsa-2048_3";
+
+ private static final String LINEAGE_RSA_2048_2_SIGNERS_RESOURCE_NAME =
+ "rsa-2048-lineage-2-signers";
+
public static void main(String[] params) throws Exception {
File outDir = (params.length > 0) ? new File(params[0]) : new File(".");
generateGoldenFiles(outDir);
@@ -63,7 +75,13 @@ public class ApkSignerTest {
throw new IOException("Failed to create directory: " + outDir);
}
List<ApkSigner.SignerConfig> rsa2048SignerConfig =
- Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048"));
+ Collections.singletonList(
+ getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
+ List<ApkSigner.SignerConfig> rsa2048SignerConfigWithLineage = Arrays.asList(
+ rsa2048SignerConfig.get(0),
+ getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME));
+ SigningCertificateLineage lineage = Resources.toSigningCertificateLineage(
+ ApkSignerTest.class, LINEAGE_RSA_2048_2_SIGNERS_RESOURCE_NAME);
signGolden(
"golden-unaligned-in.apk",
@@ -83,58 +101,205 @@ public class ApkSignerTest {
new File(outDir, "golden-unaligned-v1-out.apk"),
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(true)
- .setV2SigningEnabled(false));
+ .setV2SigningEnabled(false)
+ .setV3SigningEnabled(false));
signGolden(
"golden-legacy-aligned-in.apk",
new File(outDir, "golden-legacy-aligned-v1-out.apk"),
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(true)
- .setV2SigningEnabled(false));
+ .setV2SigningEnabled(false)
+ .setV3SigningEnabled(false));
signGolden(
"golden-aligned-in.apk",
new File(outDir, "golden-aligned-v1-out.apk"),
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(true)
- .setV2SigningEnabled(false));
+ .setV2SigningEnabled(false)
+ .setV3SigningEnabled(false));
signGolden(
"golden-unaligned-in.apk",
new File(outDir, "golden-unaligned-v2-out.apk"),
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(false)
- .setV2SigningEnabled(true));
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(false));
signGolden(
"golden-legacy-aligned-in.apk",
new File(outDir, "golden-legacy-aligned-v2-out.apk"),
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(false)
- .setV2SigningEnabled(true));
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(false));
signGolden(
"golden-aligned-in.apk",
new File(outDir, "golden-aligned-v2-out.apk"),
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(false)
- .setV2SigningEnabled(true));
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(false));
+
+ signGolden(
+ "golden-unaligned-in.apk",
+ new File(outDir, "golden-unaligned-v3-out.apk"),
+ new ApkSigner.Builder(rsa2048SignerConfig)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(false)
+ .setV3SigningEnabled(true));
+ signGolden(
+ "golden-legacy-aligned-in.apk",
+ new File(outDir, "golden-legacy-aligned-v3-out.apk"),
+ new ApkSigner.Builder(rsa2048SignerConfig)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(false)
+ .setV3SigningEnabled(true));
+ signGolden(
+ "golden-aligned-in.apk",
+ new File(outDir, "golden-aligned-v3-out.apk"),
+ new ApkSigner.Builder(rsa2048SignerConfig)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(false)
+ .setV3SigningEnabled(true));
+
+ signGolden(
+ "golden-unaligned-in.apk",
+ new File(outDir, "golden-unaligned-v3-lineage-out.apk"),
+ new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(false)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
+ signGolden(
+ "golden-legacy-aligned-in.apk",
+ new File(outDir, "golden-legacy-aligned-v3-lineage-out.apk"),
+ new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(false)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
+ signGolden(
+ "golden-aligned-in.apk",
+ new File(outDir, "golden-aligned-v3-lineage-out.apk"),
+ new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(false)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
signGolden(
"golden-unaligned-in.apk",
new File(outDir, "golden-unaligned-v1v2-out.apk"),
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(true)
- .setV2SigningEnabled(true));
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(false));
signGolden(
"golden-legacy-aligned-in.apk",
new File(outDir, "golden-legacy-aligned-v1v2-out.apk"),
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(true)
- .setV2SigningEnabled(true));
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(false));
signGolden(
"golden-aligned-in.apk",
new File(outDir, "golden-aligned-v1v2-out.apk"),
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(true)
- .setV2SigningEnabled(true));
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(false));
+
+ signGolden(
+ "golden-unaligned-in.apk",
+ new File(outDir, "golden-unaligned-v2v3-out.apk"),
+ new ApkSigner.Builder(rsa2048SignerConfig)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true));
+ signGolden(
+ "golden-legacy-aligned-in.apk",
+ new File(outDir, "golden-legacy-aligned-v2v3-out.apk"),
+ new ApkSigner.Builder(rsa2048SignerConfig)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true));
+ signGolden(
+ "golden-aligned-in.apk",
+ new File(outDir, "golden-aligned-v2v3-out.apk"),
+ new ApkSigner.Builder(rsa2048SignerConfig)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true));
+ signGolden(
+ "golden-unaligned-in.apk",
+ new File(outDir, "golden-unaligned-v2v3-lineage-out.apk"),
+ new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
+ signGolden(
+ "golden-legacy-aligned-in.apk",
+ new File(outDir, "golden-legacy-aligned-v2v3-lineage-out.apk"),
+ new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
+ signGolden(
+ "golden-aligned-in.apk",
+ new File(outDir, "golden-aligned-v2v3-lineage-out.apk"),
+ new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
+ signGolden(
+ "golden-unaligned-in.apk",
+ new File(outDir, "golden-unaligned-v1v2v3-out.apk"),
+ new ApkSigner.Builder(rsa2048SignerConfig)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true));
+ signGolden(
+ "golden-legacy-aligned-in.apk",
+ new File(outDir, "golden-legacy-aligned-v1v2v3-out.apk"),
+ new ApkSigner.Builder(rsa2048SignerConfig)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true));
+ signGolden(
+ "golden-aligned-in.apk",
+ new File(outDir, "golden-aligned-v1v2v3-out.apk"),
+ new ApkSigner.Builder(rsa2048SignerConfig)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true));
+ signGolden(
+ "golden-unaligned-in.apk",
+ new File(outDir, "golden-unaligned-v1v2v3-lineage-out.apk"),
+ new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
+ signGolden(
+ "golden-legacy-aligned-in.apk",
+ new File(outDir, "golden-legacy-aligned-v1v2v3-lineage-out.apk"),
+ new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
+ signGolden(
+ "golden-aligned-in.apk",
+ new File(outDir, "golden-aligned-v1v2v3-lineage-out.apk"),
+ new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
signGolden(
"original.apk", new File(outDir, "golden-rsa-out.apk"),
@@ -169,8 +334,13 @@ public class ApkSignerTest {
// NOTE: Expected output files can be re-generated by running the "main" method.
List<ApkSigner.SignerConfig> rsa2048SignerConfig =
- Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048"));
-
+ Collections.singletonList(
+ getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
+ List<ApkSigner.SignerConfig> rsa2048SignerConfigWithLineage = Arrays.asList(
+ rsa2048SignerConfig.get(0),
+ getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME));
+ SigningCertificateLineage lineage = Resources.toSigningCertificateLineage(getClass(),
+ LINEAGE_RSA_2048_2_SIGNERS_RESOURCE_NAME);
// Uncompressed entries in this input file are not aligned -- the file was created using
// the jar utility. temp4.txt entry was then manually added into the archive. This entry's
// ZIP Local File Header "extra" field declares that the entry's data must be aligned to
@@ -182,17 +352,59 @@ public class ApkSignerTest {
"golden-unaligned-in.apk", "golden-unaligned-v1-out.apk",
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(true)
- .setV2SigningEnabled(false));
+ .setV2SigningEnabled(false)
+ .setV3SigningEnabled(false));
assertGolden(
"golden-unaligned-in.apk", "golden-unaligned-v2-out.apk",
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(false)
- .setV2SigningEnabled(true));
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(false));
+ assertGolden(
+ "golden-unaligned-in.apk", "golden-unaligned-v3-out.apk",
+ new ApkSigner.Builder(rsa2048SignerConfig)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(false)
+ .setV3SigningEnabled(true));
+ assertGolden(
+ "golden-unaligned-in.apk", "golden-unaligned-v3-lineage-out.apk",
+ new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(false)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
assertGolden(
"golden-unaligned-in.apk", "golden-unaligned-v1v2-out.apk",
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(true)
- .setV2SigningEnabled(true));
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(false));
+ assertGolden(
+ "golden-unaligned-in.apk", "golden-unaligned-v2v3-out.apk",
+ new ApkSigner.Builder(rsa2048SignerConfig)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true));
+ assertGolden(
+ "golden-unaligned-in.apk", "golden-unaligned-v2v3-lineage-out.apk",
+ new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
+ assertGolden(
+ "golden-unaligned-in.apk", "golden-unaligned-v1v2v3-out.apk",
+ new ApkSigner.Builder(rsa2048SignerConfig)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true));
+ assertGolden(
+ "golden-unaligned-in.apk", "golden-unaligned-v1v2v3-lineage-out.apk",
+ new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
// Uncompressed entries in this input file are aligned by zero-padding the "extra" field, as
// performed by zipalign at the time of writing. This padding technique produces ZIP
@@ -205,17 +417,59 @@ public class ApkSignerTest {
"golden-legacy-aligned-in.apk", "golden-legacy-aligned-v1-out.apk",
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(true)
- .setV2SigningEnabled(false));
+ .setV2SigningEnabled(false)
+ .setV3SigningEnabled(false));
assertGolden(
"golden-legacy-aligned-in.apk", "golden-legacy-aligned-v2-out.apk",
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(false)
- .setV2SigningEnabled(true));
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(false));
+ assertGolden(
+ "golden-legacy-aligned-in.apk", "golden-legacy-aligned-v3-out.apk",
+ new ApkSigner.Builder(rsa2048SignerConfig)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(false)
+ .setV3SigningEnabled(true));
+ assertGolden(
+ "golden-legacy-aligned-in.apk", "golden-legacy-aligned-v3-lineage-out.apk",
+ new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(false)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
assertGolden(
"golden-legacy-aligned-in.apk", "golden-legacy-aligned-v1v2-out.apk",
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(true)
- .setV2SigningEnabled(true));
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(false));
+ assertGolden(
+ "golden-legacy-aligned-in.apk", "golden-legacy-aligned-v2v3-out.apk",
+ new ApkSigner.Builder(rsa2048SignerConfig)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true));
+ assertGolden(
+ "golden-legacy-aligned-in.apk", "golden-legacy-aligned-v2v3-lineage-out.apk",
+ new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
+ assertGolden(
+ "golden-legacy-aligned-in.apk", "golden-legacy-aligned-v1v2v3-out.apk",
+ new ApkSigner.Builder(rsa2048SignerConfig)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true));
+ assertGolden(
+ "golden-legacy-aligned-in.apk", "golden-legacy-aligned-v1v2v3-lineage-out.apk",
+ new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
// Uncompressed entries in this input file are aligned by padding the "extra" field, as
// generated by signapk and apksigner. This padding technique produces "extra" fields which
@@ -227,17 +481,59 @@ public class ApkSignerTest {
"golden-aligned-in.apk", "golden-aligned-v1-out.apk",
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(true)
- .setV2SigningEnabled(false));
+ .setV2SigningEnabled(false)
+ .setV3SigningEnabled(false));
assertGolden(
"golden-aligned-in.apk", "golden-aligned-v2-out.apk",
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(false)
- .setV2SigningEnabled(true));
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(false));
+ assertGolden(
+ "golden-aligned-in.apk", "golden-aligned-v3-out.apk",
+ new ApkSigner.Builder(rsa2048SignerConfig)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(false)
+ .setV3SigningEnabled(true));
+ assertGolden(
+ "golden-aligned-in.apk", "golden-aligned-v3-lineage-out.apk",
+ new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(false)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
assertGolden(
"golden-aligned-in.apk", "golden-aligned-v1v2-out.apk",
new ApkSigner.Builder(rsa2048SignerConfig)
.setV1SigningEnabled(true)
- .setV2SigningEnabled(true));
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(false));
+ assertGolden(
+ "golden-aligned-in.apk", "golden-aligned-v2v3-out.apk",
+ new ApkSigner.Builder(rsa2048SignerConfig)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true));
+ assertGolden(
+ "golden-aligned-in.apk", "golden-aligned-v2v3-lineage-out.apk",
+ new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
+ assertGolden(
+ "golden-aligned-in.apk", "golden-aligned-v1v2v3-out.apk",
+ new ApkSigner.Builder(rsa2048SignerConfig)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true));
+ assertGolden(
+ "golden-aligned-in.apk", "golden-aligned-v1v2v3-lineage-out.apk",
+ new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
}
@Test
@@ -245,8 +541,8 @@ public class ApkSignerTest {
// Regression tests for minSdkVersion-based signature/digest algorithm selection
// NOTE: Expected output files can be re-generated by running the "main" method.
- List<ApkSigner.SignerConfig> rsaSignerConfig =
- Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048"));
+ List<ApkSigner.SignerConfig> rsaSignerConfig = Collections.singletonList(
+ getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
assertGolden("original.apk", "golden-rsa-out.apk", new ApkSigner.Builder(rsaSignerConfig));
assertGolden(
"original.apk", "golden-rsa-minSdkVersion-1-out.apk",
@@ -265,8 +561,8 @@ public class ApkSignerTest {
@Test
public void testRsaSignedVerifies() throws Exception {
- List<ApkSigner.SignerConfig> signers =
- Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048"));
+ List<ApkSigner.SignerConfig> signers = Collections.singletonList(
+ getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
String in = "original.apk";
// Sign so that the APK is guaranteed to verify on API Level 1+
@@ -318,8 +614,8 @@ public class ApkSignerTest {
public void testV1SigningRejectsInvalidZipEntryNames() throws Exception {
// ZIP/JAR entry name cannot contain CR, LF, or NUL characters when the APK is being
// JAR-signed.
- List<ApkSigner.SignerConfig> signers =
- Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048"));
+ List<ApkSigner.SignerConfig> signers = Collections.singletonList(
+ getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
try {
sign("v1-only-with-cr-in-entry-name.apk",
new ApkSigner.Builder(signers).setV1SigningEnabled(true));
@@ -344,8 +640,8 @@ public class ApkSignerTest {
// Any ZIP compression method other than STORED is treated as DEFLATED by Android.
// This APK declares compression method 21 (neither STORED nor DEFLATED) for CERT.RSA entry,
// but the entry is actually Deflate-compressed.
- List<ApkSigner.SignerConfig> signers =
- Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048"));
+ List<ApkSigner.SignerConfig> signers = Collections.singletonList(
+ getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
sign("weird-compression-method.apk", new ApkSigner.Builder(signers));
}
@@ -355,8 +651,8 @@ public class ApkSignerTest {
// uses the compressionMethod from Central Directory instead.
// In this APK, compression method of CERT.RSA is declared as STORED in Local File Header
// and as DEFLATED in Central Directory. The entry is actually Deflate-compressed.
- List<ApkSigner.SignerConfig> signers =
- Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048"));
+ List<ApkSigner.SignerConfig> signers = Collections.singletonList(
+ getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
sign("mismatched-compression-method.apk", new ApkSigner.Builder(signers));
}
@@ -364,8 +660,8 @@ public class ApkSignerTest {
public void testDebuggableApk() throws Exception {
// APK which uses a boolean value "true" in its android:debuggable
String apk = "debuggable-boolean.apk";
- List<ApkSigner.SignerConfig> signers =
- Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048"));
+ List<ApkSigner.SignerConfig> signers = Collections.singletonList(
+ getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
// Signing debuggable APKs is permitted by default
sign(apk, new ApkSigner.Builder(signers));
// Signing debuggable APK succeeds when explicitly requested
@@ -391,6 +687,125 @@ public class ApkSignerTest {
} catch (SignatureException expected) {}
}
+ @Test(expected = IllegalStateException.class)
+ public void testV3SigningWithSignersNotInLineageFails() throws Exception {
+ // APKs signed with the v3 scheme after a key rotation must specify the lineage containing
+ // the proof of rotation. This test verifies that the signing will fail if the provided
+ // signers are not in the specified lineage.
+ List<ApkSigner.SignerConfig> signers = Arrays.asList(
+ getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME),
+ getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME));
+ SigningCertificateLineage lineage = Resources.toSigningCertificateLineage(getClass(),
+ "rsa-1024-lineage-2-signers");
+ sign("original.apk", new ApkSigner.Builder(signers).setSigningCertificateLineage(lineage));
+ }
+
+ @Test
+ public void testSigningWithLineageRequiresOldestSignerForV1AndV2() throws Exception {
+ // After a key rotation the oldest signer must still be specified for v1 and v2 signing.
+ // The lineage contains the proof of rotation and will be used to determine the oldest
+ // signer.
+ ApkSigner.SignerConfig firstSigner = getDefaultSignerConfigFromResources(
+ FIRST_RSA_2048_SIGNER_RESOURCE_NAME);
+ ApkSigner.SignerConfig secondSigner = getDefaultSignerConfigFromResources(
+ SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
+ ApkSigner.SignerConfig thirdSigner = getDefaultSignerConfigFromResources(
+ THIRD_RSA_2048_SIGNER_RESOURCE_NAME);
+ SigningCertificateLineage lineage = Resources.toSigningCertificateLineage(getClass(),
+ "rsa-2048-lineage-3-signers");
+
+ // Verifies that the v1 signing scheme requires the oldest signer after a key rotation.
+ List<ApkSigner.SignerConfig> signers = Collections.singletonList(thirdSigner);
+ try {
+ sign("original.apk", new ApkSigner.Builder(signers)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(false)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
+ fail("The signing should have failed due to the oldest signer in the lineage not being"
+ + " provided for v1 signing");
+ } catch (IllegalArgumentException expected) {}
+
+ // Verifies that the v2 signing scheme requires the oldest signer after a key rotation.
+ try {
+ sign("original.apk", new ApkSigner.Builder(signers)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
+ fail("The signing should have failed due to the oldest signer in the lineage not being"
+ + " provided for v2 signing");
+ } catch (IllegalArgumentException expected) {}
+
+ // Verifies that when only the v3 signing scheme is requested the oldest signer does not
+ // need to be provided.
+ sign("original.apk", new ApkSigner.Builder(signers)
+ .setV1SigningEnabled(false)
+ .setV2SigningEnabled(false)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
+
+ // Verifies that an intermediate signer in the lineage is not sufficient to satisfy the
+ // requirement that the oldest signer be provided for v1 and v2 signing.
+ signers = Arrays.asList(secondSigner, thirdSigner);
+ try {
+ sign("original.apk", new ApkSigner.Builder(signers)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
+ fail("The signing should have failed due to the oldest signer in the lineage not being"
+ + " provided for v1/v2 signing");
+ } catch (IllegalArgumentException expected) {}
+
+ // Verifies that the signing is successful when the oldest and newest signers are provided
+ // and that intermediate signers are not required.
+ signers = Arrays.asList(firstSigner, thirdSigner);
+ sign("original.apk", new ApkSigner.Builder(signers)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testV3SigningWithMultipleSignersAndNoLineageFails() throws Exception {
+ // The v3 signing scheme does not support multiple signers; if multiple signers are provided
+ // it is assumed these signers are part of the lineage. This test verifies v3 signing
+ // fails if multiple signers are provided without a lineage.
+ ApkSigner.SignerConfig firstSigner = getDefaultSignerConfigFromResources(
+ FIRST_RSA_2048_SIGNER_RESOURCE_NAME);
+ ApkSigner.SignerConfig secondSigner = getDefaultSignerConfigFromResources(
+ SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
+ List<ApkSigner.SignerConfig> signers = Arrays.asList(firstSigner, secondSigner);
+ sign("original.apk", new ApkSigner.Builder(signers)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true));
+ }
+
+ @Test
+ public void testLineageCanBeReadAfterV3Signing() throws Exception {
+ SigningCertificateLineage.SignerConfig firstSigner = Resources.toLineageSignerConfig(
+ getClass(), FIRST_RSA_2048_SIGNER_RESOURCE_NAME);
+ SigningCertificateLineage.SignerConfig secondSigner = Resources.toLineageSignerConfig(
+ getClass(), SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
+ SigningCertificateLineage lineage = new SigningCertificateLineage.Builder(firstSigner,
+ secondSigner).build();
+ List<ApkSigner.SignerConfig> signerConfigs = Arrays.asList(
+ getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME),
+ getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME));
+ DataSource out = sign("original.apk", new ApkSigner.Builder(signerConfigs)
+ .setV3SigningEnabled(true)
+ .setSigningCertificateLineage(lineage));
+ SigningCertificateLineage lineageFromApk = SigningCertificateLineage.readFromApkDataSource(
+ out);
+ assertTrue("The first signer was not in the lineage from the signed APK",
+ lineageFromApk.isSignerInLineage((firstSigner)));
+ assertTrue("The second signer was not in the lineage from the signed APK",
+ lineageFromApk.isSignerInLineage((secondSigner)));
+ }
+
/**
* Asserts that signing the specified golden input file using the provided signing
* configuration produces output identical to the specified golden output file.
diff --git a/src/test/java/com/android/apksig/ApkVerifierTest.java b/src/test/java/com/android/apksig/ApkVerifierTest.java
index 6f6c04d..1f9e208 100644
--- a/src/test/java/com/android/apksig/ApkVerifierTest.java
+++ b/src/test/java/com/android/apksig/ApkVerifierTest.java
@@ -258,6 +258,15 @@ public class ApkVerifierTest {
}
@Test
+ public void testV3StrippedRejected() throws Exception {
+ // APK signed with v2 and v3 schemes, but v3 signature was stripped from the file by
+ // modifying the v3 block ID to be the verity padding block ID. Without the stripping
+ // protection this modification ignores the v3 signing scheme block.
+ assertVerificationFailure(
+ "v3-stripped.apk", Issue.V2_SIG_MISSING_APK_SIG_REFERENCED);
+ }
+
+ @Test
public void testV2OneSignerOneSignatureAccepted() throws Exception {
// APK signed with v2 scheme only, one signer, one signature
assertVerifiedForEachForMinSdkVersion(
@@ -278,6 +287,22 @@ public class ApkVerifierTest {
}
@Test
+ public void testV3OneSignerOneSignatureAccepted() throws Exception {
+ // APK signed with v3 scheme only, one signer, one signature
+ assertVerifiedForEachForMinSdkVersion(
+ "v3-only-with-dsa-sha256-%s.apk", DSA_KEY_NAMES, AndroidSdkVersion.P);
+ assertVerifiedForEachForMinSdkVersion(
+ "v3-only-with-ecdsa-sha256-%s.apk", EC_KEY_NAMES, AndroidSdkVersion.P);
+ assertVerifiedForEachForMinSdkVersion(
+ "v3-only-with-rsa-pkcs1-sha256-%s.apk", RSA_KEY_NAMES, AndroidSdkVersion.P);
+
+ assertVerifiedForEachForMinSdkVersion(
+ "v3-only-with-ecdsa-sha512-%s.apk", EC_KEY_NAMES, AndroidSdkVersion.P);
+ assertVerifiedForEachForMinSdkVersion(
+ "v3-only-with-rsa-pkcs1-sha512-%s.apk", RSA_KEY_NAMES, AndroidSdkVersion.P);
+ }
+
+ @Test
public void testV2OneSignerOneRsaPssSignatureAccepted() throws Exception {
assumeThatRsaPssAvailable();
// APK signed with v2 scheme only, one signer, one signature
@@ -312,6 +337,27 @@ public class ApkVerifierTest {
}
@Test
+ public void testV3SignatureDoesNotMatchSignedDataRejected() throws Exception {
+ // APK signed with v3 scheme only, but the signature over signed-data does not verify
+
+ // Bitflip in DSA signature. Based on v3-only-with-dsa-sha256-2048.apk.
+ assertVerificationFailure(
+ "v3-only-with-dsa-sha256-2048-sig-does-not-verify.apk",
+ Issue.V3_SIG_DID_NOT_VERIFY);
+
+ // Bitflip in signed data. Based on v3-only-with-rsa-pkcs1-sha256-3072.apk
+ assertVerificationFailure(
+ "v3-only-with-rsa-pkcs1-sha256-3072-sig-does-not-verify.apk",
+ Issue.V3_SIG_DID_NOT_VERIFY);
+
+ // Based on v3-only-with-ecdsa-sha512-p521 with the signature ID changed to be ECDSA with
+ // SHA-256.
+ assertVerificationFailure(
+ "v3-only-with-ecdsa-sha512-p521-sig-does-not-verify.apk",
+ Issue.V3_SIG_DID_NOT_VERIFY);
+ }
+
+ @Test
public void testV2RsaPssSignatureDoesNotMatchSignedDataRejected() throws Exception {
assumeThatRsaPssAvailable();
@@ -343,6 +389,24 @@ public class ApkVerifierTest {
}
@Test
+ public void testV3ContentDigestMismatchRejected() throws Exception {
+ // APK signed with v3 scheme only, but the digest of contents does not match the digest
+ // stored in signed-data.
+
+ // Based on v3-only-with-rsa-pkcs1-sha512-8192. Obtained by flipping a bit in the local
+ // file header of the APK.
+ assertVerificationFailure(
+ "v3-only-with-rsa-pkcs1-sha512-8192-digest-mismatch.apk",
+ Issue.V3_SIG_APK_DIGEST_DID_NOT_VERIFY);
+
+ // Based on v3-only-with-dsa-sha256-3072.apk. Obtained by modifying APK signer to flip the
+ // leftmost bit in content digest before signing signed-data.
+ assertVerificationFailure(
+ "v3-only-with-dsa-sha256-3072-digest-mismatch.apk",
+ Issue.V3_SIG_APK_DIGEST_DID_NOT_VERIFY);
+ }
+
+ @Test
public void testNoApkSignatureSchemeBlockRejected() throws Exception {
// APK signed with v2 scheme only, but the rules for verifying APK Signature Scheme v2
// signatures say that this APK must not be verified using APK Signature Scheme v2.
@@ -373,6 +437,21 @@ public class ApkVerifierTest {
assertVerified(verify("v1-with-apk-sig-block-but-without-apk-sig-scheme-v2-block.apk"));
}
+ @Test
+ public void testNoV3ApkSignatureSchemeBlockRejected() throws Exception {
+ // Obtained from v3-only-with-ecdsa-sha512-p384.apk by flipping a bit in the magic field
+ // in the footer of the APK Signing Block.
+ assertVerificationFailure(
+ "v3-only-with-ecdsa-sha512-p384-wrong-apk-sig-block-magic.apk",
+ Issue.JAR_SIG_NO_MANIFEST);
+
+ // Obtained from v3-only-with-rsa-pkcs1-sha512-4096.apk by modifying the size in the APK
+ // Signature Block header and footer.
+ assertVerificationFailure(
+ "v3-only-with-rsa-pkcs1-sha512-4096-apk-sig-block-size-mismatch.apk",
+ Issue.JAR_SIG_NO_MANIFEST);
+ }
+
@Test(expected = ApkFormatException.class)
public void testTruncatedZipCentralDirectoryRejected() throws Exception {
// Obtained by modifying APK signer to truncate the ZIP Central Directory by one byte. The
@@ -392,6 +471,16 @@ public class ApkVerifierTest {
}
@Test
+ public void testV3UnknownPairIgnoredInApkSigningBlock() throws Exception {
+ // Obtained by modifying APK signer to emit an unknown ID value pair into APK Signing Block
+ // before the ID value pair containing the APK Signature Scheme v3 Block. The unknown
+ // ID value should be ignored.
+ assertVerified(
+ verifyForMinSdkVersion(
+ "v3-only-unknown-pair-in-apk-sig-block.apk", AndroidSdkVersion.P));
+ }
+
+ @Test
public void testV2UnknownSignatureAlgorithmsIgnored() throws Exception {
// APK is signed with a known signature algorithm and with a couple of unknown ones.
// Obtained by modifying APK signer to use "unknown" signature algorithms in addition to
@@ -402,6 +491,24 @@ public class ApkVerifierTest {
}
@Test
+ public void testV3UnknownSignatureAlgorithmsIgnored() throws Exception {
+ // APK is signed with a known signature algorithm and a couple of unknown ones.
+ // Obtained by modifying APK signer to use "unknown" signature algorithms in addition to
+ // known ones.
+ assertVerified(
+ verifyForMinSdkVersion(
+ "v3-only-with-ignorable-unsupported-sig-algs.apk", AndroidSdkVersion.P));
+ }
+
+ @Test
+ public void testV3WithOnlyUnknownSignatureAlgorithmsRejected() throws Exception {
+ // APK is only signed with an unknown signature algorithm. Obtained by modifying APK
+ // signer's ID for a known signature algorithm.
+ assertVerificationFailure(
+ "v3-only-no-supported-sig-algs.apk", Issue.V3_SIG_NO_SUPPORTED_SIGNATURES);
+ }
+
+ @Test
public void testV2UnknownAdditionalAttributeIgnored() throws Exception {
// APK's v2 signature contains an unknown additional attribute, but is otherwise fine.
// Obtained by modifying APK signer to output an additional attribute with ID 0x01020304
@@ -411,6 +518,19 @@ public class ApkVerifierTest {
}
@Test
+ public void testV3UnknownAdditionalAttributeIgnored() throws Exception {
+ // APK's v3 signature contains unknown additional attributes before and after the lineage.
+ // Obtained by modifying APK signer to output additional attributes with IDs 0x11223344
+ // and 0x99aabbcc with values 0x55667788 and 0xddeeff00
+ assertVerified(
+ verifyForMinSdkVersion("v3-only-unknown-additional-attr.apk", AndroidSdkVersion.P));
+
+ // APK's v2 and v3 signatures contain unknown additional attributes before and after the
+ // anti-stripping and lineage attributes.
+ assertVerified(
+ verifyForMinSdkVersion("v2v3-unknown-additional-attr.apk", AndroidSdkVersion.P)); }
+
+ @Test
public void testV2MismatchBetweenSignaturesAndDigestsBlockRejected() throws Exception {
// APK is signed with a single signature algorithm, but the digests block claims that it is
// signed with two different signature algorithms. Obtained by modifying APK Signer to
@@ -421,6 +541,16 @@ public class ApkVerifierTest {
}
@Test
+ public void testV3MismatchBetweenSignaturesAndDigestsBlockRejected() throws Exception {
+ // APK is signed with a single signature algorithm, but the digests block claims that it is
+ // signed with two different signature algorithms. Obtained by modifying APK Signer to
+ // emit an additional digest record with signature algorithm 0x11223344.
+ assertVerificationFailure(
+ "v3-only-signatures-and-digests-block-mismatch.apk",
+ Issue.V3_SIG_SIG_ALG_MISMATCH_BETWEEN_SIGNATURES_AND_DIGESTS_RECORDS);
+ }
+
+ @Test
public void testV2MismatchBetweenPublicKeyAndCertificateRejected() throws Exception {
// APK is signed with v2 only. The public key field does not match the public key in the
// leaf certificate. Obtained by modifying APK signer to write out a modified leaf
@@ -431,6 +561,16 @@ public class ApkVerifierTest {
}
@Test
+ public void testV3MismatchBetweenPublicKeyAndCertificateRejected() throws Exception {
+ // APK is signed with v3 only. The public key field does not match the public key in the
+ // leaf certificate. Obtained by modifying APK signer to write out a modified leaf
+ // certificate where the RSA modulus has a bitflip.
+ assertVerificationFailure(
+ "v3-only-cert-and-public-key-mismatch.apk",
+ Issue.V3_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD);
+ }
+
+ @Test
public void testV2SignerBlockWithNoCertificatesRejected() throws Exception {
// APK is signed with v2 only. There are no certificates listed in the signer block.
// Obtained by modifying APK signer to output no certificates.
@@ -439,6 +579,14 @@ public class ApkVerifierTest {
}
@Test
+ public void testV3SignerBlockWithNoCertificatesRejected() throws Exception {
+ // APK is signed with v3 only. There are no certificates listed in the signer block.
+ // Obtained by modifying APK signer to output no certificates.
+ assertVerificationFailure(
+ "v3-only-no-certs-in-sig.apk", Issue.V3_SIG_NO_CERTIFICATES);
+ }
+
+ @Test
public void testTwoSignersAccepted() throws Exception {
// APK signed by two different signers
assertVerified(verify("two-signers.apk"));
@@ -593,6 +741,12 @@ public class ApkVerifierTest {
verifyForMinSdkVersion("v2-only-empty.apk", AndroidSdkVersion.N);
fail("ApkFormatException should've been thrown");
} catch (ApkFormatException expected) {}
+
+ // APK Signature Scheme v3 signed empty ZIP archive
+ try {
+ verifyForMinSdkVersion("v3-only-empty.apk", AndroidSdkVersion.P);
+ fail("ApkFormatException should've been thrown");
+ } catch (ApkFormatException expected) {}
}
@Test
@@ -982,6 +1136,19 @@ public class ApkVerifierTest {
.append(signerName).append(": ").append(issue);
}
}
+ for (ApkVerifier.Result.V3SchemeSignerInfo signer : result.getV3SchemeSigners()) {
+ String signerName = "signer #" + (signer.getIndex() + 1);
+ for (IssueWithParams issue : signer.getErrors()) {
+ if (expectedIssue.equals(issue.getIssue())) {
+ return;
+ }
+ if (msg.length() > 0) {
+ msg.append('\n');
+ }
+ msg.append("APK Signature Scheme v3 signer ")
+ .append(signerName).append(": ").append(issue);
+ }
+ }
fail("APK failed verification for the wrong reason"
+ ". Expected: " + expectedIssue + ", actual: " + msg);
diff --git a/src/test/java/com/android/apksig/SigningCertificateLineageTest.java b/src/test/java/com/android/apksig/SigningCertificateLineageTest.java
index 2f6c10d..2038421 100644
--- a/src/test/java/com/android/apksig/SigningCertificateLineageTest.java
+++ b/src/test/java/com/android/apksig/SigningCertificateLineageTest.java
@@ -19,7 +19,9 @@ package com.android.apksig;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import com.android.apksig.apk.ApkFormatException;
import com.android.apksig.internal.apk.ApkSigningBlockUtils;
import com.android.apksig.internal.apk.v3.V3SchemeSigner;
import com.android.apksig.internal.util.ByteBufferDataSource;
@@ -76,7 +78,7 @@ public class SigningCertificateLineageTest {
SigningCertificateLineage lineage = createLineageWithSignersFromResources(
FIRST_RSA_2048_SIGNER_RESOURCE_NAME, SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
assertLineageContainsExpectedSigners(lineage, mSigners);
- SignerConfig unknownSigner = getSignerConfigFromResources(
+ SignerConfig unknownSigner = Resources.toLineageSignerConfig(getClass(),
THIRD_RSA_2048_SIGNER_RESOURCE_NAME);
assertFalse("The signer " + unknownSigner.getCertificate().getSubjectDN()
+ " should not be in the lineage", lineage.isSignerInLineage(unknownSigner));
@@ -94,21 +96,26 @@ public class SigningCertificateLineageTest {
@Test
public void testLineageFromFileContainsExpectedSigners() throws Exception {
// This file contains the lineage with the three rsa-2048 signers
- DataSource lineageDataSource = getDataSourceFromResources("rsa-2048-lineage-3-signers");
+ DataSource lineageDataSource = Resources.toDataSource(getClass(),
+ "rsa-2048-lineage-3-signers");
SigningCertificateLineage lineage = SigningCertificateLineage.readFromDataSource(
lineageDataSource);
List<SignerConfig> signers = new ArrayList<>(3);
- signers.add(getSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
- signers.add(getSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME));
- signers.add(getSignerConfigFromResources(THIRD_RSA_2048_SIGNER_RESOURCE_NAME));
+ signers.add(
+ Resources.toLineageSignerConfig(getClass(), FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
+ signers.add(
+ Resources.toLineageSignerConfig(getClass(), SECOND_RSA_2048_SIGNER_RESOURCE_NAME));
+ signers.add(
+ Resources.toLineageSignerConfig(getClass(), THIRD_RSA_2048_SIGNER_RESOURCE_NAME));
assertLineageContainsExpectedSigners(lineage, signers);
}
@Test
public void testLineageFromFileDoesNotContainUnknownSigner() throws Exception {
// This file contains the lineage with the first two rsa-2048 signers
- SigningCertificateLineage lineage = getLineageFromResources("rsa-2048-lineage-2-signers");
- SignerConfig unknownSigner = getSignerConfigFromResources(
+ SigningCertificateLineage lineage = Resources.toSigningCertificateLineage(getClass(),
+ "rsa-2048-lineage-2-signers");
+ SignerConfig unknownSigner = Resources.toLineageSignerConfig(getClass(),
THIRD_RSA_2048_SIGNER_RESOURCE_NAME);
assertFalse("The signer " + unknownSigner.getCertificate().getSubjectDN()
+ " should not be in the lineage", lineage.isSignerInLineage(unknownSigner));
@@ -117,14 +124,14 @@ public class SigningCertificateLineageTest {
@Test(expected = IllegalArgumentException.class)
public void testLineageFromFileWithInvalidMagicFails() throws Exception {
// This file contains the lineage with two rsa-2048 signers and a modified MAGIC value
- getLineageFromResources("rsa-2048-lineage-invalid-magic");
+ Resources.toSigningCertificateLineage(getClass(), "rsa-2048-lineage-invalid-magic");
}
@Test(expected = IllegalArgumentException.class)
public void testLineageFromFileWithInvalidVersionFails() throws Exception {
// This file contains the lineage with two rsa-2048 signers and an invalid value of FF for
// the version
- getLineageFromResources("rsa-2048-lineage-invalid-version");
+ Resources.toSigningCertificateLineage(getClass(), "rsa-2048-lineage-invalid-version");
}
@Test
@@ -172,10 +179,11 @@ public class SigningCertificateLineageTest {
public void testCapabilitiesAreNotUpdatedWithDefaultValues() throws Exception {
// This file contains the lineage with the first two rsa-2048 signers with the first signer
// having all of the capabilities set to false.
- SigningCertificateLineage lineage = getLineageFromResources(
+ SigningCertificateLineage lineage = Resources.toSigningCertificateLineage(getClass(),
"rsa-2048-lineage-no-capabilities-first-signer");
List<Boolean> expectedCapabilityValues = Arrays.asList(false, false, false, false, false);
- SignerConfig oldSignerConfig = getSignerConfigFromResources("rsa-2048");
+ SignerConfig oldSignerConfig = Resources.toLineageSignerConfig(getClass(),
+ FIRST_RSA_2048_SIGNER_RESOURCE_NAME);
SignerCapabilities oldSignerCapabilities = lineage.getSignerCapabilities(oldSignerConfig);
assertExpectedCapabilityValues(oldSignerCapabilities, expectedCapabilityValues);
// The builder is called directly to ensure all of the capabilities are set to the default
@@ -188,8 +196,10 @@ public class SigningCertificateLineageTest {
@Test
public void testFirstRotationWitNonDefaultCapabilitiesForSigners() throws Exception {
- SignerConfig oldSigner = getSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME);
- SignerConfig newSigner = getSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
+ SignerConfig oldSigner = Resources.toLineageSignerConfig(getClass(),
+ FIRST_RSA_2048_SIGNER_RESOURCE_NAME);
+ SignerConfig newSigner = Resources.toLineageSignerConfig(getClass(),
+ SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
List<Boolean> oldSignerCapabilityValues = Arrays.asList(false, false, false, false, false);
List<Boolean> newSignerCapabilityValues = Arrays.asList(false, true, false, false, false);
SigningCertificateLineage lineage = new SigningCertificateLineage.Builder(oldSigner,
@@ -209,7 +219,8 @@ public class SigningCertificateLineageTest {
SigningCertificateLineage lineage = createLineageWithSignersFromResources(
FIRST_RSA_2048_SIGNER_RESOURCE_NAME, SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
SignerConfig oldSigner = mSigners.get(mSigners.size() - 1);
- SignerConfig newSigner = getSignerConfigFromResources(THIRD_RSA_2048_SIGNER_RESOURCE_NAME);
+ SignerConfig newSigner = Resources.toLineageSignerConfig(getClass(),
+ THIRD_RSA_2048_SIGNER_RESOURCE_NAME);
List<Boolean> newSignerCapabilityValues = Arrays.asList(false, false, false, false, false);
lineage = lineage.spawnDescendant(oldSigner, newSigner,
buildSignerCapabilities(newSignerCapabilityValues));
@@ -225,7 +236,8 @@ public class SigningCertificateLineageTest {
SigningCertificateLineage lineage = createLineageWithSignersFromResources(
FIRST_RSA_2048_SIGNER_RESOURCE_NAME, SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
SignerConfig oldestSigner = mSigners.get(0);
- SignerConfig newSigner = getSignerConfigFromResources(THIRD_RSA_2048_SIGNER_RESOURCE_NAME);
+ SignerConfig newSigner = Resources.toLineageSignerConfig(getClass(),
+ THIRD_RSA_2048_SIGNER_RESOURCE_NAME);
lineage.spawnDescendant(oldestSigner, newSigner);
}
@@ -344,6 +356,66 @@ public class SigningCertificateLineageTest {
SigningCertificateLineage.consolidateLineages(lineages);
}
+ @Test
+ public void testLineageFromAPKContainsExpectedSigners() throws Exception {
+ SignerConfig firstSigner = getSignerConfigFromResources(
+ FIRST_RSA_2048_SIGNER_RESOURCE_NAME);
+ SignerConfig secondSigner = getSignerConfigFromResources(
+ SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
+ SignerConfig thirdSigner = getSignerConfigFromResources(
+ THIRD_RSA_2048_SIGNER_RESOURCE_NAME);
+ List<SignerConfig> expectedSigners = Arrays.asList(firstSigner, secondSigner, thirdSigner);
+ DataSource apkDataSource = Resources.toDataSource(getClass(),
+ "v1v2v3-with-rsa-2048-lineage-3-signers.apk");
+ SigningCertificateLineage lineageFromApk = SigningCertificateLineage.readFromApkDataSource(
+ apkDataSource);
+ assertLineageContainsExpectedSigners(lineageFromApk, expectedSigners);
+ }
+
+ @Test(expected = ApkFormatException.class)
+ public void testLineageFromAPKWithInvalidZipCDSizeFails() throws Exception {
+ // This test verifies that attempting to read the lineage from an APK where the zip
+ // sections cannot be parsed fails. This APK is based off the
+ // v1v2v3-with-rsa-2048-lineage-3-signers.apk with a modified CD size in the EoCD.
+ DataSource apkDataSource = Resources.toDataSource(getClass(),
+ "v1v2v3-with-rsa-2048-lineage-3-signers-invalid-zip.apk");
+ SigningCertificateLineage.readFromApkDataSource(apkDataSource);
+ }
+
+ @Test
+ public void testLineageFromAPKWithNoLineageFails() throws Exception {
+ // This test verifies that attempting to read the lineage from an APK without a lineage
+ // fails.
+ // This is a valid APK that has only been signed with the V1 and V2 signature schemes;
+ // since the lineage is an attribute in the V3 signature block this test should fail.
+ DataSource apkDataSource = Resources.toDataSource(getClass(),
+ "golden-aligned-v1v2-out.apk");
+ try {
+ SigningCertificateLineage.readFromApkDataSource(apkDataSource);
+ fail("A failure should have been reported due to the APK not containing a V3 signing "
+ + "block");
+ } catch (IllegalArgumentException expected) {}
+
+ // This is a valid APK signed with the V1, V2, and V3 signature schemes, but there is no
+ // lineage in the V3 signature block.
+ apkDataSource = Resources.toDataSource(getClass(), "golden-aligned-v1v2v3-out.apk");
+ try {
+ SigningCertificateLineage.readFromApkDataSource(apkDataSource);
+ fail("A failure should have been reported due to the APK containing a V3 signing "
+ + "block without the lineage attribute");
+ } catch (IllegalArgumentException expected) {}
+
+ // This APK is based off the v1v2v3-with-rsa-2048-lineage-3-signers.apk with a bit flip
+ // in the lineage attribute ID in the V3 signature block.
+ apkDataSource = Resources.toDataSource(getClass(),
+ "v1v2v3-with-rsa-2048-lineage-3-signers-invalid-lineage-attr.apk");
+ try {
+ SigningCertificateLineage.readFromApkDataSource(apkDataSource);
+ fail("A failure should have been reported due to the APK containing a V3 signing "
+ + "block with a modified lineage attribute ID");
+ } catch (IllegalArgumentException expected) {}
+ }
+
/**
* Builds a new {@code SigningCertificateLinage.SignerCapabilities} object using the values in
* the provided {@code List}. The {@code List} should contain {@code boolean} values to be
@@ -410,10 +482,10 @@ public class SigningCertificateLineageTest {
*/
private SigningCertificateLineage createLineageWithSignersFromResources(
String oldSignerResourceName, String newSignerResourceName) throws Exception {
- SignerConfig oldSignerConfig = getSignerConfigFromResources(
+ SignerConfig oldSignerConfig = Resources.toLineageSignerConfig(getClass(),
oldSignerResourceName);
mSigners.add(oldSignerConfig);
- SignerConfig newSignerConfig = getSignerConfigFromResources(
+ SignerConfig newSignerConfig = Resources.toLineageSignerConfig(getClass(),
newSignerResourceName);
mSigners.add(newSignerConfig);
return new SigningCertificateLineage.Builder(oldSignerConfig, newSignerConfig).build();
@@ -432,7 +504,8 @@ public class SigningCertificateLineageTest {
assertTrue("The mSigners list did not contain the expected signers to update the lineage",
mSigners.size() >= 2);
SignerConfig oldSignerConfig = mSigners.get(mSigners.size() - 1);
- SignerConfig newSignerConfig = getSignerConfigFromResources(newSignerResourceName);
+ SignerConfig newSignerConfig = Resources.toLineageSignerConfig(getClass(),
+ newSignerResourceName);
mSigners.add(newSignerConfig);
return lineage.spawnDescendant(oldSignerConfig, newSignerConfig);
}
@@ -467,16 +540,4 @@ public class SigningCertificateLineageTest {
return new DefaultApkSignerEngine.SignerConfig.Builder(resourcePrefix, privateKey,
Collections.singletonList(cert)).build();
}
-
- private static DataSource getDataSourceFromResources(String dataSourceResourceName)
- throws IOException {
- return new ByteBufferDataSource(ByteBuffer.wrap(Resources
- .toByteArray(SigningCertificateLineageTest.class, dataSourceResourceName)));
- }
-
- private SigningCertificateLineage getLineageFromResources(String fileResourceName)
- throws IOException {
- DataSource lineageDataSource = getDataSourceFromResources(fileResourceName);
- return SigningCertificateLineage.readFromDataSource(lineageDataSource);
- }
}
diff --git a/src/test/java/com/android/apksig/internal/util/Resources.java b/src/test/java/com/android/apksig/internal/util/Resources.java
index 8a761ff..09c206d 100644
--- a/src/test/java/com/android/apksig/internal/util/Resources.java
+++ b/src/test/java/com/android/apksig/internal/util/Resources.java
@@ -16,8 +16,13 @@
package com.android.apksig.internal.util;
+import com.android.apksig.ApkSignerTest;
+import com.android.apksig.SigningCertificateLineage;
+import com.android.apksig.util.DataSource;
+
import java.io.IOException;
import java.io.InputStream;
+import java.nio.ByteBuffer;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
@@ -109,4 +114,24 @@ public final class Resources {
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded));
}
+
+ public static SigningCertificateLineage.SignerConfig toLineageSignerConfig(Class<?> cls,
+ String resourcePrefix) throws Exception {
+ PrivateKey privateKey = toPrivateKey(cls, resourcePrefix + ".pk8");
+ X509Certificate cert = Resources.toCertificate(cls,
+ resourcePrefix + ".x509.pem");
+ return new SigningCertificateLineage.SignerConfig.Builder(privateKey, cert).build();
+ }
+
+ public static DataSource toDataSource(Class<?> cls, String dataSourceResourceName)
+ throws IOException {
+ return new ByteBufferDataSource(ByteBuffer.wrap(Resources
+ .toByteArray(ApkSignerTest.class, dataSourceResourceName)));
+ }
+
+ public static SigningCertificateLineage toSigningCertificateLineage(Class<?> cls,
+ String fileResourceName) throws IOException {
+ DataSource lineageDataSource = toDataSource(cls, fileResourceName);
+ return SigningCertificateLineage.readFromDataSource(lineageDataSource);
+ }
}
diff --git a/src/test/resources/com/android/apksig/golden-aligned-out.apk b/src/test/resources/com/android/apksig/golden-aligned-out.apk
index ca7041b..2396782 100644
--- a/src/test/resources/com/android/apksig/golden-aligned-out.apk
+++ b/src/test/resources/com/android/apksig/golden-aligned-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-aligned-v1-out.apk b/src/test/resources/com/android/apksig/golden-aligned-v1-out.apk
index e413b4e..403e45a 100644
--- a/src/test/resources/com/android/apksig/golden-aligned-v1-out.apk
+++ b/src/test/resources/com/android/apksig/golden-aligned-v1-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-aligned-v1v2-out.apk b/src/test/resources/com/android/apksig/golden-aligned-v1v2-out.apk
index ca7041b..5133049 100644
--- a/src/test/resources/com/android/apksig/golden-aligned-v1v2-out.apk
+++ b/src/test/resources/com/android/apksig/golden-aligned-v1v2-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-aligned-v1v2v3-lineage-out.apk b/src/test/resources/com/android/apksig/golden-aligned-v1v2v3-lineage-out.apk
new file mode 100644
index 0000000..b9dc782
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-aligned-v1v2v3-lineage-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-aligned-v1v2v3-out.apk b/src/test/resources/com/android/apksig/golden-aligned-v1v2v3-out.apk
new file mode 100644
index 0000000..2396782
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-aligned-v1v2v3-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-aligned-v2-out.apk b/src/test/resources/com/android/apksig/golden-aligned-v2-out.apk
index 664d198..d947e3c 100644
--- a/src/test/resources/com/android/apksig/golden-aligned-v2-out.apk
+++ b/src/test/resources/com/android/apksig/golden-aligned-v2-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-aligned-v2v3-lineage-out.apk b/src/test/resources/com/android/apksig/golden-aligned-v2v3-lineage-out.apk
new file mode 100644
index 0000000..88c571b
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-aligned-v2v3-lineage-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-aligned-v2v3-out.apk b/src/test/resources/com/android/apksig/golden-aligned-v2v3-out.apk
new file mode 100644
index 0000000..25f35cc
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-aligned-v2v3-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-aligned-v3-lineage-out.apk b/src/test/resources/com/android/apksig/golden-aligned-v3-lineage-out.apk
new file mode 100644
index 0000000..30e1f72
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-aligned-v3-lineage-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-aligned-v3-out.apk b/src/test/resources/com/android/apksig/golden-aligned-v3-out.apk
new file mode 100644
index 0000000..f97cbeb
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-aligned-v3-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-legacy-aligned-out.apk b/src/test/resources/com/android/apksig/golden-legacy-aligned-out.apk
index fbae100..d177361 100644
--- a/src/test/resources/com/android/apksig/golden-legacy-aligned-out.apk
+++ b/src/test/resources/com/android/apksig/golden-legacy-aligned-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-legacy-aligned-v1-out.apk b/src/test/resources/com/android/apksig/golden-legacy-aligned-v1-out.apk
index bdc2165..e8a0bf2 100644
--- a/src/test/resources/com/android/apksig/golden-legacy-aligned-v1-out.apk
+++ b/src/test/resources/com/android/apksig/golden-legacy-aligned-v1-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2-out.apk b/src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2-out.apk
index fbae100..cc03744 100644
--- a/src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2-out.apk
+++ b/src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2v3-lineage-out.apk b/src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2v3-lineage-out.apk
new file mode 100644
index 0000000..e359da7
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2v3-lineage-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2v3-out.apk b/src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2v3-out.apk
new file mode 100644
index 0000000..d177361
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2v3-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-legacy-aligned-v2-out.apk b/src/test/resources/com/android/apksig/golden-legacy-aligned-v2-out.apk
index 5956da5..68f07ed 100644
--- a/src/test/resources/com/android/apksig/golden-legacy-aligned-v2-out.apk
+++ b/src/test/resources/com/android/apksig/golden-legacy-aligned-v2-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-legacy-aligned-v2v3-lineage-out.apk b/src/test/resources/com/android/apksig/golden-legacy-aligned-v2v3-lineage-out.apk
new file mode 100644
index 0000000..4b51e4f
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-legacy-aligned-v2v3-lineage-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-legacy-aligned-v2v3-out.apk b/src/test/resources/com/android/apksig/golden-legacy-aligned-v2v3-out.apk
new file mode 100644
index 0000000..7177862
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-legacy-aligned-v2v3-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-legacy-aligned-v3-lineage-out.apk b/src/test/resources/com/android/apksig/golden-legacy-aligned-v3-lineage-out.apk
new file mode 100644
index 0000000..bd3e668
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-legacy-aligned-v3-lineage-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-legacy-aligned-v3-out.apk b/src/test/resources/com/android/apksig/golden-legacy-aligned-v3-out.apk
new file mode 100644
index 0000000..67a7d3f
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-legacy-aligned-v3-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-1-out.apk b/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-1-out.apk
index 1269499..7289853 100644
--- a/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-1-out.apk
+++ b/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-1-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-18-out.apk b/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-18-out.apk
index 9bfe23b..232db96 100644
--- a/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-18-out.apk
+++ b/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-18-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-24-out.apk b/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-24-out.apk
index 9bfe23b..232db96 100644
--- a/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-24-out.apk
+++ b/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-24-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-rsa-out.apk b/src/test/resources/com/android/apksig/golden-rsa-out.apk
index 9bfe23b..232db96 100644
--- a/src/test/resources/com/android/apksig/golden-rsa-out.apk
+++ b/src/test/resources/com/android/apksig/golden-rsa-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-unaligned-out.apk b/src/test/resources/com/android/apksig/golden-unaligned-out.apk
index 1b10755..0bd34c4 100644
--- a/src/test/resources/com/android/apksig/golden-unaligned-out.apk
+++ b/src/test/resources/com/android/apksig/golden-unaligned-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-unaligned-v1-out.apk b/src/test/resources/com/android/apksig/golden-unaligned-v1-out.apk
index 1309566..6ddc448 100644
--- a/src/test/resources/com/android/apksig/golden-unaligned-v1-out.apk
+++ b/src/test/resources/com/android/apksig/golden-unaligned-v1-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-unaligned-v1v2-out.apk b/src/test/resources/com/android/apksig/golden-unaligned-v1v2-out.apk
index 1b10755..c708211 100644
--- a/src/test/resources/com/android/apksig/golden-unaligned-v1v2-out.apk
+++ b/src/test/resources/com/android/apksig/golden-unaligned-v1v2-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-unaligned-v1v2v3-lineage-out.apk b/src/test/resources/com/android/apksig/golden-unaligned-v1v2v3-lineage-out.apk
new file mode 100644
index 0000000..dd6324b
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-unaligned-v1v2v3-lineage-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-unaligned-v1v2v3-out.apk b/src/test/resources/com/android/apksig/golden-unaligned-v1v2v3-out.apk
new file mode 100644
index 0000000..0bd34c4
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-unaligned-v1v2v3-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-unaligned-v2-out.apk b/src/test/resources/com/android/apksig/golden-unaligned-v2-out.apk
index bd56d9f..4fdc18c 100644
--- a/src/test/resources/com/android/apksig/golden-unaligned-v2-out.apk
+++ b/src/test/resources/com/android/apksig/golden-unaligned-v2-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-unaligned-v2v3-lineage-out.apk b/src/test/resources/com/android/apksig/golden-unaligned-v2v3-lineage-out.apk
new file mode 100644
index 0000000..4e523ca
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-unaligned-v2v3-lineage-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-unaligned-v2v3-out.apk b/src/test/resources/com/android/apksig/golden-unaligned-v2v3-out.apk
new file mode 100644
index 0000000..74e7dbc
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-unaligned-v2v3-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-unaligned-v3-lineage-out.apk b/src/test/resources/com/android/apksig/golden-unaligned-v3-lineage-out.apk
new file mode 100644
index 0000000..831c756
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-unaligned-v3-lineage-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-unaligned-v3-out.apk b/src/test/resources/com/android/apksig/golden-unaligned-v3-out.apk
new file mode 100644
index 0000000..3196267
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-unaligned-v3-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/rsa-1024-lineage-2-signers b/src/test/resources/com/android/apksig/rsa-1024-lineage-2-signers
new file mode 100644
index 0000000..4f4315c
--- /dev/null
+++ b/src/test/resources/com/android/apksig/rsa-1024-lineage-2-signers
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers-invalid-lineage-attr.apk b/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers-invalid-lineage-attr.apk
new file mode 100644
index 0000000..1bd3828
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers-invalid-lineage-attr.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers-invalid-zip.apk b/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers-invalid-zip.apk
new file mode 100644
index 0000000..2621f21
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers-invalid-zip.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers-no-sig-block.apk b/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers-no-sig-block.apk
new file mode 100644
index 0000000..17dea33
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers-no-sig-block.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers.apk b/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers.apk
new file mode 100644
index 0000000..fd141b5
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1v2v3-with-rsa-2048-lineage-3-signers.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2v3-signed-v3-block-stripped.apk b/src/test/resources/com/android/apksig/v2v3-signed-v3-block-stripped.apk
new file mode 100644
index 0000000..751c67d
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2v3-signed-v3-block-stripped.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2v3-unknown-additional-attr.apk b/src/test/resources/com/android/apksig/v2v3-unknown-additional-attr.apk
new file mode 100644
index 0000000..cb080f4
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2v3-unknown-additional-attr.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-cert-and-public-key-mismatch.apk b/src/test/resources/com/android/apksig/v3-only-cert-and-public-key-mismatch.apk
new file mode 100644
index 0000000..2291e7e
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-cert-and-public-key-mismatch.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-empty.apk b/src/test/resources/com/android/apksig/v3-only-empty.apk
new file mode 100644
index 0000000..15cb0ec
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-empty.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-no-certs-in-sig.apk b/src/test/resources/com/android/apksig/v3-only-no-certs-in-sig.apk
new file mode 100644
index 0000000..86e7971
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-no-certs-in-sig.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-no-supported-sig-algs.apk b/src/test/resources/com/android/apksig/v3-only-no-supported-sig-algs.apk
new file mode 100644
index 0000000..f0debf3
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-no-supported-sig-algs.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-signatures-and-digests-block-mismatch.apk b/src/test/resources/com/android/apksig/v3-only-signatures-and-digests-block-mismatch.apk
new file mode 100644
index 0000000..31aea2f
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-signatures-and-digests-block-mismatch.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-unknown-additional-attr.apk b/src/test/resources/com/android/apksig/v3-only-unknown-additional-attr.apk
new file mode 100644
index 0000000..2245922
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-unknown-additional-attr.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-unknown-pair-in-apk-sig-block.apk b/src/test/resources/com/android/apksig/v3-only-unknown-pair-in-apk-sig-block.apk
new file mode 100644
index 0000000..49eeaf3
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-unknown-pair-in-apk-sig-block.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-1024.apk b/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-1024.apk
new file mode 100644
index 0000000..af6b0d7
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-2048-sig-does-not-verify.apk b/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-2048-sig-does-not-verify.apk
new file mode 100644
index 0000000..50dbab2
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-2048-sig-does-not-verify.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-2048.apk b/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-2048.apk
new file mode 100644
index 0000000..3d2161e
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-3072-digest-mismatch.apk b/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-3072-digest-mismatch.apk
new file mode 100644
index 0000000..42f885b
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-3072-digest-mismatch.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-3072.apk b/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-3072.apk
new file mode 100644
index 0000000..c58902d
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-dsa-sha256-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha256-p256.apk b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha256-p256.apk
new file mode 100644
index 0000000..5ef4fec
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha256-p256.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha256-p384.apk b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha256-p384.apk
new file mode 100644
index 0000000..75135af
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha256-p384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha256-p521.apk b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha256-p521.apk
new file mode 100644
index 0000000..74071f0
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha256-p521.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p256.apk b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p256.apk
new file mode 100644
index 0000000..543c1f3
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p256.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p384-wrong-apk-sig-block-magic.apk b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p384-wrong-apk-sig-block-magic.apk
new file mode 100644
index 0000000..ce79751
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p384-wrong-apk-sig-block-magic.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p384.apk b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p384.apk
new file mode 100644
index 0000000..36fa0ee
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p521-sig-does-not-verify.apk b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p521-sig-does-not-verify.apk
new file mode 100644
index 0000000..8e89c98
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p521-sig-does-not-verify.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p521.apk b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p521.apk
new file mode 100644
index 0000000..b74b4fb
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-ecdsa-sha512-p521.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-ignorable-unsupported-sig-algs.apk b/src/test/resources/com/android/apksig/v3-only-with-ignorable-unsupported-sig-algs.apk
new file mode 100644
index 0000000..88ae376
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-ignorable-unsupported-sig-algs.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-1024.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-1024.apk
new file mode 100644
index 0000000..7a62c24
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-16384.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-16384.apk
new file mode 100644
index 0000000..825cfba
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-16384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-2048.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-2048.apk
new file mode 100644
index 0000000..1ab85f8
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-3072-sig-does-not-verify.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-3072-sig-does-not-verify.apk
new file mode 100644
index 0000000..ddaaccd
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-3072-sig-does-not-verify.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-3072.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-3072.apk
new file mode 100644
index 0000000..8bcc82c
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-4096.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-4096.apk
new file mode 100644
index 0000000..0c9391c
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-4096.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-8192.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-8192.apk
new file mode 100644
index 0000000..41db21b
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha256-8192.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-1024.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-1024.apk
new file mode 100644
index 0000000..776d366
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-16384.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-16384.apk
new file mode 100644
index 0000000..85146f1
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-16384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-2048.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-2048.apk
new file mode 100644
index 0000000..8b1b915
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-3072.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-3072.apk
new file mode 100644
index 0000000..5b364fd
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-4096-apk-sig-block-size-mismatch.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-4096-apk-sig-block-size-mismatch.apk
new file mode 100644
index 0000000..52d5a67
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-4096-apk-sig-block-size-mismatch.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-4096.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-4096.apk
new file mode 100644
index 0000000..c210b70
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-4096.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-8192-digest-mismatch.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-8192-digest-mismatch.apk
new file mode 100644
index 0000000..2800929
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-8192-digest-mismatch.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-8192.apk b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-8192.apk
new file mode 100644
index 0000000..3c2cc79
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-only-with-rsa-pkcs1-sha512-8192.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v3-stripped.apk b/src/test/resources/com/android/apksig/v3-stripped.apk
new file mode 100644
index 0000000..751c67d
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v3-stripped.apk
Binary files differ