Delay loadEscrowData from locksettings
For server-based ror, we want to deplay loadEscrowData until 1. Binding to Gmscore works 2. Network is available So we factor out the loadEscrowData to be called later in the start sequence of system server. Also make the function async so that we can retry on temporarily failures. Bug: 172780686 Test: run server based ror Change-Id: If71f8974f94eb1be2ab896e95601377196e01790
This commit is contained in:
@@ -280,6 +280,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
super.onBootPhase(phase);
|
||||
if (phase == PHASE_ACTIVITY_MANAGER_READY) {
|
||||
mLockSettingsService.migrateOldDataAfterSystemReady();
|
||||
mLockSettingsService.loadEscrowData();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -832,11 +833,15 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
mSpManager.initWeaverService();
|
||||
getAuthSecretHal();
|
||||
mDeviceProvisionedObserver.onSystemReady();
|
||||
mRebootEscrowManager.loadRebootEscrowDataIfAvailable();
|
||||
|
||||
// TODO: maybe skip this for split system user mode.
|
||||
mStorage.prefetchUser(UserHandle.USER_SYSTEM);
|
||||
}
|
||||
|
||||
private void loadEscrowData() {
|
||||
mRebootEscrowManager.loadRebootEscrowDataIfAvailable(mHandler);
|
||||
}
|
||||
|
||||
private void getAuthSecretHal() {
|
||||
try {
|
||||
mAuthSecretService = IAuthSecret.getService(/* retry */ true);
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.annotation.NonNull;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.content.Context;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserManager;
|
||||
import android.provider.DeviceConfig;
|
||||
@@ -39,6 +40,7 @@ import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
@@ -75,6 +77,13 @@ class RebootEscrowManager {
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@@ -148,6 +157,14 @@ class RebootEscrowManager {
|
||||
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() {
|
||||
return mContext;
|
||||
}
|
||||
@@ -199,7 +216,18 @@ class RebootEscrowManager {
|
||||
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> rebootEscrowUsers = new ArrayList<>();
|
||||
for (UserInfo user : users) {
|
||||
@@ -212,17 +240,49 @@ class RebootEscrowManager {
|
||||
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
|
||||
// generated before reboot. Note that we will clear the escrow key even if the keystore key
|
||||
// is null.
|
||||
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) {
|
||||
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);
|
||||
onGetRebootEscrowKeyFailed(users);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -249,7 +309,7 @@ class RebootEscrowManager {
|
||||
}
|
||||
}
|
||||
|
||||
private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) {
|
||||
private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) throws IOException {
|
||||
RebootEscrowProviderInterface rebootEscrowProvider = mInjector.getRebootEscrowProvider();
|
||||
if (rebootEscrowProvider == null) {
|
||||
Slog.w(TAG,
|
||||
|
||||
@@ -33,7 +33,7 @@ import javax.crypto.SecretKey;
|
||||
* An implementation of the {@link RebootEscrowProviderInterface} by calling the RebootEscrow HAL.
|
||||
*/
|
||||
class RebootEscrowProviderHalImpl implements RebootEscrowProviderInterface {
|
||||
private static final String TAG = "RebootEscrowProvider";
|
||||
private static final String TAG = "RebootEscrowProviderHal";
|
||||
|
||||
private final Injector mInjector;
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.server.locksettings;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
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,
|
||||
* 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.
|
||||
|
||||
@@ -35,7 +35,7 @@ import javax.crypto.SecretKey;
|
||||
* encrypt & decrypt the blob.
|
||||
*/
|
||||
class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterface {
|
||||
private static final String TAG = "RebootEscrowProvider";
|
||||
private static final String TAG = "RebootEscrowProviderServerBased";
|
||||
|
||||
// Timeout for service binding
|
||||
private static final long DEFAULT_SERVICE_TIMEOUT_IN_SECONDS = 10;
|
||||
@@ -50,6 +50,8 @@ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterfa
|
||||
|
||||
private final Injector mInjector;
|
||||
|
||||
private byte[] mServerBlob;
|
||||
|
||||
static class Injector {
|
||||
private ResumeOnRebootServiceConnection mServiceConnection = null;
|
||||
|
||||
@@ -124,17 +126,20 @@ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterfa
|
||||
}
|
||||
|
||||
@Override
|
||||
public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) {
|
||||
byte[] serverBlob = mStorage.readRebootEscrowServerBlob();
|
||||
public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) throws IOException {
|
||||
if (mServerBlob == null) {
|
||||
mServerBlob = mStorage.readRebootEscrowServerBlob();
|
||||
}
|
||||
// Delete the server blob in storage.
|
||||
mStorage.removeRebootEscrowServerBlob();
|
||||
if (serverBlob == null) {
|
||||
if (mServerBlob == null) {
|
||||
Slog.w(TAG, "Failed to read reboot escrow server blob from storage");
|
||||
return null;
|
||||
}
|
||||
|
||||
Slog.i(TAG, "Loaded reboot escrow server blob from storage");
|
||||
try {
|
||||
byte[] escrowKeyBytes = unwrapServerBlob(serverBlob, decryptionKey);
|
||||
byte[] escrowKeyBytes = unwrapServerBlob(mServerBlob, decryptionKey);
|
||||
if (escrowKeyBytes == null) {
|
||||
Slog.w(TAG, "Decrypted reboot escrow key bytes should not be null");
|
||||
return null;
|
||||
@@ -145,7 +150,7 @@ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterfa
|
||||
}
|
||||
|
||||
return RebootEscrowKey.fromKeyBytes(escrowKeyBytes);
|
||||
} catch (TimeoutException | RemoteException | IOException e) {
|
||||
} catch (TimeoutException | RemoteException e) {
|
||||
Slog.w(TAG, "Failed to decrypt the server blob ", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.hardware.rebootescrow.IRebootEscrow;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceSpecificException;
|
||||
import android.os.UserManager;
|
||||
@@ -154,6 +155,11 @@ public class RebootEscrowManagerTests {
|
||||
mInjected = injected;
|
||||
}
|
||||
|
||||
@Override
|
||||
void post(Handler handler, Runnable runnable) {
|
||||
runnable.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserManager getUserManager() {
|
||||
return mUserManager;
|
||||
@@ -369,7 +375,7 @@ public class RebootEscrowManagerTests {
|
||||
|
||||
@Test
|
||||
public void loadRebootEscrowDataIfAvailable_NothingAvailable_Success() throws Exception {
|
||||
mService.loadRebootEscrowDataIfAvailable();
|
||||
mService.loadRebootEscrowDataIfAvailable(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -401,7 +407,7 @@ public class RebootEscrowManagerTests {
|
||||
doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture());
|
||||
when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue());
|
||||
|
||||
mService.loadRebootEscrowDataIfAvailable();
|
||||
mService.loadRebootEscrowDataIfAvailable(null);
|
||||
verify(mRebootEscrow).retrieveKey();
|
||||
assertTrue(metricsSuccessCaptor.getValue());
|
||||
verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
|
||||
@@ -435,7 +441,7 @@ public class RebootEscrowManagerTests {
|
||||
|
||||
when(mServiceConnection.unwrap(any(), anyLong()))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
mService.loadRebootEscrowDataIfAvailable();
|
||||
mService.loadRebootEscrowDataIfAvailable(null);
|
||||
verify(mServiceConnection).unwrap(any(), anyLong());
|
||||
assertTrue(metricsSuccessCaptor.getValue());
|
||||
verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
|
||||
@@ -466,7 +472,7 @@ public class RebootEscrowManagerTests {
|
||||
when(mInjected.getBootCount()).thenReturn(10);
|
||||
when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]);
|
||||
|
||||
mService.loadRebootEscrowDataIfAvailable();
|
||||
mService.loadRebootEscrowDataIfAvailable(null);
|
||||
verify(mRebootEscrow).retrieveKey();
|
||||
verify(mInjected, never()).reportMetric(anyBoolean());
|
||||
}
|
||||
@@ -493,7 +499,7 @@ public class RebootEscrowManagerTests {
|
||||
when(mInjected.getBootCount()).thenReturn(10);
|
||||
when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]);
|
||||
|
||||
mService.loadRebootEscrowDataIfAvailable();
|
||||
mService.loadRebootEscrowDataIfAvailable(null);
|
||||
verify(mInjected, never()).reportMetric(anyBoolean());
|
||||
}
|
||||
|
||||
@@ -527,7 +533,7 @@ public class RebootEscrowManagerTests {
|
||||
when(mInjected.getBootCount()).thenReturn(10);
|
||||
when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue());
|
||||
|
||||
mService.loadRebootEscrowDataIfAvailable();
|
||||
mService.loadRebootEscrowDataIfAvailable(null);
|
||||
verify(mInjected).reportMetric(eq(true));
|
||||
}
|
||||
|
||||
@@ -557,7 +563,7 @@ public class RebootEscrowManagerTests {
|
||||
ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
|
||||
doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture());
|
||||
when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> new byte[32]);
|
||||
mService.loadRebootEscrowDataIfAvailable();
|
||||
mService.loadRebootEscrowDataIfAvailable(null);
|
||||
verify(mRebootEscrow).retrieveKey();
|
||||
assertFalse(metricsSuccessCaptor.getValue());
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.os.RemoteException;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
@@ -42,7 +43,6 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
@@ -130,7 +130,7 @@ public class RebootEscrowProviderServerBasedImplTests {
|
||||
@Test
|
||||
public void getAndClearRebootEscrowKey_ServiceConnectionException_failure() throws Exception {
|
||||
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());
|
||||
mRebootEscrowProvider.storeRebootEscrowKey(mRebootEscrowKey, mKeyStoreEncryptionKey);
|
||||
|
||||
Reference in New Issue
Block a user