| /* |
| * Copyright (C) 2020 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.locksettings; |
| |
| import static android.os.UserHandle.USER_SYSTEM; |
| |
| import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_ESCROW_NOT_READY; |
| import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_KEYSTORE_FAILURE; |
| import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_NONE; |
| import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_NO_ESCROW_KEY; |
| import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_NO_PROVIDER; |
| import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_PROVIDER_MISMATCH; |
| import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_STORE_ESCROW_KEY; |
| import static com.android.internal.widget.LockSettingsInternal.ArmRebootEscrowErrorCode; |
| |
| import android.annotation.IntDef; |
| import android.annotation.NonNull; |
| import android.annotation.UserIdInt; |
| import android.content.Context; |
| import android.content.pm.PackageManager; |
| import android.content.pm.UserInfo; |
| import android.net.ConnectivityManager; |
| import android.net.Network; |
| import android.net.NetworkCapabilities; |
| import android.os.Handler; |
| import android.os.PowerManager; |
| import android.os.SystemClock; |
| import android.os.SystemProperties; |
| import android.os.UserManager; |
| import android.provider.DeviceConfig; |
| import android.provider.Settings; |
| import android.util.Slog; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.util.FrameworkStatsLog; |
| import com.android.internal.util.IndentingPrintWriter; |
| import com.android.internal.widget.RebootEscrowListener; |
| |
| import java.io.IOException; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.text.SimpleDateFormat; |
| import java.util.ArrayList; |
| import java.util.Date; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Objects; |
| |
| import javax.crypto.SecretKey; |
| |
| /** |
| * This class aims to persists the synthetic password(SP) across reboot in a secure way. In |
| * particular, it manages the encryption of the sp before reboot, and decryption of the sp after |
| * reboot. Here are the meaning of some terms. |
| * SP: synthetic password |
| * K_s: The RebootEscrowKey, i.e. AES-GCM key stored in memory |
| * K_k: AES-GCM key in android keystore |
| * RebootEscrowData: The synthetic password and its encrypted blob. We encrypt SP with K_s first, |
| * then with K_k, i.e. E(K_k, E(K_s, SP)) |
| */ |
| class RebootEscrowManager { |
| private static final String TAG = "RebootEscrowManager"; |
| |
| /** |
| * Used in the database storage to indicate the boot count at which the reboot escrow was |
| * previously armed. |
| */ |
| @VisibleForTesting |
| public static final String REBOOT_ESCROW_ARMED_KEY = "reboot_escrow_armed_count"; |
| |
| static final String REBOOT_ESCROW_KEY_ARMED_TIMESTAMP = "reboot_escrow_key_stored_timestamp"; |
| static final String REBOOT_ESCROW_KEY_PROVIDER = "reboot_escrow_key_provider"; |
| |
| /** |
| * The verified boot 2.0 vbmeta digest of the current slot, the property value is always |
| * available after boot. |
| */ |
| static final String VBMETA_DIGEST_PROP_NAME = "ro.boot.vbmeta.digest"; |
| /** |
| * The system prop contains vbmeta digest of the inactive slot. The build property is set after |
| * an OTA update. RebootEscrowManager will store it in disk before the OTA reboot, so the value |
| * is available for vbmeta digest verification after the device reboots. |
| */ |
| static final String OTHER_VBMETA_DIGEST_PROP_NAME = "ota.other.vbmeta_digest"; |
| static final String REBOOT_ESCROW_KEY_VBMETA_DIGEST = "reboot_escrow_key_vbmeta_digest"; |
| static final String REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST = |
| "reboot_escrow_key_other_vbmeta_digest"; |
| |
| /** |
| * Number of boots until we consider the escrow data to be stale for the purposes of metrics. |
| * <p> |
| * If the delta between the current boot number and the boot number stored when the mechanism |
| * was armed is under this number and the escrow mechanism fails, we report it as a failure of |
| * the mechanism. |
| * <p> |
| * If the delta over this number and escrow fails, we will not report the metric as failed |
| * since there most likely was some other issue if the device rebooted several times before |
| * getting to the escrow restore code. |
| */ |
| private static final int BOOT_COUNT_TOLERANCE = 5; |
| |
| /** |
| * The default retry specs for loading reboot escrow data. We will attempt to retry loading |
| * escrow data on temporarily errors, e.g. unavailable network. |
| */ |
| private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT = 3; |
| private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS = 30; |
| // 3 minutes. It's enough for the default 3 retries with 30 seconds interval |
| private static final int DEFAULT_WAKE_LOCK_TIMEOUT_MILLIS = 180_000; |
| |
| @IntDef(prefix = {"ERROR_"}, value = { |
| ERROR_NONE, |
| ERROR_UNKNOWN, |
| ERROR_NO_PROVIDER, |
| ERROR_LOAD_ESCROW_KEY, |
| ERROR_RETRY_COUNT_EXHAUSTED, |
| ERROR_UNLOCK_ALL_USERS, |
| ERROR_PROVIDER_MISMATCH, |
| ERROR_KEYSTORE_FAILURE, |
| ERROR_NO_NETWORK, |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| @interface RebootEscrowErrorCode { |
| } |
| |
| static final int ERROR_NONE = 0; |
| static final int ERROR_UNKNOWN = 1; |
| static final int ERROR_NO_PROVIDER = 2; |
| static final int ERROR_LOAD_ESCROW_KEY = 3; |
| static final int ERROR_RETRY_COUNT_EXHAUSTED = 4; |
| static final int ERROR_UNLOCK_ALL_USERS = 5; |
| static final int ERROR_PROVIDER_MISMATCH = 6; |
| static final int ERROR_KEYSTORE_FAILURE = 7; |
| static final int ERROR_NO_NETWORK = 8; |
| |
| private @RebootEscrowErrorCode int mLoadEscrowDataErrorCode = ERROR_NONE; |
| |
| /** |
| * Logs events for later debugging in bugreports. |
| */ |
| private final RebootEscrowEventLog mEventLog; |
| |
| /** |
| * Used to track when the reboot escrow is wanted. Should stay true once escrow is requested |
| * unless clearRebootEscrow is called. This will allow all the active users to be unlocked |
| * after reboot. |
| */ |
| private boolean mRebootEscrowWanted; |
| |
| /** Used to track when reboot escrow is ready. */ |
| private boolean mRebootEscrowReady; |
| |
| /** Notified when mRebootEscrowReady changes. */ |
| private RebootEscrowListener mRebootEscrowListener; |
| |
| /** |
| * Hold this lock when checking or generating the reboot escrow key. |
| */ |
| private final Object mKeyGenerationLock = new Object(); |
| |
| /** |
| * Stores the reboot escrow data between when it's supplied and when |
| * {@link #armRebootEscrowIfNeeded()} is called. |
| */ |
| @GuardedBy("mKeyGenerationLock") |
| private RebootEscrowKey mPendingRebootEscrowKey; |
| |
| private final UserManager mUserManager; |
| |
| private final Injector mInjector; |
| |
| private final LockSettingsStorage mStorage; |
| |
| private final Callbacks mCallbacks; |
| |
| private final RebootEscrowKeyStoreManager mKeyStoreManager; |
| |
| PowerManager.WakeLock mWakeLock; |
| |
| |
| interface Callbacks { |
| boolean isUserSecure(int userId); |
| |
| void onRebootEscrowRestored(byte spVersion, byte[] syntheticPassword, int userId); |
| } |
| |
| static class Injector { |
| protected Context mContext; |
| private final RebootEscrowKeyStoreManager mKeyStoreManager; |
| private final LockSettingsStorage mStorage; |
| private RebootEscrowProviderInterface mRebootEscrowProvider; |
| |
| Injector(Context context, LockSettingsStorage storage) { |
| mContext = context; |
| mStorage = storage; |
| mKeyStoreManager = new RebootEscrowKeyStoreManager(); |
| } |
| |
| private RebootEscrowProviderInterface createRebootEscrowProvider() { |
| RebootEscrowProviderInterface rebootEscrowProvider; |
| if (serverBasedResumeOnReboot()) { |
| Slog.i(TAG, "Using server based resume on reboot"); |
| rebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(mContext, mStorage); |
| } else { |
| Slog.i(TAG, "Using HAL based resume on reboot"); |
| rebootEscrowProvider = new RebootEscrowProviderHalImpl(); |
| } |
| |
| if (rebootEscrowProvider.hasRebootEscrowSupport()) { |
| return rebootEscrowProvider; |
| } |
| return null; |
| } |
| |
| void post(Handler handler, Runnable runnable) { |
| handler.post(runnable); |
| } |
| |
| void postDelayed(Handler handler, Runnable runnable, long delayMillis) { |
| handler.postDelayed(runnable, delayMillis); |
| } |
| |
| public boolean serverBasedResumeOnReboot() { |
| // Always use the server based RoR if the HAL isn't installed on device. |
| if (!mContext.getPackageManager().hasSystemFeature( |
| PackageManager.FEATURE_REBOOT_ESCROW)) { |
| return true; |
| } |
| |
| return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA, |
| "server_based_ror_enabled", false); |
| } |
| |
| public boolean isNetworkConnected() { |
| final ConnectivityManager connectivityManager = |
| mContext.getSystemService(ConnectivityManager.class); |
| if (connectivityManager == null) { |
| return false; |
| } |
| |
| Network activeNetwork = connectivityManager.getActiveNetwork(); |
| NetworkCapabilities networkCapabilities = |
| connectivityManager.getNetworkCapabilities(activeNetwork); |
| return networkCapabilities != null |
| && networkCapabilities.hasCapability( |
| NetworkCapabilities.NET_CAPABILITY_INTERNET) |
| && networkCapabilities.hasCapability( |
| NetworkCapabilities.NET_CAPABILITY_VALIDATED); |
| } |
| |
| public Context getContext() { |
| return mContext; |
| } |
| |
| public UserManager getUserManager() { |
| return (UserManager) mContext.getSystemService(Context.USER_SERVICE); |
| } |
| |
| public RebootEscrowKeyStoreManager getKeyStoreManager() { |
| return mKeyStoreManager; |
| } |
| |
| public RebootEscrowProviderInterface createRebootEscrowProviderIfNeeded() { |
| // Initialize for the provider lazily. Because the device_config and service |
| // implementation apps may change when system server is running. |
| if (mRebootEscrowProvider == null) { |
| mRebootEscrowProvider = createRebootEscrowProvider(); |
| } |
| |
| return mRebootEscrowProvider; |
| } |
| |
| PowerManager.WakeLock getWakeLock() { |
| final PowerManager pm = mContext.getSystemService(PowerManager.class); |
| return pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "RebootEscrowManager"); |
| } |
| |
| public RebootEscrowProviderInterface getRebootEscrowProvider() { |
| return mRebootEscrowProvider; |
| } |
| |
| public void clearRebootEscrowProvider() { |
| mRebootEscrowProvider = null; |
| } |
| |
| public int getBootCount() { |
| return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.BOOT_COUNT, |
| 0); |
| } |
| |
| public long getCurrentTimeMillis() { |
| return System.currentTimeMillis(); |
| } |
| |
| public int getLoadEscrowDataRetryLimit() { |
| return DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA, |
| "load_escrow_data_retry_count", DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT); |
| } |
| |
| public int getLoadEscrowDataRetryIntervalSeconds() { |
| return DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA, |
| "load_escrow_data_retry_interval_seconds", |
| DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS); |
| } |
| |
| public void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount, |
| int escrowDurationInSeconds, int vbmetaDigestStatus, |
| int durationSinceBootCompleteInSeconds) { |
| FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, success, |
| errorCode, serviceType, attemptCount, escrowDurationInSeconds, |
| vbmetaDigestStatus, durationSinceBootCompleteInSeconds); |
| } |
| |
| public RebootEscrowEventLog getEventLog() { |
| return new RebootEscrowEventLog(); |
| } |
| |
| public String getVbmetaDigest(boolean other) { |
| return other ? SystemProperties.get(OTHER_VBMETA_DIGEST_PROP_NAME) |
| : SystemProperties.get(VBMETA_DIGEST_PROP_NAME); |
| } |
| } |
| |
| RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage) { |
| this(new Injector(context, storage), callbacks, storage); |
| } |
| |
| @VisibleForTesting |
| RebootEscrowManager(Injector injector, Callbacks callbacks, |
| LockSettingsStorage storage) { |
| mInjector = injector; |
| mCallbacks = callbacks; |
| mStorage = storage; |
| mUserManager = injector.getUserManager(); |
| mEventLog = injector.getEventLog(); |
| mKeyStoreManager = injector.getKeyStoreManager(); |
| } |
| |
| private void onGetRebootEscrowKeyFailed(List<UserInfo> users, int attemptCount) { |
| Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage."); |
| for (UserInfo user : users) { |
| mStorage.removeRebootEscrow(user.id); |
| } |
| |
| onEscrowRestoreComplete(false, attemptCount); |
| } |
| |
| void loadRebootEscrowDataIfAvailable(Handler retryHandler) { |
| List<UserInfo> users = mUserManager.getUsers(); |
| List<UserInfo> rebootEscrowUsers = new ArrayList<>(); |
| for (UserInfo user : users) { |
| if (mCallbacks.isUserSecure(user.id) && mStorage.hasRebootEscrow(user.id)) { |
| rebootEscrowUsers.add(user); |
| } |
| } |
| |
| if (rebootEscrowUsers.isEmpty()) { |
| Slog.i(TAG, "No reboot escrow data found for users," |
| + " skipping loading escrow data"); |
| clearMetricsStorage(); |
| return; |
| } |
| |
| // Acquire the wake lock to make sure our scheduled task will run. |
| mWakeLock = mInjector.getWakeLock(); |
| if (mWakeLock != null) { |
| mWakeLock.setReferenceCounted(false); |
| mWakeLock.acquire(DEFAULT_WAKE_LOCK_TIMEOUT_MILLIS); |
| } |
| |
| mInjector.post(retryHandler, () -> loadRebootEscrowDataWithRetry( |
| retryHandler, 0, users, rebootEscrowUsers)); |
| } |
| |
| void scheduleLoadRebootEscrowDataOrFail(Handler retryHandler, int attemptNumber, |
| List<UserInfo> users, List<UserInfo> rebootEscrowUsers) { |
| Objects.requireNonNull(retryHandler); |
| |
| final int retryLimit = mInjector.getLoadEscrowDataRetryLimit(); |
| final int retryIntervalInSeconds = mInjector.getLoadEscrowDataRetryIntervalSeconds(); |
| |
| if (attemptNumber < retryLimit) { |
| Slog.i(TAG, "Scheduling loadRebootEscrowData retry number: " + attemptNumber); |
| mInjector.postDelayed(retryHandler, () -> loadRebootEscrowDataWithRetry( |
| retryHandler, attemptNumber, users, rebootEscrowUsers), |
| retryIntervalInSeconds * 1000); |
| return; |
| } |
| |
| Slog.w(TAG, "Failed to load reboot escrow data after " + attemptNumber + " attempts"); |
| if (mInjector.serverBasedResumeOnReboot() && !mInjector.isNetworkConnected()) { |
| mLoadEscrowDataErrorCode = ERROR_NO_NETWORK; |
| } else { |
| mLoadEscrowDataErrorCode = ERROR_RETRY_COUNT_EXHAUSTED; |
| } |
| onGetRebootEscrowKeyFailed(users, attemptNumber); |
| } |
| |
| void loadRebootEscrowDataWithRetry(Handler retryHandler, int attemptNumber, |
| List<UserInfo> users, List<UserInfo> rebootEscrowUsers) { |
| // Fetch the key from keystore to decrypt the escrow data & escrow key; this key is |
| // generated before reboot. Note that we will clear the escrow key even if the keystore key |
| // is null. |
| SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey(); |
| if (kk == null) { |
| Slog.i(TAG, "Failed to load the key for resume on reboot from key store."); |
| } |
| |
| RebootEscrowKey escrowKey; |
| try { |
| escrowKey = getAndClearRebootEscrowKey(kk); |
| } catch (IOException e) { |
| Slog.i(TAG, "Failed to load escrow key, scheduling retry.", e); |
| scheduleLoadRebootEscrowDataOrFail(retryHandler, attemptNumber + 1, users, |
| rebootEscrowUsers); |
| return; |
| } |
| |
| if (escrowKey == null) { |
| if (mLoadEscrowDataErrorCode == ERROR_NONE) { |
| // Specifically check if the RoR provider has changed after reboot. |
| int providerType = mInjector.serverBasedResumeOnReboot() |
| ? RebootEscrowProviderInterface.TYPE_SERVER_BASED |
| : RebootEscrowProviderInterface.TYPE_HAL; |
| if (providerType != mStorage.getInt(REBOOT_ESCROW_KEY_PROVIDER, -1, USER_SYSTEM)) { |
| mLoadEscrowDataErrorCode = ERROR_PROVIDER_MISMATCH; |
| } else { |
| mLoadEscrowDataErrorCode = ERROR_LOAD_ESCROW_KEY; |
| } |
| } |
| onGetRebootEscrowKeyFailed(users, attemptNumber + 1); |
| return; |
| } |
| |
| mEventLog.addEntry(RebootEscrowEvent.FOUND_ESCROW_DATA); |
| |
| boolean allUsersUnlocked = true; |
| for (UserInfo user : rebootEscrowUsers) { |
| allUsersUnlocked &= restoreRebootEscrowForUser(user.id, escrowKey, kk); |
| } |
| |
| if (!allUsersUnlocked && mLoadEscrowDataErrorCode == ERROR_NONE) { |
| mLoadEscrowDataErrorCode = ERROR_UNLOCK_ALL_USERS; |
| } |
| onEscrowRestoreComplete(allUsersUnlocked, attemptNumber + 1); |
| } |
| |
| private void clearMetricsStorage() { |
| mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM); |
| mStorage.removeKey(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, USER_SYSTEM); |
| mStorage.removeKey(REBOOT_ESCROW_KEY_VBMETA_DIGEST, USER_SYSTEM); |
| mStorage.removeKey(REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST, USER_SYSTEM); |
| mStorage.removeKey(REBOOT_ESCROW_KEY_PROVIDER, USER_SYSTEM); |
| } |
| |
| private int getVbmetaDigestStatusOnRestoreComplete() { |
| String currentVbmetaDigest = mInjector.getVbmetaDigest(false); |
| String vbmetaDigestStored = mStorage.getString(REBOOT_ESCROW_KEY_VBMETA_DIGEST, |
| "", USER_SYSTEM); |
| String vbmetaDigestOtherStored = mStorage.getString(REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST, |
| "", USER_SYSTEM); |
| |
| // The other vbmeta digest is never set, assume no slot switch is attempted. |
| if (vbmetaDigestOtherStored.isEmpty()) { |
| if (currentVbmetaDigest.equals(vbmetaDigestStored)) { |
| return FrameworkStatsLog |
| .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_EXPECTED_SLOT; |
| } |
| return FrameworkStatsLog |
| .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MISMATCH; |
| } |
| |
| // The other vbmeta digest is set, we expect to boot into the new slot. |
| if (currentVbmetaDigest.equals(vbmetaDigestOtherStored)) { |
| return FrameworkStatsLog |
| .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_EXPECTED_SLOT; |
| } else if (currentVbmetaDigest.equals(vbmetaDigestStored)) { |
| return FrameworkStatsLog |
| .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_FALLBACK_SLOT; |
| } |
| return FrameworkStatsLog |
| .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MISMATCH; |
| } |
| |
| private void reportMetricOnRestoreComplete(boolean success, int attemptCount) { |
| int serviceType = mInjector.serverBasedResumeOnReboot() |
| ? FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED__TYPE__SERVER_BASED |
| : FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED__TYPE__HAL; |
| |
| long armedTimestamp = mStorage.getLong(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, -1, |
| USER_SYSTEM); |
| int escrowDurationInSeconds = -1; |
| long currentTimeStamp = mInjector.getCurrentTimeMillis(); |
| if (armedTimestamp != -1 && currentTimeStamp > armedTimestamp) { |
| escrowDurationInSeconds = (int) (currentTimeStamp - armedTimestamp) / 1000; |
| } |
| |
| int vbmetaDigestStatus = getVbmetaDigestStatusOnRestoreComplete(); |
| if (!success && mLoadEscrowDataErrorCode == ERROR_NONE) { |
| mLoadEscrowDataErrorCode = ERROR_UNKNOWN; |
| } |
| |
| Slog.i(TAG, "Reporting RoR recovery metrics, success: " + success + ", service type: " |
| + serviceType + ", error code: " + mLoadEscrowDataErrorCode); |
| // TODO(179105110) report the duration since boot complete. |
| mInjector.reportMetric(success, mLoadEscrowDataErrorCode, serviceType, attemptCount, |
| escrowDurationInSeconds, vbmetaDigestStatus, -1); |
| |
| mLoadEscrowDataErrorCode = ERROR_NONE; |
| } |
| |
| private void onEscrowRestoreComplete(boolean success, int attemptCount) { |
| int previousBootCount = mStorage.getInt(REBOOT_ESCROW_ARMED_KEY, -1, USER_SYSTEM); |
| |
| int bootCountDelta = mInjector.getBootCount() - previousBootCount; |
| if (success || (previousBootCount != -1 && bootCountDelta <= BOOT_COUNT_TOLERANCE)) { |
| reportMetricOnRestoreComplete(success, attemptCount); |
| } |
| |
| // Clear the old key in keystore. A new key will be generated by new RoR requests. |
| mKeyStoreManager.clearKeyStoreEncryptionKey(); |
| // Clear the saved reboot escrow provider |
| mInjector.clearRebootEscrowProvider(); |
| clearMetricsStorage(); |
| |
| if (mWakeLock != null) { |
| mWakeLock.release(); |
| } |
| } |
| |
| private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) throws IOException { |
| RebootEscrowProviderInterface rebootEscrowProvider = |
| mInjector.createRebootEscrowProviderIfNeeded(); |
| if (rebootEscrowProvider == null) { |
| Slog.w(TAG, |
| "Had reboot escrow data for users, but RebootEscrowProvider is unavailable"); |
| mLoadEscrowDataErrorCode = ERROR_NO_PROVIDER; |
| return null; |
| } |
| |
| // Server based RoR always need the decryption key from keystore. |
| if (rebootEscrowProvider.getType() == RebootEscrowProviderInterface.TYPE_SERVER_BASED |
| && kk == null) { |
| mLoadEscrowDataErrorCode = ERROR_KEYSTORE_FAILURE; |
| return null; |
| } |
| |
| // The K_s blob maybe encrypted by K_k as well. |
| RebootEscrowKey key = rebootEscrowProvider.getAndClearRebootEscrowKey(kk); |
| if (key != null) { |
| mEventLog.addEntry(RebootEscrowEvent.RETRIEVED_STORED_KEK); |
| } |
| return key; |
| } |
| |
| private boolean restoreRebootEscrowForUser(@UserIdInt int userId, RebootEscrowKey ks, |
| SecretKey kk) { |
| if (!mStorage.hasRebootEscrow(userId)) { |
| return false; |
| } |
| |
| try { |
| byte[] blob = mStorage.readRebootEscrow(userId); |
| mStorage.removeRebootEscrow(userId); |
| |
| RebootEscrowData escrowData = RebootEscrowData.fromEncryptedData(ks, blob, kk); |
| |
| mCallbacks.onRebootEscrowRestored(escrowData.getSpVersion(), |
| escrowData.getSyntheticPassword(), userId); |
| Slog.i(TAG, "Restored reboot escrow data for user " + userId); |
| mEventLog.addEntry(RebootEscrowEvent.RETRIEVED_LSKF_FOR_USER, userId); |
| return true; |
| } catch (IOException e) { |
| Slog.w(TAG, "Could not load reboot escrow data for user " + userId, e); |
| return false; |
| } |
| } |
| |
| void callToRebootEscrowIfNeeded(@UserIdInt int userId, byte spVersion, |
| byte[] syntheticPassword) { |
| if (!mRebootEscrowWanted) { |
| return; |
| } |
| |
| if (mInjector.createRebootEscrowProviderIfNeeded() == null) { |
| Slog.w(TAG, "Not storing escrow data, RebootEscrowProvider is unavailable"); |
| return; |
| } |
| |
| RebootEscrowKey escrowKey = generateEscrowKeyIfNeeded(); |
| if (escrowKey == null) { |
| Slog.e(TAG, "Could not generate escrow key"); |
| return; |
| } |
| |
| SecretKey kk = mKeyStoreManager.generateKeyStoreEncryptionKeyIfNeeded(); |
| if (kk == null) { |
| Slog.e(TAG, "Failed to generate encryption key from keystore."); |
| return; |
| } |
| |
| final RebootEscrowData escrowData; |
| try { |
| escrowData = RebootEscrowData.fromSyntheticPassword(escrowKey, spVersion, |
| syntheticPassword, kk); |
| } catch (IOException e) { |
| setRebootEscrowReady(false); |
| Slog.w(TAG, "Could not escrow reboot data", e); |
| return; |
| } |
| |
| mStorage.writeRebootEscrow(userId, escrowData.getBlob()); |
| mEventLog.addEntry(RebootEscrowEvent.STORED_LSKF_FOR_USER, userId); |
| |
| setRebootEscrowReady(true); |
| } |
| |
| private RebootEscrowKey generateEscrowKeyIfNeeded() { |
| synchronized (mKeyGenerationLock) { |
| if (mPendingRebootEscrowKey != null) { |
| return mPendingRebootEscrowKey; |
| } |
| |
| RebootEscrowKey key; |
| try { |
| key = RebootEscrowKey.generate(); |
| } catch (IOException e) { |
| Slog.w(TAG, "Could not generate reboot escrow key"); |
| return null; |
| } |
| |
| mPendingRebootEscrowKey = key; |
| return key; |
| } |
| } |
| |
| private void clearRebootEscrowIfNeeded() { |
| mRebootEscrowWanted = false; |
| setRebootEscrowReady(false); |
| |
| // We want to clear the internal data inside the provider, so always try to create the |
| // provider. |
| RebootEscrowProviderInterface rebootEscrowProvider = |
| mInjector.createRebootEscrowProviderIfNeeded(); |
| if (rebootEscrowProvider == null) { |
| Slog.w(TAG, "RebootEscrowProvider is unavailable for clear request"); |
| } else { |
| rebootEscrowProvider.clearRebootEscrowKey(); |
| } |
| |
| mInjector.clearRebootEscrowProvider(); |
| clearMetricsStorage(); |
| |
| List<UserInfo> users = mUserManager.getUsers(); |
| for (UserInfo user : users) { |
| mStorage.removeRebootEscrow(user.id); |
| } |
| |
| mEventLog.addEntry(RebootEscrowEvent.CLEARED_LSKF_REQUEST); |
| } |
| |
| @ArmRebootEscrowErrorCode int armRebootEscrowIfNeeded() { |
| if (!mRebootEscrowReady) { |
| return ARM_REBOOT_ERROR_ESCROW_NOT_READY; |
| } |
| |
| RebootEscrowProviderInterface rebootEscrowProvider = mInjector.getRebootEscrowProvider(); |
| if (rebootEscrowProvider == null) { |
| Slog.w(TAG, "Not storing escrow key, RebootEscrowProvider is unavailable"); |
| clearRebootEscrowIfNeeded(); |
| return ARM_REBOOT_ERROR_NO_PROVIDER; |
| } |
| |
| int expectedProviderType = mInjector.serverBasedResumeOnReboot() |
| ? RebootEscrowProviderInterface.TYPE_SERVER_BASED |
| : RebootEscrowProviderInterface.TYPE_HAL; |
| int actualProviderType = rebootEscrowProvider.getType(); |
| if (expectedProviderType != actualProviderType) { |
| Slog.w(TAG, "Expect reboot escrow provider " + expectedProviderType |
| + ", but the RoR is prepared with " + actualProviderType |
| + ". Please prepare the RoR again."); |
| clearRebootEscrowIfNeeded(); |
| return ARM_REBOOT_ERROR_PROVIDER_MISMATCH; |
| } |
| |
| RebootEscrowKey escrowKey; |
| synchronized (mKeyGenerationLock) { |
| escrowKey = mPendingRebootEscrowKey; |
| } |
| |
| if (escrowKey == null) { |
| Slog.e(TAG, "Escrow key is null, but escrow was marked as ready"); |
| clearRebootEscrowIfNeeded(); |
| return ARM_REBOOT_ERROR_NO_ESCROW_KEY; |
| } |
| |
| // We will use the same key from keystore to encrypt the escrow key and escrow data blob. |
| SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey(); |
| if (kk == null) { |
| Slog.e(TAG, "Failed to get encryption key from keystore."); |
| clearRebootEscrowIfNeeded(); |
| return ARM_REBOOT_ERROR_KEYSTORE_FAILURE; |
| } |
| |
| // TODO(b/183140900) design detailed errors for store escrow key errors. |
| // We don't clear rebootEscrow here, because some errors may be recoverable, e.g. network |
| // unavailable for server based provider. |
| boolean armedRebootEscrow = rebootEscrowProvider.storeRebootEscrowKey(escrowKey, kk); |
| if (!armedRebootEscrow) { |
| return ARM_REBOOT_ERROR_STORE_ESCROW_KEY; |
| } |
| |
| mStorage.setInt(REBOOT_ESCROW_ARMED_KEY, mInjector.getBootCount(), USER_SYSTEM); |
| mStorage.setLong(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, mInjector.getCurrentTimeMillis(), |
| USER_SYSTEM); |
| // Store the vbmeta digest of both slots. |
| mStorage.setString(REBOOT_ESCROW_KEY_VBMETA_DIGEST, mInjector.getVbmetaDigest(false), |
| USER_SYSTEM); |
| mStorage.setString(REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST, |
| mInjector.getVbmetaDigest(true), USER_SYSTEM); |
| mStorage.setInt(REBOOT_ESCROW_KEY_PROVIDER, actualProviderType, USER_SYSTEM); |
| mEventLog.addEntry(RebootEscrowEvent.SET_ARMED_STATUS); |
| |
| return ARM_REBOOT_ERROR_NONE; |
| } |
| |
| private void setRebootEscrowReady(boolean ready) { |
| if (mRebootEscrowReady != ready) { |
| mRebootEscrowListener.onPreparedForReboot(ready); |
| } |
| mRebootEscrowReady = ready; |
| } |
| |
| boolean prepareRebootEscrow() { |
| clearRebootEscrowIfNeeded(); |
| if (mInjector.createRebootEscrowProviderIfNeeded() == null) { |
| Slog.w(TAG, "No reboot escrow provider, skipping resume on reboot preparation."); |
| return false; |
| } |
| |
| mRebootEscrowWanted = true; |
| mEventLog.addEntry(RebootEscrowEvent.REQUESTED_LSKF); |
| return true; |
| } |
| |
| boolean clearRebootEscrow() { |
| clearRebootEscrowIfNeeded(); |
| return true; |
| } |
| |
| void setRebootEscrowListener(RebootEscrowListener listener) { |
| mRebootEscrowListener = listener; |
| } |
| |
| @VisibleForTesting |
| public static class RebootEscrowEvent { |
| static final int FOUND_ESCROW_DATA = 1; |
| static final int SET_ARMED_STATUS = 2; |
| static final int CLEARED_LSKF_REQUEST = 3; |
| static final int RETRIEVED_STORED_KEK = 4; |
| static final int REQUESTED_LSKF = 5; |
| static final int STORED_LSKF_FOR_USER = 6; |
| static final int RETRIEVED_LSKF_FOR_USER = 7; |
| |
| final int mEventId; |
| final Integer mUserId; |
| final long mWallTime; |
| final long mTimestamp; |
| |
| RebootEscrowEvent(int eventId) { |
| this(eventId, null); |
| } |
| |
| RebootEscrowEvent(int eventId, Integer userId) { |
| mEventId = eventId; |
| mUserId = userId; |
| mTimestamp = SystemClock.uptimeMillis(); |
| mWallTime = System.currentTimeMillis(); |
| } |
| |
| String getEventDescription() { |
| switch (mEventId) { |
| case FOUND_ESCROW_DATA: |
| return "Found escrow data"; |
| case SET_ARMED_STATUS: |
| return "Set armed status"; |
| case CLEARED_LSKF_REQUEST: |
| return "Cleared request for LSKF"; |
| case RETRIEVED_STORED_KEK: |
| return "Retrieved stored KEK"; |
| case REQUESTED_LSKF: |
| return "Requested LSKF"; |
| case STORED_LSKF_FOR_USER: |
| return "Stored LSKF for user"; |
| case RETRIEVED_LSKF_FOR_USER: |
| return "Retrieved LSKF for user"; |
| default: |
| return "Unknown event ID " + mEventId; |
| } |
| } |
| } |
| |
| @VisibleForTesting |
| public static class RebootEscrowEventLog { |
| private RebootEscrowEvent[] mEntries = new RebootEscrowEvent[16]; |
| private int mNextIndex = 0; |
| |
| void addEntry(int eventId) { |
| addEntryInternal(new RebootEscrowEvent(eventId)); |
| } |
| |
| void addEntry(int eventId, int userId) { |
| addEntryInternal(new RebootEscrowEvent(eventId, userId)); |
| } |
| |
| private void addEntryInternal(RebootEscrowEvent event) { |
| final int index = mNextIndex; |
| mEntries[index] = event; |
| mNextIndex = (mNextIndex + 1) % mEntries.length; |
| } |
| |
| void dump(@NonNull IndentingPrintWriter pw) { |
| SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US); |
| |
| for (int i = 0; i < mEntries.length; ++i) { |
| RebootEscrowEvent event = mEntries[(i + mNextIndex) % mEntries.length]; |
| if (event == null) { |
| continue; |
| } |
| |
| pw.print("Event #"); |
| pw.println(i); |
| |
| pw.println(" time=" + sdf.format(new Date(event.mWallTime)) |
| + " (timestamp=" + event.mTimestamp + ")"); |
| |
| pw.print(" event="); |
| pw.println(event.getEventDescription()); |
| |
| if (event.mUserId != null) { |
| pw.print(" user="); |
| pw.println(event.mUserId); |
| } |
| } |
| } |
| } |
| |
| void dump(@NonNull IndentingPrintWriter pw) { |
| pw.print("mRebootEscrowWanted="); |
| pw.println(mRebootEscrowWanted); |
| |
| pw.print("mRebootEscrowReady="); |
| pw.println(mRebootEscrowReady); |
| |
| pw.print("mRebootEscrowListener="); |
| pw.println(mRebootEscrowListener); |
| |
| boolean keySet; |
| synchronized (mKeyGenerationLock) { |
| keySet = mPendingRebootEscrowKey != null; |
| } |
| |
| pw.print("mPendingRebootEscrowKey is "); |
| pw.println(keySet ? "set" : "not set"); |
| |
| RebootEscrowProviderInterface provider = mInjector.getRebootEscrowProvider(); |
| String providerType = provider == null ? "null" : String.valueOf(provider.getType()); |
| pw.print("RebootEscrowProvider type is " + providerType); |
| |
| pw.println(); |
| pw.println("Event log:"); |
| pw.increaseIndent(); |
| mEventLog.dump(pw); |
| pw.println(); |
| pw.decreaseIndent(); |
| } |
| } |