Add API to obtain certificates in source stamp signer's lineage am: 5983b339ae am: e98caa1db5 am: 60af2e0f5b

Original change: https://googleplex-android-review.googlesource.com/c/platform/tools/apksig/+/12692890

Change-Id: I8152c36508075a5cc6b5b093378868d186849aec
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