diff options
| -rw-r--r-- | api/current.txt | 6 | ||||
| -rw-r--r-- | core/java/android/app/SystemServiceRegistry.java | 14 | ||||
| -rw-r--r-- | core/java/android/content/Context.java | 8 | ||||
| -rw-r--r-- | core/java/android/security/FileIntegrityManager.java | 77 | ||||
| -rw-r--r-- | core/java/android/security/IFileIntegrityService.aidl | 26 | ||||
| -rw-r--r-- | services/core/java/com/android/server/security/FileIntegrityService.java | 170 | ||||
| -rw-r--r-- | services/java/com/android/server/SystemServer.java | 8 |
7 files changed, 309 insertions, 0 deletions
diff --git a/api/current.txt b/api/current.txt index 5d12f81eebcf..4d5d1a1c02d3 100644 --- a/api/current.txt +++ b/api/current.txt @@ -9973,6 +9973,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"; @@ -41389,6 +41390,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 b5e7f4151eaa..31c73b90aace 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -162,6 +162,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; @@ -1214,6 +1216,7 @@ public final class SystemServiceRegistry { return new DynamicSystemManager( IDynamicSystemService.Stub.asInterface(b)); }}); + registerService(Context.BATTERY_STATS_SERVICE, BatteryStatsManager.class, new CachedServiceFetcher<BatteryStatsManager>() { @Override @@ -1247,6 +1250,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 registerService(Context.APP_INTEGRITY_SERVICE, AppIntegrityManager.class, new CachedServiceFetcher<AppIntegrityManager>() { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 483bf3f1ec87..4815d7847115 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -5045,6 +5045,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 50ae3761b255..cfe131899987 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -146,6 +146,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; @@ -673,6 +674,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. |