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

Commit 50b077d6 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Signature verification for checksums."

parents 3f4545f0 1d85df2d
Loading
Loading
Loading
Loading
+34 −4
Original line number Diff line number Diff line
@@ -23,6 +23,9 @@ import android.os.Parcelable;

import com.android.internal.util.DataClass;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@@ -122,6 +125,33 @@ public final class Checksum implements Parcelable {
    @Retention(RetentionPolicy.SOURCE)
    public @interface Type {}

    /**
     * Serialize checksum to the stream in binary format.
     * @hide
     */
    public static void writeToStream(@NonNull DataOutputStream dos, @NonNull Checksum checksum)
            throws IOException {
        dos.writeInt(checksum.getType());

        final byte[] valueBytes = checksum.getValue();
        dos.writeInt(valueBytes.length);
        dos.write(valueBytes);
    }

    /**
     * Deserialize checksum previously stored in
     * {@link #writeToStream(DataOutputStream, Checksum)}.
     * @hide
     */
    public static @NonNull Checksum readFromStream(@NonNull DataInputStream dis)
            throws IOException {
        final int type = dis.readInt();

        final byte[] valueBytes = new byte[dis.readInt()];
        dis.read(valueBytes);
        return new Checksum(type, valueBytes);
    }

    /**
     * Checksum type.
     */
@@ -133,7 +163,7 @@ public final class Checksum implements Parcelable {



    // Code below generated by codegen v1.0.15.
    // Code below generated by codegen v1.0.22.
    //
    // DO NOT MODIFY!
    // CHECKSTYLE:OFF Generated code
@@ -233,10 +263,10 @@ public final class Checksum implements Parcelable {
    };

    @DataClass.Generated(
            time = 1601955017774L,
            codegenVersion = "1.0.15",
            time = 1611601571576L,
            codegenVersion = "1.0.22",
            sourceFile = "frameworks/base/core/java/android/content/pm/Checksum.java",
            inputSignatures = "public static final  int TYPE_WHOLE_MERKLE_ROOT_4K_SHA256\npublic static final @java.lang.Deprecated int TYPE_WHOLE_MD5\npublic static final @java.lang.Deprecated int TYPE_WHOLE_SHA1\npublic static final @java.lang.Deprecated int TYPE_WHOLE_SHA256\npublic static final @java.lang.Deprecated int TYPE_WHOLE_SHA512\npublic static final  int TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256\npublic static final  int TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512\nprivate final @android.content.pm.Checksum.Type int mType\nprivate final @android.annotation.NonNull byte[] mValue\nclass Checksum extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstDefs=false)")
            inputSignatures = "public static final  int TYPE_WHOLE_MERKLE_ROOT_4K_SHA256\npublic static final @java.lang.Deprecated int TYPE_WHOLE_MD5\npublic static final @java.lang.Deprecated int TYPE_WHOLE_SHA1\npublic static final @java.lang.Deprecated int TYPE_WHOLE_SHA256\npublic static final @java.lang.Deprecated int TYPE_WHOLE_SHA512\npublic static final  int TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256\npublic static final  int TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512\nprivate final @android.content.pm.Checksum.Type int mType\nprivate final @android.annotation.NonNull byte[] mValue\npublic static  void writeToStream(java.io.DataOutputStream,android.content.pm.Checksum)\npublic static @android.annotation.NonNull android.content.pm.Checksum readFromStream(java.io.DataInputStream)\nclass Checksum extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstDefs=false)")
    @Deprecated
    private void __metadata() {}

+11 −3
Original line number Diff line number Diff line
@@ -1253,9 +1253,17 @@ public class PackageInstaller {
         *                  {@link #openWrite}
         * @param checksums installer intends to make available via
         *                  {@link PackageManager#requestChecksums}.
         * @param signature PKCS#7 detached signature bytes over serialized checksums to enable
         *                  fs-verity for the checksums or null if fs-verity should not be enabled.
         *                  @see <a href="https://www.kernel.org/doc/html/latest/filesystems/fsverity.html#built-in-signature-verification">fs-verity</a>
         * @param signature DER PKCS#7 detached signature bytes over binary serialized checksums
         *                  to enable integrity checking for the checksums or null for no integrity
         *                  checking. {@link PackageManager#requestChecksums} will return
         *                  the certificate used to create signature.
         *                  Binary format for checksums:
         *                  <pre>{@code DataOutputStream dos;
         *                  dos.writeInt(checksum.getType());
         *                  dos.writeInt(checksum.getValue().length);
         *                  dos.write(checksum.getValue());}</pre>
         *                  If using <b>openssl cms</b>, make sure to specify -binary -nosmimecap.
         *                  @see <a href="https://www.openssl.org/docs/man1.0.2/man1/cms.html">openssl cms</a>
         * @throws SecurityException if called after the session has been
         *                           committed or abandoned.
         * @throws IllegalStateException if checksums for this file have already been added.
+160 −41
Original line number Diff line number Diff line
@@ -63,8 +63,10 @@ import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.security.VerityUtils;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -72,17 +74,24 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.security.DigestException;
import java.security.InvalidParameterException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

import sun.security.pkcs.PKCS7;
import sun.security.pkcs.SignerInfo;

/**
 * Provides checksums for APK.
 */
@@ -90,6 +99,7 @@ public class ApkChecksums {
    static final String TAG = "ApkChecksums";

    private static final String DIGESTS_FILE_EXTENSION = ".digests";
    private static final String DIGESTS_SIGNATURE_FILE_EXTENSION = ".signature";

    // MessageDigest algorithms.
    static final String ALGO_MD5 = "MD5";
@@ -97,6 +107,8 @@ public class ApkChecksums {
    static final String ALGO_SHA256 = "SHA256";
    static final String ALGO_SHA512 = "SHA512";

    private static final Certificate[] EMPTY_CERTIFICATE_ARRAY = {};

    /**
     * Check back in 1 second after we detected we needed to wait for the APK to be fully available.
     */
@@ -166,6 +178,21 @@ public class ApkChecksums {
                + DIGESTS_FILE_EXTENSION;
    }

    /**
     * Return the signature path associated with the given digests path.
     * (appends '.signature' to the end)
     */
    public static String buildSignaturePathForDigests(String digestsPath) {
        return digestsPath + DIGESTS_SIGNATURE_FILE_EXTENSION;
    }

    /** Returns true if the given file looks like containing digests or digests' signature. */
    public static boolean isDigestOrDigestSignatureFile(File file) {
        final String name = file.getName();
        return name.endsWith(DIGESTS_FILE_EXTENSION) || name.endsWith(
                DIGESTS_SIGNATURE_FILE_EXTENSION);
    }

    /**
     * Search for the digests file associated with the given target file.
     * If it exists, the method returns the digests file; otherwise it returns null.
@@ -176,41 +203,91 @@ public class ApkChecksums {
        return digestsFile.exists() ? digestsFile : null;
    }

    /**
     * Search for the signature file associated with the given digests file.
     * If it exists, the method returns the signature file; otherwise it returns null.
     */
    public static File findSignatureForDigests(File digestsFile) {
        String signaturePath = buildSignaturePathForDigests(digestsFile.getAbsolutePath());
        File signatureFile = new File(signaturePath);
        return signatureFile.exists() ? signatureFile : null;
    }

    /**
     * Serialize checksums to the stream in binary format.
     */
    public static void writeChecksums(OutputStream os, Checksum[] checksums)
            throws IOException, CertificateException {
            throws IOException {
        try (DataOutputStream dos = new DataOutputStream(os)) {
            dos.writeInt(checksums.length);
            for (Checksum checksum : checksums) {
                dos.writeInt(checksum.getType());

                final byte[] valueBytes = checksum.getValue();
                dos.writeInt(valueBytes.length);
                dos.write(valueBytes);
                Checksum.writeToStream(dos, checksum);
            }
        }
    }

    private static Checksum[] readChecksums(File file) throws IOException {
        try (InputStream is = new FileInputStream(file)) {
            return readChecksums(is);
        }
    }

    /**
     * Deserialize array of checksums previously stored in
     * {@link #writeChecksums(OutputStream, Checksum[])}.
     */
    private static Checksum[] readChecksums(File file) throws IOException {
        try (InputStream is = new FileInputStream(file);
             DataInputStream dis = new DataInputStream(is)) {
            final int size = dis.readInt();
            Checksum[] checksums = new Checksum[size];
            for (int i = 0; i < size; ++i) {
                final int type = dis.readInt();
    public static Checksum[] readChecksums(InputStream is) throws IOException {
        try (DataInputStream dis = new DataInputStream(is)) {
            ArrayList<Checksum> checksums = new ArrayList<>();
            try {
                // 100 is an arbitrary very big number. We should stop at EOF.
                for (int i = 0; i < 100; ++i) {
                    checksums.add(Checksum.readFromStream(dis));
                }
            } catch (EOFException e) {
                // expected
            }
            return checksums.toArray(new Checksum[checksums.size()]);
        }
    }

                final byte[] valueBytes = new byte[dis.readInt()];
                dis.read(valueBytes);
                checksums[i] = new Checksum(type, valueBytes);
    /**
     * Verifies signature over binary serialized checksums.
     * @param checksums array of checksums
     * @param signature detached PKCS7 signature in DER format
     * @return all certificates that passed verification
     * @throws SignatureException if verification fails
     */
    public static @NonNull Certificate[] verifySignature(Checksum[] checksums, byte[] signature)
            throws NoSuchAlgorithmException, IOException, SignatureException {
        final byte[] blob;
        try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
            writeChecksums(os, checksums);
            blob = os.toByteArray();
        }
            return checksums;

        PKCS7 pkcs7 = new PKCS7(signature);

        final Certificate[] certs = pkcs7.getCertificates();
        if (certs == null || certs.length == 0) {
            throw new SignatureException("Signature missing certificates");
        }

        final SignerInfo[] signerInfos = pkcs7.verify(blob);
        if (signerInfos == null || signerInfos.length == 0) {
            throw new SignatureException("Verification failed");
        }

        ArrayList<Certificate> certificates = new ArrayList<>(signerInfos.length);
        for (SignerInfo signerInfo : signerInfos) {
            ArrayList<X509Certificate> chain = signerInfo.getCertificateChain(pkcs7);
            if (chain == null) {
                throw new SignatureException(
                        "Verification passed, but certification chain is empty.");
            }
            certificates.addAll(chain);
        }

        return certificates.toArray(new Certificate[certificates.size()]);
    }

    /**
@@ -335,6 +412,8 @@ public class ApkChecksums {
            }
        }

        // Note: this compares installer and system digests internally and
        // has to be called right after all system digests are populated.
        getInstallerChecksums(split, file, types, installerPackageName, trustedInstallers,
                checksums, injector);
    }
@@ -352,11 +431,32 @@ public class ApkChecksums {
            return;
        }

        final File digestsFile = new File(buildDigestsPathForApk(file.getAbsolutePath()));
        if (!digestsFile.exists()) {
        final File digestsFile = findDigestsForFile(file);
        if (digestsFile == null) {
            return;
        }
        final File signatureFile = findSignatureForDigests(digestsFile);

        try {
            final Checksum[] digests = readChecksums(digestsFile);
            final Signature[] certs;
            final Signature[] pastCerts;

            if (signatureFile != null) {
                final Certificate[] certificates = verifySignature(digests,
                        Files.readAllBytes(signatureFile.toPath()));
                if (certificates == null || certificates.length == 0) {
                    Slog.e(TAG, "Error validating signature");
                    return;
                }

                certs = new Signature[certificates.length];
                for (int i = 0, size = certificates.length; i < size; i++) {
                    certs[i] = new Signature(certificates[i].getEncoded());
                }

                pastCerts = null;
            } else {
                final AndroidPackage installer = injector.getPackageManagerInternal().getPackage(
                        installerPackageName);
                if (installer == null) {
@@ -365,18 +465,18 @@ public class ApkChecksums {
                }

                // Obtaining array of certificates used for signing the installer package.
        final Signature[] certs = installer.getSigningDetails().signatures;
        final Signature[] pastCerts = installer.getSigningDetails().pastSigningCertificates;
                certs = installer.getSigningDetails().signatures;
                pastCerts = installer.getSigningDetails().pastSigningCertificates;
            }
            if (certs == null || certs.length == 0 || certs[0] == null) {
            Slog.e(TAG, "Can't obtain calling installer package's certificates.");
                Slog.e(TAG, "Can't obtain certificates.");
                return;
            }
        // According to V2/V3 signing schema, the first certificate corresponds to the public key
        // in the signing block.

            // According to V2/V3 signing schema, the first certificate corresponds to the public
            // key in the signing block.
            byte[] trustedCertBytes = certs[0].toByteArray();

        try {
            final Checksum[] digests = readChecksums(digestsFile);
            final Set<Signature> trusted = convertToSet(trustedInstallers);

            if (trusted != null && !trusted.isEmpty()) {
@@ -391,6 +491,16 @@ public class ApkChecksums {
                trustedCertBytes = trustedCert.toByteArray();
            }

            // Compare OS-enforced digests.
            for (Checksum digest : digests) {
                final ApkChecksum system = checksums.get(digest.getType());
                if (system != null && !Arrays.equals(system.getValue(), digest.getValue())) {
                    throw new InvalidParameterException("System digest " + digest.getType()
                            + " mismatch, can't bind installer-provided digests to the APK.");
                }
            }

            // Append missing digests.
            for (Checksum digest : digests) {
                if (isRequired(digest.getType(), types, checksums)) {
                    checksums.put(digest.getType(),
@@ -398,7 +508,16 @@ public class ApkChecksums {
                }
            }
        } catch (IOException e) {
            Slog.e(TAG, "Error reading .digests", e);
            Slog.e(TAG, "Error reading .digests or .signature", e);
        } catch (NoSuchAlgorithmException | SignatureException | InvalidParameterException e) {
            Slog.e(TAG, "Error validating digests. Invalid digests will be removed", e);
            try {
                Files.deleteIfExists(digestsFile.toPath());
                if (signatureFile != null) {
                    Files.deleteIfExists(signatureFile.toPath());
                }
            } catch (IOException ignored) {
            }
        } catch (CertificateEncodingException e) {
            Slog.e(TAG, "Error encoding trustedInstallers", e);
        }
+26 −18
Original line number Diff line number Diff line
@@ -27,7 +27,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
import static android.content.pm.PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
import static android.content.pm.PackageManager.INSTALL_STAGED;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
import static android.system.OsConstants.O_CREAT;
@@ -59,7 +59,6 @@ import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ApkChecksum;
import android.content.pm.ApplicationInfo;
import android.content.pm.Checksum;
import android.content.pm.DataLoaderManager;
@@ -160,7 +159,9 @@ import java.io.FileDescriptor;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -249,7 +250,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
    private static final int[] EMPTY_CHILD_SESSION_ARRAY = EmptyArray.INT;
    private static final InstallationFile[] EMPTY_INSTALLATION_FILE_ARRAY = {};
    private static final ApkChecksum[] EMPTY_FILE_CHECKSUM_ARRAY = {};

    private static final String SYSTEM_DATA_LOADER_PACKAGE = "android";
    private static final String APEX_FILE_EXTENSION = ".apex";
@@ -805,6 +805,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            if (file.getName().endsWith(REMOVE_MARKER_EXTENSION)) return false;
            if (DexMetadataHelper.isDexMetadataFile(file)) return false;
            if (VerityUtils.isFsveritySignatureFile(file)) return false;
            if (ApkChecksums.isDigestOrDigestSignatureFile(file)) return false;
            return true;
        }
    };
@@ -1294,13 +1295,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        }

        if (signature != null && signature.length != 0) {
            final boolean standardMode = PackageManagerServiceUtils.isApkVerityEnabled();
            final boolean legacyMode = PackageManagerServiceUtils.isLegacyApkVerityEnabled();
            if (!standardMode || legacyMode) {
                Slog.e(TAG,
                        "Can't enforce checksum's signature: Apk-Verity is disabled or in legacy "
                                + "mode.");
                signature = null;
            try {
                Certificate[] ignored = ApkChecksums.verifySignature(checksums, signature);
            } catch (IOException | NoSuchAlgorithmException | SignatureException e) {
                throw new IllegalArgumentException("Can't verify signature", e);
            }
        }

@@ -3093,30 +3091,35 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        final String targetDigestsPath = ApkChecksums.buildDigestsPathForApk(targetFile.getName());
        final File targetDigestsFile = new File(stageDir, targetDigestsPath);
        try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
            // Storing and staging checksums.
            ApkChecksums.writeChecksums(os, checksums);

            final byte[] signature = perFileChecksum.getSignature();
            if (signature != null && signature.length > 0) {
                Certificate[] ignored = ApkChecksums.verifySignature(checksums, signature);
            }

            // Storing and staging checksums.
            storeBytesToInstallationFile(targetDigestsPath, targetDigestsFile.getAbsolutePath(),
                    os.toByteArray());
            stageFileLocked(targetDigestsFile, targetDigestsFile);

            final byte[] signature = perFileChecksum.getSignature();
            // Storing and staging signature.
            if (signature == null || signature.length == 0) {
                return;
            }

            // Storing and staging signature.
            final String targetDigestsSignaturePath = VerityUtils.getFsveritySignatureFilePath(
            final String targetDigestsSignaturePath = ApkChecksums.buildSignaturePathForDigests(
                    targetDigestsPath);
            final File targetDigestsSignatureFile = new File(stageDir, targetDigestsSignaturePath);
            storeBytesToInstallationFile(targetDigestsSignaturePath,
                    targetDigestsSignatureFile.getAbsolutePath(), signature);
            stageFileLocked(targetDigestsSignatureFile, targetDigestsSignatureFile);
        } catch (CertificateException e) {
            throw new PackageManagerException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
                    "Failed to encode certificate for " + mPackageName, e);
        } catch (IOException e) {
            throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
                    "Failed to store digests for " + mPackageName, e);
        } catch (NoSuchAlgorithmException | SignatureException e) {
            throw new PackageManagerException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                    "Failed to verify digests' signature for " + mPackageName, e);
        }
    }

@@ -3160,6 +3163,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        final File digestsFile = ApkChecksums.findDigestsForFile(origFile);
        if (digestsFile != null) {
            mResolvedInheritedFiles.add(digestsFile);

            final File signatureFile = ApkChecksums.findSignatureForDigests(digestsFile);
            if (signatureFile != null) {
                mResolvedInheritedFiles.add(signatureFile);
            }
        }
    }