From 5aa65c1967bdd16e95bf95a3975d16c0e55457b3 Mon Sep 17 00:00:00 2001 From: Khaled Abdelmohsen Date: Fri, 11 Sep 2020 16:15:45 +0100 Subject: [PATCH] Parse source stamp lineage in platform Bug: 167962287 Test: atest FrameworksCoreTests:SourceStampVerifierTest Change-Id: I9e30dde9cebe97ff7b92f02feb5c82b3a58a97b1 --- .../apk/ApkSignatureSchemeV3Verifier.java | 168 ++++-------------- .../util/apk/ApkSigningBlockUtils.java | 127 ++++++++++++- .../apk/SourceStampVerificationResult.java | 25 ++- .../android/util/apk/SourceStampVerifier.java | 104 +++++++++-- .../stamp-lineage-invalid.apk | Bin 0 -> 16854 bytes .../stamp-lineage-valid.apk | Bin 0 -> 16854 bytes .../util/apk/SourceStampVerifierTest.java | 33 ++++ 7 files changed, 295 insertions(+), 162 deletions(-) create mode 100644 core/tests/coretests/assets/SourceStampVerifierTest/stamp-lineage-invalid.apk create mode 100644 core/tests/coretests/assets/SourceStampVerifierTest/stamp-lineage-valid.apk diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java index 5f963b0193351..4d1402a0d6d65 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java @@ -25,6 +25,7 @@ import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyA import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm; import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm; import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray; +import static android.util.apk.ApkSigningBlockUtils.verifyProofOfRotationStruct; import android.os.Build; import android.util.ArrayMap; @@ -53,7 +54,6 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; import java.util.List; import java.util.Map; @@ -90,9 +90,10 @@ public class ApkSignatureSchemeV3Verifier { * associated with each signer. * * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3. - * @throws SecurityException if the APK Signature Scheme v3 signature of this APK does not - * verify. - * @throws IOException if an I/O error occurs while reading the APK file. + * @throws SecurityException if the APK Signature Scheme v3 signature of this APK does + * not + * verify. + * @throws IOException if an I/O error occurs while reading the APK file. */ public static VerifiedSigner verify(String apkFile) throws SignatureNotFoundException, SecurityException, IOException { @@ -106,7 +107,7 @@ public class ApkSignatureSchemeV3Verifier { * Block while gathering signer information. The APK contents are not verified. * * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3. - * @throws IOException if an I/O error occurs while reading the APK file. + * @throws IOException if an I/O error occurs while reading the APK file. */ public static VerifiedSigner unsafeGetCertsWithoutVerification(String apkFile) throws SignatureNotFoundException, SecurityException, IOException { @@ -125,9 +126,10 @@ public class ApkSignatureSchemeV3Verifier { * associated with each signer. * * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3. - * @throws SecurityException if an APK Signature Scheme v3 signature of this APK does not - * verify. - * @throws IOException if an I/O error occurs while reading the APK file. + * @throws SecurityException if an APK Signature Scheme v3 signature of this APK does + * not + * verify. + * @throws IOException if an I/O error occurs while reading the APK file. */ private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity) throws SignatureNotFoundException, SecurityException, IOException { @@ -140,7 +142,7 @@ public class ApkSignatureSchemeV3Verifier { * additional information relevant for verifying the block against the file. * * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3. - * @throws IOException if an I/O error occurs while reading the APK file. + * @throws IOException if an I/O error occurs while reading the APK file. */ public static SignatureInfo findSignature(RandomAccessFile apk) throws IOException, SignatureNotFoundException { @@ -152,7 +154,7 @@ public class ApkSignatureSchemeV3Verifier { * Block. * * @param signatureInfo APK Signature Scheme v3 Block and information relevant for verifying it - * against the APK file. + * against the APK file. */ private static VerifiedSigner verify( RandomAccessFile apk, @@ -160,7 +162,7 @@ public class ApkSignatureSchemeV3Verifier { boolean doVerifyIntegrity) throws SecurityException, IOException { int signerCount = 0; Map contentDigests = new ArrayMap<>(); - Pair result = null; + Pair result = null; CertificateFactory certFactory; try { certFactory = CertificateFactory.getInstance("X.509"); @@ -215,10 +217,11 @@ public class ApkSignatureSchemeV3Verifier { return new VerifiedSigner(result.first, result.second, verityRootHash, contentDigests); } - private static Pair verifySigner( - ByteBuffer signerBlock, - Map contentDigests, - CertificateFactory certFactory) + private static Pair + verifySigner( + ByteBuffer signerBlock, + Map contentDigests, + CertificateFactory certFactory) throws SecurityException, IOException, PlatformNotSupportedException { ByteBuffer signedData = getLengthPrefixedSlice(signerBlock); int minSdkVersion = signerBlock.getInt(); @@ -228,9 +231,9 @@ public class ApkSignatureSchemeV3Verifier { // this signature isn't meant to be used with this platform, skip it. throw new PlatformNotSupportedException( "Signer not supported by this platform " - + "version. This platform: " + Build.VERSION.SDK_INT - + ", signer minSdkVersion: " + minSdkVersion - + ", maxSdkVersion: " + maxSdkVersion); + + "version. This platform: " + Build.VERSION.SDK_INT + + ", signer minSdkVersion: " + minSdkVersion + + ", maxSdkVersion: " + maxSdkVersion); } ByteBuffer signatures = getLengthPrefixedSlice(signerBlock); @@ -331,7 +334,8 @@ public class ApkSignatureSchemeV3Verifier { && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) { throw new SecurityException( getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm) - + " contents digest does not match the digest specified by a preceding signer"); + + " contents digest does not match the digest specified by a " + + "preceding signer"); } ByteBuffer certificates = getLengthPrefixedSlice(signedData); @@ -379,11 +383,11 @@ public class ApkSignatureSchemeV3Verifier { private static final int PROOF_OF_ROTATION_ATTR_ID = 0x3ba06f8c; - private static Pair verifyAdditionalAttributes( - ByteBuffer attrs, List certs, CertificateFactory certFactory) - throws IOException { + private static Pair + verifyAdditionalAttributes(ByteBuffer attrs, List certs, + CertificateFactory certFactory) throws IOException { X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]); - VerifiedProofOfRotation por = null; + ApkSigningBlockUtils.VerifiedProofOfRotation por = null; while (attrs.hasRemaining()) { ByteBuffer attr = getLengthPrefixedSlice(attrs); @@ -392,7 +396,7 @@ public class ApkSignatureSchemeV3Verifier { + "ID. Remaining: " + attr.remaining()); } int id = attr.getInt(); - switch(id) { + switch (id) { case PROOF_OF_ROTATION_ATTR_ID: if (por != null) { throw new SecurityException("Encountered multiple Proof-of-rotation records" @@ -404,7 +408,7 @@ public class ApkSignatureSchemeV3Verifier { try { if (por.certs.size() > 0 && !Arrays.equals(por.certs.get(por.certs.size() - 1).getEncoded(), - certChain[0].getEncoded())) { + certChain[0].getEncoded())) { throw new SecurityException("Terminal certificate in Proof-of-rotation" + " record does not match APK signing certificate"); } @@ -422,96 +426,6 @@ public class ApkSignatureSchemeV3Verifier { return Pair.create(certChain, por); } - private static VerifiedProofOfRotation verifyProofOfRotationStruct( - ByteBuffer porBuf, - CertificateFactory certFactory) - throws SecurityException, IOException { - int levelCount = 0; - int lastSigAlgorithm = -1; - X509Certificate lastCert = null; - List certs = new ArrayList<>(); - List flagsList = new ArrayList<>(); - - // Proof-of-rotation struct: - // A uint32 version code followed by basically a singly linked list of nodes, called levels - // here, each of which have the following structure: - // * length-prefix for the entire level - // - length-prefixed signed data (if previous level exists) - // * length-prefixed X509 Certificate - // * uint32 signature algorithm ID describing how this signed data was signed - // - uint32 flags describing how to treat the cert contained in this level - // - uint32 signature algorithm ID to use to verify the signature of the next level. The - // algorithm here must match the one in the signed data section of the next level. - // - length-prefixed signature over the signed data in this level. The signature here - // is verified using the certificate from the previous level. - // The linking is provided by the certificate of each level signing the one of the next. - - try { - - // get the version code, but don't do anything with it: creator knew about all our flags - porBuf.getInt(); - HashSet certHistorySet = new HashSet<>(); - while (porBuf.hasRemaining()) { - levelCount++; - ByteBuffer level = getLengthPrefixedSlice(porBuf); - ByteBuffer signedData = getLengthPrefixedSlice(level); - int flags = level.getInt(); - int sigAlgorithm = level.getInt(); - byte[] signature = readLengthPrefixedByteArray(level); - - if (lastCert != null) { - // Use previous level cert to verify current level - Pair sigAlgParams = - getSignatureAlgorithmJcaSignatureAlgorithm(lastSigAlgorithm); - PublicKey publicKey = lastCert.getPublicKey(); - Signature sig = Signature.getInstance(sigAlgParams.first); - sig.initVerify(publicKey); - if (sigAlgParams.second != null) { - sig.setParameter(sigAlgParams.second); - } - sig.update(signedData); - if (!sig.verify(signature)) { - throw new SecurityException("Unable to verify signature of certificate #" - + levelCount + " using " + sigAlgParams.first + " when verifying" - + " Proof-of-rotation record"); - } - } - - signedData.rewind(); - byte[] encodedCert = readLengthPrefixedByteArray(signedData); - int signedSigAlgorithm = signedData.getInt(); - if (lastCert != null && lastSigAlgorithm != signedSigAlgorithm) { - throw new SecurityException("Signing algorithm ID mismatch for certificate #" - + levelCount + " when verifying Proof-of-rotation record"); - } - lastCert = (X509Certificate) - certFactory.generateCertificate(new ByteArrayInputStream(encodedCert)); - lastCert = new VerbatimX509Certificate(lastCert, encodedCert); - - lastSigAlgorithm = sigAlgorithm; - if (certHistorySet.contains(lastCert)) { - throw new SecurityException("Encountered duplicate entries in " - + "Proof-of-rotation record at certificate #" + levelCount + ". All " - + "signing certificates should be unique"); - } - certHistorySet.add(lastCert); - certs.add(lastCert); - flagsList.add(flags); - } - } catch (IOException | BufferUnderflowException e) { - throw new IOException("Failed to parse Proof-of-rotation record", e); - } catch (NoSuchAlgorithmException | InvalidKeyException - | InvalidAlgorithmParameterException | SignatureException e) { - throw new SecurityException( - "Failed to verify signature over signed data for certificate #" - + levelCount + " when verifying Proof-of-rotation record", e); - } catch (CertificateException e) { - throw new SecurityException("Failed to decode certificate #" + levelCount - + " when verifying Proof-of-rotation record", e); - } - return new VerifiedProofOfRotation(certs, flagsList); - } - static byte[] getVerityRootHash(String apkPath) throws IOException, SignatureNotFoundException, SecurityException { try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { @@ -523,7 +437,7 @@ public class ApkSignatureSchemeV3Verifier { static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory) throws IOException, SignatureNotFoundException, SecurityException, DigestException, - NoSuchAlgorithmException { + NoSuchAlgorithmException { try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { SignatureInfo signatureInfo = findSignature(apk); return VerityBuilder.generateApkVerity(apkPath, bufferFactory, signatureInfo); @@ -532,7 +446,7 @@ public class ApkSignatureSchemeV3Verifier { static byte[] generateApkVerityRootHash(String apkPath) throws NoSuchAlgorithmException, DigestException, IOException, - SignatureNotFoundException { + SignatureNotFoundException { try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { SignatureInfo signatureInfo = findSignature(apk); VerifiedSigner vSigner = verify(apk, false); @@ -544,21 +458,6 @@ public class ApkSignatureSchemeV3Verifier { } } - /** - * Verified processed proof of rotation. - * - * @hide for internal use only. - */ - public static class VerifiedProofOfRotation { - public final List certs; - public final List flagsList; - - public VerifiedProofOfRotation(List certs, List flagsList) { - this.certs = certs; - this.flagsList = flagsList; - } - } - /** * Verified APK Signature Scheme v3 signer, including the proof of rotation structure. * @@ -566,14 +465,15 @@ public class ApkSignatureSchemeV3Verifier { */ public static class VerifiedSigner { public final X509Certificate[] certs; - public final VerifiedProofOfRotation por; + public final ApkSigningBlockUtils.VerifiedProofOfRotation por; public final byte[] verityRootHash; // Algorithm -> digest map of signed digests in the signature. // All these are verified if requested. public final Map contentDigests; - public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por, + public VerifiedSigner(X509Certificate[] certs, + ApkSigningBlockUtils.VerifiedProofOfRotation por, byte[] verityRootHash, Map contentDigests) { this.certs = certs; this.por = por; diff --git a/core/java/android/util/apk/ApkSigningBlockUtils.java b/core/java/android/util/apk/ApkSigningBlockUtils.java index 021f232979ef0..c97c995641d14 100644 --- a/core/java/android/util/apk/ApkSigningBlockUtils.java +++ b/core/java/android/util/apk/ApkSigningBlockUtils.java @@ -19,6 +19,7 @@ package android.util.apk; import android.util.ArrayMap; import android.util.Pair; +import java.io.ByteArrayInputStream; import java.io.FileDescriptor; import java.io.IOException; import java.io.RandomAccessFile; @@ -26,12 +27,23 @@ import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.DigestException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.MGF1ParameterSpec; import java.security.spec.PSSParameterSpec; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; +import java.util.List; import java.util.Map; /** @@ -51,9 +63,8 @@ public final class ApkSigningBlockUtils { * @param blockId the ID value in the APK Signing Block's sequence of ID-value pairs * identifying the appropriate block to find, e.g. the APK Signature Scheme v2 * block ID. - * * @throws SignatureNotFoundException if the APK is not signed using this scheme. - * @throws IOException if an I/O error occurs while reading the APK file. + * @throws IOException if an I/O error occurs while reading the APK file. */ static SignatureInfo findSignature(RandomAccessFile apk, int blockId) throws IOException, SignatureNotFoundException { @@ -377,7 +388,7 @@ public final class ApkSigningBlockUtils { /** * Returns the ZIP End of Central Directory (EoCD) and its offset in the file. * - * @throws IOException if an I/O error occurs while reading the file. + * @throws IOException if an I/O error occurs while reading the file. * @throws SignatureNotFoundException if the EoCD could not be found. */ static Pair getEocd(RandomAccessFile apk) @@ -398,13 +409,13 @@ public final class ApkSigningBlockUtils { if (centralDirOffset > eocdOffset) { throw new SignatureNotFoundException( "ZIP Central Directory offset out of range: " + centralDirOffset - + ". ZIP End of Central Directory offset: " + eocdOffset); + + ". ZIP End of Central Directory offset: " + eocdOffset); } long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd); if (centralDirOffset + centralDirSize != eocdOffset) { throw new SignatureNotFoundException( "ZIP Central Directory is not immediately followed by End of Central" - + " Directory"); + + " Directory"); } return centralDirOffset; } @@ -687,7 +698,7 @@ public final class ApkSigningBlockUtils { static Pair findApkSigningBlock( RandomAccessFile apk, long centralDirOffset) - throws IOException, SignatureNotFoundException { + throws IOException, SignatureNotFoundException { // FORMAT: // OFFSET DATA TYPE DESCRIPTION // * @+0 bytes uint64: size in bytes (excluding this field) @@ -806,4 +817,108 @@ public final class ApkSigningBlockUtils { } } + static VerifiedProofOfRotation verifyProofOfRotationStruct( + ByteBuffer porBuf, + CertificateFactory certFactory) + throws SecurityException, IOException { + int levelCount = 0; + int lastSigAlgorithm = -1; + X509Certificate lastCert = null; + List certs = new ArrayList<>(); + List flagsList = new ArrayList<>(); + + // Proof-of-rotation struct: + // A uint32 version code followed by basically a singly linked list of nodes, called levels + // here, each of which have the following structure: + // * length-prefix for the entire level + // - length-prefixed signed data (if previous level exists) + // * length-prefixed X509 Certificate + // * uint32 signature algorithm ID describing how this signed data was signed + // - uint32 flags describing how to treat the cert contained in this level + // - uint32 signature algorithm ID to use to verify the signature of the next level. The + // algorithm here must match the one in the signed data section of the next level. + // - length-prefixed signature over the signed data in this level. The signature here + // is verified using the certificate from the previous level. + // The linking is provided by the certificate of each level signing the one of the next. + + try { + + // get the version code, but don't do anything with it: creator knew about all our flags + porBuf.getInt(); + HashSet certHistorySet = new HashSet<>(); + while (porBuf.hasRemaining()) { + levelCount++; + ByteBuffer level = getLengthPrefixedSlice(porBuf); + ByteBuffer signedData = getLengthPrefixedSlice(level); + int flags = level.getInt(); + int sigAlgorithm = level.getInt(); + byte[] signature = readLengthPrefixedByteArray(level); + + if (lastCert != null) { + // Use previous level cert to verify current level + Pair sigAlgParams = + getSignatureAlgorithmJcaSignatureAlgorithm(lastSigAlgorithm); + PublicKey publicKey = lastCert.getPublicKey(); + Signature sig = Signature.getInstance(sigAlgParams.first); + sig.initVerify(publicKey); + if (sigAlgParams.second != null) { + sig.setParameter(sigAlgParams.second); + } + sig.update(signedData); + if (!sig.verify(signature)) { + throw new SecurityException("Unable to verify signature of certificate #" + + levelCount + " using " + sigAlgParams.first + " when verifying" + + " Proof-of-rotation record"); + } + } + + signedData.rewind(); + byte[] encodedCert = readLengthPrefixedByteArray(signedData); + int signedSigAlgorithm = signedData.getInt(); + if (lastCert != null && lastSigAlgorithm != signedSigAlgorithm) { + throw new SecurityException("Signing algorithm ID mismatch for certificate #" + + levelCount + " when verifying Proof-of-rotation record"); + } + lastCert = (X509Certificate) + certFactory.generateCertificate(new ByteArrayInputStream(encodedCert)); + lastCert = new VerbatimX509Certificate(lastCert, encodedCert); + + lastSigAlgorithm = sigAlgorithm; + if (certHistorySet.contains(lastCert)) { + throw new SecurityException("Encountered duplicate entries in " + + "Proof-of-rotation record at certificate #" + levelCount + ". All " + + "signing certificates should be unique"); + } + certHistorySet.add(lastCert); + certs.add(lastCert); + flagsList.add(flags); + } + } catch (IOException | BufferUnderflowException e) { + throw new IOException("Failed to parse Proof-of-rotation record", e); + } catch (NoSuchAlgorithmException | InvalidKeyException + | InvalidAlgorithmParameterException | SignatureException e) { + throw new SecurityException( + "Failed to verify signature over signed data for certificate #" + + levelCount + " when verifying Proof-of-rotation record", e); + } catch (CertificateException e) { + throw new SecurityException("Failed to decode certificate #" + levelCount + + " when verifying Proof-of-rotation record", e); + } + return new VerifiedProofOfRotation(certs, flagsList); + } + + /** + * Verified processed proof of rotation. + * + * @hide for internal use only. + */ + public static class VerifiedProofOfRotation { + public final List certs; + public final List flagsList; + + public VerifiedProofOfRotation(List certs, List flagsList) { + this.certs = certs; + this.flagsList = flagsList; + } + } } diff --git a/core/java/android/util/apk/SourceStampVerificationResult.java b/core/java/android/util/apk/SourceStampVerificationResult.java index 2edaf623fb94e..8b9eee2f796ea 100644 --- a/core/java/android/util/apk/SourceStampVerificationResult.java +++ b/core/java/android/util/apk/SourceStampVerificationResult.java @@ -19,6 +19,8 @@ package android.util.apk; import android.annotation.Nullable; import java.security.cert.Certificate; +import java.util.Collections; +import java.util.List; /** * A class encapsulating the result from the source stamp verifier @@ -32,12 +34,15 @@ public final class SourceStampVerificationResult { private final boolean mPresent; private final boolean mVerified; private final Certificate mCertificate; + private final List mCertificateLineage; private SourceStampVerificationResult( - boolean present, boolean verified, @Nullable Certificate certificate) { + boolean present, boolean verified, @Nullable Certificate certificate, + List certificateLineage) { this.mPresent = present; this.mVerified = verified; this.mCertificate = certificate; + this.mCertificateLineage = certificateLineage; } public boolean isPresent() { @@ -52,6 +57,10 @@ public final class SourceStampVerificationResult { return mCertificate; } + public List getCertificateLineage() { + return mCertificateLineage; + } + /** * Create a non-present source stamp outcome. * @@ -59,18 +68,21 @@ public final class SourceStampVerificationResult { */ public static SourceStampVerificationResult notPresent() { return new SourceStampVerificationResult( - /* present= */ false, /* verified= */ false, /* certificate= */ null); + /* present= */ false, /* verified= */ false, /* certificate= */ + null, /* certificateLineage= */ Collections.emptyList()); } /** * Create a verified source stamp outcome. * - * @param certificate The source stamp certificate. + * @param certificate The source stamp certificate. + * @param certificateLineage The proof-of-rotation lineage for the source stamp. * @return A verified source stamp result, and the source stamp certificate. */ - public static SourceStampVerificationResult verified(Certificate certificate) { + public static SourceStampVerificationResult verified(Certificate certificate, + List certificateLineage) { return new SourceStampVerificationResult( - /* present= */ true, /* verified= */ true, certificate); + /* present= */ true, /* verified= */ true, certificate, certificateLineage); } /** @@ -80,6 +92,7 @@ public final class SourceStampVerificationResult { */ public static SourceStampVerificationResult notVerified() { return new SourceStampVerificationResult( - /* present= */ true, /* verified= */ false, /* certificate= */ null); + /* present= */ true, /* verified= */ false, /* certificate= */ + null, /* certificateLineage= */ Collections.emptyList()); } } diff --git a/core/java/android/util/apk/SourceStampVerifier.java b/core/java/android/util/apk/SourceStampVerifier.java index 5fc242353d519..f9e312146ccf0 100644 --- a/core/java/android/util/apk/SourceStampVerifier.java +++ b/core/java/android/util/apk/SourceStampVerifier.java @@ -23,6 +23,7 @@ import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContent import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm; import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm; import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray; +import static android.util.apk.ApkSigningBlockUtils.verifyProofOfRotationStruct; import android.util.Pair; import android.util.Slog; @@ -44,12 +45,14 @@ import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.AlgorithmParameterSpec; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; @@ -76,6 +79,7 @@ public abstract class SourceStampVerifier { private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a; private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0; private static final int SOURCE_STAMP_BLOCK_ID = 0x6dff800d; + private static final int PROOF_OF_ROTATION_ATTR_ID = 0x9d6303f7; private static final int VERSION_JAR_SIGNATURE_SCHEME = 1; private static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2; @@ -85,11 +89,13 @@ public abstract class SourceStampVerifier { private static final String SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME = "stamp-cert-sha256"; /** Hidden constructor to prevent instantiation. */ - private SourceStampVerifier() {} + private SourceStampVerifier() { + } - /** Verifies SourceStamp present in a list of APKs. */ + /** Verifies SourceStamp present in a list of (split) APKs for the same app. */ public static SourceStampVerificationResult verify(List apkFiles) { Certificate stampCertificate = null; + List stampCertificateLineage = Collections.emptyList(); for (String apkFile : apkFiles) { SourceStampVerificationResult sourceStampVerificationResult = verify(apkFile); if (!sourceStampVerificationResult.isPresent() @@ -97,12 +103,15 @@ public abstract class SourceStampVerifier { return sourceStampVerificationResult; } if (stampCertificate != null - && !stampCertificate.equals(sourceStampVerificationResult.getCertificate())) { + && (!stampCertificate.equals(sourceStampVerificationResult.getCertificate()) + || !stampCertificateLineage.equals( + sourceStampVerificationResult.getCertificateLineage()))) { return SourceStampVerificationResult.notVerified(); } stampCertificate = sourceStampVerificationResult.getCertificate(); + stampCertificateLineage = sourceStampVerificationResult.getCertificateLineage(); } - return SourceStampVerificationResult.verified(stampCertificate); + return SourceStampVerificationResult.verified(stampCertificate, stampCertificateLineage); } /** Verifies SourceStamp present in the provided APK. */ @@ -177,21 +186,44 @@ public abstract class SourceStampVerifier { "No signatures found for signature scheme %d", signatureSchemeDigest.getKey())); } + ByteBuffer signatures = ApkSigningBlockUtils.getLengthPrefixedSlice( + signedSignatureSchemeData.get(signatureSchemeDigest.getKey())); verifySourceStampSignature( - signedSignatureSchemeData.get(signatureSchemeDigest.getKey()), + signatureSchemeDigest.getValue(), sourceStampCertificate, - signatureSchemeDigest.getValue()); + signatures); } - return SourceStampVerificationResult.verified(sourceStampCertificate); + List sourceStampCertificateLineage = Collections.emptyList(); + 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, sourceStampCertificate, + stampAttributeDataSignatures); + ApkSigningBlockUtils.VerifiedProofOfRotation verifiedProofOfRotation = + verifySourceStampAttributes(stampAttributeData, sourceStampCertificate); + if (verifiedProofOfRotation != null) { + sourceStampCertificateLineage = verifiedProofOfRotation.certs; + } + } + + return SourceStampVerificationResult.verified(sourceStampCertificate, + sourceStampCertificateLineage); } /** * Verify the SourceStamp certificate found in the signing block is the same as the SourceStamp * certificate found in the APK. It returns the verified certificate. * - * @param sourceStampBlockData the source stamp block in the APK signing block which contains - * the certificate used to sign the stamp digests. + * @param sourceStampBlockData the source stamp block in the APK signing block which + * contains + * the certificate used to sign the stamp digests. * @param sourceStampCertificateDigest the source stamp certificate digest found in the APK. */ private static X509Certificate verifySourceStampCertificate( @@ -230,16 +262,16 @@ public abstract class SourceStampVerifier { * Verify the SourceStamp signature found in the signing block is signed by the SourceStamp * certificate found in the APK. * - * @param signedBlockData the source stamp block in the APK signing block which contains the - * stamp signed digests. + * @param data the digest to be verified being signed by the source stamp + * certificate. * @param sourceStampCertificate the source stamp certificate used to sign the stamp digests. - * @param digest the digest to be verified being signed by the source stamp certificate. + * @param signatures the source stamp block in the APK signing block which contains + * the stamp signed digests. */ - private static void verifySourceStampSignature( - ByteBuffer signedBlockData, X509Certificate sourceStampCertificate, byte[] digest) + private static void verifySourceStampSignature(byte[] data, + X509Certificate sourceStampCertificate, ByteBuffer signatures) throws IOException { // Parse the signatures block and identify supported signatures - ByteBuffer signatures = ApkSigningBlockUtils.getLengthPrefixedSlice(signedBlockData); int signatureCount = 0; int bestSigAlgorithm = -1; byte[] bestSigAlgorithmSignatureBytes = null; @@ -285,7 +317,7 @@ public abstract class SourceStampVerifier { if (jcaSignatureAlgorithmParams != null) { sig.setParameter(jcaSignatureAlgorithmParams); } - sig.update(digest); + sig.update(data); sigVerified = sig.verify(bestSigAlgorithmSignatureBytes); } catch (InvalidKeyException | InvalidAlgorithmParameterException @@ -414,6 +446,46 @@ public abstract class SourceStampVerifier { return result.array(); } + private static ApkSigningBlockUtils.VerifiedProofOfRotation verifySourceStampAttributes( + ByteBuffer stampAttributeData, + X509Certificate sourceStampCertificate) + throws IOException { + CertificateFactory certFactory; + try { + certFactory = CertificateFactory.getInstance("X.509"); + } catch (CertificateException e) { + throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); + } + ByteBuffer stampAttributes = getLengthPrefixedSlice(stampAttributeData); + ApkSigningBlockUtils.VerifiedProofOfRotation verifiedProofOfRotation = null; + while (stampAttributes.hasRemaining()) { + ByteBuffer attribute = getLengthPrefixedSlice(stampAttributes); + int id = attribute.getInt(); + if (id == PROOF_OF_ROTATION_ATTR_ID) { + if (verifiedProofOfRotation != null) { + throw new SecurityException("Encountered multiple Proof-of-rotation records" + + " when verifying source stamp signature"); + } + verifiedProofOfRotation = verifyProofOfRotationStruct(attribute, certFactory); + // Make sure that the last certificate in the Proof-of-rotation record matches + // the one used to sign this APK. + try { + if (verifiedProofOfRotation.certs.size() > 0 + && !Arrays.equals(verifiedProofOfRotation.certs.get( + verifiedProofOfRotation.certs.size() - 1).getEncoded(), + sourceStampCertificate.getEncoded())) { + throw new SecurityException("Terminal certificate in Proof-of-rotation" + + " record does not match source stamp certificate"); + } + } catch (CertificateEncodingException e) { + throw new SecurityException("Failed to encode certificate when comparing" + + " Proof-of-rotation record and source stamp certificate", e); + } + } + } + return verifiedProofOfRotation; + } + private static byte[] computeSha256Digest(byte[] input) { try { MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); diff --git a/core/tests/coretests/assets/SourceStampVerifierTest/stamp-lineage-invalid.apk b/core/tests/coretests/assets/SourceStampVerifierTest/stamp-lineage-invalid.apk new file mode 100644 index 0000000000000000000000000000000000000000..f9777c3f5ca5a55ec84129495a41fdf9140bc2a5 GIT binary patch literal 16854 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 literal 0 HcmV?d00001 diff --git a/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java b/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java index 81d54b57486ca..bc0bddba2f207 100644 --- a/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java +++ b/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java @@ -17,6 +17,7 @@ package android.util.apk; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -198,6 +199,38 @@ public class SourceStampVerifierTest { assertNull(result.getCertificate()); } + @Test + public void testSourceStamp_validStampLineage() throws Exception { + mPrimaryApk = getApk("SourceStampVerifierTest/stamp-lineage-valid.apk"); + byte[] expectedStampCertHash = getSourceStampCertificateHashFromApk(mPrimaryApk); + + SourceStampVerificationResult result = + SourceStampVerifier.verify(mPrimaryApk.getAbsolutePath()); + + assertTrue(result.isPresent()); + assertTrue(result.isVerified()); + assertNotNull(result.getCertificate()); + byte[] actualStampCertHash = + MessageDigest.getInstance("SHA-256").digest(result.getCertificate().getEncoded()); + assertArrayEquals(expectedStampCertHash, actualStampCertHash); + assertEquals(2, result.getCertificateLineage().size()); + assertEquals(result.getCertificate(), + result.getCertificateLineage().get(result.getCertificateLineage().size() - 1)); + } + + @Test + public void testSourceStamp_invalidStampLineage() throws Exception { + mPrimaryApk = getApk("SourceStampVerifierTest/stamp-lineage-invalid.apk"); + + SourceStampVerificationResult result = + SourceStampVerifier.verify(mPrimaryApk.getAbsolutePath()); + + assertTrue(result.isPresent()); + assertFalse(result.isVerified()); + assertNull(result.getCertificate()); + assertTrue(result.getCertificateLineage().isEmpty()); + } + private File getApk(String apkPath) throws IOException { File apk = File.createTempFile("SourceStampApk", ".apk"); try (InputStream inputStream = mContext.getAssets().open(apkPath)) {