summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Victor Hsieh <victorhsieh@google.com> 2019-09-30 13:36:21 -0700
committer Victor Hsieh <victorhsieh@google.com> 2019-12-23 09:21:19 -0800
commit20fe1f6f2214c659698eab7677cb583bc67981b5 (patch)
tree09c3b67c523ef9a92eabe565c7c1626fed44fff8
parent18e52123cee888bdc322668f1a61d2ebef6d4ae6 (diff)
New API for query trust of a fs-verity certificate
The corresponding service is also added. The API can be used by a store to know whether their certificate is trusted on the device. As optimization, they only need to download .fsv_sig signature file if it will be used. The API can also be used to gradually switch to stronger key. The store can query with their certificates in priority order and download the best signature. Test: Passed new GTS working in progress Bug: 142573505 Change-Id: Ic788cd04aeaed35ad62113fe9e7535b8fa63b5ee
-rw-r--r--api/current.txt6
-rw-r--r--core/java/android/app/SystemServiceRegistry.java14
-rw-r--r--core/java/android/content/Context.java8
-rw-r--r--core/java/android/security/FileIntegrityManager.java77
-rw-r--r--core/java/android/security/IFileIntegrityService.aidl26
-rw-r--r--services/core/java/com/android/server/security/FileIntegrityService.java170
-rw-r--r--services/java/com/android/server/SystemServer.java8
7 files changed, 309 insertions, 0 deletions
diff --git a/api/current.txt b/api/current.txt
index 9569be8707ae..627beb7ab512 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9971,6 +9971,7 @@ package android.content {
field public static final String DOWNLOAD_SERVICE = "download";
field public static final String DROPBOX_SERVICE = "dropbox";
field public static final String EUICC_SERVICE = "euicc";
+ field public static final String FILE_INTEGRITY_SERVICE = "file_integrity";
field public static final String FINGERPRINT_SERVICE = "fingerprint";
field public static final String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
field public static final String INPUT_METHOD_SERVICE = "input_method";
@@ -41157,6 +41158,11 @@ package android.security {
method public android.security.ConfirmationPrompt.Builder setPromptText(CharSequence);
}
+ public final class FileIntegrityManager {
+ method public boolean isApkVeritySupported();
+ method @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public boolean isAppSourceCertificateTrusted(@NonNull java.security.cert.X509Certificate) throws java.security.cert.CertificateEncodingException;
+ }
+
public final class KeyChain {
ctor public KeyChain();
method public static void choosePrivateKeyAlias(@NonNull android.app.Activity, @NonNull android.security.KeyChainAliasCallback, @Nullable String[], @Nullable java.security.Principal[], @Nullable String, int, @Nullable String);
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index ce21db335615..7574c4903434 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -159,6 +159,8 @@ import android.permission.PermissionControllerManager;
import android.permission.PermissionManager;
import android.print.IPrintManager;
import android.print.PrintManager;
+import android.security.FileIntegrityManager;
+import android.security.IFileIntegrityService;
import android.service.oemlock.IOemLockService;
import android.service.oemlock.OemLockManager;
import android.service.persistentdata.IPersistentDataBlockService;
@@ -1208,6 +1210,7 @@ public final class SystemServiceRegistry {
return new DynamicSystemManager(
IDynamicSystemService.Stub.asInterface(b));
}});
+
registerService(Context.BATTERY_STATS_SERVICE, BatteryStatsManager.class,
new CachedServiceFetcher<BatteryStatsManager>() {
@Override
@@ -1241,6 +1244,17 @@ public final class SystemServiceRegistry {
return new IncrementalManager(
IIncrementalManagerNative.Stub.asInterface(b));
}});
+
+ registerService(Context.FILE_INTEGRITY_SERVICE, FileIntegrityManager.class,
+ new CachedServiceFetcher<FileIntegrityManager>() {
+ @Override
+ public FileIntegrityManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(
+ Context.FILE_INTEGRITY_SERVICE);
+ return new FileIntegrityManager(
+ IFileIntegrityService.Stub.asInterface(b));
+ }});
//CHECKSTYLE:ON IndentationCheck
sInitializing = true;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 85424119e37c..1d32045e1986 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5036,6 +5036,14 @@ public abstract class Context {
public static final String INCREMENTAL_SERVICE = "incremental_service";
/**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.security.FileIntegrityManager}.
+ * @see #getSystemService(String)
+ * @see android.security.FileIntegrityManager
+ */
+ public static final String FILE_INTEGRITY_SERVICE = "file_integrity";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/security/FileIntegrityManager.java b/core/java/android/security/FileIntegrityManager.java
new file mode 100644
index 000000000000..cdd6584e9b35
--- /dev/null
+++ b/core/java/android/security/FileIntegrityManager.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 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.security;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+
+/**
+ * This class provides access to file integrity related operations.
+ */
+@SystemService(Context.FILE_INTEGRITY_SERVICE)
+public final class FileIntegrityManager {
+ @NonNull private final IFileIntegrityService mService;
+
+ /** @hide */
+ public FileIntegrityManager(@NonNull IFileIntegrityService service) {
+ mService = service;
+ }
+
+ /**
+ * Returns true if APK Verity is supported on the device. When supported, an APK can be
+ * installed with a fs-verity signature (if verified with trusted App Source Certificate) for
+ * continuous on-access verification.
+ */
+ public boolean isApkVeritySupported() {
+ try {
+ // Go through the service just to avoid exposing the vendor controlled system property
+ // to all apps.
+ return mService.isApkVeritySupported();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether the given certificate can be used to prove app's install source. Always
+ * return false if the feature is not supported.
+ *
+ * <p>A store can use this API to decide if a signature file needs to be downloaded. Also, if a
+ * store has shipped different certificates before (e.g. with stronger and weaker key), it can
+ * also use this API to download the best signature on the running device.
+ *
+ * @return whether the certificate is trusted in the system
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.INSTALL_PACKAGES,
+ android.Manifest.permission.REQUEST_INSTALL_PACKAGES
+ })
+ public boolean isAppSourceCertificateTrusted(@NonNull X509Certificate certificate)
+ throws CertificateEncodingException {
+ try {
+ return mService.isAppSourceCertificateTrusted(certificate.getEncoded());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/security/IFileIntegrityService.aidl b/core/java/android/security/IFileIntegrityService.aidl
new file mode 100644
index 000000000000..ebb8bcb85350
--- /dev/null
+++ b/core/java/android/security/IFileIntegrityService.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright 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.security;
+
+/**
+ * Binder interface to communicate with FileIntegrityService.
+ * @hide
+ */
+interface IFileIntegrityService {
+ boolean isApkVeritySupported();
+ boolean isAppSourceCertificateTrusted(in byte[] certificateBytes);
+}
diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
new file mode 100644
index 000000000000..0bbb17950856
--- /dev/null
+++ b/services/core/java/com/android/server/security/FileIntegrityService.java
@@ -0,0 +1,170 @@
+/*
+ * 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 com.android.server.security;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.SystemProperties;
+import android.security.Credentials;
+import android.security.IFileIntegrityService;
+import android.security.KeyStore;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.security.cert.Certificate;
+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.
+ * @hide
+ */
+public class FileIntegrityService extends SystemService {
+ private static final String TAG = "FileIntegrityService";
+
+ private static CertificateFactory sCertFactory;
+
+ private Collection<X509Certificate> mTrustedCertificates = new ArrayList<X509Certificate>();
+
+ private final IBinder mService = new IFileIntegrityService.Stub() {
+ @Override
+ public boolean isApkVeritySupported() {
+ return SystemProperties.getInt("ro.apk_verity.mode", 0) == 2;
+ }
+
+ @Override
+ public boolean isAppSourceCertificateTrusted(byte[] certificateBytes) {
+ enforceAnyCallingPermissions(
+ android.Manifest.permission.REQUEST_INSTALL_PACKAGES,
+ android.Manifest.permission.INSTALL_PACKAGES);
+ try {
+ if (!isApkVeritySupported()) {
+ return false;
+ }
+
+ return mTrustedCertificates.contains(toCertificate(certificateBytes));
+ } catch (CertificateException e) {
+ Slog.e(TAG, "Failed to convert the certificate: " + e);
+ return false;
+ }
+ }
+
+ private void enforceAnyCallingPermissions(String ...permissions) {
+ for (String permission : permissions) {
+ if (getContext().checkCallingPermission(permission)
+ == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ }
+ throw new SecurityException("Insufficient permission");
+ }
+ };
+
+ public FileIntegrityService(final Context context) {
+ super(context);
+ try {
+ sCertFactory = CertificateFactory.getInstance("X.509");
+ } catch (CertificateException e) {
+ Slog.wtf(TAG, "Cannot get an instance of X.509 certificate factory");
+ }
+ }
+
+ @Override
+ public void onStart() {
+ loadAllCertificates();
+ publishBinderService(Context.FILE_INTEGRITY_SERVICE, mService);
+ }
+
+ 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
+ // below. But since the read operation from keyring is not provided in kernel, we need to
+ // duplicate the same loading logic here.
+
+ // Load certificates trusted by the device manufacturer.
+ loadCertificatesFromDirectory("/product/etc/security/fsverity");
+
+ // Load certificates trusted by the device owner.
+ loadCertificatesFromKeystore(KeyStore.getInstance());
+ }
+
+ private void loadCertificatesFromDirectory(String path) {
+ try {
+ File[] files = new File(path).listFiles();
+ if (files == null) {
+ return;
+ }
+
+ for (File cert : files) {
+ collectCertificate(Files.readAllBytes(cert.toPath()));
+ }
+ } catch (IOException e) {
+ Slog.wtf(TAG, "Failed to load fs-verity certificate from " + path, e);
+ }
+ }
+
+ private void loadCertificatesFromKeystore(KeyStore keystore) {
+ for (final String alias : keystore.list(Credentials.APP_SOURCE_CERTIFICATE,
+ Process.FSVERITY_CERT_UID)) {
+ byte[] certificateBytes = keystore.get(Credentials.APP_SOURCE_CERTIFICATE + alias,
+ Process.FSVERITY_CERT_UID, false /* suppressKeyNotFoundWarning */);
+ if (certificateBytes == null) {
+ Slog.w(TAG, "The retrieved fs-verity certificate is null, ignored " + alias);
+ continue;
+ }
+ collectCertificate(certificateBytes);
+ }
+ }
+
+ /**
+ * Tries to convert {@code bytes} into an X.509 certificate and store in memory.
+ * Errors need to be surpressed in order fo the next certificates to still be collected.
+ */
+ private void collectCertificate(@Nullable byte[] bytes) {
+ try {
+ mTrustedCertificates.add(toCertificate(bytes));
+ } catch (CertificateException | AssertionError e) {
+ Slog.e(TAG, "Invalid certificate, ignored: " + e);
+ }
+ }
+
+ /**
+ * Converts byte array into one X.509 certificate. If multiple certificate is defined, ignore
+ * the rest. The rational is to make it harder to smuggle.
+ */
+ @NonNull
+ private static X509Certificate toCertificate(@Nullable byte[] bytes)
+ throws CertificateException {
+ Certificate certificate = sCertFactory.generateCertificate(new ByteArrayInputStream(bytes));
+ if (!(certificate instanceof X509Certificate)) {
+ throw new CertificateException("Expected to contain an X.509 certificate");
+ }
+ return (X509Certificate) certificate;
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f05bbe215b78..053ac1a2ed0f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -145,6 +145,7 @@ import com.android.server.recoverysystem.RecoverySystemService;
import com.android.server.restrictions.RestrictionsManagerService;
import com.android.server.role.RoleManagerService;
import com.android.server.rollback.RollbackManagerService;
+import com.android.server.security.FileIntegrityService;
import com.android.server.security.KeyAttestationApplicationIdProviderService;
import com.android.server.security.KeyChainSystemService;
import com.android.server.signedconfig.SignedConfigService;
@@ -672,6 +673,13 @@ public final class SystemServer {
AppCompatCallbacks.install(new long[0]);
t.traceEnd();
+ // FileIntegrityService responds to requests from apps and the system. It needs to run after
+ // the source (i.e. keystore) is ready, and before the apps (or the first customer in the
+ // system) run.
+ t.traceBegin("StartFileIntegrityService");
+ mSystemServiceManager.startService(FileIntegrityService.class);
+ t.traceEnd();
+
// Wait for installd to finish starting up so that it has a chance to
// create critical directories such as /data/user with the appropriate
// permissions. We need this to complete before we initialize other services.