Merge "Fix insider attack resistance on headless devices"
This commit is contained in:
@@ -29,6 +29,7 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRY
|
||||
import static android.content.Context.KEYGUARD_SERVICE;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static android.os.UserHandle.USER_ALL;
|
||||
import static android.os.UserHandle.USER_SYSTEM;
|
||||
import static android.provider.DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION;
|
||||
|
||||
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
|
||||
@@ -70,6 +71,7 @@ import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.database.ContentObserver;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.hardware.authsecret.IAuthSecret;
|
||||
@@ -219,6 +221,8 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
private static final String PROFILE_KEY_NAME_ENCRYPT = "profile_key_name_encrypt_";
|
||||
private static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_";
|
||||
|
||||
private static final int HEADLESS_VENDOR_AUTH_SECRET_LENGTH = 32;
|
||||
|
||||
// Order of holding lock: mSeparateChallengeLock -> mSpManager -> this
|
||||
// Do not call into ActivityManager while holding mSpManager lock.
|
||||
private final Object mSeparateChallengeLock = new Object();
|
||||
@@ -267,6 +271,13 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
@VisibleForTesting
|
||||
protected boolean mHasSecureLockScreen;
|
||||
|
||||
@VisibleForTesting
|
||||
protected final Object mHeadlessAuthSecretLock = new Object();
|
||||
|
||||
@VisibleForTesting
|
||||
@GuardedBy("mHeadlessAuthSecretLock")
|
||||
protected byte[] mAuthSecret;
|
||||
|
||||
protected IGateKeeperService mGateKeeperService;
|
||||
protected IAuthSecret mAuthSecretService;
|
||||
|
||||
@@ -563,6 +574,15 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
java.security.KeyStore ks) {
|
||||
return new ManagedProfilePasswordCache(ks, getUserManager());
|
||||
}
|
||||
|
||||
public boolean isHeadlessSystemUserMode() {
|
||||
return UserManager.isHeadlessSystemUserMode();
|
||||
}
|
||||
|
||||
public boolean isMainUserPermanentAdmin() {
|
||||
return Resources.getSystem()
|
||||
.getBoolean(com.android.internal.R.bool.config_isMainUserPermanentAdmin);
|
||||
}
|
||||
}
|
||||
|
||||
public LockSettingsService(Context context) {
|
||||
@@ -1697,7 +1717,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
throw new IllegalStateException("password change failed");
|
||||
}
|
||||
|
||||
onSyntheticPasswordKnown(userId, sp);
|
||||
onSyntheticPasswordUnlocked(userId, sp);
|
||||
setLockCredentialWithSpLocked(credential, sp, userId);
|
||||
sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
|
||||
return true;
|
||||
@@ -2009,7 +2029,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
|
||||
return;
|
||||
}
|
||||
onSyntheticPasswordKnown(userId, result.syntheticPassword);
|
||||
onSyntheticPasswordUnlocked(userId, result.syntheticPassword);
|
||||
unlockUserKey(userId, result.syntheticPassword);
|
||||
}
|
||||
}
|
||||
@@ -2602,43 +2622,112 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
private void onSyntheticPasswordKnown(@UserIdInt int userId, SyntheticPassword sp) {
|
||||
private void onSyntheticPasswordCreated(@UserIdInt int userId, SyntheticPassword sp) {
|
||||
onSyntheticPasswordKnown(userId, sp, true);
|
||||
}
|
||||
|
||||
private void onSyntheticPasswordUnlocked(@UserIdInt int userId, SyntheticPassword sp) {
|
||||
onSyntheticPasswordKnown(userId, sp, false);
|
||||
}
|
||||
|
||||
private void onSyntheticPasswordKnown(
|
||||
@UserIdInt int userId, SyntheticPassword sp, boolean justCreated) {
|
||||
if (mInjector.isGsiRunning()) {
|
||||
Slog.w(TAG, "Running in GSI; skipping calls to AuthSecret and RebootEscrow");
|
||||
return;
|
||||
}
|
||||
|
||||
mRebootEscrowManager.callToRebootEscrowIfNeeded(userId, sp.getVersion(),
|
||||
sp.getSyntheticPassword());
|
||||
|
||||
callToAuthSecretIfNeeded(userId, sp);
|
||||
mRebootEscrowManager.callToRebootEscrowIfNeeded(
|
||||
userId, sp.getVersion(), sp.getSyntheticPassword());
|
||||
callToAuthSecretIfNeeded(userId, sp, justCreated);
|
||||
}
|
||||
|
||||
private void callToAuthSecretIfNeeded(@UserIdInt int userId, SyntheticPassword sp) {
|
||||
// If the given user is the primary user, pass the auth secret to the HAL. Only the system
|
||||
// user can be primary. Check for the system user ID before calling getUserInfo(), as other
|
||||
// users may still be under construction.
|
||||
/**
|
||||
* Handles generation, storage, and sending of the vendor auth secret. Here we try to retrieve
|
||||
* the auth secret to send it to the auth secret HAL, generate a fresh secret if need be, store
|
||||
* it encrypted on disk so that the given user can unlock it in future, and stash it in memory
|
||||
* so that when future users are created they can also unlock it.
|
||||
*
|
||||
* <p>Called whenever the SP of a user is available, except in GSI.
|
||||
*/
|
||||
private void callToAuthSecretIfNeeded(
|
||||
@UserIdInt int userId, SyntheticPassword sp, boolean justCreated) {
|
||||
if (mAuthSecretService == null) {
|
||||
// If there's no IAuthSecret service, we don't need to maintain a auth secret
|
||||
return;
|
||||
}
|
||||
if (userId == UserHandle.USER_SYSTEM &&
|
||||
mUserManager.getUserInfo(userId).isPrimary()) {
|
||||
final byte[] secret = sp.deriveVendorAuthSecret();
|
||||
try {
|
||||
mAuthSecretService.setPrimaryUserCredential(secret);
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Failed to pass primary user secret to AuthSecret HAL", e);
|
||||
// User may be partially created, so use the internal user manager interface
|
||||
final UserManagerInternal userManagerInternal = mInjector.getUserManagerInternal();
|
||||
final UserInfo userInfo = userManagerInternal.getUserInfo(userId);
|
||||
if (userInfo == null) {
|
||||
// User may be partially deleted, skip this.
|
||||
return;
|
||||
}
|
||||
final byte[] authSecret;
|
||||
if (!mInjector.isHeadlessSystemUserMode()) {
|
||||
// On non-headless systems, the auth secret is derived from user 0's
|
||||
// SP, and only user 0 passes it to the HAL.
|
||||
if (userId != USER_SYSTEM) {
|
||||
return;
|
||||
}
|
||||
authSecret = sp.deriveVendorAuthSecret();
|
||||
} else if (!mInjector.isMainUserPermanentAdmin() || !userInfo.isFull()) {
|
||||
// Only full users can receive or pass on the auth secret.
|
||||
// If there is no main permanent admin user, we don't try to create or send
|
||||
// an auth secret, since there may sometimes be no full users.
|
||||
return;
|
||||
} else if (justCreated) {
|
||||
if (userInfo.isMain()) {
|
||||
// The first user is just being created, so we create a new auth secret
|
||||
// at the same time.
|
||||
Slog.i(TAG, "Generating new vendor auth secret and storing for user: " + userId);
|
||||
authSecret = SecureRandomUtils.randomBytes(HEADLESS_VENDOR_AUTH_SECRET_LENGTH);
|
||||
// Store it in memory, for when new users are created.
|
||||
synchronized (mHeadlessAuthSecretLock) {
|
||||
mAuthSecret = authSecret;
|
||||
}
|
||||
} else {
|
||||
// A new user is being created. Another user should already have logged in at
|
||||
// this point, and therefore the auth secret should be stored in memory.
|
||||
synchronized (mHeadlessAuthSecretLock) {
|
||||
authSecret = mAuthSecret;
|
||||
}
|
||||
if (authSecret == null) {
|
||||
Slog.e(TAG, "Creating non-main user " + userId
|
||||
+ " but vendor auth secret is not in memory");
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Store the auth secret encrypted using the user's SP (which was just created).
|
||||
mSpManager.writeVendorAuthSecret(authSecret, sp, userId);
|
||||
} else {
|
||||
// The user already exists, so the auth secret should be stored encrypted
|
||||
// with that user's SP.
|
||||
authSecret = mSpManager.readVendorAuthSecret(sp, userId);
|
||||
if (authSecret == null) {
|
||||
Slog.e(TAG, "Unable to read vendor auth secret for user: " + userId);
|
||||
return;
|
||||
}
|
||||
// Store it in memory, for when new users are created.
|
||||
synchronized (mHeadlessAuthSecretLock) {
|
||||
mAuthSecret = authSecret;
|
||||
}
|
||||
}
|
||||
Slog.i(TAG, "Sending vendor auth secret to IAuthSecret HAL as user: " + userId);
|
||||
try {
|
||||
mAuthSecretService.setPrimaryUserCredential(authSecret);
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Failed to send vendor auth secret to IAuthSecret HAL", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the synthetic password (SP) for the given user, protects it with an empty LSKF, and
|
||||
* protects the user's CE key with a key derived from the SP.
|
||||
* <p>
|
||||
* This is called just once in the lifetime of the user: at user creation time (possibly delayed
|
||||
* until the time when Weaver is guaranteed to be available), or when upgrading from Android 13
|
||||
* or earlier where users with no LSKF didn't necessarily have an SP.
|
||||
*
|
||||
* <p>This is called just once in the lifetime of the user: at user creation time (possibly
|
||||
* delayed until the time when Weaver is guaranteed to be available), or when upgrading from
|
||||
* Android 13 or earlier where users with no LSKF didn't necessarily have an SP.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
SyntheticPassword initializeSyntheticPassword(int userId) {
|
||||
@@ -2653,7 +2742,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
LockscreenCredential.createNone(), sp, userId);
|
||||
setCurrentLskfBasedProtectorId(protectorId, userId);
|
||||
setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
|
||||
onSyntheticPasswordKnown(userId, sp);
|
||||
onSyntheticPasswordCreated(userId, sp);
|
||||
return sp;
|
||||
}
|
||||
}
|
||||
@@ -2720,7 +2809,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
}
|
||||
mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
|
||||
|
||||
onSyntheticPasswordKnown(userId, sp);
|
||||
onSyntheticPasswordUnlocked(userId, sp);
|
||||
}
|
||||
|
||||
private void setDeviceUnlockedForUser(int userId) {
|
||||
@@ -3008,7 +3097,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
+ "verification.");
|
||||
return false;
|
||||
}
|
||||
onSyntheticPasswordKnown(userId, result.syntheticPassword);
|
||||
onSyntheticPasswordUnlocked(userId, result.syntheticPassword);
|
||||
setLockCredentialWithSpLocked(credential, result.syntheticPassword, userId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChang
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.admin.PasswordMetrics;
|
||||
import android.content.Context;
|
||||
import android.content.pm.UserInfo;
|
||||
@@ -93,6 +94,9 @@ import java.util.Set;
|
||||
* while the LSKF is nonempty.
|
||||
* SP_E0_NAME, SP_P1_NAME: Information needed to create and use escrow token-based protectors.
|
||||
* Deleted when escrow token support is disabled for the user.
|
||||
* VENDOR_AUTH_SECRET_NAME: A copy of the secret passed using the IAuthSecret interface,
|
||||
* encrypted using a secret derived from the SP using
|
||||
* PERSONALIZATION_AUTHSECRET_ENCRYPTION_KEY.
|
||||
*
|
||||
* For each protector, stored under the corresponding protector ID:
|
||||
* SP_BLOB_NAME: The encrypted SP secret (the SP itself or the P0 value). Always exists.
|
||||
@@ -120,6 +124,7 @@ class SyntheticPasswordManager {
|
||||
private static final String PASSWORD_DATA_NAME = "pwd";
|
||||
private static final String WEAVER_SLOT_NAME = "weaver";
|
||||
private static final String PASSWORD_METRICS_NAME = "metrics";
|
||||
private static final String VENDOR_AUTH_SECRET_NAME = "vendor_auth_secret";
|
||||
|
||||
// used for files associated with the SP itself, not with a particular protector
|
||||
public static final long NULL_PROTECTOR_ID = 0L;
|
||||
@@ -158,6 +163,8 @@ class SyntheticPasswordManager {
|
||||
private static final byte[] PERSONALIZATION_SP_GK_AUTH = "sp-gk-authentication".getBytes();
|
||||
private static final byte[] PERSONALIZATION_FBE_KEY = "fbe-key".getBytes();
|
||||
private static final byte[] PERSONALIZATION_AUTHSECRET_KEY = "authsecret-hal".getBytes();
|
||||
private static final byte[] PERSONALIZATION_AUTHSECRET_ENCRYPTION_KEY =
|
||||
"vendor-authsecret-encryption-key".getBytes();
|
||||
private static final byte[] PERSONALIZATION_SP_SPLIT = "sp-split".getBytes();
|
||||
private static final byte[] PERSONALIZATION_PASSWORD_HASH = "pw-hash".getBytes();
|
||||
private static final byte[] PERSONALIZATION_E0 = "e0-encryption".getBytes();
|
||||
@@ -249,6 +256,10 @@ class SyntheticPasswordManager {
|
||||
return deriveSubkey(PERSONALIZATION_PASSWORD_METRICS);
|
||||
}
|
||||
|
||||
public byte[] deriveVendorAuthSecretEncryptionKey() {
|
||||
return deriveSubkey(PERSONALIZATION_AUTHSECRET_ENCRYPTION_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns escrow data to this synthetic password. This is a prerequisite to call
|
||||
* {@link SyntheticPassword#recreateFromEscrow}.
|
||||
@@ -1737,4 +1748,25 @@ class SyntheticPasswordManager {
|
||||
mListeners.finishBroadcast();
|
||||
}
|
||||
}
|
||||
|
||||
public void writeVendorAuthSecret(
|
||||
@NonNull final byte[] vendorAuthSecret,
|
||||
@NonNull final SyntheticPassword sp,
|
||||
@UserIdInt final int userId) {
|
||||
final byte[] encrypted =
|
||||
SyntheticPasswordCrypto.encrypt(
|
||||
sp.deriveVendorAuthSecretEncryptionKey(), new byte[0], vendorAuthSecret);
|
||||
saveState(VENDOR_AUTH_SECRET_NAME, encrypted, NULL_PROTECTOR_ID, userId);
|
||||
syncState(userId);
|
||||
}
|
||||
|
||||
public @Nullable byte[] readVendorAuthSecret(
|
||||
@NonNull final SyntheticPassword sp, @UserIdInt final int userId) {
|
||||
final byte[] encrypted = loadState(VENDOR_AUTH_SECRET_NAME, NULL_PROTECTOR_ID, userId);
|
||||
if (encrypted == null) {
|
||||
return null;
|
||||
}
|
||||
return SyntheticPasswordCrypto.decrypt(
|
||||
sp.deriveVendorAuthSecretEncryptionKey(), new byte[0], encrypted);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,8 +49,8 @@ import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.os.storage.IStorageManager;
|
||||
import android.os.storage.StorageManager;
|
||||
import android.provider.Settings;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.provider.Settings;
|
||||
import android.security.KeyStore;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
@@ -83,16 +83,15 @@ public abstract class BaseLockSettingsServiceTests {
|
||||
protected static final int MANAGED_PROFILE_USER_ID = 12;
|
||||
protected static final int TURNED_OFF_PROFILE_USER_ID = 17;
|
||||
protected static final int SECONDARY_USER_ID = 20;
|
||||
protected static final int TERTIARY_USER_ID = 21;
|
||||
|
||||
private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER_ID, null, null,
|
||||
UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY
|
||||
| UserInfo.FLAG_MAIN);
|
||||
private static final UserInfo SECONDARY_USER_INFO = new UserInfo(SECONDARY_USER_ID, null, null,
|
||||
UserInfo.FLAG_INITIALIZED);
|
||||
protected UserInfo mPrimaryUserInfo;
|
||||
protected UserInfo mSecondaryUserInfo;
|
||||
protected UserInfo mTertiaryUserInfo;
|
||||
|
||||
private ArrayList<UserInfo> mPrimaryUserProfiles = new ArrayList<>();
|
||||
|
||||
LockSettingsService mService;
|
||||
LockSettingsServiceTestable mService;
|
||||
LockSettingsInternal mLocalService;
|
||||
|
||||
MockLockSettingsContext mContext;
|
||||
@@ -117,6 +116,7 @@ public abstract class BaseLockSettingsServiceTests {
|
||||
FingerprintManager mFingerprintManager;
|
||||
FaceManager mFaceManager;
|
||||
PackageManager mPackageManager;
|
||||
LockSettingsServiceTestable.MockInjector mInjector;
|
||||
@Rule
|
||||
public FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();
|
||||
|
||||
@@ -162,22 +162,61 @@ public abstract class BaseLockSettingsServiceTests {
|
||||
mSpManager = new MockSyntheticPasswordManager(mContext, mStorage, mGateKeeperService,
|
||||
mUserManager, mPasswordSlotManager);
|
||||
mAuthSecretService = mock(IAuthSecret.class);
|
||||
mService = new LockSettingsServiceTestable(mContext, mStorage,
|
||||
mGateKeeperService, mKeyStore, setUpStorageManagerMock(), mActivityManager,
|
||||
mSpManager, mAuthSecretService, mGsiService, mRecoverableKeyStoreManager,
|
||||
mUserManagerInternal, mDeviceStateCache);
|
||||
mInjector =
|
||||
new LockSettingsServiceTestable.MockInjector(
|
||||
mContext,
|
||||
mStorage,
|
||||
mKeyStore,
|
||||
mActivityManager,
|
||||
setUpStorageManagerMock(),
|
||||
mSpManager,
|
||||
mGsiService,
|
||||
mRecoverableKeyStoreManager,
|
||||
mUserManagerInternal,
|
||||
mDeviceStateCache);
|
||||
mService =
|
||||
new LockSettingsServiceTestable(mInjector, mGateKeeperService, mAuthSecretService);
|
||||
mService.mHasSecureLockScreen = true;
|
||||
when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO);
|
||||
mPrimaryUserProfiles.add(PRIMARY_USER_INFO);
|
||||
mPrimaryUserInfo =
|
||||
new UserInfo(
|
||||
PRIMARY_USER_ID,
|
||||
null,
|
||||
null,
|
||||
UserInfo.FLAG_INITIALIZED
|
||||
| UserInfo.FLAG_ADMIN
|
||||
| UserInfo.FLAG_PRIMARY
|
||||
| UserInfo.FLAG_MAIN
|
||||
| UserInfo.FLAG_FULL);
|
||||
mSecondaryUserInfo =
|
||||
new UserInfo(
|
||||
SECONDARY_USER_ID,
|
||||
null,
|
||||
null,
|
||||
UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_FULL);
|
||||
mTertiaryUserInfo =
|
||||
new UserInfo(
|
||||
TERTIARY_USER_ID,
|
||||
null,
|
||||
null,
|
||||
UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_FULL);
|
||||
|
||||
when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(mPrimaryUserInfo);
|
||||
when(mUserManagerInternal.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(mPrimaryUserInfo);
|
||||
mPrimaryUserProfiles.add(mPrimaryUserInfo);
|
||||
installChildProfile(MANAGED_PROFILE_USER_ID);
|
||||
installAndTurnOffChildProfile(TURNED_OFF_PROFILE_USER_ID);
|
||||
for (UserInfo profile : mPrimaryUserProfiles) {
|
||||
when(mUserManager.getProfiles(eq(profile.id))).thenReturn(mPrimaryUserProfiles);
|
||||
}
|
||||
when(mUserManager.getUserInfo(eq(SECONDARY_USER_ID))).thenReturn(SECONDARY_USER_INFO);
|
||||
when(mUserManager.getUserInfo(eq(SECONDARY_USER_ID))).thenReturn(mSecondaryUserInfo);
|
||||
when(mUserManagerInternal.getUserInfo(eq(SECONDARY_USER_ID)))
|
||||
.thenReturn(mSecondaryUserInfo);
|
||||
when(mUserManager.getUserInfo(eq(TERTIARY_USER_ID))).thenReturn(mTertiaryUserInfo);
|
||||
when(mUserManagerInternal.getUserInfo(eq(TERTIARY_USER_ID))).thenReturn(mTertiaryUserInfo);
|
||||
|
||||
final ArrayList<UserInfo> allUsers = new ArrayList<>(mPrimaryUserProfiles);
|
||||
allUsers.add(SECONDARY_USER_INFO);
|
||||
allUsers.add(mSecondaryUserInfo);
|
||||
allUsers.add(mTertiaryUserInfo);
|
||||
when(mUserManager.getUsers()).thenReturn(allUsers);
|
||||
|
||||
when(mActivityManager.unlockUser2(anyInt(), any())).thenAnswer(
|
||||
@@ -227,9 +266,10 @@ public abstract class BaseLockSettingsServiceTests {
|
||||
userInfo.profileGroupId = PRIMARY_USER_ID;
|
||||
mPrimaryUserProfiles.add(userInfo);
|
||||
when(mUserManager.getUserInfo(eq(profileId))).thenReturn(userInfo);
|
||||
when(mUserManager.getProfileParent(eq(profileId))).thenReturn(PRIMARY_USER_INFO);
|
||||
when(mUserManager.getProfileParent(eq(profileId))).thenReturn(mPrimaryUserInfo);
|
||||
when(mUserManager.isUserRunning(eq(profileId))).thenReturn(true);
|
||||
when(mUserManager.isUserUnlocked(eq(profileId))).thenReturn(true);
|
||||
when(mUserManagerInternal.getUserInfo(eq(profileId))).thenReturn(userInfo);
|
||||
// TODO(b/258213147): Remove
|
||||
when(mUserManagerInternal.isUserManaged(eq(profileId))).thenReturn(true);
|
||||
when(mDeviceStateCache.isUserOrganizationManaged(eq(profileId)))
|
||||
|
||||
@@ -30,6 +30,7 @@ import android.os.RemoteException;
|
||||
import android.os.storage.IStorageManager;
|
||||
import android.security.KeyStore;
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
||||
import android.service.gatekeeper.IGateKeeperService;
|
||||
|
||||
import com.android.internal.widget.LockscreenCredential;
|
||||
import com.android.server.ServiceThread;
|
||||
@@ -40,7 +41,7 @@ import java.io.FileNotFoundException;
|
||||
|
||||
public class LockSettingsServiceTestable extends LockSettingsService {
|
||||
|
||||
private static class MockInjector extends LockSettingsService.Injector {
|
||||
public static class MockInjector extends LockSettingsService.Injector {
|
||||
|
||||
private LockSettingsStorage mLockSettingsStorage;
|
||||
private KeyStore mKeyStore;
|
||||
@@ -52,6 +53,9 @@ public class LockSettingsServiceTestable extends LockSettingsService {
|
||||
private UserManagerInternal mUserManagerInternal;
|
||||
private DeviceStateCache mDeviceStateCache;
|
||||
|
||||
public boolean mIsHeadlessSystemUserMode = false;
|
||||
public boolean mIsMainUserPermanentAdmin = false;
|
||||
|
||||
public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
|
||||
IActivityManager activityManager,
|
||||
IStorageManager storageManager, SyntheticPasswordManager spManager,
|
||||
@@ -140,19 +144,22 @@ public class LockSettingsServiceTestable extends LockSettingsService {
|
||||
return mock(ManagedProfilePasswordCache.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHeadlessSystemUserMode() {
|
||||
return mIsHeadlessSystemUserMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMainUserPermanentAdmin() {
|
||||
return mIsMainUserPermanentAdmin;
|
||||
}
|
||||
}
|
||||
|
||||
public MockInjector mInjector;
|
||||
|
||||
protected LockSettingsServiceTestable(Context context,
|
||||
LockSettingsStorage storage, FakeGateKeeperService gatekeeper, KeyStore keystore,
|
||||
IStorageManager storageManager, IActivityManager mActivityManager,
|
||||
SyntheticPasswordManager spManager, IAuthSecret authSecretService,
|
||||
FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager,
|
||||
UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
|
||||
super(new MockInjector(context, storage, keystore, mActivityManager,
|
||||
storageManager, spManager, gsiService, recoverableKeyStoreManager,
|
||||
userManagerInternal, deviceStateCache));
|
||||
protected LockSettingsServiceTestable(
|
||||
LockSettingsService.Injector injector,
|
||||
IGateKeeperService gatekeeper,
|
||||
IAuthSecret authSecretService) {
|
||||
super(injector);
|
||||
mGateKeeperService = gatekeeper;
|
||||
mAuthSecretService = authSecretService;
|
||||
}
|
||||
@@ -199,4 +206,10 @@ public class LockSettingsServiceTestable extends LockSettingsService {
|
||||
UserInfo userInfo = mUserManager.getUserInfo(userId);
|
||||
return userInfo.isCloneProfile() || userInfo.isManagedProfile();
|
||||
}
|
||||
|
||||
void clearAuthSecret() {
|
||||
synchronized (mHeadlessAuthSecretLock) {
|
||||
mAuthSecret = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
package com.android.server.locksettings;
|
||||
|
||||
import static android.content.pm.UserInfo.FLAG_FULL;
|
||||
import static android.content.pm.UserInfo.FLAG_MAIN;
|
||||
import static android.content.pm.UserInfo.FLAG_PRIMARY;
|
||||
|
||||
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
|
||||
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
|
||||
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
|
||||
@@ -27,9 +31,9 @@ import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
@@ -247,6 +251,15 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
|
||||
|
||||
@Test
|
||||
public void testUnlockUserKeyIfUnsecuredPassesPrimaryUserAuthSecret() throws RemoteException {
|
||||
initSpAndSetCredential(PRIMARY_USER_ID, newPassword(null));
|
||||
reset(mAuthSecretService);
|
||||
mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
|
||||
verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnlockUserKeyIfUnsecuredPassesPrimaryUserAuthSecretIfPasswordIsCleared()
|
||||
throws RemoteException {
|
||||
LockscreenCredential password = newPassword("password");
|
||||
initSpAndSetCredential(PRIMARY_USER_ID, password);
|
||||
mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID);
|
||||
@@ -256,6 +269,56 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
|
||||
verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
|
||||
}
|
||||
|
||||
private void setupHeadlessTest() {
|
||||
mInjector.mIsHeadlessSystemUserMode = true;
|
||||
mInjector.mIsMainUserPermanentAdmin = true;
|
||||
mPrimaryUserInfo.flags &= ~(FLAG_FULL | FLAG_PRIMARY);
|
||||
mSecondaryUserInfo.flags |= FLAG_MAIN;
|
||||
mService.initializeSyntheticPassword(PRIMARY_USER_ID);
|
||||
mService.initializeSyntheticPassword(SECONDARY_USER_ID);
|
||||
mService.initializeSyntheticPassword(TERTIARY_USER_ID);
|
||||
reset(mAuthSecretService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeadlessSystemUserDoesNotPassAuthSecret() throws RemoteException {
|
||||
setupHeadlessTest();
|
||||
mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
|
||||
verify(mAuthSecretService, never()).setPrimaryUserCredential(any(byte[].class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeadlessSecondaryUserPassesAuthSecret() throws RemoteException {
|
||||
setupHeadlessTest();
|
||||
mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
|
||||
verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeadlessTertiaryUserPassesSameAuthSecret() throws RemoteException {
|
||||
setupHeadlessTest();
|
||||
mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
|
||||
var captor = ArgumentCaptor.forClass(byte[].class);
|
||||
verify(mAuthSecretService).setPrimaryUserCredential(captor.capture());
|
||||
var value = captor.getValue();
|
||||
reset(mAuthSecretService);
|
||||
mLocalService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
|
||||
verify(mAuthSecretService).setPrimaryUserCredential(eq(value));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeadlessTertiaryUserPassesSameAuthSecretAfterReset() throws RemoteException {
|
||||
setupHeadlessTest();
|
||||
mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
|
||||
var captor = ArgumentCaptor.forClass(byte[].class);
|
||||
verify(mAuthSecretService).setPrimaryUserCredential(captor.capture());
|
||||
var value = captor.getValue();
|
||||
mService.clearAuthSecret();
|
||||
reset(mAuthSecretService);
|
||||
mLocalService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
|
||||
verify(mAuthSecretService).setPrimaryUserCredential(eq(value));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTokenBasedResetPassword() throws RemoteException {
|
||||
LockscreenCredential password = newPassword("password");
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
|
||||
|
||||
import com.google.android.collect.Sets;
|
||||
|
||||
import org.junit.Before;
|
||||
@@ -29,7 +30,7 @@ public class WeaverBasedSyntheticPasswordTests extends SyntheticPasswordTests {
|
||||
// sequentially, starting at slot 0.
|
||||
@Test
|
||||
public void testFrpWeaverSlotNotReused() {
|
||||
final int userId = 10;
|
||||
final int userId = SECONDARY_USER_ID;
|
||||
final int frpWeaverSlot = 0;
|
||||
|
||||
setDeviceProvisioned(false);
|
||||
@@ -45,7 +46,7 @@ public class WeaverBasedSyntheticPasswordTests extends SyntheticPasswordTests {
|
||||
// it's here as a control for testFrpWeaverSlotNotReused().
|
||||
@Test
|
||||
public void testFrpWeaverSlotReused() {
|
||||
final int userId = 10;
|
||||
final int userId = SECONDARY_USER_ID;
|
||||
final int frpWeaverSlot = 0;
|
||||
|
||||
setDeviceProvisioned(true);
|
||||
|
||||
Reference in New Issue
Block a user