Add timestamp attribute to source stamp block

This commit adds a timestamp attribute to the source stamp block
containing the epoch time at which the block was signed.

Bug: 216204639
Test: gradlew test
Change-Id: I1ef084b04c8c252b3d9257e297196a55405549c8
Merged-In: I1ef084b04c8c252b3d9257e297196a55405549c8
(cherry picked from commit b5f70f9d1f4b9aa6b71883fe911b1d13d1bf493c)
diff --git a/src/main/java/com/android/apksig/ApkVerificationIssue.java b/src/main/java/com/android/apksig/ApkVerificationIssue.java
index 2aa9d0b..fa2b7aa 100644
--- a/src/main/java/com/android/apksig/ApkVerificationIssue.java
+++ b/src/main/java/com/android/apksig/ApkVerificationIssue.java
@@ -116,6 +116,8 @@
     public static final int JAR_SIG_NO_SIGNATURES = 36;
     /** An exception was encountered when parsing the V1 / jar signer in the signature block. */
     public static final int JAR_SIG_PARSE_EXCEPTION = 37;
+    /** The source stamp timestamp attribute has an invalid value. */
+    public static final int SOURCE_STAMP_INVALID_TIMESTAMP = 38;
 
     private final int mIssueId;
     private final String mFormat;
diff --git a/src/main/java/com/android/apksig/ApkVerifier.java b/src/main/java/com/android/apksig/ApkVerifier.java
index 1613c26..fc28864 100644
--- a/src/main/java/com/android/apksig/ApkVerifier.java
+++ b/src/main/java/com/android/apksig/ApkVerifier.java
@@ -1767,6 +1767,8 @@
 
             private final SourceStampVerificationStatus mSourceStampVerificationStatus;
 
+            private final long mTimestamp;
+
             private SourceStampInfo(ApkSignerInfo result) {
                 mCertificates = result.certs;
                 mCertificateLineage = result.certificateLineage;
@@ -1780,6 +1782,7 @@
                     mSourceStampVerificationStatus =
                             SourceStampVerificationStatus.STAMP_VERIFICATION_FAILED;
                 }
+                mTimestamp = result.timestamp;
             }
 
             SourceStampInfo(SourceStampVerificationStatus sourceStampVerificationStatus) {
@@ -1788,6 +1791,7 @@
                 mErrors = Collections.emptyList();
                 mWarnings = Collections.emptyList();
                 mSourceStampVerificationStatus = sourceStampVerificationStatus;
+                mTimestamp = 0;
             }
 
             /**
@@ -1827,6 +1831,14 @@
             public SourceStampVerificationStatus getSourceStampVerificationStatus() {
                 return mSourceStampVerificationStatus;
             }
+
+            /**
+             * Returns the epoch timestamp in seconds representing the time this source stamp block
+             * was signed, or 0 if the timestamp is not available.
+             */
+            public long getTimestampEpochSeconds() {
+                return mTimestamp;
+            }
         }
     }
 
@@ -3000,6 +3012,16 @@
                 + "contains a proof-of-rotation record with signature(s) that did not verify."),
 
         /**
+         * The source stamp timestamp attribute has an invalid value (<= 0).
+         * <ul>
+         *     <li>Parameter 1: The invalid timestamp value.
+         * </ul>
+         */
+        SOURCE_STAMP_INVALID_TIMESTAMP(
+                "The source stamp"
+                        + " timestamp attribute has an invalid value: %1$d"),
+
+        /**
          * The APK could not be properly parsed due to a ZIP or APK format exception.
          * <ul>
          *     <li>Parameter 1: The {@code Exception} caught when attempting to parse the APK.
@@ -3295,6 +3317,8 @@
                     Issue.JAR_SIG_NO_SIGNATURES);
             sVerificationIssueIdToIssue.put(ApkVerificationIssue.JAR_SIG_PARSE_EXCEPTION,
                     Issue.JAR_SIG_PARSE_EXCEPTION);
+            sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_INVALID_TIMESTAMP,
+                    Issue.SOURCE_STAMP_INVALID_TIMESTAMP);
         }
 
         /**
diff --git a/src/main/java/com/android/apksig/SourceStampVerifier.java b/src/main/java/com/android/apksig/SourceStampVerifier.java
index 587cbd3..b155341 100644
--- a/src/main/java/com/android/apksig/SourceStampVerifier.java
+++ b/src/main/java/com/android/apksig/SourceStampVerifier.java
@@ -730,6 +730,8 @@
             private final List<ApkVerificationIssue> mErrors = new ArrayList<>();
             private final List<ApkVerificationIssue> mWarnings = new ArrayList<>();
 
+            private final long mTimestamp;
+
             /*
              * Since this utility is intended just to verify the source stamp, and the source stamp
              * currently only logs warnings to prevent failing the APK signature verification, treat
@@ -744,6 +746,7 @@
                 mCertificateLineage = result.certificateLineage;
                 mErrors.addAll(result.getErrors());
                 mWarnings.addAll(result.getWarnings());
+                mTimestamp = result.timestamp;
             }
 
             /**
@@ -794,6 +797,14 @@
             public List<ApkVerificationIssue> getWarnings() {
                 return mWarnings;
             }
+
+            /**
+             * Returns the epoch timestamp in seconds representing the time this source stamp block
+             * was signed, or 0 if the timestamp is not available.
+             */
+            public long getTimestampEpochSeconds() {
+                return mTimestamp;
+            }
         }
     }
 
diff --git a/src/main/java/com/android/apksig/internal/apk/ApkSignerInfo.java b/src/main/java/com/android/apksig/internal/apk/ApkSignerInfo.java
index e0ea365..12e54d0 100644
--- a/src/main/java/com/android/apksig/internal/apk/ApkSignerInfo.java
+++ b/src/main/java/com/android/apksig/internal/apk/ApkSignerInfo.java
@@ -27,6 +27,7 @@
  */
 public class ApkSignerInfo {
     public int index;
+    public long timestamp;
     public List<X509Certificate> certs = new ArrayList<>();
     public List<X509Certificate> certificateLineage = new ArrayList<>();
 
diff --git a/src/main/java/com/android/apksig/internal/apk/stamp/SourceStampConstants.java b/src/main/java/com/android/apksig/internal/apk/stamp/SourceStampConstants.java
index 465fbb0..2a949ad 100644
--- a/src/main/java/com/android/apksig/internal/apk/stamp/SourceStampConstants.java
+++ b/src/main/java/com/android/apksig/internal/apk/stamp/SourceStampConstants.java
@@ -24,4 +24,11 @@
     public static final int V2_SOURCE_STAMP_BLOCK_ID = 0x6dff800d;
     public static final String SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME = "stamp-cert-sha256";
     public static final int PROOF_OF_ROTATION_ATTR_ID = 0x9d6303f7;
+    /**
+     * The source stamp timestamp attribute value is an 8-byte little-endian encoded long
+     * representing the epoch time in seconds when the stamp block was signed. The first 8 bytes
+     * of the attribute value buffer will be used to read the timestamp, and any additional buffer
+     * space will be ignored.
+     */
+    public static final int STAMP_TIME_ATTR_ID = 0xe43c5946;
 }
diff --git a/src/main/java/com/android/apksig/internal/apk/stamp/SourceStampVerifier.java b/src/main/java/com/android/apksig/internal/apk/stamp/SourceStampVerifier.java
index 59fa791..aace413 100644
--- a/src/main/java/com/android/apksig/internal/apk/stamp/SourceStampVerifier.java
+++ b/src/main/java/com/android/apksig/internal/apk/stamp/SourceStampVerifier.java
@@ -318,6 +318,15 @@
                 byte[] value = ByteBufferUtils.toByteArray(attribute);
                 if (id == SourceStampConstants.PROOF_OF_ROTATION_ATTR_ID) {
                     readStampCertificateLineage(value, sourceStampCertificate, result);
+                } else if (id == SourceStampConstants.STAMP_TIME_ATTR_ID) {
+                    long timestamp = ByteBuffer.wrap(value).order(
+                            ByteOrder.LITTLE_ENDIAN).getLong();
+                    if (timestamp > 0) {
+                        result.timestamp = timestamp;
+                    } else {
+                        result.addWarning(ApkVerificationIssue.SOURCE_STAMP_INVALID_TIMESTAMP,
+                                timestamp);
+                    }
                 } else {
                     result.addWarning(ApkVerificationIssue.SOURCE_STAMP_UNKNOWN_ATTRIBUTE, id);
                 }
diff --git a/src/main/java/com/android/apksig/internal/apk/stamp/V2SourceStampSigner.java b/src/main/java/com/android/apksig/internal/apk/stamp/V2SourceStampSigner.java
index 1c1570a..9c00a88 100644
--- a/src/main/java/com/android/apksig/internal/apk/stamp/V2SourceStampSigner.java
+++ b/src/main/java/com/android/apksig/internal/apk/stamp/V2SourceStampSigner.java
@@ -35,6 +35,7 @@
 import java.security.NoSuchAlgorithmException;
 import java.security.SignatureException;
 import java.security.cert.CertificateEncodingException;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -202,6 +203,22 @@
 
     private static Map<Integer, byte[]> generateStampAttributes(SigningCertificateLineage lineage) {
         HashMap<Integer, byte[]> stampAttributes = new HashMap<>();
+
+        // Write the current epoch time as the timestamp for the source stamp.
+        long timestamp = Instant.now().getEpochSecond();
+        if (timestamp > 0) {
+            ByteBuffer attributeBuffer = ByteBuffer.allocate(8);
+            attributeBuffer.order(ByteOrder.LITTLE_ENDIAN);
+            attributeBuffer.putLong(timestamp);
+            stampAttributes.put(SourceStampConstants.STAMP_TIME_ATTR_ID, attributeBuffer.array());
+        } else {
+            // The epoch time should never be <= 0, and since security decisions can potentially
+            // be made based on the value in the timestamp, throw an Exception to ensure the issues
+            // with the environment are resolved before allowing the signing.
+            throw new IllegalStateException(
+                    "Received an invalid value from Instant#getTimestamp: " + timestamp);
+        }
+
         if (lineage != null) {
             stampAttributes.put(SourceStampConstants.PROOF_OF_ROTATION_ATTR_ID,
                     lineage.encodeSigningCertificateLineage());
diff --git a/src/test/java/com/android/apksig/ApkSignerTest.java b/src/test/java/com/android/apksig/ApkSignerTest.java
index 9004d80..9740d75 100644
--- a/src/test/java/com/android/apksig/ApkSignerTest.java
+++ b/src/test/java/com/android/apksig/ApkSignerTest.java
@@ -51,7 +51,6 @@
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -1999,6 +1998,31 @@
                 SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
     }
 
+    @Test
+    public void testSourceStampTimestamp_signWithSourceStamp_validTimestampValue()
+            throws Exception {
+        // Source stamps should include a timestamp attribute with the epoch time the stamp block
+        // was signed. This test verifies a standard signing with a source stamp includes a valid
+        // value for the source stamp timestamp attribute.
+        ApkSigner.SignerConfig rsa2048SignerConfig = getDefaultSignerConfigFromResources(
+                FIRST_RSA_2048_SIGNER_RESOURCE_NAME);
+        List<ApkSigner.SignerConfig> ecP256SignerConfig = Collections.singletonList(
+                getDefaultSignerConfigFromResources(EC_P256_SIGNER_RESOURCE_NAME));
+
+        File signedApk = sign("original.apk",
+                new ApkSigner.Builder(ecP256SignerConfig)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(true)
+                        .setV3SigningEnabled(true)
+                        .setV4SigningEnabled(false)
+                        .setSourceStampSignerConfig(rsa2048SignerConfig));
+        ApkVerifier.Result result = verify(signedApk, null);
+
+        assertSourceStampVerified(signedApk, result);
+        long timestamp = result.getSourceStampInfo().getTimestampEpochSeconds();
+        assertTrue("Invalid source stamp timestamp value: " + timestamp, timestamp > 0);
+    }
+
     /**
      * Asserts the provided {@code signedApk} contains a signature block with the expected
      * {@code byte[]} value and block ID as specified in the {@code expectedBlock}.
diff --git a/src/test/java/com/android/apksig/ApkVerifierTest.java b/src/test/java/com/android/apksig/ApkVerifierTest.java
index 31ed430..9de2b59 100644
--- a/src/test/java/com/android/apksig/ApkVerifierTest.java
+++ b/src/test/java/com/android/apksig/ApkVerifierTest.java
@@ -1313,6 +1313,123 @@
     }
 
     @Test
+    public void verifySourceStamp_noTimestamp_returnsDefaultValue() throws Exception {
+        // A timestamp attribute was added to the source stamp, but verification of APKs that were
+        // generated prior to the addition of the timestamp should still complete successfully,
+        // returning a default value of 0 for the timestamp.
+        ApkVerifier.Result verificationResult = verifySourceStamp("v3-only-with-stamp.apk");
+
+        assertTrue(verificationResult.isSourceStampVerified());
+        assertEquals(
+                "A value of 0 should be returned for the timestamp when the attribute is not "
+                        + "present",
+                0, verificationResult.getSourceStampInfo().getTimestampEpochSeconds());
+    }
+
+    @Test
+    public void verifySourceStamp_validTimestamp_returnsExpectedValue() throws Exception {
+        // Once an APK is signed with a source stamp that contains a valid value for the timestamp
+        // attribute, verification of the source stamp should result in the same value for the
+        // timestamp returned to the verifier.
+        ApkVerifier.Result verificationResult = verifySourceStamp(
+                "stamp-valid-timestamp-value.apk");
+
+        assertTrue(verificationResult.isSourceStampVerified());
+        assertEquals(1644886584, verificationResult.getSourceStampInfo().getTimestampEpochSeconds());
+    }
+
+    @Test
+    public void verifySourceStamp_validTimestampLargerBuffer_returnsExpectedValue()
+            throws Exception {
+        // The source stamp timestamp attribute value is expected to be written to an 8 byte buffer
+        // as a little-endian long; while a larger buffer will not result in an error, any
+        // additional space after the buffer's initial 8 bytes will be ignored. This test verifies a
+        // valid timestamp value written to the first 8 bytes of a 16 byte buffer can still be read
+        // successfully.
+        ApkVerifier.Result verificationResult = verifySourceStamp(
+                "stamp-valid-timestamp-16-byte-buffer.apk");
+
+        assertTrue(verificationResult.isSourceStampVerified());
+        assertEquals(1645126786,
+                verificationResult.getSourceStampInfo().getTimestampEpochSeconds());
+    }
+
+    @Test
+    public void verifySourceStamp_invalidTimestampValueEqualsZero_verificationFails()
+            throws Exception {
+        // If the source stamp timestamp attribute exists and is <= 0, then a warning should be
+        // reported to notify the caller to the invalid attribute value. This test verifies a
+        // a warning is reported when the timestamp attribute value is 0.
+        ApkVerifier.Result verificationResult = verifySourceStamp(
+                "stamp-invalid-timestamp-value-zero.apk");
+
+        assertSourceStampVerificationStatus(verificationResult,
+                SourceStampVerificationStatus.STAMP_VERIFICATION_FAILED);
+        assertSourceStampVerificationFailure(verificationResult,
+                Issue.SOURCE_STAMP_INVALID_TIMESTAMP);
+    }
+
+    @Test
+    public void verifySourceStamp_invalidTimestampValueLessThanZero_verificationFails()
+            throws Exception {
+        // If the source stamp timestamp attribute exists and is <= 0, then a warning should be
+        // reported to notify the caller to the invalid attribute value. This test verifies a
+        // a warning is reported when the timestamp attribute value is < 0.
+        ApkVerifier.Result verificationResult = verifySourceStamp(
+                "stamp-invalid-timestamp-value-less-than-zero.apk");
+
+        assertSourceStampVerificationStatus(verificationResult,
+                SourceStampVerificationStatus.STAMP_VERIFICATION_FAILED);
+        assertSourceStampVerificationFailure(verificationResult,
+                Issue.SOURCE_STAMP_INVALID_TIMESTAMP);
+    }
+
+    @Test
+    public void verifySourceStamp_invalidTimestampZeroInFirst8BytesOfBuffer_verificationFails()
+            throws Exception {
+        // The source stamp's timestamp attribute value is expected to be written to the first 8
+        // bytes of the attribute's value buffer; if a larger buffer is used and the timestamp
+        // value is not written as a little-endian long to the first 8 bytes of the buffer, then
+        // an error should be reported for the timestamp attribute since the rest of the buffer will
+        // be ignored.
+        ApkVerifier.Result verificationResult = verifySourceStamp(
+                "stamp-timestamp-in-last-8-of-16-byte-buffer.apk");
+
+        assertSourceStampVerificationStatus(verificationResult,
+                SourceStampVerificationStatus.STAMP_VERIFICATION_FAILED);
+        assertSourceStampVerificationFailure(verificationResult,
+                Issue.SOURCE_STAMP_INVALID_TIMESTAMP);
+    }
+
+
+    @Test
+    public void verifySourceStamp_intTimestampValue_verificationFails() throws Exception {
+        // Since the source stamp timestamp attribute value is a long, an attribute value with
+        // insufficient space to hold a long value should result in a warning reported to the user.
+        ApkVerifier.Result verificationResult = verifySourceStamp(
+                "stamp-int-timestamp-value.apk");
+
+        assertSourceStampVerificationStatus(verificationResult,
+                SourceStampVerificationStatus.STAMP_VERIFICATION_FAILED);
+        assertSourceStampVerificationFailure(verificationResult,
+                Issue.SOURCE_STAMP_MALFORMED_ATTRIBUTE);
+    }
+
+    @Test
+    public void verifySourceStamp_modifiedTimestampValue_verificationFails() throws Exception {
+        // The source stamp timestamp attribute is part of the block's signed data; this test
+        // verifies if the value of the timestamp in the stamp block is modified then verification
+        // of the source stamp should fail.
+        ApkVerifier.Result verificationResult = verifySourceStamp(
+                "stamp-valid-timestamp-value-modified.apk");
+
+        assertSourceStampVerificationStatus(verificationResult,
+                SourceStampVerificationStatus.STAMP_VERIFICATION_FAILED);
+        assertSourceStampVerificationFailure(verificationResult,
+                Issue.SOURCE_STAMP_DID_NOT_VERIFY);
+    }
+
+    @Test
     public void apkVerificationIssueAdapter_verifyAllBaseIssuesMapped() throws Exception {
         Field[] fields = ApkVerificationIssue.class.getFields();
         StringBuilder msg = new StringBuilder();
diff --git a/src/test/java/com/android/apksig/SourceStampVerifierTest.java b/src/test/java/com/android/apksig/SourceStampVerifierTest.java
index f5020cc..2e54a8a 100644
--- a/src/test/java/com/android/apksig/SourceStampVerifierTest.java
+++ b/src/test/java/com/android/apksig/SourceStampVerifierTest.java
@@ -21,10 +21,12 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 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.AndroidSdkVersion;
 import com.android.apksig.internal.util.Resources;
 import com.android.apksig.util.DataSources;
 
@@ -269,6 +271,110 @@
                 ApkVerificationIssue.JAR_SIG_NO_SIGNATURES);
     }
 
+    @Test
+    public void verifySourceStamp_noTimestamp_returnsDefaultValue() throws Exception {
+        // A timestamp attribute was added to the source stamp, but verification of APKs that were
+        // generated prior to the addition of the timestamp should still complete successfully,
+        // returning a default value of 0 for the timestamp.
+        Result verificationResult = verifySourceStamp("v3-only-with-stamp.apk", AndroidSdkVersion.P,
+                AndroidSdkVersion.P);
+
+        assertVerified(verificationResult);
+        assertEquals(
+                "A value of 0 should be returned for the timestamp when the attribute is not "
+                        + "present",
+                0, verificationResult.getSourceStampInfo().getTimestampEpochSeconds());
+    }
+
+    @Test
+    public void verifySourceStamp_validTimestamp_returnsExpectedValue() throws Exception {
+        // Once an APK is signed with a source stamp that contains a valid value for the timestamp
+        // attribute, verification of the source stamp should result in the same value for the
+        // timestamp returned to the verifier.
+        Result verificationResult = verifySourceStamp("stamp-valid-timestamp-value.apk");
+
+        assertVerified(verificationResult);
+        assertEquals(1644886584, verificationResult.getSourceStampInfo().getTimestampEpochSeconds());
+    }
+
+    @Test
+    public void verifySourceStamp_validTimestampLargerBuffer_returnsExpectedValue()
+            throws Exception {
+        // The source stamp timestamp attribute value is expected to be written to an 8 byte buffer
+        // as a little-endian long; while a larger buffer will not result in an error, any
+        // additional space after the buffer's initial 8 bytes will be ignored. This test verifies a
+        // valid timestamp value written to the first 8 bytes of a 16 byte buffer can still be read
+        // successfully.
+        Result verificationResult = verifySourceStamp("stamp-valid-timestamp-16-byte-buffer.apk");
+
+        assertEquals(1645126786,
+                verificationResult.getSourceStampInfo().getTimestampEpochSeconds());
+    }
+
+    @Test
+    public void verifySourceStamp_invalidTimestampValueEqualsZero_verificationFails()
+            throws Exception {
+        // If the source stamp timestamp attribute exists and is <= 0, then a warning should be
+        // reported to notify the caller to the invalid attribute value. This test verifies a
+        // a warning is reported when the timestamp attribute value is 0.
+        Result verificationResult = verifySourceStamp("stamp-invalid-timestamp-value-zero.apk");
+
+        assertSourceStampVerificationFailure(verificationResult,
+                ApkVerificationIssue.SOURCE_STAMP_INVALID_TIMESTAMP);
+    }
+
+    @Test
+    public void verifySourceStamp_invalidTimestampValueLessThanZero_verificationFails()
+            throws Exception {
+        // If the source stamp timestamp attribute exists and is <= 0, then a warning should be
+        // reported to notify the caller to the invalid attribute value. This test verifies a
+        // a warning is reported when the timestamp attribute value is < 0.
+        Result verificationResult = verifySourceStamp(
+                "stamp-invalid-timestamp-value-less-than-zero.apk");
+
+        assertSourceStampVerificationFailure(verificationResult,
+                ApkVerificationIssue.SOURCE_STAMP_INVALID_TIMESTAMP);
+    }
+
+    @Test
+    public void verifySourceStamp_invalidTimestampZeroInFirst8BytesOfBuffer_verificationFails()
+            throws Exception {
+        // The source stamp's timestamp attribute value is expected to be written to the first 8
+        // bytes of the attribute's value buffer; if a larger buffer is used and the timestamp
+        // value is not written as a little-endian long to the first 8 bytes of the buffer, then
+        // an error should be reported for the timestamp attribute since the rest of the buffer will
+        // be ignored.
+        Result verificationResult = verifySourceStamp(
+                "stamp-timestamp-in-last-8-of-16-byte-buffer.apk");
+
+        assertSourceStampVerificationFailure(verificationResult,
+                ApkVerificationIssue.SOURCE_STAMP_INVALID_TIMESTAMP);
+    }
+
+    @Test
+    public void verifySourceStamp_intTimestampValue_verificationFails() throws Exception {
+        // Since the source stamp timestamp attribute value is a long, an attribute value with
+        // insufficient space to hold a long value should result in a warning reported to the user.
+        Result verificationResult = verifySourceStamp(
+                "stamp-int-timestamp-value.apk");
+
+        assertSourceStampVerificationFailure(verificationResult,
+                ApkVerificationIssue.SOURCE_STAMP_MALFORMED_ATTRIBUTE);
+    }
+
+    @Test
+    public void verifySourceStamp_modifiedTimestampValue_verificationFails() throws Exception {
+        // The source stamp timestamp attribute is part of the block's signed data; this test
+        // verifies if the value of the timestamp in the stamp block is modified then verification
+        // of the source stamp should fail.
+        Result verificationResult = verifySourceStamp(
+                "stamp-valid-timestamp-value-modified.apk");
+
+        assertSourceStampVerificationFailure(verificationResult,
+                ApkVerificationIssue.SOURCE_STAMP_DID_NOT_VERIFY);
+    }
+
+
     private Result verifySourceStamp(String apkFilenameInResources)
             throws Exception {
         return verifySourceStamp(apkFilenameInResources, null, null, null);
diff --git a/src/test/resources/com/android/apksig/stamp-int-timestamp-value.apk b/src/test/resources/com/android/apksig/stamp-int-timestamp-value.apk
new file mode 100644
index 0000000..0cd740c
--- /dev/null
+++ b/src/test/resources/com/android/apksig/stamp-int-timestamp-value.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/stamp-invalid-timestamp-value-less-than-zero.apk b/src/test/resources/com/android/apksig/stamp-invalid-timestamp-value-less-than-zero.apk
new file mode 100644
index 0000000..f4a189e
--- /dev/null
+++ b/src/test/resources/com/android/apksig/stamp-invalid-timestamp-value-less-than-zero.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/stamp-invalid-timestamp-value-zero.apk b/src/test/resources/com/android/apksig/stamp-invalid-timestamp-value-zero.apk
new file mode 100644
index 0000000..6cfd082
--- /dev/null
+++ b/src/test/resources/com/android/apksig/stamp-invalid-timestamp-value-zero.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/stamp-timestamp-in-last-8-of-16-byte-buffer.apk b/src/test/resources/com/android/apksig/stamp-timestamp-in-last-8-of-16-byte-buffer.apk
new file mode 100644
index 0000000..260b0ca
--- /dev/null
+++ b/src/test/resources/com/android/apksig/stamp-timestamp-in-last-8-of-16-byte-buffer.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/stamp-valid-timestamp-16-byte-buffer.apk b/src/test/resources/com/android/apksig/stamp-valid-timestamp-16-byte-buffer.apk
new file mode 100644
index 0000000..da9c34d
--- /dev/null
+++ b/src/test/resources/com/android/apksig/stamp-valid-timestamp-16-byte-buffer.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/stamp-valid-timestamp-value-modified.apk b/src/test/resources/com/android/apksig/stamp-valid-timestamp-value-modified.apk
new file mode 100644
index 0000000..eefc148
--- /dev/null
+++ b/src/test/resources/com/android/apksig/stamp-valid-timestamp-value-modified.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/stamp-valid-timestamp-value.apk b/src/test/resources/com/android/apksig/stamp-valid-timestamp-value.apk
new file mode 100644
index 0000000..3c6a501
--- /dev/null
+++ b/src/test/resources/com/android/apksig/stamp-valid-timestamp-value.apk
Binary files differ