blob: 8fc30e43d1a0bf13269f89d72c5988a4c691d951 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.apex.ApexInfo;
import android.apex.IApexService;
import android.app.compat.CompatChanges;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.compat.annotation.ChangeId;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApexStagedEvent;
import android.content.pm.ApplicationInfo;
import android.content.pm.IBackgroundInstallControlService;
import android.content.pm.IPackageManagerNative;
import android.content.pm.IStagedApexObserver;
import android.content.pm.InstallSourceInfo;
import android.content.pm.ModuleInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
import android.content.pm.SigningInfo;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.hardware.biometrics.SensorProperties;
import android.hardware.biometrics.SensorProperties.ComponentInfo;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.text.TextUtils;
import android.util.PackageUtils;
import android.util.Slog;
import android.util.apk.ApkSignatureVerifier;
import android.util.apk.ApkSigningBlockUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.expresslog.Histogram;
import com.android.internal.os.IBinaryTransparencyService;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.pm.ApexManager;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.AndroidPackageSplit;
import com.android.server.pm.pkg.PackageState;
import libcore.util.HexEncoding;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
/**
* @hide
*/
public class BinaryTransparencyService extends SystemService {
private static final String TAG = "TransparencyService";
@VisibleForTesting
static final String VBMETA_DIGEST_UNINITIALIZED = "vbmeta-digest-uninitialized";
@VisibleForTesting
static final String VBMETA_DIGEST_UNAVAILABLE = "vbmeta-digest-unavailable";
@VisibleForTesting
static final String SYSPROP_NAME_VBETA_DIGEST = "ro.boot.vbmeta.digest";
@VisibleForTesting
static final String BINARY_HASH_ERROR = "SHA256HashError";
static final long RECORD_MEASUREMENTS_COOLDOWN_MS = 24 * 60 * 60 * 1000;
static final String APEX_PRELOAD_LOCATION_ERROR = "could-not-be-determined";
// Copy from the atom. Consistent for both ApexInfoGathered and MobileBundledAppInfoGathered.
static final int DIGEST_ALGORITHM_UNKNOWN = 0;
static final int DIGEST_ALGORITHM_CHUNKED_SHA256 = 1;
static final int DIGEST_ALGORITHM_CHUNKED_SHA512 = 2;
static final int DIGEST_ALGORITHM_VERITY_CHUNKED_SHA256 = 3;
static final int DIGEST_ALGORITHM_SHA256 = 4;
// used for indicating any type of error during MBA measurement
static final int MBA_STATUS_ERROR = 0;
// used for indicating factory condition preloads
static final int MBA_STATUS_PRELOADED = 1;
// used for indicating preloaded apps that are updated
static final int MBA_STATUS_UPDATED_PRELOAD = 2;
// used for indicating newly installed MBAs
static final int MBA_STATUS_NEW_INSTALL = 3;
// used for indicating newly installed MBAs that are updated (but unused currently)
static final int MBA_STATUS_UPDATED_NEW_INSTALL = 4;
@VisibleForTesting
static final String KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION =
"enable_biometric_property_verification";
private static final boolean DEBUG = false; // toggle this for local debug
private static final Histogram digestAllPackagesLatency = new Histogram(
"binary_transparency.value_digest_all_packages_latency_uniform",
new Histogram.UniformOptions(50, 0, 500));
private final Context mContext;
private String mVbmetaDigest;
// the system time (in ms) the last measurement was taken
private long mMeasurementsLastRecordedMs;
private PackageManagerInternal mPackageManagerInternal;
private BiometricLogger mBiometricLogger;
/**
* Guards whether or not measurements of MBA to be performed. When this change is enabled,
* measurements of MBAs are performed. But when it is disabled, only measurements of APEX
* and modules are done.
*/
@ChangeId
public static final long LOG_MBA_INFO = 245692487L;
final class BinaryTransparencyServiceImpl extends IBinaryTransparencyService.Stub {
@Override
public String getSignedImageInfo() {
return mVbmetaDigest;
}
/**
* A helper function to compute the SHA256 digest of APK package signer.
* @param signingInfo The signingInfo of a package, usually {@link PackageInfo#signingInfo}.
* @return an array of {@code String} representing hex encoded string of the
* SHA256 digest of APK signer(s). The number of signers will be reflected by the
* size of the array.
* However, {@code null} is returned if there is any error.
*/
private String[] computePackageSignerSha256Digests(@Nullable SigningInfo signingInfo) {
if (signingInfo == null) {
Slog.e(TAG, "signingInfo is null");
return null;
}
Signature[] packageSigners = signingInfo.getApkContentsSigners();
List<String> resultList = new ArrayList<>();
for (Signature packageSigner : packageSigners) {
byte[] digest = PackageUtils.computeSha256DigestBytes(packageSigner.toByteArray());
String digestHexString = HexEncoding.encodeToString(digest, false);
resultList.add(digestHexString);
}
return resultList.toArray(new String[1]);
}
/*
* Perform basic measurement (i.e. content digest) on a given app, including the split APKs.
*
* @param packageState The package to be measured.
* @param mbaStatus Assign this value of MBA status to the returned elements.
* @return a @{@code List<IBinaryTransparencyService.AppInfo>}
*/
private @NonNull List<IBinaryTransparencyService.AppInfo> collectAppInfo(
PackageState packageState, int mbaStatus) {
// compute content digest
if (DEBUG) {
Slog.d(TAG, "Computing content digest for " + packageState.getPackageName() + " at "
+ packageState.getPath());
}
var results = new ArrayList<IBinaryTransparencyService.AppInfo>();
// Same attributes across base and splits.
String packageName = packageState.getPackageName();
long versionCode = packageState.getVersionCode();
String[] signerDigests =
computePackageSignerSha256Digests(packageState.getSigningInfo());
AndroidPackage pkg = packageState.getAndroidPackage();
for (AndroidPackageSplit split : pkg.getSplits()) {
var appInfo = new IBinaryTransparencyService.AppInfo();
appInfo.packageName = packageName;
appInfo.longVersion = versionCode;
appInfo.splitName = split.getName(); // base's split name is null
// Signer digests are consistent between splits, guaranteed by Package Manager.
appInfo.signerDigests = signerDigests;
appInfo.mbaStatus = mbaStatus;
// Only digest and split name are different between splits.
Digest digest = measureApk(split.getPath());
appInfo.digest = digest.value;
appInfo.digestAlgorithm = digest.algorithm;
results.add(appInfo);
}
// InstallSourceInfo is only available per package name, so store it only on the base
// APK. It's not current currently available in PackageState (there's a TODO), to we
// need to extract manually with another call.
//
// Base APK is already the 0-th split from getSplits() and can't be null.
AppInfo base = results.get(0);
InstallSourceInfo installSourceInfo = getInstallSourceInfo(
packageState.getPackageName());
if (installSourceInfo != null) {
base.initiator = installSourceInfo.getInitiatingPackageName();
SigningInfo initiatorSignerInfo =
installSourceInfo.getInitiatingPackageSigningInfo();
if (initiatorSignerInfo != null) {
base.initiatorSignerDigests =
computePackageSignerSha256Digests(initiatorSignerInfo);
}
base.installer = installSourceInfo.getInstallingPackageName();
base.originator = installSourceInfo.getOriginatingPackageName();
}
return results;
}
/**
* Perform basic measurement (i.e. content digest) on a given APK.
*
* @param apkPath The APK (or APEX, since it's also an APK) file to be measured.
* @return a {@link #Digest} with preferred digest algorithm type and the value.
*/
private @Nullable Digest measureApk(@NonNull String apkPath) {
// compute content digest
Map<Integer, byte[]> contentDigests = computeApkContentDigest(apkPath);
if (contentDigests == null) {
Slog.d(TAG, "Failed to compute content digest for " + apkPath);
} else {
// in this iteration, we'll be supporting only 2 types of digests:
// CHUNKED_SHA256 and CHUNKED_SHA512.
// And only one of them will be available per package.
if (contentDigests.containsKey(
ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256)) {
return new Digest(
DIGEST_ALGORITHM_CHUNKED_SHA256,
contentDigests.get(ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256));
} else if (contentDigests.containsKey(
ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512)) {
return new Digest(
DIGEST_ALGORITHM_CHUNKED_SHA512,
contentDigests.get(ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512));
}
}
// When something went wrong, fall back to simple sha256.
byte[] digest = PackageUtils.computeSha256DigestForLargeFileAsBytes(apkPath,
PackageUtils.createLargeFileBuffer());
return new Digest(DIGEST_ALGORITHM_SHA256, digest);
}
/**
* Measures and records digests for *all* covered binaries/packages.
*
* This method will be called in a Job scheduled to take measurements periodically. If the
* last measurement was performaned recently (less than RECORD_MEASUREMENT_COOLDOWN_MS
* ago), the measurement and recording will be skipped.
*
* Packages that are covered so far are:
* - all APEXs (introduced in Android T)
* - all mainline modules (introduced in Android T)
* - all preloaded apps and their update(s) (new in Android U)
* - dynamically installed mobile bundled apps (MBAs) (new in Android U)
*/
public void recordMeasurementsForAllPackages() {
// check if we should measure and record
long currentTimeMs = System.currentTimeMillis();
if ((currentTimeMs - mMeasurementsLastRecordedMs) < RECORD_MEASUREMENTS_COOLDOWN_MS) {
Slog.d(TAG, "Skip measurement since the last measurement was only taken at "
+ mMeasurementsLastRecordedMs + " within the cooldown period");
return;
}
Slog.d(TAG, "Measurement was last taken at " + mMeasurementsLastRecordedMs
+ " and is now updated to: " + currentTimeMs);
mMeasurementsLastRecordedMs = currentTimeMs;
Bundle packagesMeasured = new Bundle();
// measure all APEXs first
if (DEBUG) {
Slog.d(TAG, "Measuring APEXs...");
}
List<IBinaryTransparencyService.ApexInfo> allApexInfo = collectAllApexInfo(
/* includeTestOnly */ false);
for (IBinaryTransparencyService.ApexInfo apexInfo : allApexInfo) {
packagesMeasured.putBoolean(apexInfo.packageName, true);
recordApexInfo(apexInfo);
}
if (DEBUG) {
Slog.d(TAG, "Measured " + packagesMeasured.size()
+ " packages after considering APEXs.");
}
// proceed with all preloaded apps
List<IBinaryTransparencyService.AppInfo> allUpdatedPreloadInfo =
collectAllUpdatedPreloadInfo(packagesMeasured);
for (IBinaryTransparencyService.AppInfo appInfo : allUpdatedPreloadInfo) {
packagesMeasured.putBoolean(appInfo.packageName, true);
writeAppInfoToLog(appInfo);
}
if (DEBUG) {
Slog.d(TAG, "Measured " + packagesMeasured.size()
+ " packages after considering preloads");
}
if (CompatChanges.isChangeEnabled(LOG_MBA_INFO)) {
// lastly measure all newly installed MBAs
List<IBinaryTransparencyService.AppInfo> allMbaInfo =
collectAllSilentInstalledMbaInfo(packagesMeasured);
for (IBinaryTransparencyService.AppInfo appInfo : allMbaInfo) {
packagesMeasured.putBoolean(appInfo.packageName, true);
writeAppInfoToLog(appInfo);
}
}
long timeSpentMeasuring = System.currentTimeMillis() - currentTimeMs;
digestAllPackagesLatency.logSample(timeSpentMeasuring);
if (DEBUG) {
Slog.d(TAG, "Measured " + packagesMeasured.size()
+ " packages altogether in " + timeSpentMeasuring + "ms");
}
}
@Override
public List<IBinaryTransparencyService.ApexInfo> collectAllApexInfo(
boolean includeTestOnly) {
var results = new ArrayList<IBinaryTransparencyService.ApexInfo>();
for (PackageInfo packageInfo : getCurrentInstalledApexs()) {
PackageState packageState = mPackageManagerInternal.getPackageStateInternal(
packageInfo.packageName);
if (packageState == null) {
Slog.w(TAG, "Package state is unavailable, ignoring the APEX "
+ packageInfo.packageName);
continue;
}
AndroidPackage pkg = packageState.getAndroidPackage();
if (pkg == null) {
Slog.w(TAG, "Skipping the missing APK in " + pkg.getPath());
continue;
}
Digest apexChecksum = measureApk(pkg.getPath());
if (apexChecksum == null) {
Slog.w(TAG, "Skipping the missing APEX in " + pkg.getPath());
continue;
}
var apexInfo = new IBinaryTransparencyService.ApexInfo();
apexInfo.packageName = packageState.getPackageName();
apexInfo.longVersion = packageState.getVersionCode();
apexInfo.digest = apexChecksum.value;
apexInfo.digestAlgorithm = apexChecksum.algorithm;
apexInfo.signerDigests =
computePackageSignerSha256Digests(packageState.getSigningInfo());
if (includeTestOnly) {
apexInfo.moduleName = apexPackageNameToModuleName(
packageState.getPackageName());
}
results.add(apexInfo);
}
return results;
}
@Override
public List<IBinaryTransparencyService.AppInfo> collectAllUpdatedPreloadInfo(
Bundle packagesToSkip) {
final var results = new ArrayList<IBinaryTransparencyService.AppInfo>();
PackageManager pm = mContext.getPackageManager();
mPackageManagerInternal.forEachPackageState((packageState) -> {
if (!packageState.isUpdatedSystemApp()) {
return;
}
if (packagesToSkip.containsKey(packageState.getPackageName())) {
return;
}
Slog.d(TAG, "Preload " + packageState.getPackageName() + " at "
+ packageState.getPath() + " has likely been updated.");
List<IBinaryTransparencyService.AppInfo> resultsForApp = collectAppInfo(
packageState, MBA_STATUS_UPDATED_PRELOAD);
results.addAll(resultsForApp);
});
return results;
}
@Override
public List<IBinaryTransparencyService.AppInfo> collectAllSilentInstalledMbaInfo(
Bundle packagesToSkip) {
var results = new ArrayList<IBinaryTransparencyService.AppInfo>();
for (PackageInfo packageInfo : getNewlyInstalledMbas()) {
if (packagesToSkip.containsKey(packageInfo.packageName)) {
continue;
}
PackageState packageState = mPackageManagerInternal.getPackageStateInternal(
packageInfo.packageName);
if (packageState == null) {
Slog.w(TAG, "Package state is unavailable, ignoring the package "
+ packageInfo.packageName);
continue;
}
List<IBinaryTransparencyService.AppInfo> resultsForApp = collectAppInfo(
packageState, MBA_STATUS_NEW_INSTALL);
results.addAll(resultsForApp);
}
return results;
}
private void recordApexInfo(IBinaryTransparencyService.ApexInfo apexInfo) {
// Must order by the proto's field number.
FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED,
apexInfo.packageName,
apexInfo.longVersion,
(apexInfo.digest != null) ? HexEncoding.encodeToString(apexInfo.digest, false)
: null,
apexInfo.digestAlgorithm,
apexInfo.signerDigests);
}
private void writeAppInfoToLog(IBinaryTransparencyService.AppInfo appInfo) {
// Must order by the proto's field number.
FrameworkStatsLog.write(FrameworkStatsLog.MOBILE_BUNDLED_APP_INFO_GATHERED,
appInfo.packageName,
appInfo.longVersion,
(appInfo.digest != null) ? HexEncoding.encodeToString(appInfo.digest, false)
: null,
appInfo.digestAlgorithm,
appInfo.signerDigests,
appInfo.mbaStatus,
appInfo.initiator,
appInfo.initiatorSignerDigests,
appInfo.installer,
appInfo.originator,
appInfo.splitName);
}
/**
* A wrapper around
* {@link ApkSignatureVerifier#verifySignaturesInternal(ParseInput, String, int, boolean)}.
* @param pathToApk The APK's installation path
* @return a {@code Map<Integer, byte[]>} with algorithm type as the key and content
* digest as the value.
* a {@code null} is returned upon encountering any error.
*/
private Map<Integer, byte[]> computeApkContentDigest(String pathToApk) {
final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
ParseResult<ApkSignatureVerifier.SigningDetailsWithDigests> parseResult =
ApkSignatureVerifier.verifySignaturesInternal(input,
pathToApk,
SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2, false);
if (parseResult.isError()) {
Slog.e(TAG, "Failed to compute content digest for "
+ pathToApk + " due to: "
+ parseResult.getErrorMessage());
return null;
}
return parseResult.getResult().contentDigests;
}
@Override
public void onShellCommand(@Nullable FileDescriptor in,
@Nullable FileDescriptor out,
@Nullable FileDescriptor err,
@NonNull String[] args,
@Nullable ShellCallback callback,
@NonNull ResultReceiver resultReceiver) throws RemoteException {
(new ShellCommand() {
private int printSignedImageInfo() {
final PrintWriter pw = getOutPrintWriter();
boolean listAllPartitions = false;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-a":
listAllPartitions = true;
break;
default:
pw.println("ERROR: Unknown option: " + opt);
return 1;
}
}
final String signedImageInfo = getSignedImageInfo();
pw.println("Image Info:");
pw.println(Build.FINGERPRINT);
pw.println(signedImageInfo);
pw.println("");
if (listAllPartitions) {
PackageManager pm = mContext.getPackageManager();
if (pm == null) {
pw.println("ERROR: Failed to obtain an instance of package manager.");
return -1;
}
pw.println("Other partitions:");
List<Build.Partition> buildPartitions = Build.getFingerprintedPartitions();
for (Build.Partition buildPartition : buildPartitions) {
pw.println("Name: " + buildPartition.getName());
pw.println("Fingerprint: " + buildPartition.getFingerprint());
pw.println("Build time (ms): " + buildPartition.getBuildTimeMillis());
}
}
return 0;
}
private void printPackageMeasurements(PackageInfo packageInfo,
boolean useSha256,
final PrintWriter pw) {
Map<Integer, byte[]> contentDigests = computeApkContentDigest(
packageInfo.applicationInfo.sourceDir);
if (contentDigests == null) {
pw.println("ERROR: Failed to compute package content digest for "
+ packageInfo.applicationInfo.sourceDir);
return;
}
if (useSha256) {
byte[] fileBuff = PackageUtils.createLargeFileBuffer();
String hexEncodedSha256Digest =
PackageUtils.computeSha256DigestForLargeFile(
packageInfo.applicationInfo.sourceDir, fileBuff);
pw.print(hexEncodedSha256Digest + ",");
}
for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
Integer algorithmId = entry.getKey();
byte[] contentDigest = entry.getValue();
pw.print(translateContentDigestAlgorithmIdToString(algorithmId));
pw.print(":");
pw.print(HexEncoding.encodeToString(contentDigest, false));
pw.print("\n");
}
}
private void printPackageInstallationInfo(PackageInfo packageInfo,
boolean useSha256,
final PrintWriter pw) {
pw.println("--- Package Installation Info ---");
pw.println("Current install location: "
+ packageInfo.applicationInfo.sourceDir);
if (packageInfo.applicationInfo.sourceDir.startsWith("/data/apex/")) {
String origPackageFilepath = getOriginalApexPreinstalledLocation(
packageInfo.packageName);
pw.println("|--> Pre-installed package install location: "
+ origPackageFilepath);
if (!origPackageFilepath.equals(APEX_PRELOAD_LOCATION_ERROR)) {
if (useSha256) {
String sha256Digest = PackageUtils.computeSha256DigestForLargeFile(
origPackageFilepath, PackageUtils.createLargeFileBuffer());
pw.println("|--> Pre-installed package SHA-256 digest: "
+ sha256Digest);
}
Map<Integer, byte[]> contentDigests = computeApkContentDigest(
origPackageFilepath);
if (contentDigests == null) {
pw.println("|--> ERROR: Failed to compute package content digest "
+ "for " + origPackageFilepath);
} else {
for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
Integer algorithmId = entry.getKey();
byte[] contentDigest = entry.getValue();
pw.println("|--> Pre-installed package content digest: "
+ HexEncoding.encodeToString(contentDigest, false));
pw.println("|--> Pre-installed package content digest "
+ "algorithm: "
+ translateContentDigestAlgorithmIdToString(
algorithmId));
}
}
}
}
pw.println("First install time (ms): " + packageInfo.firstInstallTime);
pw.println("Last update time (ms): " + packageInfo.lastUpdateTime);
// TODO(b/261493591): Determination of whether a package is preinstalled can be
// made more robust
boolean isPreloaded = (packageInfo.firstInstallTime
== packageInfo.lastUpdateTime);
pw.println("Is preloaded: " + isPreloaded);
InstallSourceInfo installSourceInfo = getInstallSourceInfo(
packageInfo.packageName);
if (installSourceInfo == null) {
pw.println("ERROR: Unable to obtain installSourceInfo of "
+ packageInfo.packageName);
} else {
pw.println("Installation initiated by: "
+ installSourceInfo.getInitiatingPackageName());
pw.println("Installation done by: "
+ installSourceInfo.getInstallingPackageName());
pw.println("Installation originating from: "
+ installSourceInfo.getOriginatingPackageName());
}
if (packageInfo.isApex) {
pw.println("Is an active APEX: " + packageInfo.isActiveApex);
}
}
private void printPackageSignerDetails(SigningInfo signerInfo,
final PrintWriter pw) {
if (signerInfo == null) {
pw.println("ERROR: Package's signingInfo is null.");
return;
}
pw.println("--- Package Signer Info ---");
pw.println("Has multiple signers: " + signerInfo.hasMultipleSigners());
pw.println("Signing key has been rotated: "
+ signerInfo.hasPastSigningCertificates());
Signature[] packageSigners = signerInfo.getApkContentsSigners();
for (Signature packageSigner : packageSigners) {
byte[] packageSignerDigestBytes =
PackageUtils.computeSha256DigestBytes(packageSigner.toByteArray());
String packageSignerDigestHextring =
HexEncoding.encodeToString(packageSignerDigestBytes, false);
pw.println("Signer cert's SHA256-digest: " + packageSignerDigestHextring);
try {
PublicKey publicKey = packageSigner.getPublicKey();
pw.println("Signing key algorithm: " + publicKey.getAlgorithm());
} catch (CertificateException e) {
Slog.e(TAG,
"Failed to obtain public key of signer for cert with hash: "
+ packageSignerDigestHextring, e);
}
}
if (!signerInfo.hasMultipleSigners()
&& signerInfo.hasPastSigningCertificates()) {
pw.println("== Signing Cert Lineage (Excluding The Most Recent) ==");
pw.println("(Certs are sorted in the order of rotation, beginning with the "
+ "original signing cert)");
Signature[] signingCertHistory = signerInfo.getSigningCertificateHistory();
for (int i = 0; i < (signingCertHistory.length - 1); i++) {
Signature signature = signingCertHistory[i];
byte[] signatureDigestBytes = PackageUtils.computeSha256DigestBytes(
signature.toByteArray());
String certHashHexString = HexEncoding.encodeToString(
signatureDigestBytes, false);
pw.println(" ++ Signer cert #" + (i + 1) + " ++");
pw.println(" Cert SHA256-digest: " + certHashHexString);
try {
PublicKey publicKey = signature.getPublicKey();
pw.println(" Signing key algorithm: " + publicKey.getAlgorithm());
} catch (CertificateException e) {
Slog.e(TAG, "Failed to obtain public key of signer for cert "
+ "with hash: " + certHashHexString, e);
}
}
}
}
private void printModuleDetails(ModuleInfo moduleInfo, final PrintWriter pw) {
pw.println("--- Module Details ---");
pw.println("Module name: " + moduleInfo.getName());
pw.println("Module visibility: "
+ (moduleInfo.isHidden() ? "hidden" : "visible"));
}
private void printAppDetails(PackageInfo packageInfo,
boolean printLibraries,
final PrintWriter pw) {
pw.println("--- App Details ---");
pw.println("Name: " + packageInfo.applicationInfo.name);
pw.println("Label: " + mContext.getPackageManager().getApplicationLabel(
packageInfo.applicationInfo));
pw.println("Description: " + packageInfo.applicationInfo.loadDescription(
mContext.getPackageManager()));
pw.println("Has code: " + packageInfo.applicationInfo.hasCode());
pw.println("Is enabled: " + packageInfo.applicationInfo.enabled);
pw.println("Is suspended: " + ((packageInfo.applicationInfo.flags
& ApplicationInfo.FLAG_SUSPENDED) != 0));
pw.println("Compile SDK version: " + packageInfo.compileSdkVersion);
pw.println("Target SDK version: "
+ packageInfo.applicationInfo.targetSdkVersion);
pw.println("Is privileged: "
+ packageInfo.applicationInfo.isPrivilegedApp());
pw.println("Is a stub: " + packageInfo.isStub);
pw.println("Is a core app: " + packageInfo.coreApp);
pw.println("SEInfo: " + packageInfo.applicationInfo.seInfo);
pw.println("Component factory: "
+ packageInfo.applicationInfo.appComponentFactory);
pw.println("Process name: " + packageInfo.applicationInfo.processName);
pw.println("Task affinity: " + packageInfo.applicationInfo.taskAffinity);
pw.println("UID: " + packageInfo.applicationInfo.uid);
pw.println("Shared UID: " + packageInfo.sharedUserId);
if (printLibraries) {
pw.println("== App's Shared Libraries ==");
List<SharedLibraryInfo> sharedLibraryInfos =
packageInfo.applicationInfo.getSharedLibraryInfos();
if (sharedLibraryInfos == null || sharedLibraryInfos.isEmpty()) {
pw.println("<none>");
}
for (int i = 0; i < sharedLibraryInfos.size(); i++) {
SharedLibraryInfo sharedLibraryInfo = sharedLibraryInfos.get(i);
pw.println(" ++ Library #" + (i + 1) + " ++");
pw.println(" Lib name: " + sharedLibraryInfo.getName());
long libVersion = sharedLibraryInfo.getLongVersion();
pw.print(" Lib version: ");
if (libVersion == SharedLibraryInfo.VERSION_UNDEFINED) {
pw.print("undefined");
} else {
pw.print(libVersion);
}
pw.print("\n");
pw.println(" Lib package name (if available): "
+ sharedLibraryInfo.getPackageName());
pw.println(" Lib path: " + sharedLibraryInfo.getPath());
pw.print(" Lib type: ");
switch (sharedLibraryInfo.getType()) {
case SharedLibraryInfo.TYPE_BUILTIN:
pw.print("built-in");
break;
case SharedLibraryInfo.TYPE_DYNAMIC:
pw.print("dynamic");
break;
case SharedLibraryInfo.TYPE_STATIC:
pw.print("static");
break;
case SharedLibraryInfo.TYPE_SDK_PACKAGE:
pw.print("SDK");
break;
case SharedLibraryInfo.VERSION_UNDEFINED:
default:
pw.print("undefined");
break;
}
pw.print("\n");
pw.println(" Is a native lib: " + sharedLibraryInfo.isNative());
}
}
}
private void printHeadersHelper(@NonNull String packageType,
boolean useSha256,
@NonNull final PrintWriter pw) {
pw.print(packageType + " Info [Format: package_name,package_version,");
if (useSha256) {
pw.print("package_sha256_digest,");
}
pw.print("content_digest_algorithm:content_digest]:\n");
}
private int printAllApexs() {
final PrintWriter pw = getOutPrintWriter();
boolean verbose = false;
boolean useSha256 = false;
boolean printHeaders = true;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-v":
case "--verbose":
verbose = true;
break;
case "-o":
case "--old":
useSha256 = true;
break;
case "--no-headers":
printHeaders = false;
break;
default:
pw.println("ERROR: Unknown option: " + opt);
return 1;
}
}
PackageManager pm = mContext.getPackageManager();
if (pm == null) {
pw.println("ERROR: Failed to obtain an instance of package manager.");
return -1;
}
if (!verbose && printHeaders) {
printHeadersHelper("APEX", useSha256, pw);
}
for (PackageInfo packageInfo : getCurrentInstalledApexs()) {
if (verbose && printHeaders) {
printHeadersHelper("APEX", useSha256, pw);
}
String packageName = packageInfo.packageName;
pw.print(packageName + ","
+ packageInfo.getLongVersionCode() + ",");
printPackageMeasurements(packageInfo, useSha256, pw);
if (verbose) {
ModuleInfo moduleInfo;
try {
moduleInfo = pm.getModuleInfo(packageInfo.packageName, 0);
pw.println("Is a module: true");
printModuleDetails(moduleInfo, pw);
} catch (PackageManager.NameNotFoundException e) {
pw.println("Is a module: false");
}
printPackageInstallationInfo(packageInfo, useSha256, pw);
printPackageSignerDetails(packageInfo.signingInfo, pw);
pw.println("");
}
}
return 0;
}
private int printAllModules() {
final PrintWriter pw = getOutPrintWriter();
boolean verbose = false;
boolean useSha256 = false;
boolean printHeaders = true;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-v":
case "--verbose":
verbose = true;
break;
case "-o":
case "--old":
useSha256 = true;
break;
case "--no-headers":
printHeaders = false;
break;
default:
pw.println("ERROR: Unknown option: " + opt);
return 1;
}
}
PackageManager pm = mContext.getPackageManager();
if (pm == null) {
pw.println("ERROR: Failed to obtain an instance of package manager.");
return -1;
}
if (!verbose && printHeaders) {
printHeadersHelper("Module", useSha256, pw);
}
for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
String packageName = module.getPackageName();
if (verbose && printHeaders) {
printHeadersHelper("Module", useSha256, pw);
}
try {
PackageInfo packageInfo = pm.getPackageInfo(packageName,
PackageManager.MATCH_APEX
| PackageManager.GET_SIGNING_CERTIFICATES);
pw.print(packageInfo.packageName + ",");
pw.print(packageInfo.getLongVersionCode() + ",");
printPackageMeasurements(packageInfo, useSha256, pw);
if (verbose) {
printModuleDetails(module, pw);
printPackageInstallationInfo(packageInfo, useSha256, pw);
printPackageSignerDetails(packageInfo.signingInfo, pw);
pw.println("");
}
} catch (PackageManager.NameNotFoundException e) {
pw.println(packageName
+ ",ERROR:Unable to find PackageInfo for this module.");
if (verbose) {
printModuleDetails(module, pw);
pw.println("");
}
continue;
}
}
return 0;
}
private int printAllMbas() {
final PrintWriter pw = getOutPrintWriter();
boolean verbose = false;
boolean printLibraries = false;
boolean useSha256 = false;
boolean printHeaders = true;
boolean preloadsOnly = false;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-v":
case "--verbose":
verbose = true;
break;
case "-l":
printLibraries = true;
break;
case "-o":
case "--old":
useSha256 = true;
break;
case "--no-headers":
printHeaders = false;
break;
case "--preloads-only":
preloadsOnly = true;
break;
default:
pw.println("ERROR: Unknown option: " + opt);
return 1;
}
}
if (!verbose && printHeaders) {
if (preloadsOnly) {
printHeadersHelper("Preload", useSha256, pw);
} else {
printHeadersHelper("MBA", useSha256, pw);
}
}
PackageManager pm = mContext.getPackageManager();
for (PackageInfo packageInfo : pm.getInstalledPackages(
PackageManager.PackageInfoFlags.of(PackageManager.MATCH_FACTORY_ONLY
| PackageManager.GET_SIGNING_CERTIFICATES))) {
if (packageInfo.signingInfo == null) {
PackageInfo origPackageInfo = packageInfo;
try {
pm.getPackageInfo(packageInfo.packageName,
PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL
| PackageManager.GET_SIGNING_CERTIFICATES));
} catch (PackageManager.NameNotFoundException e) {
Slog.e(TAG, "Failed to obtain an updated PackageInfo of "
+ origPackageInfo.packageName);
packageInfo = origPackageInfo;
}
}
if (verbose && printHeaders) {
printHeadersHelper("Preload", useSha256, pw);
}
pw.print(packageInfo.packageName + ",");
pw.print(packageInfo.getLongVersionCode() + ",");
printPackageMeasurements(packageInfo, useSha256, pw);
if (verbose) {
printAppDetails(packageInfo, printLibraries, pw);
printPackageInstallationInfo(packageInfo, useSha256, pw);
printPackageSignerDetails(packageInfo.signingInfo, pw);
pw.println("");
}
}
if (preloadsOnly) {
return 0;
}
for (PackageInfo packageInfo : getNewlyInstalledMbas()) {
if (verbose && printHeaders) {
printHeadersHelper("MBA", useSha256, pw);
}
pw.print(packageInfo.packageName + ",");
pw.print(packageInfo.getLongVersionCode() + ",");
printPackageMeasurements(packageInfo, useSha256, pw);
if (verbose) {
printAppDetails(packageInfo, printLibraries, pw);
printPackageInstallationInfo(packageInfo, useSha256, pw);
printPackageSignerDetails(packageInfo.signingInfo, pw);
pw.println("");
}
}
return 0;
}
@Override
public int onCommand(String cmd) {
if (cmd == null) {
return handleDefaultCommands(cmd);
}
final PrintWriter pw = getOutPrintWriter();
switch (cmd) {
case "get": {
final String infoType = getNextArg();
if (infoType == null) {
printHelpMenu();
return -1;
}
switch (infoType) {
case "image_info":
return printSignedImageInfo();
case "apex_info":
return printAllApexs();
case "module_info":
return printAllModules();
case "mba_info":
return printAllMbas();
default:
pw.println(String.format("ERROR: Unknown info type '%s'",
infoType));
return 1;
}
}
default:
return handleDefaultCommands(cmd);
}
}
private void printHelpMenu() {
final PrintWriter pw = getOutPrintWriter();
pw.println("Transparency manager (transparency) commands:");
pw.println(" help");
pw.println(" Print this help text.");
pw.println("");
pw.println(" get image_info [-a]");
pw.println(" Print information about loaded image (firmware). Options:");
pw.println(" -a: lists all other identifiable partitions.");
pw.println("");
pw.println(" get apex_info [-o] [-v] [--no-headers]");
pw.println(" Print information about installed APEXs on device.");
pw.println(" -o: also uses the old digest scheme (SHA256) to compute "
+ "APEX hashes. WARNING: This can be a very slow and CPU-intensive "
+ "computation.");
pw.println(" -v: lists more verbose information about each APEX.");
pw.println(" --no-headers: does not print the header if specified.");
pw.println("");
pw.println(" get module_info [-o] [-v] [--no-headers]");
pw.println(" Print information about installed modules on device.");
pw.println(" -o: also uses the old digest scheme (SHA256) to compute "
+ "module hashes. WARNING: This can be a very slow and "
+ "CPU-intensive computation.");
pw.println(" -v: lists more verbose information about each module.");
pw.println(" --no-headers: does not print the header if specified.");
pw.println("");
pw.println(" get mba_info [-o] [-v] [-l] [--no-headers] [--preloads-only]");
pw.println(" Print information about installed mobile bundle apps "
+ "(MBAs on device).");
pw.println(" -o: also uses the old digest scheme (SHA256) to compute "
+ "MBA hashes. WARNING: This can be a very slow and CPU-intensive "
+ "computation.");
pw.println(" -v: lists more verbose information about each app.");
pw.println(" -l: lists shared library info. (This option only works "
+ "when -v option is also specified)");
pw.println(" --no-headers: does not print the header if specified.");
pw.println(" --preloads-only: lists only preloaded apps. This options can "
+ "also be combined with others.");
pw.println("");
}
@Override
public void onHelp() {
printHelpMenu();
}
}).exec(this, in, out, err, args, callback, resultReceiver);
}
}
private final BinaryTransparencyServiceImpl mServiceImpl;
/**
* A wrapper of {@link FrameworkStatsLog} for easier testing
*/
@VisibleForTesting
public static class BiometricLogger {
private static final String TAG = "BiometricLogger";
private static final BiometricLogger sInstance = new BiometricLogger();
private BiometricLogger() {}
public static BiometricLogger getInstance() {
return sInstance;
}
/**
* A wrapper of {@link FrameworkStatsLog}
*
* @param sensorId The sensorId of the biometric to be logged
* @param modality The modality of the biometric
* @param sensorType The sensor type of the biometric
* @param sensorStrength The sensor strength of the biometric
* @param componentId The component Id of a component of the biometric
* @param hardwareVersion The hardware version of a component of the biometric
* @param firmwareVersion The firmware version of a component of the biometric
* @param serialNumber The serial number of a component of the biometric
* @param softwareVersion The software version of a component of the biometric
*/
public void logStats(int sensorId, int modality, int sensorType, int sensorStrength,
String componentId, String hardwareVersion, String firmwareVersion,
String serialNumber, String softwareVersion) {
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_PROPERTIES_COLLECTED,
sensorId, modality, sensorType, sensorStrength, componentId, hardwareVersion,
firmwareVersion, serialNumber, softwareVersion);
}
}
public BinaryTransparencyService(Context context) {
this(context, BiometricLogger.getInstance());
}
@VisibleForTesting
BinaryTransparencyService(Context context, BiometricLogger biometricLogger) {
super(context);
mContext = context;
mServiceImpl = new BinaryTransparencyServiceImpl();
mVbmetaDigest = VBMETA_DIGEST_UNINITIALIZED;
mMeasurementsLastRecordedMs = 0;
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mBiometricLogger = biometricLogger;
}
/**
* Called when the system service should publish a binder service using
* {@link #publishBinderService(String, IBinder).}
*/
@Override
public void onStart() {
try {
publishBinderService(Context.BINARY_TRANSPARENCY_SERVICE, mServiceImpl);
Slog.i(TAG, "Started BinaryTransparencyService");
} catch (Throwable t) {
Slog.e(TAG, "Failed to start BinaryTransparencyService.", t);
}
}
/**
* Called on each phase of the boot process. Phases before the service's start phase
* (as defined in the @Service annotation) are never received.
*
* @param phase The current boot phase.
*/
@Override
public void onBootPhase(int phase) {
// we are only interested in doing things at PHASE_BOOT_COMPLETED
if (phase == PHASE_BOOT_COMPLETED) {
Slog.i(TAG, "Boot completed. Getting VBMeta Digest.");
getVBMetaDigestInformation();
// Log to statsd
// TODO(b/264061957): For now, biometric system properties are always collected if users
// share usage & diagnostics information. In the future, collect biometric system
// properties only when transparency log verification of the target partitions fails
// (e.g. when the system/vendor partitions have been changed) once the binary
// transparency infrastructure is ready.
Slog.i(TAG, "Boot completed. Collecting biometric system properties.");
collectBiometricProperties();
// to avoid the risk of holding up boot time, computations to measure APEX, Module, and
// MBA digests are scheduled here, but only executed when the device is idle and plugged
// in.
Slog.i(TAG, "Scheduling measurements to be taken.");
UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext,
BinaryTransparencyService.this);
registerAllPackageUpdateObservers();
}
}
/**
* JobService to measure all covered binaries and record results to statsd.
*/
public static class UpdateMeasurementsJobService extends JobService {
private static long sTimeLastRanMs = 0;
private static final int DO_BINARY_MEASUREMENTS_JOB_ID = 1740526926;
@Override
public boolean onStartJob(JobParameters params) {
Slog.d(TAG, "Job to update binary measurements started.");
if (params.getJobId() != DO_BINARY_MEASUREMENTS_JOB_ID) {
return false;
}
// we'll perform binary measurements via threads to be mindful of low-end devices
// where this operation might take longer than expected, and so that we don't block
// system_server's main thread.
Executors.defaultThreadFactory().newThread(() -> {
IBinder b = ServiceManager.getService(Context.BINARY_TRANSPARENCY_SERVICE);
IBinaryTransparencyService iBtsService =
IBinaryTransparencyService.Stub.asInterface(b);
try {
iBtsService.recordMeasurementsForAllPackages();
} catch (RemoteException e) {
Slog.e(TAG, "Taking binary measurements was interrupted.", e);
return;
}
sTimeLastRanMs = System.currentTimeMillis();
jobFinished(params, false);
}).start();
return true;
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
@SuppressLint("DefaultLocale")
static void scheduleBinaryMeasurements(Context context, BinaryTransparencyService service) {
Slog.i(TAG, "Scheduling binary content-digest computation job");
final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
if (jobScheduler == null) {
Slog.e(TAG, "Failed to obtain an instance of JobScheduler.");
return;
}
if (jobScheduler.getPendingJob(DO_BINARY_MEASUREMENTS_JOB_ID) != null) {
Slog.d(TAG, "A measurement job has already been scheduled.");
return;
}
long minWaitingPeriodMs = 0;
if (sTimeLastRanMs != 0) {
minWaitingPeriodMs = RECORD_MEASUREMENTS_COOLDOWN_MS
- (System.currentTimeMillis() - sTimeLastRanMs);
// bound the range of minWaitingPeriodMs in the case where > 24h has elapsed
minWaitingPeriodMs = Math.max(0,
Math.min(minWaitingPeriodMs, RECORD_MEASUREMENTS_COOLDOWN_MS));
Slog.d(TAG, "Scheduling the next measurement to be done at least "
+ minWaitingPeriodMs + "ms from now.");
}
final JobInfo jobInfo = new JobInfo.Builder(DO_BINARY_MEASUREMENTS_JOB_ID,
new ComponentName(context, UpdateMeasurementsJobService.class))
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
.setMinimumLatency(minWaitingPeriodMs)
.build();
if (jobScheduler.schedule(jobInfo) != JobScheduler.RESULT_SUCCESS) {
Slog.e(TAG, "Failed to schedule job to measure binaries.");
return;
}
Slog.d(TAG, TextUtils.formatSimple(
"Job %d to measure binaries was scheduled successfully.",
DO_BINARY_MEASUREMENTS_JOB_ID));
}
}
/**
* Convert a {@link FingerprintSensorProperties} sensor type to the corresponding enum to be
* logged.
*
* @param sensorType See {@link FingerprintSensorProperties}
* @return The enum to be logged
*/
private int toFingerprintSensorType(@FingerprintSensorProperties.SensorType int sensorType) {
switch (sensorType) {
case FingerprintSensorProperties.TYPE_REAR:
return FrameworkStatsLog
.BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FP_REAR;
case FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC:
return FrameworkStatsLog
.BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FP_UDFPS_ULTRASONIC;
case FingerprintSensorProperties.TYPE_UDFPS_OPTICAL:
return FrameworkStatsLog
.BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FP_UDFPS_OPTICAL;
case FingerprintSensorProperties.TYPE_POWER_BUTTON:
return FrameworkStatsLog
.BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FP_POWER_BUTTON;
case FingerprintSensorProperties.TYPE_HOME_BUTTON:
return FrameworkStatsLog
.BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FP_HOME_BUTTON;
default:
return FrameworkStatsLog
.BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_UNKNOWN;
}
}
/**
* Convert a {@link FaceSensorProperties} sensor type to the corresponding enum to be logged.
*
* @param sensorType See {@link FaceSensorProperties}
* @return The enum to be logged
*/
private int toFaceSensorType(@FaceSensorProperties.SensorType int sensorType) {
switch (sensorType) {
case FaceSensorProperties.TYPE_RGB:
return FrameworkStatsLog
.BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FACE_RGB;
case FaceSensorProperties.TYPE_IR:
return FrameworkStatsLog
.BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FACE_IR;
default:
return FrameworkStatsLog
.BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_UNKNOWN;
}
}
/**
* Convert a {@link SensorProperties} sensor strength to the corresponding enum to be logged.
*
* @param sensorStrength See {@link SensorProperties}
* @return The enum to be logged
*/
private int toSensorStrength(@SensorProperties.Strength int sensorStrength) {
switch (sensorStrength) {
case SensorProperties.STRENGTH_CONVENIENCE:
return FrameworkStatsLog
.BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_STRENGTH__STRENGTH_CONVENIENCE;
case SensorProperties.STRENGTH_WEAK:
return FrameworkStatsLog
.BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_STRENGTH__STRENGTH_WEAK;
case SensorProperties.STRENGTH_STRONG:
return FrameworkStatsLog
.BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_STRENGTH__STRENGTH_STRONG;
default:
return FrameworkStatsLog
.BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_STRENGTH__STRENGTH_UNKNOWN;
}
}
/**
* A helper function to log detailed biometric sensor properties to statsd.
*
* @param prop The biometric sensor properties to be logged
* @param modality The modality of the biometric (e.g. fingerprint, face) to be logged
* @param sensorType The specific type of the biometric to be logged
*/
private void logBiometricProperties(SensorProperties prop, int modality, int sensorType) {
final int sensorId = prop.getSensorId();
final int sensorStrength = toSensorStrength(prop.getSensorStrength());
// Log data for each component
// Note: none of the component info is a device identifier since every device of a given
// model and build share the same biometric system info (see b/216195167)
for (ComponentInfo componentInfo : prop.getComponentInfo()) {
mBiometricLogger.logStats(
sensorId,
modality,
sensorType,
sensorStrength,
componentInfo.getComponentId().trim(),
componentInfo.getHardwareVersion().trim(),
componentInfo.getFirmwareVersion().trim(),
componentInfo.getSerialNumber().trim(),
componentInfo.getSoftwareVersion().trim());
}
}
@VisibleForTesting
void collectBiometricProperties() {
// Check the flag to determine whether biometric property verification is enabled. It's
// disabled by default.
if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BIOMETRICS,
KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION, false)) {
if (DEBUG) {
Slog.d(TAG, "Do not collect/verify biometric properties. Feature disabled by "
+ "DeviceConfig");
}
return;
}
PackageManager pm = mContext.getPackageManager();
FingerprintManager fpManager = null;
FaceManager faceManager = null;
if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
fpManager = mContext.getSystemService(FingerprintManager.class);
}
if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_FACE)) {
faceManager = mContext.getSystemService(FaceManager.class);
}
if (fpManager != null) {
final int fpModality = FrameworkStatsLog
.BIOMETRIC_PROPERTIES_COLLECTED__MODALITY__MODALITY_FINGERPRINT;
fpManager.addAuthenticatorsRegisteredCallback(
new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@Override
public void onAllAuthenticatorsRegistered(
List<FingerprintSensorPropertiesInternal> sensors) {
if (DEBUG) {
Slog.d(TAG, "Retrieve fingerprint sensor properties. "
+ "sensors.size()=" + sensors.size());
}
// Log data for each fingerprint sensor
for (FingerprintSensorPropertiesInternal propInternal : sensors) {
final FingerprintSensorProperties prop =
FingerprintSensorProperties.from(propInternal);
logBiometricProperties(prop,
fpModality,
toFingerprintSensorType(prop.getSensorType()));
}
}
});
}
if (faceManager != null) {
final int faceModality = FrameworkStatsLog
.BIOMETRIC_PROPERTIES_COLLECTED__MODALITY__MODALITY_FACE;
faceManager.addAuthenticatorsRegisteredCallback(
new IFaceAuthenticatorsRegisteredCallback.Stub() {
@Override
public void onAllAuthenticatorsRegistered(
List<FaceSensorPropertiesInternal> sensors) {
if (DEBUG) {
Slog.d(TAG, "Retrieve face sensor properties. sensors.size()="
+ sensors.size());
}
// Log data for each face sensor
for (FaceSensorPropertiesInternal propInternal : sensors) {
final FaceSensorProperties prop =
FaceSensorProperties.from(propInternal);
logBiometricProperties(prop,
faceModality,
toFaceSensorType(prop.getSensorType()));
}
}
});
}
}
private void getVBMetaDigestInformation() {
mVbmetaDigest = SystemProperties.get(SYSPROP_NAME_VBETA_DIGEST, VBMETA_DIGEST_UNAVAILABLE);
Slog.d(TAG, String.format("VBMeta Digest: %s", mVbmetaDigest));
FrameworkStatsLog.write(FrameworkStatsLog.VBMETA_DIGEST_REPORTED, mVbmetaDigest);
}
/**
* Listen for APK updates.
*
* There are two ways available to us to do this:
* 1. Register an observer using
* {@link PackageManagerInternal#getPackageList(PackageManagerInternal.PackageListObserver)}.
* 2. Register broadcast receivers, listening to either {@code ACTION_PACKAGE_ADDED} or
* {@code ACTION_PACKAGE_REPLACED}.
*
* After experimentation, we found that Option #1 does not catch updates to non-staged APEXs.
* Thus, we are implementing Option #2 here. More specifically, listening to
* {@link Intent#ACTION_PACKAGE_ADDED} allows us to capture all events we care about.
*
* We did not use {@link Intent#ACTION_PACKAGE_REPLACED} because it unfortunately does not
* detect updates to non-staged APEXs. Thus, we rely on {@link Intent#EXTRA_REPLACING} to
* filter out new installation from updates instead.
*/
private void registerApkAndNonStagedApexUpdateListener() {
Slog.d(TAG, "Registering APK & Non-Staged APEX updates...");
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addDataScheme("package"); // this is somehow necessary
mContext.registerReceiver(new PackageUpdatedReceiver(), filter);
}
/**
* Listen for staged-APEX updates.
*
* This method basically covers cases that are not caught by
* {@link #registerApkAndNonStagedApexUpdateListener()}, namely updates to APEXs that are staged
* for the subsequent reboot.
*/
private void registerStagedApexUpdateObserver() {
Slog.d(TAG, "Registering APEX updates...");
IPackageManagerNative iPackageManagerNative = IPackageManagerNative.Stub.asInterface(
ServiceManager.getService("package_native"));
if (iPackageManagerNative == null) {
Slog.e(TAG, "IPackageManagerNative is null");
return;
}
try {
iPackageManagerNative.registerStagedApexObserver(new IStagedApexObserver.Stub() {
@Override
public void onApexStaged(ApexStagedEvent event) throws RemoteException {
Slog.d(TAG, "A new APEX has been staged for update. There are currently "
+ event.stagedApexModuleNames.length + " APEX(s) staged for update. "
+ "Scheduling measurement...");
UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext,
BinaryTransparencyService.this);
}
});
} catch (RemoteException e) {
Slog.e(TAG, "Failed to register a StagedApexObserver.");
}
}
private boolean isPackagePreloaded(String packageName) {
PackageManager pm = mContext.getPackageManager();
try {
pm.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(
PackageManager.MATCH_FACTORY_ONLY));
} catch (PackageManager.NameNotFoundException e) {
return false;
}
return true;
}
private boolean isPackageAnApex(String packageName) {
PackageManager pm = mContext.getPackageManager();
try {
PackageInfo packageInfo = pm.getPackageInfo(packageName,
PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
return packageInfo.isApex;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
private class PackageUpdatedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (!intent.getAction().equals(Intent.ACTION_PACKAGE_ADDED)) {
return;
}
Uri data = intent.getData();
if (data == null) {
Slog.e(TAG, "Shouldn't happen: intent data is null!");
return;
}
if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
Slog.d(TAG, "Not an update. Skipping...");
return;
}
String packageName = data.getSchemeSpecificPart();
// now we've got to check what package is this
if (isPackagePreloaded(packageName) || isPackageAnApex(packageName)) {
Slog.d(TAG, packageName + " was updated. Scheduling measurement...");
UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext,
BinaryTransparencyService.this);
}
}
}
/**
* Register observers for APK and APEX updates. The current implementation breaks this process
* into 2 cases/methods because PackageManager does not offer a unified interface to register
* for all package updates in a universal and comprehensive manner.
* Thus, the observers will be invoked when either
* i) APK or non-staged APEX update; or
* ii) APEX staging happens.
* This will then be used as signals to schedule measurement for the relevant binaries.
*/
private void registerAllPackageUpdateObservers() {
registerApkAndNonStagedApexUpdateListener();
registerStagedApexUpdateObserver();
}
private String translateContentDigestAlgorithmIdToString(int algorithmId) {
switch (algorithmId) {
case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256:
return "CHUNKED_SHA256";
case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512:
return "CHUNKED_SHA512";
case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
return "VERITY_CHUNKED_SHA256";
case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256:
return "SHA256";
default:
return "UNKNOWN_ALGO_ID(" + algorithmId + ")";
}
}
@NonNull
private List<PackageInfo> getCurrentInstalledApexs() {
List<PackageInfo> results = new ArrayList<>();
PackageManager pm = mContext.getPackageManager();
if (pm == null) {
Slog.e(TAG, "Error obtaining an instance of PackageManager.");
return results;
}
List<PackageInfo> allPackages = pm.getInstalledPackages(
PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX
| PackageManager.GET_SIGNING_CERTIFICATES));
if (allPackages == null) {
Slog.e(TAG, "Error obtaining installed packages (including APEX)");
return results;
}
results = allPackages.stream().filter(p -> p.isApex).collect(Collectors.toList());
return results;
}
@Nullable
private InstallSourceInfo getInstallSourceInfo(String packageName) {
PackageManager pm = mContext.getPackageManager();
if (pm == null) {
Slog.e(TAG, "Error obtaining an instance of PackageManager.");
return null;
}
try {
return pm.getInstallSourceInfo(packageName);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return null;
}
}
@NonNull
private String getOriginalApexPreinstalledLocation(String packageName) {
try {
final String moduleName = apexPackageNameToModuleName(packageName);
IApexService apexService = IApexService.Stub.asInterface(
Binder.allowBlocking(ServiceManager.waitForService("apexservice")));
for (ApexInfo info : apexService.getAllPackages()) {
if (moduleName.equals(info.moduleName)) {
return info.preinstalledModulePath;
}
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to get package list from apexservice", e);
}
return APEX_PRELOAD_LOCATION_ERROR;
}
private String apexPackageNameToModuleName(String packageName) {
// It appears that only apexd knows the preinstalled location, and it uses module name as
// the identifier instead of package name. Given the input is a package name, we need to
// covert to module name.
return ApexManager.getInstance().getApexModuleNameForPackageName(packageName);
}
/**
* Wrapper method to call into IBICS to get a list of all newly installed MBAs.
*
* We expect IBICS to maintain an accurate list of installed MBAs, and we merely make use of
* the results within this service. This means we do not further check whether the
* apps in the returned slice is still installed or not, esp. considering that preloaded apps
* could be updated, or post-setup installed apps *might* be deleted in real time.
*
* Note that we do *not* cache the results from IBICS because of the more dynamic nature of
* MBAs v.s. other binaries that we measure.
*
* @return a list of preloaded apps + dynamically installed apps that fit the definition of MBA.
*/
@NonNull
private List<PackageInfo> getNewlyInstalledMbas() {
List<PackageInfo> result = new ArrayList<>();
IBackgroundInstallControlService iBics = IBackgroundInstallControlService.Stub.asInterface(
ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE));
if (iBics == null) {
Slog.e(TAG,
"Failed to obtain an IBinder instance of IBackgroundInstallControlService");
return result;
}
ParceledListSlice<PackageInfo> slice;
try {
slice = iBics.getBackgroundInstalledPackages(
PackageManager.MATCH_ALL | PackageManager.GET_SIGNING_CERTIFICATES,
UserHandle.USER_SYSTEM);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to get a list of MBAs.", e);
return result;
}
return slice.getList();
}
private static class Digest {
public int algorithm;
public byte[] value;
Digest(int algorithm, byte[] value) {
this.algorithm = algorithm;
this.value = value;
}
}
}