From dbd97c06849482ff1290008eb01fc5b3d55cbaa5 Mon Sep 17 00:00:00 2001 From: Alex Buynytskyy Date: Mon, 11 Jan 2021 20:28:28 -0800 Subject: [PATCH] Digest API: support for certificate rotation. Bug: 160605420 Test: atest ChecksumsTest Change-Id: I438da11aa2b76858d2e81f5f4efe55fd49c15661 --- .../com/android/server/pm/ApkChecksums.java | 207 ++++++++++-------- .../server/pm/PackageInstallerSession.java | 108 +++------ .../server/pm/PackageManagerService.java | 10 +- 3 files changed, 144 insertions(+), 181 deletions(-) diff --git a/services/core/java/com/android/server/pm/ApkChecksums.java b/services/core/java/com/android/server/pm/ApkChecksums.java index 7e8ff9499ff8d..08739cbeaee49 100644 --- a/services/core/java/com/android/server/pm/ApkChecksums.java +++ b/services/core/java/com/android/server/pm/ApkChecksums.java @@ -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 mContext; private final Producer mHandlerProducer; private final Producer mIncrementalManagerProducer; + private final Producer mPackageManagerInternalProducer; Injector(Producer context, Producer handlerProducer, - Producer incrementalManagerProducer) { + Producer incrementalManagerProducer, + Producer 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,104 +179,56 @@ 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 trustedInstallers array of certificate to trust, two specific cases: - * null - trust anybody, - * [] - trust nobody. - * @param statusReceiver to receive the resulting checksums + * @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. + * @param statusReceiver to receive the resulting checksums */ public static void getChecksums(List> 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); } @@ -337,18 +299,21 @@ public class ApkChecksums { /** * Fetch readily available checksums - enforced by kernel or provided by Installer. * - * @param split split name, null for base - * @param file to fetch checksums for - * @param types mask to fetch checksums - * @param trustedInstallers array of certificate to trust, two specific cases: - * null - trust anybody, - * [] - trust nobody. - * @param checksums resulting checksums + * @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. + * @param checksums resulting checksums */ private static void getAvailableApkChecksums(String split, File file, @Checksum.Type int types, + @Nullable String installerPackageName, @Nullable Certificate[] trustedInstallers, - Map checksums) { + Map checksums, + @NonNull Injector injector) { final String filePath = file.getAbsolutePath(); // Always available: FSI or IncFs. @@ -370,24 +335,72 @@ public class ApkChecksums { } } - if (trustedInstallers == null || trustedInstallers.length > 0) { - final File digestsFile = new File(buildDigestsPathForApk(filePath)); - if (digestsFile.exists()) { - try { - final ApkChecksum[] digests = readChecksums(digestsFile); - final Set trusted = convertToSet(trustedInstallers); - for (ApkChecksum digest : digests) { - if (isRequired(digest.getType(), types, checksums) && isTrusted(digest, - trusted)) { - checksums.put(digest.getType(), digest); - } - } - } catch (IOException e) { - Slog.e(TAG, "Error reading .digests", e); - } catch (CertificateEncodingException e) { - Slog.e(TAG, "Error encoding trustedInstallers", e); + 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 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 Checksum[] digests = readChecksums(digestsFile); + final Set trusted = convertToSet(trustedInstallers); + + 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) { + Slog.e(TAG, "Error reading .digests", e); + } catch (CertificateEncodingException e) { + Slog.e(TAG, "Error encoding trustedInstallers", e); } } @@ -494,12 +507,16 @@ public class ApkChecksums { return set; } - private static boolean isTrusted(ApkChecksum checksum, Set trusted) { - if (trusted == null) { - return true; + private static Signature isTrusted(Signature[] signatures, Set 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) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 3d04b56079227..f4472b4835c39 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -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 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> mChecksums = new ArrayMap<>(); + private ArrayMap 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> checksums, + ArrayMap> 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 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 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 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 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 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 childSessionIds = new ArrayList<>(); List files = new ArrayList<>(); - ArrayMap> checksums = new ArrayMap<>(); + ArrayMap> 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)); + final Checksum checksum = new Checksum( + in.getAttributeInt(null, ATTR_CHECKSUM_KIND, 0), + readByteArrayAttribute(in, ATTR_CHECKSUM_VALUE)); - List certifiedChecksums = checksums.get(fileName); - if (certifiedChecksums == null) { - certifiedChecksums = new ArrayList<>(); - checksums.put(fileName, certifiedChecksums); + List fileChecksums = checksums.get(fileName); + if (fileChecksums == null) { + fileChecksums = new ArrayList<>(); + checksums.put(fileName, fileChecksums); } - certifiedChecksums.add(certifiedChecksum); + fileChecksums.add(checksum); } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 4467b5110d08d..24faa8742d121 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -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> 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); }); }