Loading core/java/android/transparency/BinaryTransparencyManager.java +17 −18 Original line number Diff line number Diff line Loading @@ -66,24 +66,6 @@ public class BinaryTransparencyManager { } } /** * Gets binary measurements of all installed APEXs, each packed in a Bundle. * @return A List of {@link android.os.Bundle}s with the following keys: * {@link com.android.server.BinaryTransparencyService#BUNDLE_PACKAGE_INFO} * {@link com.android.server.BinaryTransparencyService#BUNDLE_CONTENT_DIGEST_ALGORITHM} * {@link com.android.server.BinaryTransparencyService#BUNDLE_CONTENT_DIGEST} */ // TODO(b/259422958): Fix static constants referenced here - should be defined here @NonNull public List getApexInfo() { try { Slog.d(TAG, "Calling backend's getApexInfo()"); return mService.getApexInfo(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Collects the APEX information on the device. * Loading Loading @@ -116,4 +98,21 @@ public class BinaryTransparencyManager { throw e.rethrowFromSystemServer(); } } /** * Collects the silent installed MBA information on the device. * * @return A List containing the MBA info of silent installed. * @hide */ @NonNull public List<IBinaryTransparencyService.AppInfo> collectAllSilentInstalledMbaInfo( Bundle packagesToSkip) { try { Slog.d(TAG, "Calling backend's collectAllSilentInstalledMbaInfo()"); return mService.collectAllSilentInstalledMbaInfo(packagesToSkip); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } core/java/com/android/internal/os/IBinaryTransparencyService.aidl +1 −2 Original line number Diff line number Diff line Loading @@ -28,8 +28,6 @@ import android.os.Bundle; interface IBinaryTransparencyService { String getSignedImageInfo(); List getApexInfo(); void recordMeasurementsForAllPackages(); parcelable ApexInfo { Loading Loading @@ -60,4 +58,5 @@ interface IBinaryTransparencyService { /** Test only */ List<ApexInfo> collectAllApexInfo(boolean includeTestOnly); List<AppInfo> collectAllUpdatedPreloadInfo(in Bundle packagesToSkip); List<AppInfo> collectAllSilentInstalledMbaInfo(in Bundle packagesToSkip); } No newline at end of file services/core/java/com/android/server/BinaryTransparencyService.java +96 −123 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApexStagedEvent; import android.content.pm.ApplicationInfo; import android.content.pm.Checksum; import android.content.pm.IBackgroundInstallControlService; import android.content.pm.IPackageManagerNative; import android.content.pm.IStagedApexObserver; Loading Loading @@ -84,6 +85,7 @@ 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; Loading Loading @@ -120,15 +122,6 @@ public class BinaryTransparencyService extends SystemService { static final long RECORD_MEASUREMENTS_COOLDOWN_MS = 24 * 60 * 60 * 1000; @VisibleForTesting static final String BUNDLE_PACKAGE_NAME = "package-name"; @VisibleForTesting static final String BUNDLE_PACKAGE_IS_APEX = "package-is-apex"; @VisibleForTesting static final String BUNDLE_CONTENT_DIGEST_ALGORITHM = "content-digest-algo"; @VisibleForTesting static final String BUNDLE_CONTENT_DIGEST = "content-digest"; static final String APEX_PRELOAD_LOCATION_ERROR = "could-not-be-determined"; // used for indicating any type of error during MBA measurement Loading Loading @@ -170,29 +163,6 @@ public class BinaryTransparencyService extends SystemService { return mVbmetaDigest; } @Override public List getApexInfo() { List<Bundle> results = new ArrayList<>(); for (PackageInfo packageInfo : getCurrentInstalledApexs()) { PackageState packageState = mPackageManagerInternal.getPackageStateInternal( packageInfo.packageName); if (packageState == null) { Slog.w(TAG, "Package state is unavailable, ignoring the package " + packageInfo.packageName); continue; } Bundle apexMeasurement = measurePackage(packageState); if (apexMeasurement == null) { Slog.w(TAG, "Skipping the missing APEX in " + packageState.getPath()); continue; } results.add(apexMeasurement); } return results; } /** * A helper function to compute the SHA256 digest of APK package signer. * @param signingInfo The signingInfo of a package, usually {@link PackageInfo#signingInfo}. Loading @@ -217,58 +187,102 @@ public class BinaryTransparencyService extends SystemService { return resultList.toArray(new String[1]); } /** * Perform basic measurement (i.e. content digest) on a given package. /* * Perform basic measurement (i.e. content digest) on a given app, including the split APKs. * * @param packageState The package to be measured. * @return a {@link android.os.Bundle} that packs the measurement result with the following * keys: {@link #BUNDLE_PACKAGE_NAME}, * {@link #BUNDLE_PACKAGE_IS_APEX} * {@link #BUNDLE_CONTENT_DIGEST_ALGORITHM} * {@link #BUNDLE_CONTENT_DIGEST} * @param mbaStatus Assign this value of MBA status to the returned elements. * @return a @{@code List<IBinaryTransparencyService.AppInfo>} */ private @Nullable Bundle measurePackage(PackageState packageState) { Bundle result = new Bundle(); 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(); if (pkg == null) { Slog.w(TAG, "Skipping the missing APK in " + packageState.getPath()); return null; 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. Checksum checksum = measureApk(split.getPath()); appInfo.digest = checksum.getValue(); appInfo.digestAlgorithm = checksum.getType(); 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; } Map<Integer, byte[]> contentDigests = computeApkContentDigest(pkg.getBaseApkPath()); result.putString(BUNDLE_PACKAGE_NAME, pkg.getPackageName()); /** * 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 android.content.pm.Checksum} with preferred digest algorithm type and * the checksum. */ private @Nullable Checksum 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 " + pkg.getBaseApkPath()); result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, 0); result.putByteArray(BUNDLE_CONTENT_DIGEST, null); return result; Slog.d(TAG, "Failed to compute content digest for " + apkPath); return new Checksum(0, new byte[] { -1 }); } // 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)) { Integer algorithmId = ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256; result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, algorithmId); result.putByteArray(BUNDLE_CONTENT_DIGEST, contentDigests.get(algorithmId)); return new Checksum( Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256, contentDigests.get(ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256)); } else if (contentDigests.containsKey( ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512)) { Integer algorithmId = ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512; result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, algorithmId); result.putByteArray(BUNDLE_CONTENT_DIGEST, contentDigests.get(algorithmId)); return new Checksum( Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512, contentDigests.get(ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512)); } else { // TODO(b/259423111): considering putting the raw values for the algorithm & digest // into the bundle to track potential other digest algorithms that may be in use result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, 0); result.putByteArray(BUNDLE_CONTENT_DIGEST, null); return new Checksum(0, new byte[] { -1 }); } result.putBoolean(BUNDLE_PACKAGE_IS_APEX, packageState.isApex()); return result; } Loading Loading @@ -330,7 +344,7 @@ public class BinaryTransparencyService extends SystemService { if (CompatChanges.isChangeEnabled(LOG_MBA_INFO)) { // lastly measure all newly installed MBAs List<IBinaryTransparencyService.AppInfo> allMbaInfo = collectAllMbaInfo(packagesMeasured); collectAllSilentInstalledMbaInfo(packagesMeasured); for (IBinaryTransparencyService.AppInfo appInfo : allUpdatedPreloadInfo) { packagesMeasured.putBoolean(appInfo.packageName, true); writeAppInfoToLog(appInfo); Loading @@ -356,18 +370,22 @@ public class BinaryTransparencyService extends SystemService { continue; } Bundle apexMeasurement = measurePackage(packageState); if (apexMeasurement == null) { Slog.w(TAG, "Skipping the missing APEX in " + packageState.getPath()); AndroidPackage pkg = packageState.getAndroidPackage(); if (pkg == null) { Slog.w(TAG, "Skipping the missing APK in " + pkg.getPath()); continue; } Checksum 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 = apexMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST); apexInfo.digestAlgorithm = apexMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM); apexInfo.digest = apexChecksum.getValue(); apexInfo.digestAlgorithm = apexChecksum.getType(); apexInfo.signerDigests = computePackageSignerSha256Digests(packageState.getSigningInfo()); Loading Loading @@ -398,28 +416,16 @@ public class BinaryTransparencyService extends SystemService { Slog.d(TAG, "Preload " + packageState.getPackageName() + " at " + packageState.getPath() + " has likely been updated."); Bundle packageMeasurement = measurePackage(packageState); if (packageMeasurement == null) { Slog.w(TAG, "Skipping the missing APK in " + packageState.getPath()); return; } var appInfo = new IBinaryTransparencyService.AppInfo(); appInfo.packageName = packageState.getPackageName(); appInfo.longVersion = packageState.getVersionCode(); appInfo.digest = packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST); appInfo.digestAlgorithm = packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM); appInfo.signerDigests = computePackageSignerSha256Digests(packageState.getSigningInfo()); appInfo.mbaStatus = MBA_STATUS_UPDATED_PRELOAD; results.add(appInfo); List<IBinaryTransparencyService.AppInfo> resultsForApp = collectAppInfo( packageState, MBA_STATUS_UPDATED_PRELOAD); results.addAll(resultsForApp); }); return results; } public List<IBinaryTransparencyService.AppInfo> collectAllMbaInfo(Bundle packagesToSkip) { @Override public List<IBinaryTransparencyService.AppInfo> collectAllSilentInstalledMbaInfo( Bundle packagesToSkip) { var results = new ArrayList<IBinaryTransparencyService.AppInfo>(); for (PackageInfo packageInfo : getNewlyInstalledMbas()) { if (packagesToSkip.containsKey(packageInfo.packageName)) { Loading @@ -433,42 +439,9 @@ public class BinaryTransparencyService extends SystemService { continue; } Bundle packageMeasurement = measurePackage(packageState); if (packageMeasurement == null) { Slog.w(TAG, "Skipping the missing APK in " + packageState.getPath()); continue; } if (DEBUG) { Slog.d(TAG, "Extracting InstallSourceInfo for " + packageState.getPackageName()); } var appInfo = new IBinaryTransparencyService.AppInfo(); appInfo.packageName = packageState.getPackageName(); appInfo.longVersion = packageState.getVersionCode(); appInfo.digest = packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST); appInfo.digestAlgorithm = packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM); appInfo.signerDigests = computePackageSignerSha256Digests(packageState.getSigningInfo()); appInfo.mbaStatus = MBA_STATUS_NEW_INSTALL; // Install source isn't currently available in PackageState (there's a TODO). // Extract manually with another call. InstallSourceInfo installSourceInfo = getInstallSourceInfo( packageState.getPackageName()); if (installSourceInfo != null) { appInfo.initiator = installSourceInfo.getInitiatingPackageName(); SigningInfo initiatorSignerInfo = installSourceInfo.getInitiatingPackageSigningInfo(); if (initiatorSignerInfo != null) { appInfo.initiatorSignerDigests = computePackageSignerSha256Digests(initiatorSignerInfo); } appInfo.installer = installSourceInfo.getInstallingPackageName(); appInfo.originator = installSourceInfo.getOriginatingPackageName(); } results.add(appInfo); List<IBinaryTransparencyService.AppInfo> resultsForApp = collectAppInfo( packageState, MBA_STATUS_NEW_INSTALL); results.addAll(resultsForApp); } return results; } Loading services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java +0 −31 Original line number Diff line number Diff line Loading @@ -40,7 +40,6 @@ import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; import android.os.Bundle; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.SystemProperties; Loading Loading @@ -165,36 +164,6 @@ public class BinaryTransparencyServiceTest { BinaryTransparencyService.VBMETA_DIGEST_UNAVAILABLE), result); } @Test public void getApexInfo_postInitialize_returnsValidEntries() throws RemoteException { prepApexInfo(); List result = mTestInterface.getApexInfo(); Assert.assertNotNull("Apex info map should not be null", result); // TODO(265244016): When PackageManagerInternal is a mock, it's harder to keep the // `measurePackage` working in unit test. Disable it for now. We may need more refactoring // or cover this in integration tests. // Assert.assertFalse("Apex info map should not be empty", result.isEmpty()); } @Test public void getApexInfo_postInitialize_returnsActualApexs() throws RemoteException, PackageManager.NameNotFoundException { prepApexInfo(); List resultList = mTestInterface.getApexInfo(); PackageManager pm = mContext.getPackageManager(); Assert.assertNotNull(pm); List<Bundle> castedResult = (List<Bundle>) resultList; for (Bundle resultBundle : castedResult) { String packageName = resultBundle.getString( BinaryTransparencyService.BUNDLE_PACKAGE_NAME); Assert.assertNotNull("Package name for APEX should not be null", packageName); Assert.assertTrue(packageName + "is not an APEX!", resultBundle.getBoolean( BinaryTransparencyService.BUNDLE_PACKAGE_IS_APEX)); } } @Test public void testCollectBiometricProperties_disablesFeature() { DeviceConfig.setProperty(DeviceConfig.NAMESPACE_BIOMETRICS, Loading tests/BinaryTransparencyHostTest/src/android/transparency/test/BaseInstallMultiple.java 0 → 100644 +140 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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 android.transparency.test; import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; import com.android.tradefed.build.IBuildInfo; import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.device.ITestDevice; import junit.framework.TestCase; import java.io.File; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Base class for invoking the install-multiple command via ADB. Subclass this for less typing: * * <code> private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> { public * InstallMultiple() { super(getDevice(), null); } } </code> */ /*package*/ class BaseInstallMultiple<T extends BaseInstallMultiple<?>> { private final ITestDevice mDevice; private final IBuildInfo mBuild; private final List<String> mArgs = new ArrayList<>(); private final Map<File, String> mFileToRemoteMap = new HashMap<>(); /*package*/ BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo) { mDevice = device; mBuild = buildInfo; addArg("-g"); } T addArg(String arg) { mArgs.add(arg); return (T) this; } T addFile(String filename) throws FileNotFoundException { return addFile(filename, filename); } T addFile(String filename, String remoteName) throws FileNotFoundException { CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild); mFileToRemoteMap.put(buildHelper.getTestFile(filename), remoteName); return (T) this; } T inheritFrom(String packageName) { addArg("-r"); addArg("-p " + packageName); return (T) this; } void run() throws DeviceNotAvailableException { run(true); } void runExpectingFailure() throws DeviceNotAvailableException { run(false); } private void run(boolean expectingSuccess) throws DeviceNotAvailableException { final ITestDevice device = mDevice; // Create an install session final StringBuilder cmd = new StringBuilder(); cmd.append("pm install-create"); for (String arg : mArgs) { cmd.append(' ').append(arg); } String result = device.executeShellCommand(cmd.toString()); TestCase.assertTrue(result, result.startsWith("Success")); final int start = result.lastIndexOf("["); final int end = result.lastIndexOf("]"); int sessionId = -1; try { if (start != -1 && end != -1 && start < end) { sessionId = Integer.parseInt(result.substring(start + 1, end)); } } catch (NumberFormatException e) { throw new IllegalStateException("Failed to parse install session: " + result); } if (sessionId == -1) { throw new IllegalStateException("Failed to create install session: " + result); } // Push our files into session. Ideally we'd use stdin streaming, // but ddmlib doesn't support it yet. for (final Map.Entry<File, String> entry : mFileToRemoteMap.entrySet()) { final File file = entry.getKey(); final String remoteName = entry.getValue(); final String remotePath = "/data/local/tmp/" + file.getName(); if (!device.pushFile(file, remotePath)) { throw new IllegalStateException("Failed to push " + file); } cmd.setLength(0); cmd.append("pm install-write"); cmd.append(' ').append(sessionId); cmd.append(' ').append(remoteName); cmd.append(' ').append(remotePath); result = device.executeShellCommand(cmd.toString()); TestCase.assertTrue(result, result.startsWith("Success")); } // Everything staged; let's pull trigger cmd.setLength(0); cmd.append("pm install-commit"); cmd.append(' ').append(sessionId); result = device.executeShellCommand(cmd.toString()); if (expectingSuccess) { TestCase.assertTrue(result, result.contains("Success")); } else { TestCase.assertFalse(result, result.contains("Success")); } } } Loading
core/java/android/transparency/BinaryTransparencyManager.java +17 −18 Original line number Diff line number Diff line Loading @@ -66,24 +66,6 @@ public class BinaryTransparencyManager { } } /** * Gets binary measurements of all installed APEXs, each packed in a Bundle. * @return A List of {@link android.os.Bundle}s with the following keys: * {@link com.android.server.BinaryTransparencyService#BUNDLE_PACKAGE_INFO} * {@link com.android.server.BinaryTransparencyService#BUNDLE_CONTENT_DIGEST_ALGORITHM} * {@link com.android.server.BinaryTransparencyService#BUNDLE_CONTENT_DIGEST} */ // TODO(b/259422958): Fix static constants referenced here - should be defined here @NonNull public List getApexInfo() { try { Slog.d(TAG, "Calling backend's getApexInfo()"); return mService.getApexInfo(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Collects the APEX information on the device. * Loading Loading @@ -116,4 +98,21 @@ public class BinaryTransparencyManager { throw e.rethrowFromSystemServer(); } } /** * Collects the silent installed MBA information on the device. * * @return A List containing the MBA info of silent installed. * @hide */ @NonNull public List<IBinaryTransparencyService.AppInfo> collectAllSilentInstalledMbaInfo( Bundle packagesToSkip) { try { Slog.d(TAG, "Calling backend's collectAllSilentInstalledMbaInfo()"); return mService.collectAllSilentInstalledMbaInfo(packagesToSkip); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } }
core/java/com/android/internal/os/IBinaryTransparencyService.aidl +1 −2 Original line number Diff line number Diff line Loading @@ -28,8 +28,6 @@ import android.os.Bundle; interface IBinaryTransparencyService { String getSignedImageInfo(); List getApexInfo(); void recordMeasurementsForAllPackages(); parcelable ApexInfo { Loading Loading @@ -60,4 +58,5 @@ interface IBinaryTransparencyService { /** Test only */ List<ApexInfo> collectAllApexInfo(boolean includeTestOnly); List<AppInfo> collectAllUpdatedPreloadInfo(in Bundle packagesToSkip); List<AppInfo> collectAllSilentInstalledMbaInfo(in Bundle packagesToSkip); } No newline at end of file
services/core/java/com/android/server/BinaryTransparencyService.java +96 −123 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApexStagedEvent; import android.content.pm.ApplicationInfo; import android.content.pm.Checksum; import android.content.pm.IBackgroundInstallControlService; import android.content.pm.IPackageManagerNative; import android.content.pm.IStagedApexObserver; Loading Loading @@ -84,6 +85,7 @@ 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; Loading Loading @@ -120,15 +122,6 @@ public class BinaryTransparencyService extends SystemService { static final long RECORD_MEASUREMENTS_COOLDOWN_MS = 24 * 60 * 60 * 1000; @VisibleForTesting static final String BUNDLE_PACKAGE_NAME = "package-name"; @VisibleForTesting static final String BUNDLE_PACKAGE_IS_APEX = "package-is-apex"; @VisibleForTesting static final String BUNDLE_CONTENT_DIGEST_ALGORITHM = "content-digest-algo"; @VisibleForTesting static final String BUNDLE_CONTENT_DIGEST = "content-digest"; static final String APEX_PRELOAD_LOCATION_ERROR = "could-not-be-determined"; // used for indicating any type of error during MBA measurement Loading Loading @@ -170,29 +163,6 @@ public class BinaryTransparencyService extends SystemService { return mVbmetaDigest; } @Override public List getApexInfo() { List<Bundle> results = new ArrayList<>(); for (PackageInfo packageInfo : getCurrentInstalledApexs()) { PackageState packageState = mPackageManagerInternal.getPackageStateInternal( packageInfo.packageName); if (packageState == null) { Slog.w(TAG, "Package state is unavailable, ignoring the package " + packageInfo.packageName); continue; } Bundle apexMeasurement = measurePackage(packageState); if (apexMeasurement == null) { Slog.w(TAG, "Skipping the missing APEX in " + packageState.getPath()); continue; } results.add(apexMeasurement); } return results; } /** * A helper function to compute the SHA256 digest of APK package signer. * @param signingInfo The signingInfo of a package, usually {@link PackageInfo#signingInfo}. Loading @@ -217,58 +187,102 @@ public class BinaryTransparencyService extends SystemService { return resultList.toArray(new String[1]); } /** * Perform basic measurement (i.e. content digest) on a given package. /* * Perform basic measurement (i.e. content digest) on a given app, including the split APKs. * * @param packageState The package to be measured. * @return a {@link android.os.Bundle} that packs the measurement result with the following * keys: {@link #BUNDLE_PACKAGE_NAME}, * {@link #BUNDLE_PACKAGE_IS_APEX} * {@link #BUNDLE_CONTENT_DIGEST_ALGORITHM} * {@link #BUNDLE_CONTENT_DIGEST} * @param mbaStatus Assign this value of MBA status to the returned elements. * @return a @{@code List<IBinaryTransparencyService.AppInfo>} */ private @Nullable Bundle measurePackage(PackageState packageState) { Bundle result = new Bundle(); 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(); if (pkg == null) { Slog.w(TAG, "Skipping the missing APK in " + packageState.getPath()); return null; 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. Checksum checksum = measureApk(split.getPath()); appInfo.digest = checksum.getValue(); appInfo.digestAlgorithm = checksum.getType(); 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; } Map<Integer, byte[]> contentDigests = computeApkContentDigest(pkg.getBaseApkPath()); result.putString(BUNDLE_PACKAGE_NAME, pkg.getPackageName()); /** * 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 android.content.pm.Checksum} with preferred digest algorithm type and * the checksum. */ private @Nullable Checksum 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 " + pkg.getBaseApkPath()); result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, 0); result.putByteArray(BUNDLE_CONTENT_DIGEST, null); return result; Slog.d(TAG, "Failed to compute content digest for " + apkPath); return new Checksum(0, new byte[] { -1 }); } // 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)) { Integer algorithmId = ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256; result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, algorithmId); result.putByteArray(BUNDLE_CONTENT_DIGEST, contentDigests.get(algorithmId)); return new Checksum( Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256, contentDigests.get(ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256)); } else if (contentDigests.containsKey( ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512)) { Integer algorithmId = ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512; result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, algorithmId); result.putByteArray(BUNDLE_CONTENT_DIGEST, contentDigests.get(algorithmId)); return new Checksum( Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512, contentDigests.get(ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512)); } else { // TODO(b/259423111): considering putting the raw values for the algorithm & digest // into the bundle to track potential other digest algorithms that may be in use result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, 0); result.putByteArray(BUNDLE_CONTENT_DIGEST, null); return new Checksum(0, new byte[] { -1 }); } result.putBoolean(BUNDLE_PACKAGE_IS_APEX, packageState.isApex()); return result; } Loading Loading @@ -330,7 +344,7 @@ public class BinaryTransparencyService extends SystemService { if (CompatChanges.isChangeEnabled(LOG_MBA_INFO)) { // lastly measure all newly installed MBAs List<IBinaryTransparencyService.AppInfo> allMbaInfo = collectAllMbaInfo(packagesMeasured); collectAllSilentInstalledMbaInfo(packagesMeasured); for (IBinaryTransparencyService.AppInfo appInfo : allUpdatedPreloadInfo) { packagesMeasured.putBoolean(appInfo.packageName, true); writeAppInfoToLog(appInfo); Loading @@ -356,18 +370,22 @@ public class BinaryTransparencyService extends SystemService { continue; } Bundle apexMeasurement = measurePackage(packageState); if (apexMeasurement == null) { Slog.w(TAG, "Skipping the missing APEX in " + packageState.getPath()); AndroidPackage pkg = packageState.getAndroidPackage(); if (pkg == null) { Slog.w(TAG, "Skipping the missing APK in " + pkg.getPath()); continue; } Checksum 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 = apexMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST); apexInfo.digestAlgorithm = apexMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM); apexInfo.digest = apexChecksum.getValue(); apexInfo.digestAlgorithm = apexChecksum.getType(); apexInfo.signerDigests = computePackageSignerSha256Digests(packageState.getSigningInfo()); Loading Loading @@ -398,28 +416,16 @@ public class BinaryTransparencyService extends SystemService { Slog.d(TAG, "Preload " + packageState.getPackageName() + " at " + packageState.getPath() + " has likely been updated."); Bundle packageMeasurement = measurePackage(packageState); if (packageMeasurement == null) { Slog.w(TAG, "Skipping the missing APK in " + packageState.getPath()); return; } var appInfo = new IBinaryTransparencyService.AppInfo(); appInfo.packageName = packageState.getPackageName(); appInfo.longVersion = packageState.getVersionCode(); appInfo.digest = packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST); appInfo.digestAlgorithm = packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM); appInfo.signerDigests = computePackageSignerSha256Digests(packageState.getSigningInfo()); appInfo.mbaStatus = MBA_STATUS_UPDATED_PRELOAD; results.add(appInfo); List<IBinaryTransparencyService.AppInfo> resultsForApp = collectAppInfo( packageState, MBA_STATUS_UPDATED_PRELOAD); results.addAll(resultsForApp); }); return results; } public List<IBinaryTransparencyService.AppInfo> collectAllMbaInfo(Bundle packagesToSkip) { @Override public List<IBinaryTransparencyService.AppInfo> collectAllSilentInstalledMbaInfo( Bundle packagesToSkip) { var results = new ArrayList<IBinaryTransparencyService.AppInfo>(); for (PackageInfo packageInfo : getNewlyInstalledMbas()) { if (packagesToSkip.containsKey(packageInfo.packageName)) { Loading @@ -433,42 +439,9 @@ public class BinaryTransparencyService extends SystemService { continue; } Bundle packageMeasurement = measurePackage(packageState); if (packageMeasurement == null) { Slog.w(TAG, "Skipping the missing APK in " + packageState.getPath()); continue; } if (DEBUG) { Slog.d(TAG, "Extracting InstallSourceInfo for " + packageState.getPackageName()); } var appInfo = new IBinaryTransparencyService.AppInfo(); appInfo.packageName = packageState.getPackageName(); appInfo.longVersion = packageState.getVersionCode(); appInfo.digest = packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST); appInfo.digestAlgorithm = packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM); appInfo.signerDigests = computePackageSignerSha256Digests(packageState.getSigningInfo()); appInfo.mbaStatus = MBA_STATUS_NEW_INSTALL; // Install source isn't currently available in PackageState (there's a TODO). // Extract manually with another call. InstallSourceInfo installSourceInfo = getInstallSourceInfo( packageState.getPackageName()); if (installSourceInfo != null) { appInfo.initiator = installSourceInfo.getInitiatingPackageName(); SigningInfo initiatorSignerInfo = installSourceInfo.getInitiatingPackageSigningInfo(); if (initiatorSignerInfo != null) { appInfo.initiatorSignerDigests = computePackageSignerSha256Digests(initiatorSignerInfo); } appInfo.installer = installSourceInfo.getInstallingPackageName(); appInfo.originator = installSourceInfo.getOriginatingPackageName(); } results.add(appInfo); List<IBinaryTransparencyService.AppInfo> resultsForApp = collectAppInfo( packageState, MBA_STATUS_NEW_INSTALL); results.addAll(resultsForApp); } return results; } Loading
services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java +0 −31 Original line number Diff line number Diff line Loading @@ -40,7 +40,6 @@ import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; import android.os.Bundle; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.SystemProperties; Loading Loading @@ -165,36 +164,6 @@ public class BinaryTransparencyServiceTest { BinaryTransparencyService.VBMETA_DIGEST_UNAVAILABLE), result); } @Test public void getApexInfo_postInitialize_returnsValidEntries() throws RemoteException { prepApexInfo(); List result = mTestInterface.getApexInfo(); Assert.assertNotNull("Apex info map should not be null", result); // TODO(265244016): When PackageManagerInternal is a mock, it's harder to keep the // `measurePackage` working in unit test. Disable it for now. We may need more refactoring // or cover this in integration tests. // Assert.assertFalse("Apex info map should not be empty", result.isEmpty()); } @Test public void getApexInfo_postInitialize_returnsActualApexs() throws RemoteException, PackageManager.NameNotFoundException { prepApexInfo(); List resultList = mTestInterface.getApexInfo(); PackageManager pm = mContext.getPackageManager(); Assert.assertNotNull(pm); List<Bundle> castedResult = (List<Bundle>) resultList; for (Bundle resultBundle : castedResult) { String packageName = resultBundle.getString( BinaryTransparencyService.BUNDLE_PACKAGE_NAME); Assert.assertNotNull("Package name for APEX should not be null", packageName); Assert.assertTrue(packageName + "is not an APEX!", resultBundle.getBoolean( BinaryTransparencyService.BUNDLE_PACKAGE_IS_APEX)); } } @Test public void testCollectBiometricProperties_disablesFeature() { DeviceConfig.setProperty(DeviceConfig.NAMESPACE_BIOMETRICS, Loading
tests/BinaryTransparencyHostTest/src/android/transparency/test/BaseInstallMultiple.java 0 → 100644 +140 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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 android.transparency.test; import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; import com.android.tradefed.build.IBuildInfo; import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.device.ITestDevice; import junit.framework.TestCase; import java.io.File; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Base class for invoking the install-multiple command via ADB. Subclass this for less typing: * * <code> private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> { public * InstallMultiple() { super(getDevice(), null); } } </code> */ /*package*/ class BaseInstallMultiple<T extends BaseInstallMultiple<?>> { private final ITestDevice mDevice; private final IBuildInfo mBuild; private final List<String> mArgs = new ArrayList<>(); private final Map<File, String> mFileToRemoteMap = new HashMap<>(); /*package*/ BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo) { mDevice = device; mBuild = buildInfo; addArg("-g"); } T addArg(String arg) { mArgs.add(arg); return (T) this; } T addFile(String filename) throws FileNotFoundException { return addFile(filename, filename); } T addFile(String filename, String remoteName) throws FileNotFoundException { CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild); mFileToRemoteMap.put(buildHelper.getTestFile(filename), remoteName); return (T) this; } T inheritFrom(String packageName) { addArg("-r"); addArg("-p " + packageName); return (T) this; } void run() throws DeviceNotAvailableException { run(true); } void runExpectingFailure() throws DeviceNotAvailableException { run(false); } private void run(boolean expectingSuccess) throws DeviceNotAvailableException { final ITestDevice device = mDevice; // Create an install session final StringBuilder cmd = new StringBuilder(); cmd.append("pm install-create"); for (String arg : mArgs) { cmd.append(' ').append(arg); } String result = device.executeShellCommand(cmd.toString()); TestCase.assertTrue(result, result.startsWith("Success")); final int start = result.lastIndexOf("["); final int end = result.lastIndexOf("]"); int sessionId = -1; try { if (start != -1 && end != -1 && start < end) { sessionId = Integer.parseInt(result.substring(start + 1, end)); } } catch (NumberFormatException e) { throw new IllegalStateException("Failed to parse install session: " + result); } if (sessionId == -1) { throw new IllegalStateException("Failed to create install session: " + result); } // Push our files into session. Ideally we'd use stdin streaming, // but ddmlib doesn't support it yet. for (final Map.Entry<File, String> entry : mFileToRemoteMap.entrySet()) { final File file = entry.getKey(); final String remoteName = entry.getValue(); final String remotePath = "/data/local/tmp/" + file.getName(); if (!device.pushFile(file, remotePath)) { throw new IllegalStateException("Failed to push " + file); } cmd.setLength(0); cmd.append("pm install-write"); cmd.append(' ').append(sessionId); cmd.append(' ').append(remoteName); cmd.append(' ').append(remotePath); result = device.executeShellCommand(cmd.toString()); TestCase.assertTrue(result, result.startsWith("Success")); } // Everything staged; let's pull trigger cmd.setLength(0); cmd.append("pm install-commit"); cmd.append(' ').append(sessionId); result = device.executeShellCommand(cmd.toString()); if (expectingSuccess) { TestCase.assertTrue(result, result.contains("Success")); } else { TestCase.assertFalse(result, result.contains("Success")); } } }