summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java13
-rw-r--r--services/core/java/com/android/server/security/FileIntegrityService.java140
-rw-r--r--tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java8
3 files changed, 143 insertions, 18 deletions
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 70bd24ce20a2..6a0225b7922d 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -174,6 +174,7 @@ import com.android.server.pm.pkg.component.ParsedPermission;
import com.android.server.pm.pkg.component.ParsedPermissionGroup;
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import com.android.server.rollback.RollbackManagerInternal;
+import com.android.server.security.FileIntegrityService;
import com.android.server.utils.WatchedArrayMap;
import com.android.server.utils.WatchedLongSparseArray;
@@ -1836,6 +1837,7 @@ final class InstallPackageHelper {
}
}
+ var fis = FileIntegrityService.getService();
for (Map.Entry<String, String> entry : fsverityCandidates.entrySet()) {
try {
final String filePath = entry.getKey();
@@ -1843,13 +1845,16 @@ final class InstallPackageHelper {
continue;
}
- // Set up fs-verity with optional signature.
+ VerityUtils.setUpFsverity(filePath, (byte[]) null);
+
+ // Verify fs-verity signature if exists.
final String signaturePath = entry.getValue();
- String optionalSignaturePath = null;
if (new File(signaturePath).exists()) {
- optionalSignaturePath = signaturePath;
+ if (!fis.verifyPkcs7DetachedSignature(signaturePath, filePath)) {
+ throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
+ "fs-verity signature does not verify against a known key");
+ }
}
- VerityUtils.setUpFsverity(filePath, optionalSignaturePath);
} catch (IOException e) {
throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
"Failed to enable fs-verity: " + e);
diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
index 466ac74a8322..5ae697315ed1 100644
--- a/services/core/java/com/android/server/security/FileIntegrityService.java
+++ b/services/core/java/com/android/server/security/FileIntegrityService.java
@@ -23,27 +23,37 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
+import android.os.Build;
import android.os.Environment;
import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
import android.os.UserHandle;
import android.security.IFileIntegrityService;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.security.VerityUtils;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import java.io.ByteArrayInputStream;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.Paths;
import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
-import java.util.Collection;
/**
* A {@link SystemService} that provides file integrity related operations.
@@ -52,9 +62,19 @@ import java.util.Collection;
public class FileIntegrityService extends SystemService {
private static final String TAG = "FileIntegrityService";
+ /** The maximum size of signature file. This is just to avoid potential abuse. */
+ private static final int MAX_SIGNATURE_FILE_SIZE_BYTES = 8192;
+
private static CertificateFactory sCertFactory;
- private Collection<X509Certificate> mTrustedCertificates = new ArrayList<X509Certificate>();
+ @GuardedBy("mTrustedCertificates")
+ private final ArrayList<X509Certificate> mTrustedCertificates =
+ new ArrayList<X509Certificate>();
+
+ /** Gets the instance of the service */
+ public static FileIntegrityService getService() {
+ return LocalServices.getService(FileIntegrityService.class);
+ }
private final IBinder mService = new IFileIntegrityService.Stub() {
@Override
@@ -75,13 +95,23 @@ public class FileIntegrityService extends SystemService {
Slog.w(TAG, "Received a null certificate");
return false;
}
- return mTrustedCertificates.contains(toCertificate(certificateBytes));
+ synchronized (mTrustedCertificates) {
+ return mTrustedCertificates.contains(toCertificate(certificateBytes));
+ }
} catch (CertificateException e) {
Slog.e(TAG, "Failed to convert the certificate: " + e);
return false;
}
}
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out,
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
+ new FileIntegrityServiceShellCommand()
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+
private void checkCallerPermission(String packageName) {
final int callingUid = Binder.getCallingUid();
final int callingUserId = UserHandle.getUserId(callingUid);
@@ -116,6 +146,7 @@ public class FileIntegrityService extends SystemService {
} catch (CertificateException e) {
Slog.wtf(TAG, "Cannot get an instance of X.509 certificate factory");
}
+ LocalServices.addService(FileIntegrityService.class, this);
}
@Override
@@ -124,6 +155,34 @@ public class FileIntegrityService extends SystemService {
publishBinderService(Context.FILE_INTEGRITY_SERVICE, mService);
}
+ /**
+ * Returns whether the signature over the file's fs-verity digest can be verified by one of the
+ * known certiticates.
+ */
+ public boolean verifyPkcs7DetachedSignature(String signaturePath, String filePath)
+ throws IOException {
+ if (Files.size(Paths.get(signaturePath)) > MAX_SIGNATURE_FILE_SIZE_BYTES) {
+ throw new SecurityException("Signature file is unexpectedly large: "
+ + signaturePath);
+ }
+ byte[] signatureBytes = Files.readAllBytes(Paths.get(signaturePath));
+ byte[] digest = VerityUtils.getFsverityDigest(filePath);
+ synchronized (mTrustedCertificates) {
+ for (var cert : mTrustedCertificates) {
+ try {
+ byte[] derEncoded = cert.getEncoded();
+ if (VerityUtils.verifyPkcs7DetachedSignature(signatureBytes, digest,
+ new ByteArrayInputStream(derEncoded))) {
+ return true;
+ }
+ } catch (CertificateEncodingException e) {
+ Slog.w(TAG, "Ignoring ill-formed certificate: " + e);
+ }
+ }
+ }
+ return false;
+ }
+
private void loadAllCertificates() {
// A better alternative to load certificates would be to read from .fs-verity kernel
// keyring, which fsverity_init loads to during earlier boot time from the same sources
@@ -148,10 +207,6 @@ public class FileIntegrityService extends SystemService {
for (File cert : files) {
byte[] certificateBytes = Files.readAllBytes(cert.toPath());
- if (certificateBytes == null) {
- Slog.w(TAG, "The certificate file is empty, ignoring " + cert);
- continue;
- }
collectCertificate(certificateBytes);
}
} catch (IOException e) {
@@ -165,7 +220,9 @@ public class FileIntegrityService extends SystemService {
*/
private void collectCertificate(@NonNull byte[] bytes) {
try {
- mTrustedCertificates.add(toCertificate(bytes));
+ synchronized (mTrustedCertificates) {
+ mTrustedCertificates.add(toCertificate(bytes));
+ }
} catch (CertificateException e) {
Slog.e(TAG, "Invalid certificate, ignored: " + e);
}
@@ -184,4 +241,71 @@ public class FileIntegrityService extends SystemService {
}
return (X509Certificate) certificate;
}
+
+
+ private class FileIntegrityServiceShellCommand extends ShellCommand {
+ @Override
+ public int onCommand(String cmd) {
+ if (!Build.IS_DEBUGGABLE) {
+ return -1;
+ }
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ switch (cmd) {
+ case "append-cert":
+ String nextArg = getNextArg();
+ if (nextArg == null) {
+ pw.println("Invalid argument");
+ pw.println("");
+ onHelp();
+ return -1;
+ }
+ ParcelFileDescriptor pfd = openFileForSystem(nextArg, "r");
+ if (pfd == null) {
+ pw.println("Cannot open the file");
+ return -1;
+ }
+ InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+ try {
+ collectCertificate(is.readAllBytes());
+ } catch (IOException e) {
+ pw.println("Failed to add certificate: " + e);
+ return -1;
+ }
+ pw.println("Certificate is added successfully");
+ return 0;
+
+ case "remove-last-cert":
+ synchronized (mTrustedCertificates) {
+ if (mTrustedCertificates.size() == 0) {
+ pw.println("Certificate list is already empty");
+ return -1;
+ }
+ mTrustedCertificates.remove(mTrustedCertificates.size() - 1);
+ }
+ pw.println("Certificate is removed successfully");
+ return 0;
+ default:
+ pw.println("Unknown action");
+ pw.println("");
+ onHelp();
+ }
+ return -1;
+ }
+
+ @Override
+ public void onHelp() {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.println("File integrity service commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println(" append-cert path/to/cert.der");
+ pw.println(" Add the DER-encoded certificate (only in debug builds)");
+ pw.println(" remove-last-cert");
+ pw.println(" Remove the last certificate in the key list (only in debug builds)");
+ pw.println("");
+ }
+ }
}
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
index d96005b8a71a..16f005f28856 100644
--- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
+++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
@@ -25,7 +25,6 @@ import static org.junit.Assert.fail;
import android.platform.test.annotations.RootPermissionTest;
import com.android.blockdevicewriter.BlockDeviceWriter;
-import com.android.fsverity.AddFsVerityCertRule;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
@@ -36,7 +35,6 @@ import com.android.tradefed.util.CommandStatus;
import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -90,10 +88,6 @@ public class ApkVerityTest extends BaseHostJUnit4Test {
/** Only 4K page is supported by fs-verity currently. */
private static final int FSVERITY_PAGE_SIZE = 4096;
- @Rule
- public final AddFsVerityCertRule mAddFsVerityCertRule =
- new AddFsVerityCertRule(this, CERT_PATH);
-
private ITestDevice mDevice;
private boolean mDmRequireFsVerity;
@@ -103,11 +97,13 @@ public class ApkVerityTest extends BaseHostJUnit4Test {
mDmRequireFsVerity = "true".equals(
mDevice.getProperty("pm.dexopt.dm.require_fsverity"));
+ expectRemoteCommandToSucceed("cmd file_integrity append-cert " + CERT_PATH);
uninstallPackage(TARGET_PACKAGE);
}
@After
public void tearDown() throws DeviceNotAvailableException {
+ expectRemoteCommandToSucceed("cmd file_integrity remove-last-cert");
uninstallPackage(TARGET_PACKAGE);
}