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

Commit f2afb166 authored by Khaled Abdelmohsen's avatar Khaled Abdelmohsen Committed by Android (Google) Code Review
Browse files

Merge "Parse source stamp lineage in platform"

parents 5659637c 5aa65c19
Loading
Loading
Loading
Loading
+34 −134
Original line number Diff line number Diff line
@@ -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,7 +90,8 @@ 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
     * @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.
     */
@@ -125,7 +126,8 @@ 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
     * @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.
     */
@@ -160,7 +162,7 @@ public class ApkSignatureSchemeV3Verifier {
            boolean doVerifyIntegrity) throws SecurityException, IOException {
        int signerCount = 0;
        Map<Integer, byte[]> contentDigests = new ArrayMap<>();
        Pair<X509Certificate[], VerifiedProofOfRotation> result = null;
        Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation> result = null;
        CertificateFactory certFactory;
        try {
            certFactory = CertificateFactory.getInstance("X.509");
@@ -215,7 +217,8 @@ public class ApkSignatureSchemeV3Verifier {
        return new VerifiedSigner(result.first, result.second, verityRootHash, contentDigests);
    }

    private static Pair<X509Certificate[], VerifiedProofOfRotation> verifySigner(
    private static Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation>
            verifySigner(
                ByteBuffer signerBlock,
                Map<Integer, byte[]> contentDigests,
                CertificateFactory certFactory)
@@ -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<X509Certificate[], VerifiedProofOfRotation> verifyAdditionalAttributes(
            ByteBuffer attrs, List<X509Certificate> certs, CertificateFactory certFactory)
            throws IOException {
    private static Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation>
            verifyAdditionalAttributes(ByteBuffer attrs, List<X509Certificate> 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);
@@ -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<X509Certificate> certs = new ArrayList<>();
        List<Integer> 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<X509Certificate> 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<String, ? extends AlgorithmParameterSpec> 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")) {
@@ -544,21 +458,6 @@ public class ApkSignatureSchemeV3Verifier {
        }
    }

    /**
     * Verified processed proof of rotation.
     *
     * @hide for internal use only.
     */
    public static class VerifiedProofOfRotation {
        public final List<X509Certificate> certs;
        public final List<Integer> flagsList;

        public VerifiedProofOfRotation(List<X509Certificate> certs, List<Integer> 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<Integer, byte[]> contentDigests;

        public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por,
        public VerifiedSigner(X509Certificate[] certs,
                ApkSigningBlockUtils.VerifiedProofOfRotation por,
                byte[] verityRootHash, Map<Integer, byte[]> contentDigests) {
            this.certs = certs;
            this.por = por;
+121 −6
Original line number Diff line number Diff line
@@ -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,7 +63,6 @@ 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.
     */
@@ -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<X509Certificate> certs = new ArrayList<>();
        List<Integer> 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<X509Certificate> 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<String, ? extends AlgorithmParameterSpec> 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<X509Certificate> certs;
        public final List<Integer> flagsList;

        public VerifiedProofOfRotation(List<X509Certificate> certs, List<Integer> flagsList) {
            this.certs = certs;
            this.flagsList = flagsList;
        }
    }
}
+19 −6
Original line number Diff line number Diff line
@@ -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<? extends Certificate> mCertificateLineage;

    private SourceStampVerificationResult(
            boolean present, boolean verified, @Nullable Certificate certificate) {
            boolean present, boolean verified, @Nullable Certificate certificate,
            List<? extends Certificate> 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<? extends Certificate> 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 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<? extends Certificate> 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());
    }
}
+88 −16

File changed.

Preview size limit exceeded, changes collapsed.

+16.5 KiB

File added.

No diff preview for this file type.

Loading