diff options
author | Khaled Abdelmohsen <khelmy@google.com> | 2020-09-25 22:02:00 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-09-25 22:02:00 +0000 |
commit | 2679c0dc9500555e176c9fffc0e2b85165beff09 (patch) | |
tree | 26a23437478a5d21d59688e365a465923bbb59ac | |
parent | 76bdd3e5d53f4b17061eb0e2fba9a33018ed5d77 (diff) | |
parent | 93371f21af3ac42d2ab51911e0f34fefcfb63067 (diff) | |
download | apksig-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.java | 19 | ||||
-rw-r--r-- | src/main/java/com/android/apksig/ApkVerifier.java | 105 | ||||
-rw-r--r-- | src/main/java/com/android/apksig/SourceStampVerifier.java | 2 | ||||
-rw-r--r-- | src/main/java/com/android/apksig/internal/apk/ApkSignerInfo.java | 1 | ||||
-rw-r--r-- | src/main/java/com/android/apksig/internal/apk/stamp/SourceStampVerifier.java | 115 | ||||
-rw-r--r-- | src/main/java/com/android/apksig/internal/apk/stamp/V2SourceStampSigner.java | 9 | ||||
-rw-r--r-- | src/test/java/com/android/apksig/ApkSignerTest.java | 32 | ||||
-rw-r--r-- | src/test/java/com/android/apksig/ApkVerifierTest.java | 19 | ||||
-rw-r--r-- | src/test/java/com/android/apksig/SourceStampVerifierTest.java | 15 | ||||
-rw-r--r-- | src/test/resources/com/android/apksig/stamp-lineage-invalid.apk | bin | 0 -> 16854 bytes | |||
-rw-r--r-- | src/test/resources/com/android/apksig/stamp-lineage-valid.apk | bin | 0 -> 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 Binary files differnew file mode 100644 index 0000000..f9777c3 --- /dev/null +++ b/src/test/resources/com/android/apksig/stamp-lineage-invalid.apk diff --git a/src/test/resources/com/android/apksig/stamp-lineage-valid.apk b/src/test/resources/com/android/apksig/stamp-lineage-valid.apk Binary files differnew file mode 100644 index 0000000..955652e --- /dev/null +++ b/src/test/resources/com/android/apksig/stamp-lineage-valid.apk |