Merge "Create a verify flag to support verification for entering repair mode" into udc-qpr-dev

This commit is contained in:
Rhed Jao
2023-06-13 02:14:22 +00:00
committed by Android (Google) Code Review
9 changed files with 335 additions and 4 deletions

View File

@@ -116,6 +116,17 @@ public class KeyguardManager {
public static final String ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL =
"android.app.action.CONFIRM_REMOTE_DEVICE_CREDENTIAL";
/**
* Intent used to prompt user for device credential for entering repair
* mode. If the credential is verified successfully, then the information
* needed to verify the credential again will be written to a location that
* is available to repair mode. This makes it possible for repair mode to
* require that the same credential be provided to exit repair mode.
* @hide
*/
public static final String ACTION_PREPARE_REPAIR_MODE_DEVICE_CREDENTIAL =
"android.app.action.PREPARE_REPAIR_MODE_DEVICE_CREDENTIAL";
/**
* A CharSequence dialog title to show to the user when used with a
* {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL}.

View File

@@ -47,6 +47,7 @@ import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
@@ -160,9 +161,17 @@ public class LockPatternUtils {
*/
public static final int VERIFY_FLAG_REQUEST_GK_PW_HANDLE = 1 << 0;
/**
* Flag provided to {@link #verifyCredential(LockscreenCredential, int, int)} . If set, the
* method writes the password data to the repair mode file after the credential is verified
* successfully.
*/
public static final int VERIFY_FLAG_WRITE_REPAIR_MODE_PW = 1 << 1;
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {
VERIFY_FLAG_REQUEST_GK_PW_HANDLE
VERIFY_FLAG_REQUEST_GK_PW_HANDLE,
VERIFY_FLAG_WRITE_REPAIR_MODE_PW
})
public @interface VerifyFlag {}
@@ -201,6 +210,8 @@ public class LockPatternUtils {
public static final String CURRENT_LSKF_BASED_PROTECTOR_ID_KEY = "sp-handle";
public static final String PASSWORD_HISTORY_DELIMITER = ",";
private static final String GSI_RUNNING_PROP = "ro.gsid.image_running";
/**
* drives the pin auto confirmation feature availability in code logic.
*/
@@ -1846,6 +1857,37 @@ public class LockPatternUtils {
com.android.internal.R.bool.config_enableCredentialFactoryResetProtection);
}
/**
* Return {@code true} if repair mode is supported by the device.
*/
public static boolean isRepairModeSupported(Context context) {
return context.getResources().getBoolean(
com.android.internal.R.bool.config_repairModeSupported);
}
/**
* Return {@code true} if repair mode is active on the device.
*/
public static boolean isRepairModeActive(Context context) {
return Settings.Global.getInt(context.getContentResolver(),
Settings.Global.REPAIR_MODE_ACTIVE, /* def= */ 0) > 0;
}
/**
* Return {@code true} if repair mode is supported by the device and the user has been granted
* admin privileges.
*/
public static boolean canUserEnterRepairMode(Context context, UserInfo info) {
return info != null && info.isAdmin() && isRepairModeSupported(context);
}
/**
* Return {@code true} if GSI is running on the device.
*/
public static boolean isGsiRunning() {
return SystemProperties.getInt(GSI_RUNNING_PROP, 0) > 0;
}
/**
* Attempt to rederive the unified work challenge for the specified profile user and unlock the
* user. If successful, this would allow the user to leave quiet mode automatically without

View File

@@ -41,6 +41,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
import static com.android.internal.widget.LockPatternUtils.USER_FRP;
import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE;
import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW;
import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential;
import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_STRONG;
@@ -278,8 +279,6 @@ public class LockSettingsService extends ILockSettings.Stub {
protected IGateKeeperService mGateKeeperService;
protected IAuthSecret mAuthSecretService;
private static final String GSI_RUNNING_PROP = "ro.gsid.image_running";
/**
* The UIDs that are used for system credential storage in keystore.
*/
@@ -311,6 +310,7 @@ public class LockSettingsService extends ILockSettings.Stub {
if (phase == PHASE_ACTIVITY_MANAGER_READY) {
mLockSettingsService.migrateOldDataAfterSystemReady();
mLockSettingsService.loadEscrowData();
mLockSettingsService.deleteRepairModePersistentDataIfNeeded();
}
}
@@ -541,7 +541,7 @@ public class LockSettingsService extends ILockSettings.Stub {
}
public boolean isGsiRunning() {
return SystemProperties.getInt(GSI_RUNNING_PROP, 0) > 0;
return LockPatternUtils.isGsiRunning();
}
public FingerprintManager getFingerprintManager() {
@@ -949,6 +949,16 @@ public class LockSettingsService extends ILockSettings.Stub {
return success;
}
@VisibleForTesting
void deleteRepairModePersistentDataIfNeeded() {
if (!LockPatternUtils.isRepairModeSupported(mContext)
|| LockPatternUtils.isRepairModeActive(mContext)
|| mInjector.isGsiRunning()) {
return;
}
mStorage.deleteRepairModePersistentData();
}
// This is called when Weaver is guaranteed to be available (if the device supports Weaver).
// It does any synthetic password related work that was delayed from earlier in the boot.
private void onThirdPartyAppsStarted() {
@@ -2202,6 +2212,12 @@ public class LockSettingsService extends ILockSettings.Stub {
response = authResult.gkResponse;
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
if ((flags & VERIFY_FLAG_WRITE_REPAIR_MODE_PW) != 0) {
if (!mSpManager.writeRepairModeCredentialLocked(protectorId, userId)) {
Slog.e(TAG, "Failed to write repair mode credential");
return VerifyCredentialResponse.ERROR;
}
}
// credential has matched
mBiometricDeferredQueue.addPendingLockoutResetForUser(userId,
authResult.syntheticPassword.deriveGkPassword());

View File

@@ -90,6 +90,9 @@ class LockSettingsStorage {
private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/";
private static final String REPAIR_MODE_DIRECTORY = "repair-mode/";
private static final String REPAIR_MODE_PERSISTENT_FILE = "pst";
private static final Object DEFAULT = new Object();
private static final String[] SETTINGS_TO_BACKUP = new String[] {
@@ -390,6 +393,29 @@ class LockSettingsStorage {
}
}
@VisibleForTesting
File getRepairModePersistentDataFile() {
final File directory = new File(Environment.getMetadataDirectory(), REPAIR_MODE_DIRECTORY);
return new File(directory, REPAIR_MODE_PERSISTENT_FILE);
}
public PersistentData readRepairModePersistentData() {
final byte[] data = readFile(getRepairModePersistentDataFile());
if (data == null) {
return PersistentData.NONE;
}
return PersistentData.fromBytes(data);
}
public void writeRepairModePersistentData(int persistentType, int userId, byte[] payload) {
writeFile(getRepairModePersistentDataFile(),
PersistentData.toBytes(persistentType, userId, /* qualityForUi= */0, payload));
}
public void deleteRepairModePersistentData() {
deleteFile(getRepairModePersistentDataFile());
}
/**
* Writes the synthetic password state file for the given user ID, protector ID, and state name.
* If the file already exists, then it is atomically replaced.
@@ -583,6 +609,17 @@ class LockSettingsStorage {
}
}
/**
* Provides a concrete data structure to represent the minimal information from
* a user's LSKF-based SP protector that is needed to verify the user's LSKF,
* in combination with the corresponding Gatekeeper enrollment or Weaver slot.
* It can be stored in {@link com.android.server.PersistentDataBlockService} for
* FRP to live across factory resets not initiated via the Settings UI.
* Written to {@link #REPAIR_MODE_PERSISTENT_FILE} to support verification for
* exiting repair mode, since the device runs with an empty data partition in
* repair mode and the same credential be provided to exit repair mode is
* required.
*/
public static class PersistentData {
static final byte VERSION_1 = 1;
static final int VERSION_1_HEADER_SIZE = 1 + 1 + 4 + 4;
@@ -685,6 +722,19 @@ class LockSettingsStorage {
}
pw.decreaseIndent();
}
// Dump repair mode file states
final File repairModeFile = getRepairModePersistentDataFile();
if (repairModeFile.exists()) {
pw.println(TextUtils.formatSimple("Repair Mode [%s]:", repairModeFile.getParent()));
pw.increaseIndent();
pw.println(TextUtils.formatSimple("%6d %s %s", repairModeFile.length(),
LockSettingsService.timestampToString(repairModeFile.lastModified()),
repairModeFile.getName()));
final PersistentData data = readRepairModePersistentData();
pw.println(TextUtils.formatSimple("type: %d, user id: %d, payload size: %d",
data.type, data.userId, data.payload != null ? data.payload.length : 0));
pw.decreaseIndent();
}
}
static class DatabaseHelper extends SQLiteOpenHelper {

View File

@@ -1114,6 +1114,57 @@ class SyntheticPasswordManager {
}
}
/**
* Writes the user's synthetic password data to the repair mode file.
*
* @param protectorId current LSKF based protectorId
* @param userId user id of the user
*/
public boolean writeRepairModeCredentialLocked(long protectorId, int userId) {
if (!shouldWriteRepairModeCredential(userId)) {
return false;
}
final byte[] data = loadState(PASSWORD_DATA_NAME, protectorId, userId);
if (data == null) {
Slogf.w(TAG, "Password data not found for user %d", userId);
return false;
}
final PasswordData pwd = PasswordData.fromBytes(data);
if (isNoneCredential(pwd)) {
Slogf.w(TAG, "User %d has NONE credential", userId);
return false;
}
Slogf.d(TAG, "Writing repair mode credential tied to user %d", userId);
final int weaverSlot = loadWeaverSlot(protectorId, userId);
if (weaverSlot != INVALID_WEAVER_SLOT) {
// write weaver password
mStorage.writeRepairModePersistentData(
PersistentData.TYPE_SP_WEAVER, weaverSlot, pwd.toBytes());
} else {
// write gatekeeper password
mStorage.writeRepairModePersistentData(
PersistentData.TYPE_SP_GATEKEEPER, userId, pwd.toBytes());
}
return true;
}
private boolean shouldWriteRepairModeCredential(int userId) {
final UserInfo userInfo = mUserManager.getUserInfo(userId);
if (!LockPatternUtils.canUserEnterRepairMode(mContext, userInfo)) {
Slogf.w(TAG, "User %d can't enter repair mode", userId);
return false;
}
if (LockPatternUtils.isRepairModeActive(mContext)) {
Slog.w(TAG, "Can't write repair mode credential while repair mode is already active");
return false;
}
if (LockPatternUtils.isGsiRunning()) {
Slog.w(TAG, "Can't write repair mode credential while GSI is running");
return false;
}
return true;
}
private ArrayMap<Integer, ArrayMap<Long, TokenData>> tokenMap = new ArrayMap<>();
/**

View File

@@ -254,6 +254,8 @@ public abstract class BaseLockSettingsServiceTests {
.thenReturn(true);
when(res.getBoolean(eq(com.android.internal.R.bool.config_strongAuthRequiredOnBoot)))
.thenReturn(true);
when(res.getBoolean(eq(com.android.internal.R.bool.config_repairModeSupported)))
.thenReturn(true);
return res;
}

View File

@@ -83,6 +83,11 @@ public class LockSettingsStorageTestable extends LockSettingsStorage {
return remapToStorageDir(super.getSyntheticPasswordDirectoryForUser(userId));
}
@Override
File getRepairModePersistentDataFile() {
return remapToStorageDir(super.getRepairModePersistentDataFile());
}
@Override
PersistentDataBlockManagerInternal getPersistentDataBlockManager() {
return mPersistentDataBlockManager;

View File

@@ -457,6 +457,31 @@ public class LockSettingsStorageTests {
assertEquals(2, PersistentData.TYPE_SP_WEAVER);
}
@Test
public void testRepairMode_emptyPersistentData() {
assertSame(PersistentData.NONE, mStorage.readPersistentDataBlock());
}
@Test
public void testRepairMode_writeGatekeeperPersistentData() {
mStorage.writeRepairModePersistentData(
PersistentData.TYPE_SP_GATEKEEPER, SOME_USER_ID, PAYLOAD);
final PersistentData data = mStorage.readRepairModePersistentData();
assertEquals(PersistentData.TYPE_SP_GATEKEEPER, data.type);
assertArrayEquals(PAYLOAD, data.payload);
}
@Test
public void testRepairMode_writeWeaverPersistentData() {
mStorage.writeRepairModePersistentData(
PersistentData.TYPE_SP_WEAVER, SOME_USER_ID, PAYLOAD);
final PersistentData data = mStorage.readRepairModePersistentData();
assertEquals(PersistentData.TYPE_SP_WEAVER, data.type);
assertArrayEquals(PAYLOAD, data.payload);
}
private static void assertArrayEquals(byte[] expected, byte[] actual) {
if (!Arrays.equals(expected, actual)) {
fail("expected:<" + Arrays.toString(expected) +

View File

@@ -0,0 +1,129 @@
/*
* Copyright (C) 2023 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 com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import android.app.PropertyInvalidatedCache;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
import com.android.server.locksettings.SyntheticPasswordManager.PasswordData;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
public class LockscreenRepairModeTest extends BaseLockSettingsServiceTests {
@Before
public void setUp() throws Exception {
PropertyInvalidatedCache.disableForTestMode();
mService.initializeSyntheticPassword(PRIMARY_USER_ID);
}
@Test
public void verifyPin_writeRepairModePW() {
mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
assertEquals(VerifyCredentialResponse.RESPONSE_OK,
mService.verifyCredential(
newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
.getResponseCode());
assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
getCredentialType(mStorage.readRepairModePersistentData()));
}
@Test
public void verifyPattern_writeRepairModePW() {
mService.setLockCredential(newPattern("4321"), nonePassword(), PRIMARY_USER_ID);
assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
assertEquals(VerifyCredentialResponse.RESPONSE_OK,
mService.verifyCredential(
newPattern("4321"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
.getResponseCode());
assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
getCredentialType(mStorage.readRepairModePersistentData()));
}
@Test
public void verifyPassword_writeRepairModePW() {
mService.setLockCredential(newPassword("4321"), nonePassword(), PRIMARY_USER_ID);
assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
assertEquals(VerifyCredentialResponse.RESPONSE_OK,
mService.verifyCredential(
newPassword("4321"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
.getResponseCode());
assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
getCredentialType(mStorage.readRepairModePersistentData()));
}
@Test
public void verifyCredential_writeRepairModePW_repairModeActive() {
mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
setRepairModeActive(true);
assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
mService.verifyCredential(
newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
.getResponseCode());
assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
}
@Test
public void deleteRepairModePersistentData() {
mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
assertEquals(VerifyCredentialResponse.RESPONSE_OK,
mService.verifyCredential(
newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
.getResponseCode());
assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
getCredentialType(mStorage.readRepairModePersistentData()));
mService.deleteRepairModePersistentDataIfNeeded();
assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
}
private void setRepairModeActive(boolean active) {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.REPAIR_MODE_ACTIVE, active ? 1 : 0);
}
private static int getCredentialType(PersistentData persistentData) {
if (persistentData == null || persistentData.payload == null) {
return LockPatternUtils.CREDENTIAL_TYPE_NONE;
}
return PasswordData.fromBytes(persistentData.payload).credentialType;
}
}