aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKhaled Abdelmohsen <khelmy@google.com>2020-09-25 22:02:00 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2020-09-25 22:02:00 +0000
commit2679c0dc9500555e176c9fffc0e2b85165beff09 (patch)
tree26a23437478a5d21d59688e365a465923bbb59ac
parent76bdd3e5d53f4b17061eb0e2fba9a33018ed5d77 (diff)
parent93371f21af3ac42d2ab51911e0f34fefcfb63067 (diff)
downloadapksig-2679c0dc9500555e176c9fffc0e2b85165beff09.tar.gz
Verify source stamp lineage am: 211faf0d42 am: 93371f21af
Original change: https://googleplex-android-review.googlesource.com/c/platform/tools/apksig/+/12692887 Change-Id: I67314cca3517632bcb5e9e7f399c57df9f6e0a5f
-rw-r--r--src/main/java/com/android/apksig/ApkVerificationIssue.java19
-rw-r--r--src/main/java/com/android/apksig/ApkVerifier.java105
-rw-r--r--src/main/java/com/android/apksig/SourceStampVerifier.java2
-rw-r--r--src/main/java/com/android/apksig/internal/apk/ApkSignerInfo.java1
-rw-r--r--src/main/java/com/android/apksig/internal/apk/stamp/SourceStampVerifier.java115
-rw-r--r--src/main/java/com/android/apksig/internal/apk/stamp/V2SourceStampSigner.java9
-rw-r--r--src/test/java/com/android/apksig/ApkSignerTest.java32
-rw-r--r--src/test/java/com/android/apksig/ApkVerifierTest.java19
-rw-r--r--src/test/java/com/android/apksig/SourceStampVerifierTest.java15
-rw-r--r--src/test/resources/com/android/apksig/stamp-lineage-invalid.apkbin0 -> 16854 bytes
-rw-r--r--src/test/resources/com/android/apksig/stamp-lineage-valid.apkbin0 -> 16854 bytes
11 files changed, 264 insertions, 53 deletions
diff --git a/src/main/java/com/android/apksig/ApkVerificationIssue.java b/src/main/java/com/android/apksig/ApkVerificationIssue.java
index bb3cb65..79c50d4 100644
--- a/src/main/java/com/android/apksig/ApkVerificationIssue.java
+++ b/src/main/java/com/android/apksig/ApkVerificationIssue.java
@@ -93,6 +93,25 @@ public class ApkVerificationIssue {
public static final int UNEXPECTED_EXCEPTION = 29;
/* The APK contains the certificate digest file but does not contain a stamp signature block */
public static final int SOURCE_STAMP_SIG_MISSING = 30;
+ /* Source stamp block contains a malformed attribute. */
+ public static final int SOURCE_STAMP_MALFORMED_ATTRIBUTE = 31;
+ /* Source stamp block contains an unknown attribute. */
+ public static final int SOURCE_STAMP_UNKNOWN_ATTRIBUTE = 32;
+ /**
+ * Failed to parse the SigningCertificateLineage structure in the source stamp
+ * attributes section.
+ */
+ public static final int SOURCE_STAMP_MALFORMED_LINEAGE = 33;
+ /**
+ * The source stamp certificate does not match the terminal node in the provided
+ * proof-of-rotation structure describing the stamp certificate history.
+ */
+ public static final int SOURCE_STAMP_POR_CERT_MISMATCH = 34;
+ /**
+ * The source stamp SigningCertificateLineage attribute contains a proof-of-rotation record
+ * with signature(s) that did not verify.
+ */
+ public static final int SOURCE_STAMP_POR_DID_NOT_VERIFY = 35;
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 475ae42..c186784 100644
--- a/src/main/java/com/android/apksig/ApkVerifier.java
+++ b/src/main/java/com/android/apksig/ApkVerifier.java
@@ -84,7 +84,7 @@ public class ApkVerifier {
private static final Map<Integer, String> SUPPORTED_APK_SIG_SCHEME_NAMES =
loadSupportedApkSigSchemeNames();
- private static Map<Integer,String> loadSupportedApkSigSchemeNames() {
+ private static Map<Integer, String> loadSupportedApkSigSchemeNames() {
Map<Integer, String> supportedMap = new HashMap<>(2);
supportedMap.put(
ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2, "APK Signature Scheme v2");
@@ -125,12 +125,12 @@ public class ApkVerifier {
* or more errors and whose {@link Result#isVerified()} returns {@code false}, or this method
* throws an exception.
*
- * @throws IOException if an I/O error is encountered while reading the APK
- * @throws ApkFormatException if the APK is malformed
+ * @throws IOException if an I/O error is encountered while reading the APK
+ * @throws ApkFormatException if the APK is malformed
* @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a
- * required cryptographic algorithm implementation is missing
- * @throws IllegalStateException if this verifier's configuration is missing required
- * information.
+ * required cryptographic algorithm implementation is missing
+ * @throws IllegalStateException if this verifier's configuration is missing required
+ * information.
*/
public Result verify() throws IOException, ApkFormatException, NoSuchAlgorithmException,
IllegalStateException {
@@ -160,11 +160,10 @@ public class ApkVerifier {
* The verification result also includes errors, warnings, and information about signers.
*
* @param apk APK file contents
- *
- * @throws IOException if an I/O error is encountered while reading the APK
- * @throws ApkFormatException if the APK is malformed
+ * @throws IOException if an I/O error is encountered while reading the APK
+ * @throws ApkFormatException if the APK is malformed
* @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a
- * required cryptographic algorithm implementation is missing
+ * required cryptographic algorithm implementation is missing
*/
private Result verify(DataSource apk)
throws IOException, ApkFormatException, NoSuchAlgorithmException {
@@ -426,7 +425,7 @@ public class ApkVerifier {
}
try {
if (!Arrays.equals(oldSignerCert.getEncoded(),
- v3Signers.get(0).mCerts.get(0).getEncoded())) {
+ v3Signers.get(0).mCerts.get(0).getEncoded())) {
result.addError(Issue.V3_SIG_PAST_SIGNERS_MISMATCH);
}
} catch (CertificateEncodingException e) {
@@ -896,7 +895,8 @@ public class ApkVerifier {
return result;
}
- private static void checkV4Certificate(List<X509Certificate> v4Certs, List<X509Certificate> v2v3Certs, Result result) {
+ private static void checkV4Certificate(List<X509Certificate> v4Certs,
+ List<X509Certificate> v2v3Certs, Result result) {
try {
byte[] v4Cert = v4Certs.get(0).getEncoded();
byte[] cert = v2v3Certs.get(0).getEncoded();
@@ -908,7 +908,8 @@ public class ApkVerifier {
}
}
- private static byte[] pickBestDigestForV4(List<ApkSigningBlockUtils.Result.SignerInfo.ContentDigest> contentDigests) {
+ private static byte[] pickBestDigestForV4(
+ List<ApkSigningBlockUtils.Result.SignerInfo.ContentDigest> contentDigests) {
Map<ContentDigestAlgorithm, byte[]> apkContentDigests = new HashMap<>();
collectApkContentDigests(contentDigests, apkContentDigests);
return ApkSigningBlockUtils.pickBestDigestForV4(apkContentDigests);
@@ -955,7 +956,9 @@ public class ApkVerifier {
}
}
- private static void collectApkContentDigests(List<ApkSigningBlockUtils.Result.SignerInfo.ContentDigest> contentDigests, Map<ContentDigestAlgorithm, byte[]> apkContentDigests) {
+ private static void collectApkContentDigests(
+ List<ApkSigningBlockUtils.Result.SignerInfo.ContentDigest> contentDigests,
+ Map<ContentDigestAlgorithm, byte[]> apkContentDigests) {
for (ApkSigningBlockUtils.Result.SignerInfo.ContentDigest contentDigest : contentDigests) {
SignatureAlgorithm signatureAlgorithm =
SignatureAlgorithm.findById(contentDigest.getSignatureAlgorithmId());
@@ -971,7 +974,7 @@ public class ApkVerifier {
private static ByteBuffer getAndroidManifestFromApk(
DataSource apk, ApkUtils.ZipSections zipSections)
- throws IOException, ApkFormatException {
+ throws IOException, ApkFormatException {
List<CentralDirectoryRecord> cdRecords =
V1SchemeVerifier.parseZipCentralDirectory(apk, zipSections);
try {
@@ -1224,8 +1227,6 @@ public class ApkVerifier {
default:
throw new IllegalArgumentException("Unknown Signing Block Scheme Id");
}
- mErrors.addAll(source.getErrors());
- mWarnings.addAll(source.getWarnings());
}
/**
@@ -1633,8 +1634,10 @@ public class ApkVerifier {
STAMP_NOT_VERIFIED,
/** The stamp was not able to be verified due to an unexpected error. */
VERIFICATION_ERROR
- };
+ }
+
private final List<X509Certificate> mCertificates;
+ private final List<X509Certificate> mCertificateLineage;
private final List<IssueWithParams> mErrors;
private final List<IssueWithParams> mWarnings;
@@ -1643,6 +1646,7 @@ public class ApkVerifier {
private SourceStampInfo(ApkSignerInfo result) {
mCertificates = result.certs;
+ mCertificateLineage = result.certificateLineage;
mErrors = ApkVerificationIssueAdapter.getIssuesFromVerificationIssues(
result.getErrors());
mWarnings = ApkVerificationIssueAdapter.getIssuesFromVerificationIssues(
@@ -1657,6 +1661,7 @@ public class ApkVerifier {
SourceStampInfo(SourceStampVerificationStatus sourceStampVerificationStatus) {
mCertificates = Collections.emptyList();
+ mCertificateLineage = Collections.emptyList();
mErrors = Collections.emptyList();
mWarnings = Collections.emptyList();
mSourceStampVerificationStatus = sourceStampVerificationStatus;
@@ -1673,6 +1678,13 @@ public class ApkVerifier {
return mCertificates.isEmpty() ? null : mCertificates.get(0);
}
+ /**
+ * Returns a list containing all of the certificates in the stamp certificate lineage.
+ */
+ public List<X509Certificate> getCertificatesInLineage() {
+ return mCertificateLineage;
+ }
+
public boolean containsErrors() {
return !mErrors.isEmpty();
}
@@ -2770,6 +2782,46 @@ public class ApkVerifier {
+ "expected digest, %2$s"),
/**
+ * Source stamp block contains a malformed attribute.
+ *
+ * <ul>
+ * <li>Parameter 1: attribute number (first attribute is {@code 1}) {@code Integer})</li>
+ * </ul>
+ */
+ SOURCE_STAMP_MALFORMED_ATTRIBUTE("Malformed stamp attribute #%1$d"),
+
+ /**
+ * Source stamp block contains an unknown attribute.
+ *
+ * <ul>
+ * <li>Parameter 1: attribute ID ({@code Integer})</li>
+ * </ul>
+ */
+ SOURCE_STAMP_UNKNOWN_ATTRIBUTE("Unknown stamp attribute: ID %1$#x"),
+
+ /**
+ * Failed to parse the SigningCertificateLineage structure in the source stamp
+ * attributes section.
+ */
+ SOURCE_STAMP_MALFORMED_LINEAGE("Failed to parse the SigningCertificateLineage "
+ + "structure in the source stamp attributes section."),
+
+ /**
+ * The source stamp certificate does not match the terminal node in the provided
+ * proof-of-rotation structure describing the stamp certificate history.
+ */
+ SOURCE_STAMP_POR_CERT_MISMATCH(
+ "APK signing certificate differs from the associated certificate found in the "
+ + "signer's SigningCertificateLineage."),
+
+ /**
+ * The source stamp SigningCertificateLineage attribute contains a proof-of-rotation record
+ * with signature(s) that did not verify.
+ */
+ SOURCE_STAMP_POR_DID_NOT_VERIFY("Source stamp SigningCertificateLineage attribute "
+ + "contains a proof-of-rotation record with signature(s) that did not verify."),
+
+ /**
* 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.
@@ -2777,7 +2829,7 @@ public class ApkVerifier {
*/
MALFORMED_APK(
"Malformed APK; the following exception was caught when attempting to parse the "
- + "APK: %1$s"),
+ + "APK: %1$s"),
/**
* An unexpected exception was caught when attempting to verify the signature(s) within the
@@ -2932,7 +2984,6 @@ public class ApkVerifier {
* {@code android:minSdkVersion} attributes in the APK's {@code AndroidManifest.xml}.
*
* @param minSdkVersion API Level of the oldest platform for which to verify the APK
- *
* @see #setMinCheckedPlatformVersion(int)
*/
public Builder setMinCheckedPlatformVersion(int minSdkVersion) {
@@ -2948,7 +2999,6 @@ public class ApkVerifier {
* {@link #setMinCheckedPlatformVersion(int)}.
*
* @param maxSdkVersion API Level of the newest platform for which to verify the APK
- *
* @see #setMinCheckedPlatformVersion(int)
*/
public Builder setMaxCheckedPlatformVersion(int maxSdkVersion) {
@@ -2980,7 +3030,8 @@ public class ApkVerifier {
* IssueWithParams} equivalent.
*/
public static class ApkVerificationIssueAdapter {
- private ApkVerificationIssueAdapter() {}
+ private ApkVerificationIssueAdapter() {
+ }
private static final Map<Integer, Issue> sVerificationIssueIdToIssue = new HashMap<>();
@@ -3051,6 +3102,16 @@ public class ApkVerifier {
Issue.UNEXPECTED_EXCEPTION);
sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_SIG_MISSING,
Issue.SOURCE_STAMP_SIG_MISSING);
+ sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_MALFORMED_ATTRIBUTE,
+ Issue.SOURCE_STAMP_MALFORMED_ATTRIBUTE);
+ sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_UNKNOWN_ATTRIBUTE,
+ Issue.SOURCE_STAMP_UNKNOWN_ATTRIBUTE);
+ sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_MALFORMED_LINEAGE,
+ Issue.SOURCE_STAMP_MALFORMED_LINEAGE);
+ sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_POR_CERT_MISMATCH,
+ Issue.SOURCE_STAMP_POR_CERT_MISMATCH);
+ sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_POR_DID_NOT_VERIFY,
+ Issue.SOURCE_STAMP_POR_DID_NOT_VERIFY);
}
/**
diff --git a/src/main/java/com/android/apksig/SourceStampVerifier.java b/src/main/java/com/android/apksig/SourceStampVerifier.java
index 1f66464..aba6c57 100644
--- a/src/main/java/com/android/apksig/SourceStampVerifier.java
+++ b/src/main/java/com/android/apksig/SourceStampVerifier.java
@@ -616,6 +616,7 @@ public class SourceStampVerifier {
*/
public static class SourceStampInfo {
private final List<X509Certificate> mCertificates;
+ private final List<X509Certificate> mCertificateLineage;
private final List<ApkVerificationIssue> mErrors = new ArrayList<>();
private final List<ApkVerificationIssue> mWarnings = new ArrayList<>();
@@ -631,6 +632,7 @@ public class SourceStampVerifier {
private SourceStampInfo(ApkSignerInfo result) {
mCertificates = result.certs;
+ mCertificateLineage = result.certificateLineage;
mErrors.addAll(result.getErrors());
mWarnings.addAll(result.getWarnings());
}
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 2465c02..e0ea365 100644
--- a/src/main/java/com/android/apksig/internal/apk/ApkSignerInfo.java
+++ b/src/main/java/com/android/apksig/internal/apk/ApkSignerInfo.java
@@ -28,6 +28,7 @@ import java.util.List;
public class ApkSignerInfo {
public int index;
public List<X509Certificate> certs = new ArrayList<>();
+ public List<X509Certificate> certificateLineage = new ArrayList<>();
private final List<ApkVerificationIssue> mWarnings = new ArrayList<>();
private final List<ApkVerificationIssue> mErrors = new ArrayList<>();
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 8f59780..db3e633 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
@@ -15,18 +15,25 @@
*/
package com.android.apksig.internal.apk.stamp;
+import static com.android.apksig.internal.apk.ApkSigningBlockUtilsLite.getLengthPrefixedSlice;
+import static com.android.apksig.internal.apk.ApkSigningBlockUtilsLite.getSignaturesToVerify;
+import static com.android.apksig.internal.apk.ApkSigningBlockUtilsLite.readLengthPrefixedByteArray;
+import static com.android.apksig.internal.apk.ApkSigningBlockUtilsLite.toHex;
+
import com.android.apksig.ApkVerificationIssue;
import com.android.apksig.apk.ApkFormatException;
import com.android.apksig.internal.apk.ApkSignerInfo;
-import com.android.apksig.internal.apk.ApkSigningBlockUtilsLite;
import com.android.apksig.internal.apk.ApkSupportedSignature;
import com.android.apksig.internal.apk.NoApkSupportedSignaturesException;
import com.android.apksig.internal.apk.SignatureAlgorithm;
+import com.android.apksig.internal.apk.v3.V3SigningCertificateLineage;
+import com.android.apksig.internal.util.ByteBufferUtils;
import com.android.apksig.internal.util.GuaranteedEncodedFormX509Certificate;
import java.io.ByteArrayInputStream;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
@@ -56,7 +63,8 @@ import java.util.Map;
*/
class SourceStampVerifier {
/** Hidden constructor to prevent instantiation. */
- private SourceStampVerifier() {}
+ private SourceStampVerifier() {
+ }
/**
* Parses the SourceStamp block and populates the {@code result}.
@@ -83,12 +91,13 @@ class SourceStampVerifier {
return;
}
+ ByteBuffer apkDigestSignatures = getLengthPrefixedSlice(sourceStampBlockData);
verifySourceStampSignature(
apkDigest,
minSdkVersion,
maxSdkVersion,
sourceStampCertificate,
- sourceStampBlockData,
+ apkDigestSignatures,
result);
}
@@ -118,14 +127,13 @@ class SourceStampVerifier {
}
// Parse signed signature schemes block.
- ByteBuffer signedSignatureSchemes =
- ApkSigningBlockUtilsLite.getLengthPrefixedSlice(sourceStampBlockData);
+ ByteBuffer signedSignatureSchemes = getLengthPrefixedSlice(sourceStampBlockData);
Map<Integer, ByteBuffer> signedSignatureSchemeData = new HashMap<>();
while (signedSignatureSchemes.hasRemaining()) {
- ByteBuffer signedSignatureScheme =
- ApkSigningBlockUtilsLite.getLengthPrefixedSlice(signedSignatureSchemes);
+ ByteBuffer signedSignatureScheme = getLengthPrefixedSlice(signedSignatureSchemes);
int signatureSchemeId = signedSignatureScheme.getInt();
- signedSignatureSchemeData.put(signatureSchemeId, signedSignatureScheme);
+ ByteBuffer apkDigestSignatures = getLengthPrefixedSlice(signedSignatureScheme);
+ signedSignatureSchemeData.put(signatureSchemeId, apkDigestSignatures);
}
for (Map.Entry<Integer, byte[]> signatureSchemeApkDigest :
@@ -145,6 +153,23 @@ class SourceStampVerifier {
return;
}
}
+
+ if (sourceStampBlockData.hasRemaining()) {
+ // The stamp block contains some additional attributes.
+ ByteBuffer stampAttributeData = getLengthPrefixedSlice(sourceStampBlockData);
+ ByteBuffer stampAttributeDataSignatures = getLengthPrefixedSlice(sourceStampBlockData);
+
+ byte[] stampAttributeBytes = new byte[stampAttributeData.remaining()];
+ stampAttributeData.get(stampAttributeBytes);
+ stampAttributeData.flip();
+
+ verifySourceStampSignature(stampAttributeBytes, minSdkVersion, maxSdkVersion,
+ sourceStampCertificate, stampAttributeDataSignatures, result);
+ if (result.containsErrors() || result.containsWarnings()) {
+ return;
+ }
+ parseStampAttributes(stampAttributeData, sourceStampCertificate, result);
+ }
}
private static X509Certificate verifySourceStampCertificate(
@@ -154,8 +179,7 @@ class SourceStampVerifier {
ApkSignerInfo result)
throws NoSuchAlgorithmException, ApkFormatException {
// Parse the SourceStamp certificate.
- byte[] sourceStampEncodedCertificate =
- ApkSigningBlockUtilsLite.readLengthPrefixedByteArray(sourceStampBlockData);
+ byte[] sourceStampEncodedCertificate = readLengthPrefixedByteArray(sourceStampBlockData);
X509Certificate sourceStampCertificate;
try {
sourceStampCertificate = (X509Certificate) certFactory.generateCertificate(
@@ -181,35 +205,34 @@ class SourceStampVerifier {
result.addWarning(
ApkVerificationIssue
.SOURCE_STAMP_CERTIFICATE_MISMATCH_BETWEEN_SIGNATURE_BLOCK_AND_APK,
- ApkSigningBlockUtilsLite.toHex(sourceStampBlockCertificateDigest),
- ApkSigningBlockUtilsLite.toHex(sourceStampCertificateDigest));
+ toHex(sourceStampBlockCertificateDigest),
+ toHex(sourceStampCertificateDigest));
return null;
}
return sourceStampCertificate;
}
private static void verifySourceStampSignature(
- byte[] apkDigest,
+ byte[] data,
int minSdkVersion,
int maxSdkVersion,
X509Certificate sourceStampCertificate,
- ByteBuffer signedData,
- ApkSignerInfo result)
- throws ApkFormatException {
+ ByteBuffer signatures,
+ ApkSignerInfo result) {
// Parse the signatures block and identify supported signatures
- ByteBuffer signatures = ApkSigningBlockUtilsLite.getLengthPrefixedSlice(signedData);
int signatureCount = 0;
List<ApkSupportedSignature> supportedSignatures = new ArrayList<>(1);
while (signatures.hasRemaining()) {
signatureCount++;
try {
- ByteBuffer signature = ApkSigningBlockUtilsLite.getLengthPrefixedSlice(signatures);
+ ByteBuffer signature = getLengthPrefixedSlice(signatures);
int sigAlgorithmId = signature.getInt();
- byte[] sigBytes = ApkSigningBlockUtilsLite.readLengthPrefixedByteArray(signature);
+ byte[] sigBytes = readLengthPrefixedByteArray(signature);
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.findById(sigAlgorithmId);
if (signatureAlgorithm == null) {
result.addWarning(
- ApkVerificationIssue.SOURCE_STAMP_UNKNOWN_SIG_ALGORITHM, sigAlgorithmId);
+ ApkVerificationIssue.SOURCE_STAMP_UNKNOWN_SIG_ALGORITHM,
+ sigAlgorithmId);
continue;
}
supportedSignatures.add(
@@ -228,7 +251,7 @@ class SourceStampVerifier {
List<ApkSupportedSignature> signaturesToVerify;
try {
signaturesToVerify =
- ApkSigningBlockUtilsLite.getSignaturesToVerify(
+ getSignaturesToVerify(
supportedSignatures, minSdkVersion, maxSdkVersion, true);
} catch (NoApkSupportedSignaturesException e) {
// To facilitate debugging capture the signature algorithms and resulting exception in
@@ -257,7 +280,7 @@ class SourceStampVerifier {
if (jcaSignatureAlgorithmParams != null) {
sig.setParameter(jcaSignatureAlgorithmParams);
}
- sig.update(apkDigest);
+ sig.update(data);
byte[] sigBytes = signature.signature;
if (!sig.verify(sigBytes)) {
result.addWarning(
@@ -274,4 +297,52 @@ class SourceStampVerifier {
}
}
}
+
+ private static void parseStampAttributes(ByteBuffer stampAttributeData,
+ X509Certificate sourceStampCertificate, ApkSignerInfo result)
+ throws ApkFormatException {
+ ByteBuffer stampAttributes = getLengthPrefixedSlice(stampAttributeData);
+ int stampAttributeCount = 0;
+ while (stampAttributes.hasRemaining()) {
+ stampAttributeCount++;
+ try {
+ ByteBuffer attribute = getLengthPrefixedSlice(stampAttributes);
+ int id = attribute.getInt();
+ byte[] value = ByteBufferUtils.toByteArray(attribute);
+ if (id == SourceStampConstants.PROOF_OF_ROTATION_ATTR_ID) {
+ readStampCertificateLineage(value, sourceStampCertificate, result);
+ } else {
+ result.addWarning(ApkVerificationIssue.SOURCE_STAMP_UNKNOWN_ATTRIBUTE, id);
+ }
+ } catch (ApkFormatException | BufferUnderflowException e) {
+ result.addWarning(ApkVerificationIssue.SOURCE_STAMP_MALFORMED_ATTRIBUTE,
+ stampAttributeCount);
+ return;
+ }
+ }
+ }
+
+ private static void readStampCertificateLineage(byte[] lineageBytes,
+ X509Certificate sourceStampCertificate, ApkSignerInfo result) {
+ try {
+ // SigningCertificateLineage is verified when built
+ List<V3SigningCertificateLineage.SigningCertificateNode> nodes =
+ V3SigningCertificateLineage.readSigningCertificateLineage(
+ ByteBuffer.wrap(lineageBytes).order(ByteOrder.LITTLE_ENDIAN));
+ for (int i = 0; i < nodes.size(); i++) {
+ result.certificateLineage.add(nodes.get(i).signingCert);
+ }
+ // Make sure that the last cert in the chain matches this signer cert
+ if (!sourceStampCertificate.equals(
+ result.certificateLineage.get(result.certificateLineage.size() - 1))) {
+ result.addWarning(ApkVerificationIssue.SOURCE_STAMP_POR_CERT_MISMATCH);
+ }
+ } catch (SecurityException e) {
+ result.addWarning(ApkVerificationIssue.SOURCE_STAMP_POR_DID_NOT_VERIFY);
+ } catch (IllegalArgumentException e) {
+ result.addWarning(ApkVerificationIssue.SOURCE_STAMP_POR_CERT_MISMATCH);
+ } catch (Exception e) {
+ result.addWarning(ApkVerificationIssue.SOURCE_STAMP_MALFORMED_LINEAGE);
+ }
+ }
}
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 46c5b17..1c1570a 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
@@ -178,13 +178,10 @@ public abstract class V2SourceStampSigner {
}
private static byte[] encodeStampAttributes(Map<Integer, byte[]> stampAttributes) {
- if (stampAttributes == null || stampAttributes.isEmpty()) {
- return new byte[0];
- }
-
int payloadSize = 0;
for (byte[] attributeValue : stampAttributes.values()) {
- payloadSize += 4 + attributeValue.length;
+ // Pair size + Attribute ID + Attribute value
+ payloadSize += 4 + 4 + attributeValue.length;
}
// FORMAT (little endian):
@@ -195,6 +192,8 @@ public abstract class V2SourceStampSigner {
result.order(ByteOrder.LITTLE_ENDIAN);
result.putInt(payloadSize);
for (Map.Entry<Integer, byte[]> stampAttribute : stampAttributes.entrySet()) {
+ // Pair size
+ result.putInt(4 + stampAttribute.getValue().length);
result.putInt(stampAttribute.getKey());
result.put(stampAttribute.getValue());
}
diff --git a/src/test/java/com/android/apksig/ApkSignerTest.java b/src/test/java/com/android/apksig/ApkSignerTest.java
index 4e18852..40255a4 100644
--- a/src/test/java/com/android/apksig/ApkSignerTest.java
+++ b/src/test/java/com/android/apksig/ApkSignerTest.java
@@ -1223,6 +1223,32 @@ public class ApkSignerTest {
assertSourceStampVerified(signedApk, sourceStampVerificationResult);
}
+ @Test
+ public void testSignApk_stampBlock_withStampLineage() throws Exception {
+ List<ApkSigner.SignerConfig> signersList =
+ Collections.singletonList(
+ getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME));
+ ApkSigner.SignerConfig sourceStampSigner =
+ getDefaultSignerConfigFromResources(SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
+ SigningCertificateLineage sourceStampLineage =
+ Resources.toSigningCertificateLineage(
+ getClass(), LINEAGE_RSA_2048_2_SIGNERS_RESOURCE_NAME);
+
+ File signedApk =
+ sign(
+ "original.apk",
+ new ApkSigner.Builder(signersList)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true)
+ .setSourceStampSignerConfig(sourceStampSigner)
+ .setSourceStampSigningCertificateLineage(sourceStampLineage));
+
+ ApkVerifier.Result sourceStampVerificationResult =
+ verify(signedApk, /* minSdkVersion= */ null);
+ assertSourceStampVerified(signedApk, sourceStampVerificationResult);
+ }
+
private RSAPublicKey getRSAPublicKeyFromSigningBlock(File apk, int signatureVersionId)
throws Exception {
int signatureVersionBlockId;
@@ -1272,7 +1298,7 @@ public class ApkSignerTest {
private static SignatureInfo getSignatureInfoFromApk(
File apkFile, int signatureVersionId, int signatureVersionBlockId)
throws IOException, ZipFormatException,
- ApkSigningBlockUtils.SignatureNotFoundException {
+ ApkSigningBlockUtils.SignatureNotFoundException {
try (RandomAccessFile f = new RandomAccessFile(apkFile, "r")) {
DataSource apk = DataSources.asDataSource(f, 0, f.length());
ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(apk);
@@ -1300,7 +1326,7 @@ public class ApkSignerTest {
if (out.length() > Integer.MAX_VALUE) {
throw new RuntimeException("Output too large: " + out.length() + " bytes");
}
- byte[] outData = new byte[(int)out.length()];
+ byte[] outData = new byte[(int) out.length()];
try (FileInputStream fis = new FileInputStream(out)) {
fis.read(outData);
}
@@ -1372,7 +1398,7 @@ public class ApkSignerTest {
private static void assertSourceStampVerified(File signedApk, ApkVerifier.Result result)
throws ApkSigningBlockUtils.SignatureNotFoundException, IOException,
- ZipFormatException {
+ ZipFormatException {
SignatureInfo signatureInfo =
getSignatureInfoFromApk(
signedApk,
diff --git a/src/test/java/com/android/apksig/ApkVerifierTest.java b/src/test/java/com/android/apksig/ApkVerifierTest.java
index 7374ddf..6bb5edf 100644
--- a/src/test/java/com/android/apksig/ApkVerifierTest.java
+++ b/src/test/java/com/android/apksig/ApkVerifierTest.java
@@ -61,7 +61,7 @@ public class ApkVerifierTest {
private static final String[] EC_KEY_NAMES = {"p256", "p384", "p521"};
private static final String[] RSA_KEY_NAMES = {"1024", "2048", "3072", "4096", "8192", "16384"};
private static final String[] RSA_KEY_NAMES_2048_AND_LARGER = {
- "2048", "3072", "4096", "8192", "16384"
+ "2048", "3072", "4096", "8192", "16384"
};
private static final String RSA_2048_CERT_SHA256_DIGEST =
@@ -1286,6 +1286,23 @@ public class ApkVerifierTest {
Issue.SOURCE_STAMP_EXPECTED_DIGEST_MISMATCH);
}
+ @Test
+ public void verifySourceStamp_validStampLineage() throws Exception {
+ ApkVerifier.Result verificationResult = verifySourceStamp("stamp-lineage-valid.apk");
+ assertVerified(verificationResult);
+ assertSourceStampVerificationStatus(verificationResult,
+ SourceStampVerificationStatus.STAMP_VERIFIED);
+ }
+
+ @Test
+ public void verifySourceStamp_invalidStampLineage() throws Exception {
+ ApkVerifier.Result verificationResult = verifySourceStamp("stamp-lineage-invalid.apk");
+ assertSourceStampVerificationStatus(verificationResult,
+ SourceStampVerificationStatus.STAMP_VERIFICATION_FAILED);
+ assertSourceStampVerificationFailure(verificationResult,
+ Issue.SOURCE_STAMP_POR_CERT_MISMATCH);
+ }
+
private ApkVerifier.Result verify(String apkFilenameInResources)
throws IOException, ApkFormatException, NoSuchAlgorithmException {
return verify(apkFilenameInResources, null, null);
diff --git a/src/test/java/com/android/apksig/SourceStampVerifierTest.java b/src/test/java/com/android/apksig/SourceStampVerifierTest.java
index 2235657..46323a3 100644
--- a/src/test/java/com/android/apksig/SourceStampVerifierTest.java
+++ b/src/test/java/com/android/apksig/SourceStampVerifierTest.java
@@ -169,6 +169,21 @@ public class SourceStampVerifierTest {
ApkVerificationIssue.SOURCE_STAMP_CERT_DIGEST_AND_SIG_BLOCK_MISSING);
}
+ @Test
+ public void verifySourceStamp_validStampLineage() throws Exception {
+ SourceStampVerifier.Result verificationResult = verifySourceStamp(
+ "stamp-lineage-valid.apk");
+ assertVerified(verificationResult);
+ }
+
+ @Test
+ public void verifySourceStamp_invalidStampLineage() throws Exception {
+ SourceStampVerifier.Result verificationResult = verifySourceStamp(
+ "stamp-lineage-invalid.apk");
+ assertSourceStampVerificationFailure(verificationResult,
+ ApkVerificationIssue.SOURCE_STAMP_POR_CERT_MISMATCH);
+ }
+
private SourceStampVerifier.Result verifySourceStamp(String apkFilenameInResources)
throws Exception {
return verifySourceStamp(apkFilenameInResources, null, null, null);
diff --git a/src/test/resources/com/android/apksig/stamp-lineage-invalid.apk b/src/test/resources/com/android/apksig/stamp-lineage-invalid.apk
new file mode 100644
index 0000000..f9777c3
--- /dev/null
+++ b/src/test/resources/com/android/apksig/stamp-lineage-invalid.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/stamp-lineage-valid.apk b/src/test/resources/com/android/apksig/stamp-lineage-valid.apk
new file mode 100644
index 0000000..955652e
--- /dev/null
+++ b/src/test/resources/com/android/apksig/stamp-lineage-valid.apk
Binary files differ