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:
Tianjie
2021-02-09 11:36:45 -08:00
committed by Tianjie Xu
parent 3faed136d5
commit 1c98980d00
7 changed files with 106 additions and 27 deletions

View File

@@ -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);

View File

@@ -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,

View File

@@ -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;

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -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());
}

View File

@@ -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);