Merge "Delay loadEscrowData from locksettings" am: 60edeec07e am: cd9d960d23

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1566555

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I6ea608a47dc07a2cd96b5c6d1e9dfd4c42f366df
This commit is contained in:
Tianjie Xu
2021-02-11 22:57:36 +00:00
committed by Automerger Merge Worker
7 changed files with 106 additions and 27 deletions

View File

@@ -280,6 +280,7 @@ public class LockSettingsService extends ILockSettings.Stub {
super.onBootPhase(phase); super.onBootPhase(phase);
if (phase == PHASE_ACTIVITY_MANAGER_READY) { if (phase == PHASE_ACTIVITY_MANAGER_READY) {
mLockSettingsService.migrateOldDataAfterSystemReady(); mLockSettingsService.migrateOldDataAfterSystemReady();
mLockSettingsService.loadEscrowData();
} }
} }
@@ -832,11 +833,15 @@ public class LockSettingsService extends ILockSettings.Stub {
mSpManager.initWeaverService(); mSpManager.initWeaverService();
getAuthSecretHal(); getAuthSecretHal();
mDeviceProvisionedObserver.onSystemReady(); mDeviceProvisionedObserver.onSystemReady();
mRebootEscrowManager.loadRebootEscrowDataIfAvailable();
// TODO: maybe skip this for split system user mode. // TODO: maybe skip this for split system user mode.
mStorage.prefetchUser(UserHandle.USER_SYSTEM); mStorage.prefetchUser(UserHandle.USER_SYSTEM);
} }
private void loadEscrowData() {
mRebootEscrowManager.loadRebootEscrowDataIfAvailable(mHandler);
}
private void getAuthSecretHal() { private void getAuthSecretHal() {
try { try {
mAuthSecretService = IAuthSecret.getService(/* retry */ true); mAuthSecretService = IAuthSecret.getService(/* retry */ true);

View File

@@ -21,6 +21,7 @@ import android.annotation.NonNull;
import android.annotation.UserIdInt; import android.annotation.UserIdInt;
import android.content.Context; import android.content.Context;
import android.content.pm.UserInfo; import android.content.pm.UserInfo;
import android.os.Handler;
import android.os.SystemClock; import android.os.SystemClock;
import android.os.UserManager; import android.os.UserManager;
import android.provider.DeviceConfig; import android.provider.DeviceConfig;
@@ -39,6 +40,7 @@ import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
@@ -75,6 +77,13 @@ class RebootEscrowManager {
*/ */
private static final int BOOT_COUNT_TOLERANCE = 5; 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;
/** /**
* Logs events for later debugging in bugreports. * Logs events for later debugging in bugreports.
*/ */
@@ -148,6 +157,14 @@ class RebootEscrowManager {
return null; return null;
} }
void post(Handler handler, Runnable runnable) {
handler.post(runnable);
}
void postDelayed(Handler handler, Runnable runnable, long delayMillis) {
handler.postDelayed(runnable, delayMillis);
}
public Context getContext() { public Context getContext() {
return mContext; return mContext;
} }
@@ -199,7 +216,18 @@ class RebootEscrowManager {
mKeyStoreManager = injector.getKeyStoreManager(); mKeyStoreManager = injector.getKeyStoreManager();
} }
void loadRebootEscrowDataIfAvailable() { private void onGetRebootEscrowKeyFailed(List<UserInfo> users) {
Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage.");
for (UserInfo user : users) {
mStorage.removeRebootEscrow(user.id);
}
// Clear the old key in keystore.
mKeyStoreManager.clearKeyStoreEncryptionKey();
onEscrowRestoreComplete(false);
}
void loadRebootEscrowDataIfAvailable(Handler retryHandler) {
List<UserInfo> users = mUserManager.getUsers(); List<UserInfo> users = mUserManager.getUsers();
List<UserInfo> rebootEscrowUsers = new ArrayList<>(); List<UserInfo> rebootEscrowUsers = new ArrayList<>();
for (UserInfo user : users) { for (UserInfo user : users) {
@@ -212,17 +240,49 @@ class RebootEscrowManager {
return; return;
} }
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 = DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA,
"load_escrow_data_retry_count", DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT);
final int retryIntervalInSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA,
"load_escrow_data_retry_interval_seconds",
DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS);
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");
onGetRebootEscrowKeyFailed(users);
}
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 // 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 // generated before reboot. Note that we will clear the escrow key even if the keystore key
// is null. // is null.
SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey(); SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey();
RebootEscrowKey escrowKey = getAndClearRebootEscrowKey(kk); RebootEscrowKey escrowKey;
try {
escrowKey = getAndClearRebootEscrowKey(kk);
} catch (IOException e) {
scheduleLoadRebootEscrowDataOrFail(retryHandler, attemptNumber + 1, users,
rebootEscrowUsers);
return;
}
if (kk == null || escrowKey == null) { if (kk == null || escrowKey == null) {
Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage."); onGetRebootEscrowKeyFailed(users);
for (UserInfo user : users) {
mStorage.removeRebootEscrow(user.id);
}
onEscrowRestoreComplete(false);
return; return;
} }
@@ -249,7 +309,7 @@ class RebootEscrowManager {
} }
} }
private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) { private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) throws IOException {
RebootEscrowProviderInterface rebootEscrowProvider = mInjector.getRebootEscrowProvider(); RebootEscrowProviderInterface rebootEscrowProvider = mInjector.getRebootEscrowProvider();
if (rebootEscrowProvider == null) { if (rebootEscrowProvider == null) {
Slog.w(TAG, Slog.w(TAG,

View File

@@ -33,7 +33,7 @@ import javax.crypto.SecretKey;
* An implementation of the {@link RebootEscrowProviderInterface} by calling the RebootEscrow HAL. * An implementation of the {@link RebootEscrowProviderInterface} by calling the RebootEscrow HAL.
*/ */
class RebootEscrowProviderHalImpl implements RebootEscrowProviderInterface { class RebootEscrowProviderHalImpl implements RebootEscrowProviderInterface {
private static final String TAG = "RebootEscrowProvider"; private static final String TAG = "RebootEscrowProviderHal";
private final Injector mInjector; private final Injector mInjector;

View File

@@ -16,6 +16,8 @@
package com.android.server.locksettings; package com.android.server.locksettings;
import java.io.IOException;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
/** /**
@@ -33,9 +35,10 @@ public interface RebootEscrowProviderInterface {
/** /**
* Returns the stored RebootEscrowKey, and clears the storage. If the stored key is encrypted, * Returns the stored RebootEscrowKey, and clears the storage. If the stored key is encrypted,
* use the input key to decrypt the RebootEscrowKey. Returns null on failure. * use the input key to decrypt the RebootEscrowKey. Returns null on failure. Throws an
* IOException if the failure is non-fatal, and a retry may succeed.
*/ */
RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey); RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) throws IOException;
/** /**
* Clears the stored RebootEscrowKey. * Clears the stored RebootEscrowKey.

View File

@@ -35,7 +35,7 @@ import javax.crypto.SecretKey;
* encrypt & decrypt the blob. * encrypt & decrypt the blob.
*/ */
class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterface { class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterface {
private static final String TAG = "RebootEscrowProvider"; private static final String TAG = "RebootEscrowProviderServerBased";
// Timeout for service binding // Timeout for service binding
private static final long DEFAULT_SERVICE_TIMEOUT_IN_SECONDS = 10; private static final long DEFAULT_SERVICE_TIMEOUT_IN_SECONDS = 10;
@@ -50,6 +50,8 @@ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterfa
private final Injector mInjector; private final Injector mInjector;
private byte[] mServerBlob;
static class Injector { static class Injector {
private ResumeOnRebootServiceConnection mServiceConnection = null; private ResumeOnRebootServiceConnection mServiceConnection = null;
@@ -124,17 +126,20 @@ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterfa
} }
@Override @Override
public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) { public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) throws IOException {
byte[] serverBlob = mStorage.readRebootEscrowServerBlob(); if (mServerBlob == null) {
mServerBlob = mStorage.readRebootEscrowServerBlob();
}
// Delete the server blob in storage. // Delete the server blob in storage.
mStorage.removeRebootEscrowServerBlob(); mStorage.removeRebootEscrowServerBlob();
if (serverBlob == null) { if (mServerBlob == null) {
Slog.w(TAG, "Failed to read reboot escrow server blob from storage"); Slog.w(TAG, "Failed to read reboot escrow server blob from storage");
return null; return null;
} }
Slog.i(TAG, "Loaded reboot escrow server blob from storage");
try { try {
byte[] escrowKeyBytes = unwrapServerBlob(serverBlob, decryptionKey); byte[] escrowKeyBytes = unwrapServerBlob(mServerBlob, decryptionKey);
if (escrowKeyBytes == null) { if (escrowKeyBytes == null) {
Slog.w(TAG, "Decrypted reboot escrow key bytes should not be null"); Slog.w(TAG, "Decrypted reboot escrow key bytes should not be null");
return null; return null;
@@ -145,7 +150,7 @@ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterfa
} }
return RebootEscrowKey.fromKeyBytes(escrowKeyBytes); return RebootEscrowKey.fromKeyBytes(escrowKeyBytes);
} catch (TimeoutException | RemoteException | IOException e) { } catch (TimeoutException | RemoteException e) {
Slog.w(TAG, "Failed to decrypt the server blob ", e); Slog.w(TAG, "Failed to decrypt the server blob ", e);
return null; return null;
} }

View File

@@ -43,6 +43,7 @@ import android.content.Context;
import android.content.ContextWrapper; import android.content.ContextWrapper;
import android.content.pm.UserInfo; import android.content.pm.UserInfo;
import android.hardware.rebootescrow.IRebootEscrow; import android.hardware.rebootescrow.IRebootEscrow;
import android.os.Handler;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ServiceSpecificException; import android.os.ServiceSpecificException;
import android.os.UserManager; import android.os.UserManager;
@@ -154,6 +155,11 @@ public class RebootEscrowManagerTests {
mInjected = injected; mInjected = injected;
} }
@Override
void post(Handler handler, Runnable runnable) {
runnable.run();
}
@Override @Override
public UserManager getUserManager() { public UserManager getUserManager() {
return mUserManager; return mUserManager;
@@ -369,7 +375,7 @@ public class RebootEscrowManagerTests {
@Test @Test
public void loadRebootEscrowDataIfAvailable_NothingAvailable_Success() throws Exception { public void loadRebootEscrowDataIfAvailable_NothingAvailable_Success() throws Exception {
mService.loadRebootEscrowDataIfAvailable(); mService.loadRebootEscrowDataIfAvailable(null);
} }
@Test @Test
@@ -401,7 +407,7 @@ public class RebootEscrowManagerTests {
doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture()); doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture());
when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue()); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue());
mService.loadRebootEscrowDataIfAvailable(); mService.loadRebootEscrowDataIfAvailable(null);
verify(mRebootEscrow).retrieveKey(); verify(mRebootEscrow).retrieveKey();
assertTrue(metricsSuccessCaptor.getValue()); assertTrue(metricsSuccessCaptor.getValue());
verify(mKeyStoreManager).clearKeyStoreEncryptionKey(); verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
@@ -435,7 +441,7 @@ public class RebootEscrowManagerTests {
when(mServiceConnection.unwrap(any(), anyLong())) when(mServiceConnection.unwrap(any(), anyLong()))
.thenAnswer(invocation -> invocation.getArgument(0)); .thenAnswer(invocation -> invocation.getArgument(0));
mService.loadRebootEscrowDataIfAvailable(); mService.loadRebootEscrowDataIfAvailable(null);
verify(mServiceConnection).unwrap(any(), anyLong()); verify(mServiceConnection).unwrap(any(), anyLong());
assertTrue(metricsSuccessCaptor.getValue()); assertTrue(metricsSuccessCaptor.getValue());
verify(mKeyStoreManager).clearKeyStoreEncryptionKey(); verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
@@ -466,7 +472,7 @@ public class RebootEscrowManagerTests {
when(mInjected.getBootCount()).thenReturn(10); when(mInjected.getBootCount()).thenReturn(10);
when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]); when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]);
mService.loadRebootEscrowDataIfAvailable(); mService.loadRebootEscrowDataIfAvailable(null);
verify(mRebootEscrow).retrieveKey(); verify(mRebootEscrow).retrieveKey();
verify(mInjected, never()).reportMetric(anyBoolean()); verify(mInjected, never()).reportMetric(anyBoolean());
} }
@@ -493,7 +499,7 @@ public class RebootEscrowManagerTests {
when(mInjected.getBootCount()).thenReturn(10); when(mInjected.getBootCount()).thenReturn(10);
when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]); when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]);
mService.loadRebootEscrowDataIfAvailable(); mService.loadRebootEscrowDataIfAvailable(null);
verify(mInjected, never()).reportMetric(anyBoolean()); verify(mInjected, never()).reportMetric(anyBoolean());
} }
@@ -527,7 +533,7 @@ public class RebootEscrowManagerTests {
when(mInjected.getBootCount()).thenReturn(10); when(mInjected.getBootCount()).thenReturn(10);
when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue()); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue());
mService.loadRebootEscrowDataIfAvailable(); mService.loadRebootEscrowDataIfAvailable(null);
verify(mInjected).reportMetric(eq(true)); verify(mInjected).reportMetric(eq(true));
} }
@@ -557,7 +563,7 @@ public class RebootEscrowManagerTests {
ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture()); doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture());
when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> new byte[32]); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> new byte[32]);
mService.loadRebootEscrowDataIfAvailable(); mService.loadRebootEscrowDataIfAvailable(null);
verify(mRebootEscrow).retrieveKey(); verify(mRebootEscrow).retrieveKey();
assertFalse(metricsSuccessCaptor.getValue()); assertFalse(metricsSuccessCaptor.getValue());
} }

View File

@@ -30,6 +30,7 @@ import static org.mockito.Mockito.when;
import android.content.Context; import android.content.Context;
import android.content.ContextWrapper; import android.content.ContextWrapper;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry; import androidx.test.InstrumentationRegistry;
@@ -42,7 +43,6 @@ import org.junit.runner.RunWith;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import java.io.File; import java.io.File;
import java.io.IOException;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
@@ -130,7 +130,7 @@ public class RebootEscrowProviderServerBasedImplTests {
@Test @Test
public void getAndClearRebootEscrowKey_ServiceConnectionException_failure() throws Exception { public void getAndClearRebootEscrowKey_ServiceConnectionException_failure() throws Exception {
when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())).thenAnswer(mFakeEncryption); when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())).thenAnswer(mFakeEncryption);
doThrow(IOException.class).when(mServiceConnection).unwrap(any(), anyLong()); doThrow(RemoteException.class).when(mServiceConnection).unwrap(any(), anyLong());
assertTrue(mRebootEscrowProvider.hasRebootEscrowSupport()); assertTrue(mRebootEscrowProvider.hasRebootEscrowSupport());
mRebootEscrowProvider.storeRebootEscrowKey(mRebootEscrowKey, mKeyStoreEncryptionKey); mRebootEscrowProvider.storeRebootEscrowKey(mRebootEscrowKey, mKeyStoreEncryptionKey);