Add API to obtain certificates in source stamp signer's lineage am: 5983b339ae
Original change: https://googleplex-android-review.googlesource.com/c/platform/tools/apksig/+/12692890
Change-Id: Ib50fea2def50bab22fb7677e0371dba5f13123ba
diff --git a/src/main/java/com/android/apksig/SourceStampVerifier.java b/src/main/java/com/android/apksig/SourceStampVerifier.java
index 0c0e036..587cbd3 100644
--- a/src/main/java/com/android/apksig/SourceStampVerifier.java
+++ b/src/main/java/com/android/apksig/SourceStampVerifier.java
@@ -758,6 +758,15 @@
}
/**
+ * Returns a {@code List} of {@link X509Certificate} instances representing the source
+ * stamp signer's lineage with the oldest signer at element 0, or an empty {@code List}
+ * if the stamp's signing certificate has not been rotated.
+ */
+ public List<X509Certificate> getCertificatesInLineage() {
+ return mCertificateLineage;
+ }
+
+ /**
* Returns whether any errors were encountered during the source stamp verification.
*/
public boolean containsErrors() {
diff --git a/src/test/java/com/android/apksig/AllTests.java b/src/test/java/com/android/apksig/AllTests.java
index 4a9243d..3cb1052 100644
--- a/src/test/java/com/android/apksig/AllTests.java
+++ b/src/test/java/com/android/apksig/AllTests.java
@@ -24,6 +24,7 @@
ApkSignerTest.class,
ApkVerifierTest.class,
SigningCertificateLineageTest.class,
+ SourceStampVerifierTest.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/SourceStampVerifierTest.java b/src/test/java/com/android/apksig/SourceStampVerifierTest.java
index d99f0a0..f5020cc 100644
--- a/src/test/java/com/android/apksig/SourceStampVerifierTest.java
+++ b/src/test/java/com/android/apksig/SourceStampVerifierTest.java
@@ -16,8 +16,6 @@
package com.android.apksig;
-import static com.android.apksig.SourceStampVerifier.Result;
-import static com.android.apksig.SourceStampVerifier.Result.SignerInfo;
import static com.android.apksig.apk.ApkUtilsLite.computeSha256DigestBytes;
import static com.android.apksig.internal.apk.ApkSigningBlockUtilsLite.toHex;
@@ -25,6 +23,8 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
+import com.android.apksig.SourceStampVerifier.Result;
+import com.android.apksig.SourceStampVerifier.Result.SignerInfo;
import com.android.apksig.internal.util.Resources;
import com.android.apksig.util.DataSources;
@@ -40,6 +40,10 @@
public class SourceStampVerifierTest {
private static final String RSA_2048_CERT_SHA256_DIGEST =
"fb5dbd3c669af9fc236c6991e6387b7f11ff0590997f22d0f5c74ff40e04fca8";
+ private static final String RSA_2048_2_CERT_SHA256_DIGEST =
+ "681b0e56a796350c08647352a4db800cc44b2adc8f4c72fa350bd05d4d50264d";
+ private static final String RSA_2048_3_CERT_SHA256_DIGEST =
+ "bb77a72efc60e66501ab75953af735874f82cfe52a70d035186a01b3482180f3";
private static final String EC_P256_CERT_SHA256_DIGEST =
"6a8b96e278e58f62cfe3584022cec1d0527fcb85a9e5d2e1694eb0405be5b599";
private static final String EC_P256_2_CERT_SHA256_DIGEST =
@@ -73,7 +77,7 @@
// Verify when platform versions that support the V1 - V3 signature schemes are specified
// that an APK signed with all signature schemes has its expected signers returned in the
// result.
- Result verificationResult = verifySourceStamp("./v1v2v3-rotated-v3-key-valid-stamp.apk", 23,
+ Result verificationResult = verifySourceStamp("v1v2v3-rotated-v3-key-valid-stamp.apk", 23,
28);
assertVerified(verificationResult);
assertSigningCertificates(verificationResult, EC_P256_CERT_SHA256_DIGEST,
@@ -81,15 +85,15 @@
// Verify when the specified platform versions only support a single signature scheme that
// scheme's signer is the only one in the result.
- verificationResult = verifySourceStamp("./v1v2v3-rotated-v3-key-valid-stamp.apk", 18, 18);
+ verificationResult = verifySourceStamp("v1v2v3-rotated-v3-key-valid-stamp.apk", 18, 18);
assertVerified(verificationResult);
assertSigningCertificates(verificationResult, EC_P256_CERT_SHA256_DIGEST, null, null);
- verificationResult = verifySourceStamp("./v1v2v3-rotated-v3-key-valid-stamp.apk", 24, 24);
+ verificationResult = verifySourceStamp("v1v2v3-rotated-v3-key-valid-stamp.apk", 24, 24);
assertVerified(verificationResult);
assertSigningCertificates(verificationResult, null, EC_P256_CERT_SHA256_DIGEST, null);
- verificationResult = verifySourceStamp("./v1v2v3-rotated-v3-key-valid-stamp.apk", 28, 28);
+ verificationResult = verifySourceStamp("v1v2v3-rotated-v3-key-valid-stamp.apk", 28, 28);
assertVerified(verificationResult);
assertSigningCertificates(verificationResult, null, null, EC_P256_2_CERT_SHA256_DIGEST);
}
@@ -221,6 +225,8 @@
Result verificationResult = verifySourceStamp(
"stamp-lineage-valid.apk");
assertVerified(verificationResult);
+ assertSigningCertificatesInLineage(verificationResult, RSA_2048_CERT_SHA256_DIGEST,
+ RSA_2048_2_CERT_SHA256_DIGEST);
}
@Test
@@ -232,6 +238,22 @@
}
@Test
+ public void verifySourceStamp_multipleSignersInLineage() throws Exception {
+ Result verificationResult = verifySourceStamp("stamp-lineage-with-3-signers.apk", 18, 28);
+ assertVerified(verificationResult);
+ assertSigningCertificatesInLineage(verificationResult, RSA_2048_CERT_SHA256_DIGEST,
+ RSA_2048_2_CERT_SHA256_DIGEST, RSA_2048_3_CERT_SHA256_DIGEST);
+ }
+
+ @Test
+ public void verifySourceStamp_noSignersInLineage_returnsEmptyLineage() throws Exception {
+ // If the source stamp's signer has not yet been rotated then an empty lineage should be
+ // returned.
+ Result verificationResult = verifySourceStamp("valid-stamp.apk");
+ assertSigningCertificatesInLineage(verificationResult);
+ }
+
+ @Test
public void verifySourceStamp_noApkSignature_succeeds()
throws Exception {
// The SourceStampVerifier is designed to verify an APK's source stamp with minimal
@@ -376,4 +398,23 @@
toHex(computeSha256DigestBytes(signingCertificate.getEncoded())));
}
}
+
+ /**
+ * Asserts that the provided {@code expectedCertDigests} match their respective certificate in
+ * the source stamp's lineage with the oldest signer at element 0.
+ *
+ * <p>If no values are provided for the expectedCertDigests, the source stamp's lineage will
+ * be checked for an empty {@code List} indicating the source stamp has not been rotated.
+ */
+ private static void assertSigningCertificatesInLineage(Result result,
+ String... expectedCertDigests) throws Exception {
+ List<X509Certificate> lineageCertificates =
+ result.getSourceStampInfo().getCertificatesInLineage();
+ assertEquals("Unexpected number of lineage certificates", expectedCertDigests.length,
+ lineageCertificates.size());
+ for (int i = 0; i < expectedCertDigests.length; i++) {
+ assertEquals("Stamp lineage mismatch at signer " + i, expectedCertDigests[i],
+ toHex(computeSha256DigestBytes(lineageCertificates.get(i).getEncoded())));
+ }
+ }
}
diff --git a/src/test/resources/com/android/apksig/stamp-lineage-with-3-signers.apk b/src/test/resources/com/android/apksig/stamp-lineage-with-3-signers.apk
new file mode 100644
index 0000000..c24fa98
--- /dev/null
+++ b/src/test/resources/com/android/apksig/stamp-lineage-with-3-signers.apk
Binary files differ