Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6413ab89 authored by Khaled Abdelmohsen's avatar Khaled Abdelmohsen Committed by Automerger Merge Worker
Browse files

Merge "Modify source stamp format" into rvc-dev am: 592da34c

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/11837613

Change-Id: I00ea1bf13bb737e46598ed90cdc54e7dc8433194
parents 9fa201e0 592da34c
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -420,6 +420,7 @@ final class ApkSigningBlockUtils {
    static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
    static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
    static final int CONTENT_DIGEST_VERITY_CHUNKED_SHA256 = 3;
    static final int CONTENT_DIGEST_SHA256 = 4;

    private static final int[] V4_CONTENT_DIGEST_ALGORITHMS =
            {CONTENT_DIGEST_CHUNKED_SHA512, CONTENT_DIGEST_VERITY_CHUNKED_SHA256,
+162 −67
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.util.apk;

import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_SHA256;
import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm;
@@ -27,12 +28,10 @@ import android.util.Pair;
import android.util.Slog;
import android.util.jar.StrictJarFile;

import libcore.io.IoUtils;
import libcore.io.Streams;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
@@ -49,11 +48,13 @@ 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.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

/**
@@ -74,7 +75,11 @@ 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 = 0x2b09189e;
    private static final int SOURCE_STAMP_BLOCK_ID = 0x6dff800d;

    private static final int VERSION_JAR_SIGNATURE_SCHEME = 1;
    private static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2;
    private static final int VERSION_APK_SIGNATURE_SCHEME_V3 = 3;

    /** Name of the SourceStamp certificate hash ZIP entry in APKs. */
    private static final String SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME = "stamp-cert-sha256";
@@ -115,7 +120,8 @@ public abstract class SourceStampVerifier {
                // SourceStamp present.
                return SourceStampVerificationResult.notPresent();
            }
            return verify(apk, sourceStampCertificateDigest);
            byte[] manifestBytes = getManifestBytes(apkJar);
            return verify(apk, sourceStampCertificateDigest, manifestBytes);
        } catch (IOException e) {
            // Any exception in reading the APK returns a non-present SourceStamp outcome
            // without affecting the outcome of any of the other signature schemes.
@@ -126,22 +132,71 @@ public abstract class SourceStampVerifier {
    }

    private static SourceStampVerificationResult verify(
            RandomAccessFile apk, byte[] sourceStampCertificateDigest) {
            RandomAccessFile apk, byte[] sourceStampCertificateDigest, byte[] manifestBytes) {
        try {
            SignatureInfo signatureInfo =
                    ApkSigningBlockUtils.findSignature(apk, SOURCE_STAMP_BLOCK_ID);
            Map<Integer, byte[]> apkContentDigests = getApkContentDigests(apk);
            return verify(signatureInfo, apkContentDigests, sourceStampCertificateDigest);
        } catch (IOException | SignatureNotFoundException e) {
            Map<Integer, Map<Integer, byte[]>> signatureSchemeApkContentDigests =
                    getSignatureSchemeApkContentDigests(apk, manifestBytes);
            return verify(
                    signatureInfo,
                    getSignatureSchemeDigests(signatureSchemeApkContentDigests),
                    sourceStampCertificateDigest);
        } catch (IOException | SignatureNotFoundException | RuntimeException e) {
            return SourceStampVerificationResult.notVerified();
        }
    }

    private static SourceStampVerificationResult verify(
            SignatureInfo signatureInfo,
            Map<Integer, byte[]> apkContentDigests,
            Map<Integer, byte[]> signatureSchemeDigests,
            byte[] sourceStampCertificateDigest)
            throws SecurityException, IOException {
        ByteBuffer sourceStampBlock = signatureInfo.signatureBlock;
        ByteBuffer sourceStampBlockData =
                ApkSigningBlockUtils.getLengthPrefixedSlice(sourceStampBlock);

        X509Certificate sourceStampCertificate =
                verifySourceStampCertificate(sourceStampBlockData, sourceStampCertificateDigest);

        // Parse signed signature schemes block.
        ByteBuffer signedSignatureSchemes =
                ApkSigningBlockUtils.getLengthPrefixedSlice(sourceStampBlockData);
        Map<Integer, ByteBuffer> signedSignatureSchemeData = new HashMap<>();
        while (signedSignatureSchemes.hasRemaining()) {
            ByteBuffer signedSignatureScheme =
                    ApkSigningBlockUtils.getLengthPrefixedSlice(signedSignatureSchemes);
            int signatureSchemeId = signedSignatureScheme.getInt();
            signedSignatureSchemeData.put(signatureSchemeId, signedSignatureScheme);
        }

        for (Map.Entry<Integer, byte[]> signatureSchemeDigest : signatureSchemeDigests.entrySet()) {
            if (!signedSignatureSchemeData.containsKey(signatureSchemeDigest.getKey())) {
                throw new SecurityException(
                        String.format(
                                "No signatures found for signature scheme %d",
                                signatureSchemeDigest.getKey()));
            }
            verifySourceStampSignature(
                    signedSignatureSchemeData.get(signatureSchemeDigest.getKey()),
                    sourceStampCertificate,
                    signatureSchemeDigest.getValue());
        }

        return SourceStampVerificationResult.verified(sourceStampCertificate);
    }

    /**
     * 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 sourceStampCertificateDigest the source stamp certificate digest found in the APK.
     */
    private static X509Certificate verifySourceStampCertificate(
            ByteBuffer sourceStampBlockData, byte[] sourceStampCertificateDigest)
            throws IOException {
        CertificateFactory certFactory;
        try {
            certFactory = CertificateFactory.getInstance("X.509");
@@ -149,17 +204,6 @@ public abstract class SourceStampVerifier {
            throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
        }

        List<Pair<Integer, byte[]>> digests =
                apkContentDigests.entrySet().stream()
                        .sorted(Map.Entry.comparingByKey())
                        .map(e -> Pair.create(e.getKey(), e.getValue()))
                        .collect(Collectors.toList());
        byte[] digestBytes = encodeApkContentDigests(digests);

        ByteBuffer sourceStampBlock = signatureInfo.signatureBlock;
        ByteBuffer sourceStampBlockData =
                ApkSigningBlockUtils.getLengthPrefixedSlice(sourceStampBlock);

        // Parse the SourceStamp certificate.
        byte[] sourceStampEncodedCertificate =
                ApkSigningBlockUtils.readLengthPrefixedByteArray(sourceStampBlockData);
@@ -172,24 +216,30 @@ public abstract class SourceStampVerifier {
        } catch (CertificateException e) {
            throw new SecurityException("Failed to decode certificate", e);
        }
        sourceStampCertificate =
                new VerbatimX509Certificate(sourceStampCertificate, sourceStampEncodedCertificate);

        // Verify the SourceStamp certificate found in the signing block is the same as the
        // SourceStamp certificate found in the APK.
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update(sourceStampEncodedCertificate);
            byte[] sourceStampBlockCertificateDigest = messageDigest.digest();
        byte[] sourceStampBlockCertificateDigest =
                computeSha256Digest(sourceStampEncodedCertificate);
        if (!Arrays.equals(sourceStampCertificateDigest, sourceStampBlockCertificateDigest)) {
            throw new SecurityException("Certificate mismatch between APK and signature block");
        }
        } catch (NoSuchAlgorithmException e) {
            throw new SecurityException("Failed to find SHA-256", e);

        return new VerbatimX509Certificate(sourceStampCertificate, sourceStampEncodedCertificate);
    }

    /**
     * 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 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.
     */
    private static void verifySourceStampSignature(
            ByteBuffer signedBlockData, X509Certificate sourceStampCertificate, byte[] digest)
            throws IOException {
        // Parse the signatures block and identify supported signatures
        ByteBuffer signatures = ApkSigningBlockUtils.getLengthPrefixedSlice(sourceStampBlockData);
        ByteBuffer signatures = ApkSigningBlockUtils.getLengthPrefixedSlice(signedBlockData);
        int signatureCount = 0;
        int bestSigAlgorithm = -1;
        byte[] bestSigAlgorithmSignatureBytes = null;
@@ -235,7 +285,7 @@ public abstract class SourceStampVerifier {
            if (jcaSignatureAlgorithmParams != null) {
                sig.setParameter(jcaSignatureAlgorithmParams);
            }
            sig.update(digestBytes);
            sig.update(digest);
            sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
        } catch (InvalidKeyException
                | InvalidAlgorithmParameterException
@@ -247,27 +297,44 @@ public abstract class SourceStampVerifier {
        if (!sigVerified) {
            throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
        }

        return SourceStampVerificationResult.verified(sourceStampCertificate);
    }

    private static Map<Integer, byte[]> getApkContentDigests(RandomAccessFile apk)
            throws IOException, SignatureNotFoundException {
        // Retrieve APK content digests in V3 signing block. If a V3 signature is not found, the APK
        // content digests would be re-tried from V2 signature.
    private static Map<Integer, Map<Integer, byte[]>> getSignatureSchemeApkContentDigests(
            RandomAccessFile apk, byte[] manifestBytes) throws IOException {
        Map<Integer, Map<Integer, byte[]>> signatureSchemeApkContentDigests = new HashMap<>();

        // Retrieve APK content digests in V3 signing block.
        try {
            SignatureInfo v3SignatureInfo =
                    ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
            return getApkContentDigestsFromSignatureBlock(v3SignatureInfo.signatureBlock);
            signatureSchemeApkContentDigests.put(
                    VERSION_APK_SIGNATURE_SCHEME_V3,
                    getApkContentDigestsFromSignatureBlock(v3SignatureInfo.signatureBlock));
        } catch (SignatureNotFoundException e) {
            // It's fine not to find a V3 signature.
        }

        // Retrieve APK content digests in V2 signing block. If a V2 signature is not found, the
        // process of retrieving APK content digests stops, and the stamp is considered un-verified.
        // Retrieve APK content digests in V2 signing block.
        try {
            SignatureInfo v2SignatureInfo =
                    ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
        return getApkContentDigestsFromSignatureBlock(v2SignatureInfo.signatureBlock);
            signatureSchemeApkContentDigests.put(
                    VERSION_APK_SIGNATURE_SCHEME_V2,
                    getApkContentDigestsFromSignatureBlock(v2SignatureInfo.signatureBlock));
        } catch (SignatureNotFoundException e) {
            // It's fine not to find a V2 signature.
        }

        // Retrieve manifest digest.
        if (manifestBytes != null) {
            Map<Integer, byte[]> jarSignatureSchemeApkContentDigests = new HashMap<>();
            jarSignatureSchemeApkContentDigests.put(
                    CONTENT_DIGEST_SHA256, computeSha256Digest(manifestBytes));
            signatureSchemeApkContentDigests.put(
                    VERSION_JAR_SIGNATURE_SCHEME, jarSignatureSchemeApkContentDigests);
        }

        return signatureSchemeApkContentDigests;
    }

    private static Map<Integer, byte[]> getApkContentDigestsFromSignatureBlock(
@@ -289,27 +356,45 @@ public abstract class SourceStampVerifier {
        return apkContentDigests;
    }

    private static Map<Integer, byte[]> getSignatureSchemeDigests(
            Map<Integer, Map<Integer, byte[]>> signatureSchemeApkContentDigests) {
        Map<Integer, byte[]> digests = new HashMap<>();
        for (Map.Entry<Integer, Map<Integer, byte[]>> signatureSchemeApkContentDigest :
                signatureSchemeApkContentDigests.entrySet()) {
            List<Pair<Integer, byte[]>> apkDigests =
                    getApkDigests(signatureSchemeApkContentDigest.getValue());
            digests.put(
                    signatureSchemeApkContentDigest.getKey(), encodeApkContentDigests(apkDigests));
        }
        return digests;
    }

    private static List<Pair<Integer, byte[]>> getApkDigests(
            Map<Integer, byte[]> apkContentDigests) {
        List<Pair<Integer, byte[]>> digests = new ArrayList<>();
        for (Map.Entry<Integer, byte[]> apkContentDigest : apkContentDigests.entrySet()) {
            digests.add(Pair.create(apkContentDigest.getKey(), apkContentDigest.getValue()));
        }
        digests.sort(Comparator.comparing(pair -> pair.first));
        return digests;
    }

    private static byte[] getSourceStampCertificateDigest(StrictJarFile apkJar) throws IOException {
        InputStream inputStream = null;
        try {
        ZipEntry zipEntry = apkJar.findEntry(SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME);
        if (zipEntry == null) {
            // SourceStamp certificate hash file not found, which means that there is not
            // SourceStamp present.
            return null;
        }
            inputStream = apkJar.getInputStream(zipEntry);
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

            // Trying to read the certificate digest, which should be less than 1024 bytes.
            byte[] buffer = new byte[1024];
            int count = inputStream.read(buffer, 0, buffer.length);
            byteArrayOutputStream.write(buffer, 0, count);
        return Streams.readFully(apkJar.getInputStream(zipEntry));
    }

            return byteArrayOutputStream.toByteArray();
        } finally {
            IoUtils.closeQuietly(inputStream);
    private static byte[] getManifestBytes(StrictJarFile apkJar) throws IOException {
        ZipEntry zipEntry = apkJar.findEntry(JarFile.MANIFEST_NAME);
        if (zipEntry == null) {
            return null;
        }
        return Streams.readFully(apkJar.getInputStream(zipEntry));
    }

    private static byte[] encodeApkContentDigests(List<Pair<Integer, byte[]>> apkContentDigests) {
@@ -329,6 +414,16 @@ public abstract class SourceStampVerifier {
        return result.array();
    }

    private static byte[] computeSha256Digest(byte[] input) {
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update(input);
            return messageDigest.digest();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Failed to find SHA-256", e);
        }
    }

    private static void closeApkJar(StrictJarFile apkJar) {
        try {
            if (apkJar == null) {
+12 −0
Original line number Diff line number Diff line
{
  "presubmit": [
    {
      "name": "FrameworksCoreTests",
      "options": [
        {
          "include-filter": "android.util.apk.SourceStampVerifierTest"
        }
      ]
    }
  ]
}
(16.5 KiB)

File changed and moved.

No diff preview for this file type.

Loading