Merge "Digest API: support for certificate rotation."

This commit is contained in:
Alex Buynytskyy
2021-01-13 16:11:22 +00:00
committed by Android (Google) Code Review
3 changed files with 144 additions and 181 deletions

View File

@@ -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,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<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);
}
@@ -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<Integer, ApkChecksum> checksums) {
Map<Integer, ApkChecksum> 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<Signature> 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<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 Checksum[] digests = readChecksums(digestsFile);
final Set<Signature> 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<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) {

View File

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

View File

@@ -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);
});
}