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

Commit 13189ac1 authored by Alex Buynytskyy's avatar Alex Buynytskyy Committed by Android (Google) Code Review
Browse files

Merge "Digest API: support for certificate rotation."

parents c2141fe6 dbd97c06
Loading
Loading
Loading
Loading
+112 −95
Original line number Diff line number Diff line
@@ -36,12 +36,14 @@ import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ApkChecksum;
import android.content.pm.Checksum;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageParser;
import android.content.pm.Signature;
import android.os.Handler;
import android.os.SystemClock;
import android.os.incremental.IncrementalManager;
import android.os.incremental.IncrementalStorage;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
@@ -57,6 +59,7 @@ import android.util.apk.SignatureNotFoundException;
import android.util.apk.VerityBuilder;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.security.VerityUtils;

import java.io.BufferedInputStream;
@@ -121,12 +124,15 @@ public class ApkChecksums {
        private final Producer<Context> mContext;
        private final Producer<Handler> mHandlerProducer;
        private final Producer<IncrementalManager> mIncrementalManagerProducer;
        private final Producer<PackageManagerInternal> mPackageManagerInternalProducer;

        Injector(Producer<Context> context, Producer<Handler> handlerProducer,
                Producer<IncrementalManager> incrementalManagerProducer) {
                Producer<IncrementalManager> incrementalManagerProducer,
                Producer<PackageManagerInternal> packageManagerInternalProducer) {
            mContext = context;
            mHandlerProducer = handlerProducer;
            mIncrementalManagerProducer = incrementalManagerProducer;
            mPackageManagerInternalProducer = packageManagerInternalProducer;
        }

        public Context getContext() {
@@ -140,6 +146,10 @@ public class ApkChecksums {
        public IncrementalManager getIncrementalManager() {
            return mIncrementalManagerProducer.produce();
        }

        public PackageManagerInternal getPackageManagerInternal() {
            return mPackageManagerInternalProducer.produce();
        }
    }

    /**
@@ -169,96 +179,47 @@ public class ApkChecksums {
    /**
     * Serialize checksums to the stream in binary format.
     */
    public static void writeChecksums(OutputStream os, ApkChecksum[] checksums)
    public static void writeChecksums(OutputStream os, Checksum[] checksums)
            throws IOException, CertificateException {
        try (DataOutputStream dos = new DataOutputStream(os)) {
            dos.writeInt(checksums.length);
            for (ApkChecksum checksum : checksums) {
                final String splitName = checksum.getSplitName();
                if (splitName == null) {
                    dos.writeInt(-1);
                } else {
                    dos.writeInt(splitName.length());
                    dos.writeUTF(splitName);
                }

            for (Checksum checksum : checksums) {
                dos.writeInt(checksum.getType());

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

                final String packageName = checksum.getInstallerPackageName();
                if (packageName == null) {
                    dos.writeInt(-1);
                } else {
                    dos.writeInt(packageName.length());
                    dos.writeUTF(packageName);
                }

                final Certificate cert = checksum.getInstallerCertificate();
                final byte[] certBytes = (cert == null) ? null : cert.getEncoded();
                if (certBytes == null) {
                    dos.writeInt(-1);
                } else {
                    dos.writeInt(certBytes.length);
                    dos.write(certBytes);
                }
            }
        }
    }

    /**
     * Deserialize array of checksums previously stored in
     * {@link #writeChecksums(File, ApkChecksum[])}.
     * {@link #writeChecksums(OutputStream, Checksum[])}.
     */
    private static ApkChecksum[] readChecksums(File file) throws IOException {
    private static Checksum[] readChecksums(File file) throws IOException {
        try (InputStream is = new FileInputStream(file);
             DataInputStream dis = new DataInputStream(is)) {
            final int size = dis.readInt();
            ApkChecksum[] checksums = new ApkChecksum[size];
            Checksum[] checksums = new Checksum[size];
            for (int i = 0; i < size; ++i) {
                final String splitName;
                if (dis.readInt() < 0) {
                    splitName = null;
                } else {
                    splitName = dis.readUTF();
                }

                final int type = dis.readInt();

                final byte[] valueBytes = new byte[dis.readInt()];
                dis.read(valueBytes);

                final String packageName;
                if (dis.readInt() < 0) {
                    packageName = null;
                } else {
                    packageName = dis.readUTF();
                }

                final byte[] certBytes;
                final int certBytesLength = dis.readInt();
                if (certBytesLength < 0) {
                    certBytes = null;
                } else {
                    certBytes = new byte[certBytesLength];
                    dis.read(certBytes);
                }
                checksums[i] = new ApkChecksum(splitName, new Checksum(type, valueBytes),
                        packageName, certBytes);
                checksums[i] = new Checksum(type, valueBytes);
            }
            return checksums;
        }
    }


    /**
     * Fetch or calculate checksums for the collection of files.
     *
     * @param filesToChecksum       split name, null for base and File to fetch checksums for
     * @param optional              mask to fetch readily available checksums
     * @param required              mask to forcefully calculate if not available
     * @param installerPackageName  package name of the installer of the packages
     * @param trustedInstallers     array of certificate to trust, two specific cases:
     *                              null - trust anybody,
     *                              [] - trust nobody.
@@ -267,6 +228,7 @@ public class ApkChecksums {
    public static void getChecksums(List<Pair<String, File>> filesToChecksum,
            @Checksum.Type int optional,
            @Checksum.Type int required,
            @Nullable String installerPackageName,
            @Nullable Certificate[] trustedInstallers,
            @NonNull IntentSender statusReceiver,
            @NonNull Injector injector) {
@@ -278,8 +240,8 @@ public class ApkChecksums {
            result.add(checksums);

            try {
                getAvailableApkChecksums(split, file, optional | required, trustedInstallers,
                        checksums);
                getAvailableApkChecksums(split, file, optional | required, installerPackageName,
                        trustedInstallers, checksums, injector);
            } catch (Throwable e) {
                Slog.e(TAG, "Preferred checksum calculation error", e);
            }
@@ -340,6 +302,7 @@ public class ApkChecksums {
     * @param split                 split name, null for base
     * @param file                  to fetch checksums for
     * @param types                 mask to fetch checksums
     * @param installerPackageName  package name of the installer of the packages
     * @param trustedInstallers     array of certificate to trust, two specific cases:
     *                              null - trust anybody,
     *                              [] - trust nobody.
@@ -347,8 +310,10 @@ public class ApkChecksums {
     */
    private static void getAvailableApkChecksums(String split, File file,
            @Checksum.Type int types,
            @Nullable String installerPackageName,
            @Nullable Certificate[] trustedInstallers,
            Map<Integer, ApkChecksum> checksums) {
            Map<Integer, ApkChecksum> checksums,
            @NonNull Injector injector) {
        final String filePath = file.getAbsolutePath();

        // Always available: FSI or IncFs.
@@ -370,16 +335,66 @@ public class ApkChecksums {
            }
        }

        if (trustedInstallers == null || trustedInstallers.length > 0) {
            final File digestsFile = new File(buildDigestsPathForApk(filePath));
            if (digestsFile.exists()) {
        getInstallerChecksums(split, file, types, installerPackageName, trustedInstallers,
                checksums, injector);
    }

    private static void getInstallerChecksums(String split, File file,
            @Checksum.Type int types,
            @Nullable String installerPackageName,
            @Nullable Certificate[] trustedInstallers,
            Map<Integer, ApkChecksum> checksums,
            @NonNull Injector injector) {
        if (TextUtils.isEmpty(installerPackageName)) {
            return;
        }
        if (trustedInstallers != null && trustedInstallers.length == 0) {
            return;
        }

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

        final AndroidPackage installer = injector.getPackageManagerInternal().getPackage(
                installerPackageName);
        if (installer == null) {
            Slog.e(TAG, "Installer package not found.");
            return;
        }

        // Obtaining array of certificates used for signing the installer package.
        final Signature[] certs = installer.getSigningDetails().signatures;
        final Signature[] pastCerts = installer.getSigningDetails().pastSigningCertificates;
        if (certs == null || certs.length == 0 || certs[0] == null) {
            Slog.e(TAG, "Can't obtain calling installer package's certificates.");
            return;
        }
        // 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 ApkChecksum[] digests = readChecksums(digestsFile);
            final Checksum[] digests = readChecksums(digestsFile);
            final Set<Signature> trusted = convertToSet(trustedInstallers);
                    for (ApkChecksum digest : digests) {
                        if (isRequired(digest.getType(), types, checksums) && isTrusted(digest,
                                trusted)) {
                            checksums.put(digest.getType(), digest);

            if (trusted != null && !trusted.isEmpty()) {
                // Obtaining array of certificates used for signing the installer package.
                Signature trustedCert = isTrusted(certs, trusted);
                if (trustedCert == null) {
                    trustedCert = isTrusted(pastCerts, trusted);
                }
                if (trustedCert == null) {
                    return;
                }
                trustedCertBytes = trustedCert.toByteArray();
            }

            for (Checksum digest : digests) {
                if (isRequired(digest.getType(), types, checksums)) {
                    checksums.put(digest.getType(),
                            new ApkChecksum(split, digest, installerPackageName, trustedCertBytes));
                }
            }
        } catch (IOException e) {
@@ -388,8 +403,6 @@ public class ApkChecksums {
            Slog.e(TAG, "Error encoding trustedInstallers", e);
        }
    }
        }
    }

    /**
     * Whether the file is available for checksumming or we need to wait.
@@ -494,12 +507,16 @@ public class ApkChecksums {
        return set;
    }

    private static boolean isTrusted(ApkChecksum checksum, Set<Signature> trusted) {
        if (trusted == null) {
            return true;
    private static Signature isTrusted(Signature[] signatures, Set<Signature> trusted) {
        if (signatures == null) {
            return null;
        }
        final Signature signature = new Signature(checksum.getInstallerCertificateBytes());
        return trusted.contains(signature);
        for (Signature signature : signatures) {
            if (trusted.contains(signature)) {
                return signature;
            }
        }
        return null;
    }

    private static ApkChecksum extractHashFromFS(String split, String filePath) {
+26 −84
Original line number Diff line number Diff line
@@ -85,7 +85,6 @@ import android.content.pm.PackageParser;
import android.content.pm.PackageParser.ApkLite;
import android.content.pm.PackageParser.PackageLite;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.Signature;
import android.content.pm.dex.DexMetadataHelper;
import android.content.pm.parsing.ApkLiteParseUtils;
import android.content.pm.parsing.result.ParseResult;
@@ -245,8 +244,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    private static final String ATTR_SIGNATURE = "signature";
    private static final String ATTR_CHECKSUM_KIND = "checksumKind";
    private static final String ATTR_CHECKSUM_VALUE = "checksumValue";
    private static final String ATTR_CHECKSUM_PACKAGE = "checksumPackage";
    private static final String ATTR_CHECKSUM_CERTIFICATE = "checksumCertificate";

    private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
    private static final int[] EMPTY_CHILD_SESSION_ARRAY = EmptyArray.INT;
@@ -402,33 +399,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    @GuardedBy("mLock")
    private ArraySet<FileEntry> mFiles = new ArraySet<>();

    static class CertifiedChecksum {
        final @NonNull Checksum mChecksum;
        final @NonNull String mPackageName;
        final @NonNull byte[] mCertificate;

        CertifiedChecksum(@NonNull Checksum checksum, @NonNull String packageName,
                @NonNull byte[] certificate) {
            mChecksum = checksum;
            mPackageName = packageName;
            mCertificate = certificate;
        }

        Checksum getChecksum() {
            return mChecksum;
        }

        String getPackageName() {
            return mPackageName;
        }

        byte[] getCertificate() {
            return mCertificate;
        }
    }

    @GuardedBy("mLock")
    private ArrayMap<String, List<CertifiedChecksum>> mChecksums = new ArrayMap<>();
    private ArrayMap<String, Checksum[]> mChecksums = new ArrayMap<>();

    @Nullable
    final StagedSession mStagedSession;
@@ -946,7 +918,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            int sessionId, int userId, int installerUid, @NonNull InstallSource installSource,
            SessionParams params, long createdMillis, long committedMillis,
            File stageDir, String stageCid, InstallationFile[] files,
            ArrayMap<String, List<CertifiedChecksum>> checksums,
            ArrayMap<String, List<Checksum>> checksums,
            boolean prepared, boolean committed, boolean destroyed, boolean sealed,
            @Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
            boolean isFailed, boolean isApplied, int stagedSessionErrorCode,
@@ -992,7 +964,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        }

        if (checksums != null) {
            mChecksums.putAll(checksums);
            for (int i = 0, isize = checksums.size(); i < isize; ++i) {
                final String fileName = checksums.keyAt(i);
                final List<Checksum> fileChecksums = checksums.valueAt(i);
                mChecksums.put(fileName, fileChecksums.toArray(new Checksum[fileChecksums.size()]));
            }
        }

        if (!params.isMultiPackage && (stageDir == null) == (stageCid == null)) {
@@ -1290,16 +1266,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            throw new IllegalStateException("Can't obtain calling installer's package.");
        }

        // Obtaining array of certificates used for signing the installer package.
        final Signature[] certs = callingInstaller.getSigningDetails().signatures;
        if (certs == null || certs.length == 0 || certs[0] == null) {
            throw new IllegalStateException(
                    "Can't obtain calling installer package's certificates.");
        }
        // According to V2/V3 signing schema, the first certificate corresponds to the public key
        // in the signing block.
        final byte[] mainCertificateBytes = certs[0].toByteArray();

        synchronized (mLock) {
            assertCallerIsOwnerOrRootLocked();
            assertPreparedAndNotCommittedOrDestroyedLocked("addChecksums");
@@ -1308,13 +1274,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                throw new IllegalStateException("Duplicate checksums.");
            }

            List<CertifiedChecksum> fileChecksums = new ArrayList<>();
            mChecksums.put(name, fileChecksums);

            for (Checksum checksum : checksums) {
                fileChecksums.add(new CertifiedChecksum(checksum, initiatingPackageName,
                        mainCertificateBytes));
            }
            mChecksums.put(name, checksums);
        }
    }

@@ -3069,34 +3029,23 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        maybeStageFsveritySignatureLocked(dexMetadataFile, targetDexMetadataFile);
    }

    private static ApkChecksum[] createApkChecksums(String splitName,
            List<CertifiedChecksum> checksums) {
        ApkChecksum[] result = new ApkChecksum[checksums.size()];
        for (int i = 0, size = checksums.size(); i < size; ++i) {
            CertifiedChecksum checksum = checksums.get(i);
            result[i] = new ApkChecksum(splitName, checksum.getChecksum(),
                    checksum.getPackageName(), checksum.getCertificate());
        }
        return result;
    }

    @GuardedBy("mLock")
    private void maybeStageDigestsLocked(File origFile, File targetFile, String splitName)
            throws PackageManagerException {
        final List<CertifiedChecksum> checksums = mChecksums.get(origFile.getName());
        final Checksum[] checksums = mChecksums.get(origFile.getName());
        if (checksums == null) {
            return;
        }
        mChecksums.remove(origFile.getName());

        if (checksums.isEmpty()) {
        if (checksums.length == 0) {
            return;
        }

        final String targetDigestsPath = ApkChecksums.buildDigestsPathForApk(targetFile.getName());
        final File targetDigestsFile = new File(stageDir, targetDigestsPath);
        try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
            ApkChecksums.writeChecksums(os, createApkChecksums(splitName, checksums));
            ApkChecksums.writeChecksums(os, checksums);
            final byte[] checksumsBytes = os.toByteArray();

            if (!isIncrementalInstallation() || mIncrementalFileStorages == null) {
@@ -4338,18 +4287,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            }

            for (int i = 0, isize = mChecksums.size(); i < isize; ++i) {
                String fileName = mChecksums.keyAt(i);
                List<CertifiedChecksum> checksums = mChecksums.valueAt(i);
                for (int j = 0, jsize = checksums.size(); j < jsize; ++j) {
                    CertifiedChecksum checksum = checksums.get(j);
                final String fileName = mChecksums.keyAt(i);
                final Checksum[] checksums = mChecksums.valueAt(i);
                for (Checksum checksum : checksums) {
                    out.startTag(null, TAG_SESSION_CHECKSUM);
                    writeStringAttribute(out, ATTR_NAME, fileName);
                    out.attributeInt(null, ATTR_CHECKSUM_KIND, checksum.getChecksum().getType());
                    writeByteArrayAttribute(out, ATTR_CHECKSUM_VALUE,
                            checksum.getChecksum().getValue());
                    writeStringAttribute(out, ATTR_CHECKSUM_PACKAGE, checksum.getPackageName());
                    writeByteArrayAttribute(out, ATTR_CHECKSUM_CERTIFICATE,
                            checksum.getCertificate());
                    out.attributeInt(null, ATTR_CHECKSUM_KIND, checksum.getType());
                    writeByteArrayAttribute(out, ATTR_CHECKSUM_VALUE, checksum.getValue());
                    out.endTag(null, TAG_SESSION_CHECKSUM);
                }
            }
@@ -4467,7 +4411,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        int autoRevokePermissionsMode = MODE_DEFAULT;
        List<Integer> childSessionIds = new ArrayList<>();
        List<InstallationFile> files = new ArrayList<>();
        ArrayMap<String, List<CertifiedChecksum>> checksums = new ArrayMap<>();
        ArrayMap<String, List<Checksum>> checksums = new ArrayMap<>();
        int outerDepth = in.getDepth();
        int type;
        while ((type = in.next()) != XmlPullParser.END_DOCUMENT
@@ -4499,18 +4443,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            }
            if (TAG_SESSION_CHECKSUM.equals(in.getName())) {
                final String fileName = readStringAttribute(in, ATTR_NAME);
                final CertifiedChecksum certifiedChecksum = new CertifiedChecksum(
                        new Checksum(in.getAttributeInt(null, ATTR_CHECKSUM_KIND, 0),
                                readByteArrayAttribute(in, ATTR_CHECKSUM_VALUE)),
                        readStringAttribute(in, ATTR_CHECKSUM_PACKAGE),
                        readByteArrayAttribute(in, ATTR_CHECKSUM_CERTIFICATE));

                List<CertifiedChecksum> certifiedChecksums = checksums.get(fileName);
                if (certifiedChecksums == null) {
                    certifiedChecksums = new ArrayList<>();
                    checksums.put(fileName, certifiedChecksums);
                }
                certifiedChecksums.add(certifiedChecksum);
                final Checksum checksum = new Checksum(
                        in.getAttributeInt(null, ATTR_CHECKSUM_KIND, 0),
                        readByteArrayAttribute(in, ATTR_CHECKSUM_VALUE));

                List<Checksum> fileChecksums = checksums.get(fileName);
                if (fileChecksums == null) {
                    fileChecksums = new ArrayList<>();
                    checksums.put(fileName, fileChecksums);
                }
                fileChecksums.add(checksum);
            }
        }

+7 −3
Original line number Diff line number Diff line
@@ -5584,6 +5584,9 @@ public class PackageManagerService extends IPackageManager.Stub
        if (applicationInfo == null) {
            throw new ParcelableException(new PackageManager.NameNotFoundException(packageName));
        }
        final InstallSourceInfo installSourceInfo = getInstallSourceInfo(packageName);
        final String installerPackageName =
                installSourceInfo != null ? installSourceInfo.getInitiatingPackageName() : null;
        List<Pair<String, File>> filesToChecksum = new ArrayList<>();
@@ -5605,9 +5608,10 @@ public class PackageManagerService extends IPackageManager.Stub
            ApkChecksums.Injector injector = new ApkChecksums.Injector(
                    () -> mContext,
                    () -> handler,
                    () -> mInjector.getIncrementalManager());
            ApkChecksums.getChecksums(filesToChecksum, optional, required, trustedCerts,
                    statusReceiver, injector);
                    () -> mInjector.getIncrementalManager(),
                    () -> mPmInternal);
            ApkChecksums.getChecksums(filesToChecksum, optional, required, installerPackageName,
                    trustedCerts, statusReceiver, injector);
        });
    }