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

Commit a8e65fd8 authored by Kenny Root's avatar Kenny Root
Browse files

Only remember the signer certificates for Signatures

Previously we would use the JarEntry#getCertificates API which would
return a flattened array of all the signers and their certificate chain.
Since this isn't what was intended, switch to reading the certificate
chains and only paying attention to the signer certificate.

In order to migrate during upgrades of the platform, we'll scan on boot
with a compatibility mode which will check the stores signatures in the
old format by flattening the chains of the scanned packages then
comparing the two sets.

Bug: 13678484
Change-Id: I02a5c53121d8d6f70a51d7e3b98168a41e11482e
parent 25ff2c1e
Loading
Loading
Loading
Loading
+8 −9
Original line number Diff line number Diff line
@@ -459,7 +459,7 @@ public class PackageParser {
        return pi;
    }

    private Certificate[] loadCertificates(StrictJarFile jarFile, ZipEntry je,
    private Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry je,
            byte[] readBuffer) {
        try {
            // We must read the stream for the JarEntry to retrieve
@@ -469,7 +469,7 @@ public class PackageParser {
                // not using
            }
            is.close();
            return je != null ? jarFile.getCertificates(je) : null;
            return je != null ? jarFile.getCertificateChains(je) : null;
        } catch (IOException e) {
            Slog.w(TAG, "Exception reading " + je.getName() + " in " + jarFile, e);
        } catch (RuntimeException e) {
@@ -632,7 +632,7 @@ public class PackageParser {
        try {
            StrictJarFile jarFile = new StrictJarFile(mArchiveSourcePath);

            Certificate[] certs = null;
            Certificate[][] certs = null;

            if ((flags&PARSE_IS_SYSTEM) != 0) {
                // If this package comes from the system image, then we
@@ -656,8 +656,8 @@ public class PackageParser {
                        final int N = certs.length;
                        for (int i=0; i<N; i++) {
                            Slog.i(TAG, "  Public key: "
                                    + certs[i].getPublicKey().getEncoded()
                                    + " " + certs[i].getPublicKey());
                                    + certs[i][0].getPublicKey().getEncoded()
                                    + " " + certs[i][0].getPublicKey());
                        }
                    }
                }
@@ -677,7 +677,7 @@ public class PackageParser {
                                ManifestDigest.fromInputStream(jarFile.getInputStream(je));
                    }

                    final Certificate[] localCerts = loadCertificates(jarFile, je, readBuffer);
                    final Certificate[][] localCerts = loadCertificates(jarFile, je, readBuffer);
                    if (DEBUG_JAR) {
                        Slog.i(TAG, "File " + mArchiveSourcePath + " entry " + je.getName()
                                + ": certs=" + certs + " ("
@@ -726,8 +726,7 @@ public class PackageParser {
                final int N = certs.length;
                pkg.mSignatures = new Signature[certs.length];
                for (int i=0; i<N; i++) {
                    pkg.mSignatures[i] = new Signature(
                            certs[i].getEncoded());
                    pkg.mSignatures[i] = new Signature(certs[i]);
                }
            } else {
                Slog.e(TAG, "Package " + pkg.packageName
@@ -739,7 +738,7 @@ public class PackageParser {
            // Add the signing KeySet to the system
            pkg.mSigningKeys = new HashSet<PublicKey>();
            for (int i=0; i < certs.length; i++) {
                pkg.mSigningKeys.add(certs[i].getPublicKey());
                pkg.mSigningKeys.add(certs[i][0].getPublicKey());
            }

        } catch (CertificateEncodingException e) {
+40 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import java.io.ByteArrayInputStream;
import java.lang.ref.SoftReference;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Arrays;
@@ -38,12 +39,28 @@ public class Signature implements Parcelable {
    private int mHashCode;
    private boolean mHaveHashCode;
    private SoftReference<String> mStringRef;
    private Certificate[] mCertificateChain;

    /**
     * Create Signature from an existing raw byte array.
     */
    public Signature(byte[] signature) {
        mSignature = signature.clone();
        mCertificateChain = null;
    }

    /**
     * Create signature from a certificate chain. Used for backward
     * compatibility.
     *
     * @throws CertificateEncodingException
     * @hide
     */
    public Signature(Certificate[] certificateChain) throws CertificateEncodingException {
        mSignature = certificateChain[0].getEncoded();
        if (certificateChain.length > 1) {
            mCertificateChain = Arrays.copyOfRange(certificateChain, 1, certificateChain.length);
        }
    }

    private static final int parseHexDigit(int nibble) {
@@ -156,6 +173,29 @@ public class Signature implements Parcelable {
        return cert.getPublicKey();
    }

    /**
     * Used for compatibility code that needs to check the certificate chain
     * during upgrades.
     *
     * @throws CertificateEncodingException
     * @hide
     */
    public Signature[] getChainSignatures() throws CertificateEncodingException {
        if (mCertificateChain == null) {
            return new Signature[] { this };
        }

        Signature[] chain = new Signature[1 + mCertificateChain.length];
        chain[0] = this;

        int i = 1;
        for (Certificate c : mCertificateChain) {
            chain[i++] = new Signature(c.getEncoded());
        }

        return chain;
    }

    @Override
    public boolean equals(Object obj) {
        try {
+79 −12
Original line number Diff line number Diff line
@@ -145,6 +145,8 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -163,6 +165,7 @@ import java.util.Set;
import libcore.io.IoUtils;
import com.android.internal.R;
import com.android.server.pm.Settings.DatabaseVersion;
import com.android.server.storage.DeviceStorageMonitorInternal;
/**
@@ -2718,6 +2721,59 @@ public class PackageManagerService extends IPackageManager.Stub {
        return PackageManager.SIGNATURE_NO_MATCH;
    }
    /**
     * If the database version for this type of package (internal storage or
     * external storage) is less than the version where package signatures
     * were updated, return true.
     */
    private boolean isCompatSignatureUpdateNeeded(PackageParser.Package scannedPkg) {
        return (isExternal(scannedPkg) && mSettings.isExternalDatabaseVersionOlderThan(
                DatabaseVersion.SIGNATURE_END_ENTITY))
                || (!isExternal(scannedPkg) && mSettings.isInternalDatabaseVersionOlderThan(
                        DatabaseVersion.SIGNATURE_END_ENTITY));
    }
    /**
     * Used for backward compatibility to make sure any packages with
     * certificate chains get upgraded to the new style. {@code existingSigs}
     * will be in the old format (since they were stored on disk from before the
     * system upgrade) and {@code scannedSigs} will be in the newer format.
     */
    private int compareSignaturesCompat(PackageSignatures existingSigs,
            PackageParser.Package scannedPkg) {
        if (!isCompatSignatureUpdateNeeded(scannedPkg)) {
            return PackageManager.SIGNATURE_NO_MATCH;
        }
        HashSet<Signature> existingSet = new HashSet<Signature>();
        for (Signature sig : existingSigs.mSignatures) {
            existingSet.add(sig);
        }
        HashSet<Signature> scannedCompatSet = new HashSet<Signature>();
        for (Signature sig : scannedPkg.mSignatures) {
            try {
                Signature[] chainSignatures = sig.getChainSignatures();
                for (Signature chainSig : chainSignatures) {
                    scannedCompatSet.add(chainSig);
                }
            } catch (CertificateEncodingException e) {
                scannedCompatSet.add(sig);
            }
        }
        /*
         * Make sure the expanded scanned set contains all signatures in the
         * existing one.
         */
        if (scannedCompatSet.equals(existingSet)) {
            // Migrate the old signatures to the new scheme.
            existingSigs.assignSignatures(scannedPkg.mSignatures);
            // The new KeySets will be re-added later in the scanning process.
            mSettings.mKeySetManager.removeAppKeySetData(scannedPkg.packageName);
            return PackageManager.SIGNATURE_MATCH;
        }
        return PackageManager.SIGNATURE_NO_MATCH;
    }
    public String[] getPackagesForUid(int uid) {
        uid = UserHandle.getAppId(uid);
        // reader
@@ -3801,7 +3857,8 @@ public class PackageManagerService extends IPackageManager.Stub {
            PackageParser.Package pkg, File srcFile, int parseFlags) {
        if (ps != null
                && ps.codePath.equals(srcFile)
                && ps.timeStamp == srcFile.lastModified()) {
                && ps.timeStamp == srcFile.lastModified()
                && !isCompatSignatureUpdateNeeded(pkg)) {
            if (ps.signatures.mSignatures != null
                    && ps.signatures.mSignatures.length != 0) {
                // Optimization: reuse the existing cached certificates
@@ -4051,12 +4108,16 @@ public class PackageManagerService extends IPackageManager.Stub {
        return processName;
    }
    private boolean verifySignaturesLP(PackageSetting pkgSetting,
            PackageParser.Package pkg) {
    private boolean verifySignaturesLP(PackageSetting pkgSetting, PackageParser.Package pkg) {
        if (pkgSetting.signatures.mSignatures != null) {
            // Already existing package. Make sure signatures match
            if (compareSignatures(pkgSetting.signatures.mSignatures, pkg.mSignatures) !=
                PackageManager.SIGNATURE_MATCH) {
            boolean match = compareSignatures(pkgSetting.signatures.mSignatures, pkg.mSignatures)
                    == PackageManager.SIGNATURE_MATCH;
            if (!match) {
                match = compareSignaturesCompat(pkgSetting.signatures, pkg)
                        == PackageManager.SIGNATURE_MATCH;
            }
            if (!match) {
                Slog.e(TAG, "Package " + pkg.packageName
                        + " signatures do not match the previously installed version; ignoring!");
                mLastScanError = PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
@@ -4065,8 +4126,14 @@ public class PackageManagerService extends IPackageManager.Stub {
        }
        // Check for shared user signatures
        if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) {
            if (compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
                    pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) {
            // Already existing package. Make sure signatures match
            boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
                    pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
            if (!match) {
                match = compareSignaturesCompat(pkgSetting.sharedUser.signatures, pkg)
                        == PackageManager.SIGNATURE_MATCH;
            }
            if (!match) {
                Slog.e(TAG, "Package " + pkg.packageName
                        + " has no signatures that match those in shared user "
                        + pkgSetting.sharedUser.name + "; ignoring!");
+7 −1
Original line number Diff line number Diff line
@@ -97,7 +97,7 @@ final class Settings {
     * Note that care should be taken to make sure all database upgrades are
     * idempotent.
     */
    private static final int CURRENT_DATABASE_VERSION = DatabaseVersion.FIRST_VERSION;
    private static final int CURRENT_DATABASE_VERSION = DatabaseVersion.SIGNATURE_END_ENTITY;

    /**
     * This class contains constants that can be referred to from upgrade code.
@@ -109,6 +109,12 @@ final class Settings {
         * The initial version of the database.
         */
        public static final int FIRST_VERSION = 1;

        /**
         * Migrating the Signature array from the entire certificate chain to
         * just the signing certificate.
         */
        public static final int SIGNATURE_END_ENTITY = 2;
    }

    private static final boolean DEBUG_STOPPED = false;