Loading services/core/java/com/android/server/locksettings/LockSettingsService.java +9 −7 Original line number Diff line number Diff line Loading @@ -256,10 +256,10 @@ public class LockSettingsService extends ILockSettings.Stub { @GuardedBy("mUserCreationAndRemovalLock") private boolean mBootComplete; // Current password metric for all users on the device. Updated when user unlocks // the device or changes password. Removed when user is stopped. // Current password metrics for all secured users on the device. Updated when user unlocks the // device or changes password. Removed when user is stopped. @GuardedBy("this") final SparseArray<PasswordMetrics> mUserPasswordMetrics = new SparseArray<>(); private final SparseArray<PasswordMetrics> mUserPasswordMetrics = new SparseArray<>(); @VisibleForTesting protected boolean mHasSecureLockScreen; Loading Loading @@ -2274,8 +2274,11 @@ public class LockSettingsService extends ILockSettings.Stub { } } private PasswordMetrics loadPasswordMetrics(SyntheticPassword sp, int userHandle) { private @Nullable PasswordMetrics loadPasswordMetrics(SyntheticPassword sp, int userHandle) { synchronized (mSpManager) { if (!isUserSecure(userHandle)) { return null; } return mSpManager.getPasswordMetrics(sp, getCurrentLskfBasedProtectorId(userHandle), userHandle); } Loading Loading @@ -2703,14 +2706,13 @@ public class LockSettingsService extends ILockSettings.Stub { return handle; } private void onCredentialVerified(SyntheticPassword sp, PasswordMetrics metrics, int userId) { private void onCredentialVerified(SyntheticPassword sp, @Nullable PasswordMetrics metrics, int userId) { if (metrics != null) { synchronized (this) { mUserPasswordMetrics.put(userId, metrics); } } else { Slog.wtf(TAG, "Null metrics after credential verification"); } unlockKeystore(sp.deriveKeyStorePassword(), userId); Loading services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +77 −45 Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; import com.android.internal.widget.ICheckCredentialProgressCallback; import com.android.internal.widget.IWeakEscrowTokenRemovedListener; import com.android.internal.widget.LockPatternUtils; Loading Loading @@ -97,9 +98,11 @@ import java.util.Set; * 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. * PASSWORD_DATA_NAME: Data used for LSKF verification, such as the scrypt salt and * parameters. Only exists for LSKF-based protectors. * parameters. Only exists for LSKF-based protectors. Doesn't exist when * the LSKF is empty, except in old protectors. * PASSWORD_METRICS_NAME: Metrics about the LSKF, encrypted by a key derived from the SP. * Only exists for LSKF-based protectors. * Only exists for LSKF-based protectors. Doesn't exist when the LSKF * is empty, except in old protectors. * SECDISCARDABLE_NAME: A large number of random bytes that all need to be known in order to * decrypt SP_BLOB_NAME. When the protector is deleted, this file is * overwritten and deleted as a "best-effort" attempt to support secure Loading Loading @@ -333,24 +336,15 @@ public class SyntheticPasswordManager { byte scryptLogP; public int credentialType; byte[] salt; // This is the Gatekeeper password handle that resulted from enrolling the stretched LSKF, // when applicable. This field isn't used if Weaver is available, or in new protectors when // the LSKF is empty. // When Weaver is unavailable, this is the Gatekeeper password handle that resulted from // enrolling the stretched LSKF. public byte[] passwordHandle; public static PasswordData create(int credentialType) { PasswordData result = new PasswordData(); if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) { // When the LSKF is empty, scrypt provides no security benefit, so just use the // minimum parameters (N=2, r=1, p=1). result.scryptLogN = 1; result.scryptLogR = 0; result.scryptLogP = 0; } else { result.scryptLogN = PASSWORD_SCRYPT_LOG_N; result.scryptLogR = PASSWORD_SCRYPT_LOG_R; result.scryptLogP = PASSWORD_SCRYPT_LOG_P; } result.credentialType = credentialType; result.salt = secureRandom(PASSWORD_SALT_LENGTH); return result; Loading Loading @@ -611,7 +605,6 @@ public class SyntheticPasswordManager { int getCredentialType(long protectorId, int userId) { byte[] passwordData = loadState(PASSWORD_DATA_NAME, protectorId, userId); if (passwordData == null) { Slog.w(TAG, "getCredentialType: encountered empty password data for user " + userId); return LockPatternUtils.CREDENTIAL_TYPE_NONE; } return PasswordData.fromBytes(passwordData).credentialType; Loading Loading @@ -783,7 +776,8 @@ public class SyntheticPasswordManager { public long createLskfBasedProtector(IGateKeeperService gatekeeper, LockscreenCredential credential, SyntheticPassword sp, int userId) { long protectorId = generateProtectorId(); PasswordData pwd = PasswordData.create(credential.getType()); // There's no need to store password data about an empty LSKF. PasswordData pwd = credential.isNone() ? null : PasswordData.create(credential.getType()); byte[] stretchedLskf = stretchLskf(credential, pwd); long sid = GateKeeper.INVALID_SECURE_USER_ID; final byte[] protectorSecret; Loading Loading @@ -837,8 +831,10 @@ public class SyntheticPasswordManager { // No need to pass in quality since the credential type already encodes sufficient info synchronizeFrpPassword(pwd, 0, userId); } if (!credential.isNone()) { saveState(PASSWORD_DATA_NAME, pwd.toBytes(), protectorId, userId); savePasswordMetrics(credential, sp, protectorId, userId); } createSyntheticPasswordBlob(protectorId, PROTECTOR_TYPE_LSKF_BASED, sp, protectorSecret, sid, userId); return protectorId; Loading Loading @@ -883,10 +879,11 @@ public class SyntheticPasswordManager { public void migrateFrpPasswordLocked(long protectorId, UserInfo userInfo, int requestedQuality) { if (mStorage.getPersistentDataBlockManager() != null && LockPatternUtils.userOwnsFrpCredential(mContext, userInfo)) { && LockPatternUtils.userOwnsFrpCredential(mContext, userInfo) && getCredentialType(protectorId, userInfo.id) != LockPatternUtils.CREDENTIAL_TYPE_NONE) { PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, protectorId, userInfo.id)); if (pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) { int weaverSlot = loadWeaverSlot(protectorId, userInfo.id); if (weaverSlot != INVALID_WEAVER_SLOT) { synchronizeWeaverFrpPassword(pwd, requestedQuality, userInfo.id, weaverSlot); Loading @@ -895,14 +892,13 @@ public class SyntheticPasswordManager { } } } } private void synchronizeFrpPassword(PasswordData pwd, int requestedQuality, int userId) { private void synchronizeFrpPassword(@Nullable PasswordData pwd, int requestedQuality, int userId) { if (mStorage.getPersistentDataBlockManager() != null && LockPatternUtils.userOwnsFrpCredential(mContext, mUserManager.getUserInfo(userId))) { if (pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) { if (pwd != null && pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) { mStorage.writePersistentDataBlock(PersistentData.TYPE_SP, userId, requestedQuality, pwd.toBytes()); } else { Loading @@ -911,12 +907,12 @@ public class SyntheticPasswordManager { } } private void synchronizeWeaverFrpPassword(PasswordData pwd, int requestedQuality, int userId, int weaverSlot) { private void synchronizeWeaverFrpPassword(@Nullable PasswordData pwd, int requestedQuality, int userId, int weaverSlot) { if (mStorage.getPersistentDataBlockManager() != null && LockPatternUtils.userOwnsFrpCredential(mContext, mUserManager.getUserInfo(userId))) { if (pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) { if (pwd != null && pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) { mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, weaverSlot, requestedQuality, pwd.toBytes()); } else { Loading Loading @@ -1047,12 +1043,20 @@ public class SyntheticPasswordManager { return result; } PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, protectorId, userId)); if (!credential.checkAgainstStoredType(pwd.credentialType)) { // Load the PasswordData file. If it doesn't exist, then the LSKF is empty (i.e., // CREDENTIAL_TYPE_NONE), and we'll skip the scrypt and Gatekeeper steps. If it exists, // then either the LSKF is nonempty, or it's an old protector that uses scrypt and // Gatekeeper even though the LSKF is empty. byte[] pwdDataBytes = loadState(PASSWORD_DATA_NAME, protectorId, userId); PasswordData pwd = null; int storedType = LockPatternUtils.CREDENTIAL_TYPE_NONE; if (pwdDataBytes != null) { pwd = PasswordData.fromBytes(pwdDataBytes); storedType = pwd.credentialType; } if (!credential.checkAgainstStoredType(storedType)) { Slog.e(TAG, TextUtils.formatSimple("Credential type mismatch: expected %d actual %d", pwd.credentialType, credential.getType())); storedType, credential.getType())); result.gkResponse = VerifyCredentialResponse.ERROR; return result; } Loading @@ -1078,7 +1082,7 @@ public class SyntheticPasswordManager { } else { // Weaver is unavailable, so the protector uses Gatekeeper to verify the LSKF, unless // the LSKF is empty in which case Gatekeeper might not have been used at all. if (pwd.passwordHandle == null) { if (pwd == null || pwd.passwordHandle == null) { if (!credential.isNone()) { Slog.e(TAG, "Missing Gatekeeper password handle for nonempty LSKF"); result.gkResponse = VerifyCredentialResponse.ERROR; Loading Loading @@ -1149,7 +1153,8 @@ public class SyntheticPasswordManager { // Upgrade case: store the metrics if the device did not have stored metrics before, should // only happen once on old protectors. if (result.syntheticPassword != null && !hasPasswordMetrics(protectorId, userId)) { if (result.syntheticPassword != null && !credential.isNone() && !hasPasswordMetrics(protectorId, userId)) { savePasswordMetrics(credential, result.syntheticPassword, protectorId, userId); } return result; Loading Loading @@ -1415,6 +1420,11 @@ public class SyntheticPasswordManager { } } @VisibleForTesting boolean hasPasswordData(long protectorId, int userId) { return hasState(PASSWORD_DATA_NAME, protectorId, userId); } /** * Retrieves a user's saved password metrics from their LSKF-based SP protector. The * SyntheticPassword itself is needed to decrypt the file containing the password metrics. Loading @@ -1422,10 +1432,16 @@ public class SyntheticPasswordManager { public @Nullable PasswordMetrics getPasswordMetrics(SyntheticPassword sp, long protectorId, int userId) { final byte[] encrypted = loadState(PASSWORD_METRICS_NAME, protectorId, userId); if (encrypted == null) return null; if (encrypted == null) { Slogf.e(TAG, "Failed to read password metrics file for user %d", userId); return null; } final byte[] decrypted = SyntheticPasswordCrypto.decrypt(sp.deriveMetricsKey(), /* personalization= */ new byte[0], encrypted); if (decrypted == null) return null; if (decrypted == null) { Slogf.e(TAG, "Failed to decrypt password metrics file for user %d", userId); return null; } return VersionedPasswordMetrics.deserialize(decrypted).getMetrics(); } Loading @@ -1437,7 +1453,8 @@ public class SyntheticPasswordManager { saveState(PASSWORD_METRICS_NAME, encrypted, protectorId, userId); } private boolean hasPasswordMetrics(long protectorId, int userId) { @VisibleForTesting boolean hasPasswordMetrics(long protectorId, int userId) { return hasState(PASSWORD_METRICS_NAME, protectorId, userId); } Loading Loading @@ -1500,8 +1517,23 @@ public class SyntheticPasswordManager { return TextUtils.formatSimple("%s%x", PROTECTOR_KEY_ALIAS_PREFIX, protectorId); } private byte[] stretchLskf(LockscreenCredential credential, PasswordData data) { /** * Stretches <code>credential</code>, if needed, using the parameters from <code>data</code>. * <p> * When the credential is empty, stetching provides no security benefit. Thus, new protectors * for an empty credential use <code>null</code> {@link PasswordData} and skip the stretching. * <p> * However, old protectors always stored {@link PasswordData} and did the stretching, regardless * of whether the credential was empty or not. For this reason, this method also continues to * support stretching of empty credentials so that old protectors can still be unlocked. */ @VisibleForTesting byte[] stretchLskf(LockscreenCredential credential, @Nullable PasswordData data) { final byte[] password = credential.isNone() ? DEFAULT_PASSWORD : credential.getCredential(); if (data == null) { Preconditions.checkArgument(credential.isNone()); return Arrays.copyOf(password, STRETCHED_LSKF_LENGTH); } return scrypt(password, data.salt, 1 << data.scryptLogN, 1 << data.scryptLogR, 1 << data.scryptLogP, STRETCHED_LSKF_LENGTH); } Loading services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java +45 −11 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; 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.eq; Loading @@ -49,6 +50,8 @@ import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationRe import com.android.server.locksettings.SyntheticPasswordManager.PasswordData; import com.android.server.locksettings.SyntheticPasswordManager.SyntheticPassword; import libcore.util.HexEncoding; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; Loading Loading @@ -84,6 +87,8 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { long protectorId = manager.createLskfBasedProtector(mGateKeeperService, LockscreenCredential.createNone(), sp, USER_ID); assertFalse(lskfGatekeeperHandleExists(USER_ID)); assertFalse(manager.hasPasswordData(protectorId, USER_ID)); assertFalse(manager.hasPasswordMetrics(protectorId, USER_ID)); AuthenticationResult result = manager.unlockLskfBasedProtector(mGateKeeperService, protectorId, LockscreenCredential.createNone(), USER_ID, null); Loading @@ -103,6 +108,8 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { long protectorId = manager.createLskfBasedProtector(mGateKeeperService, password, sp, USER_ID); assertTrue(lskfGatekeeperHandleExists(USER_ID)); assertTrue(manager.hasPasswordData(protectorId, USER_ID)); assertTrue(manager.hasPasswordMetrics(protectorId, USER_ID)); AuthenticationResult result = manager.unlockLskfBasedProtector(mGateKeeperService, protectorId, password, USER_ID, null); Loading Loading @@ -452,19 +459,46 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { mService.getHashFactor(profilePassword, MANAGED_PROFILE_USER_ID)); } // Tests stretching of a nonempty LSKF. @Test public void testPasswordData_scryptParams() { // CREDENTIAL_TYPE_NONE should result in the minimum scrypt params being used. PasswordData data = PasswordData.create(CREDENTIAL_TYPE_NONE); assertEquals(1, data.scryptLogN); assertEquals(0, data.scryptLogR); assertEquals(0, data.scryptLogP); public void testStretchLskf_enabled() { byte[] actual = mSpManager.stretchLskf(newPin("12345"), createTestPasswordData()); String expected = "467986710DE8F0D4F4A3668DFF58C9B7E5DB96A79B7CCF415BBD4D7767F8CFFA"; assertEquals(expected, HexEncoding.encodeToString(actual)); } // Any other credential type should result in the real scrypt params being used. data = PasswordData.create(CREDENTIAL_TYPE_PASSWORD); assertTrue(data.scryptLogN > 1); assertTrue(data.scryptLogR > 0); assertTrue(data.scryptLogP > 0); // Tests the case where stretching is disabled for an empty LSKF. @Test public void testStretchLskf_disabled() { byte[] actual = mSpManager.stretchLskf(nonePassword(), null); // "default-password", zero padded String expected = "64656661756C742D70617373776F726400000000000000000000000000000000"; assertEquals(expected, HexEncoding.encodeToString(actual)); } // Tests the legacy case where stretching is enabled for an empty LSKF. @Test public void testStretchLskf_emptyButEnabled() { byte[] actual = mSpManager.stretchLskf(nonePassword(), createTestPasswordData()); String expected = "9E6DDCC1EC388BB1E1CD54097AF924CA80BCB90993196FA8F6122FF58EB333DE"; assertEquals(expected, HexEncoding.encodeToString(actual)); } // Tests the forbidden case where stretching is disabled for a nonempty LSKF. @Test public void testStretchLskf_nonEmptyButDisabled() { assertThrows(IllegalArgumentException.class, () -> mSpManager.stretchLskf(newPin("12345"), null)); } private PasswordData createTestPasswordData() { PasswordData data = new PasswordData(); // For the unit test, the scrypt parameters have to be constant; the salt can't be random. data.scryptLogN = 11; data.scryptLogR = 3; data.scryptLogP = 1; data.salt = "abcdefghijklmnop".getBytes(); return data; } @Test Loading Loading
services/core/java/com/android/server/locksettings/LockSettingsService.java +9 −7 Original line number Diff line number Diff line Loading @@ -256,10 +256,10 @@ public class LockSettingsService extends ILockSettings.Stub { @GuardedBy("mUserCreationAndRemovalLock") private boolean mBootComplete; // Current password metric for all users on the device. Updated when user unlocks // the device or changes password. Removed when user is stopped. // Current password metrics for all secured users on the device. Updated when user unlocks the // device or changes password. Removed when user is stopped. @GuardedBy("this") final SparseArray<PasswordMetrics> mUserPasswordMetrics = new SparseArray<>(); private final SparseArray<PasswordMetrics> mUserPasswordMetrics = new SparseArray<>(); @VisibleForTesting protected boolean mHasSecureLockScreen; Loading Loading @@ -2274,8 +2274,11 @@ public class LockSettingsService extends ILockSettings.Stub { } } private PasswordMetrics loadPasswordMetrics(SyntheticPassword sp, int userHandle) { private @Nullable PasswordMetrics loadPasswordMetrics(SyntheticPassword sp, int userHandle) { synchronized (mSpManager) { if (!isUserSecure(userHandle)) { return null; } return mSpManager.getPasswordMetrics(sp, getCurrentLskfBasedProtectorId(userHandle), userHandle); } Loading Loading @@ -2703,14 +2706,13 @@ public class LockSettingsService extends ILockSettings.Stub { return handle; } private void onCredentialVerified(SyntheticPassword sp, PasswordMetrics metrics, int userId) { private void onCredentialVerified(SyntheticPassword sp, @Nullable PasswordMetrics metrics, int userId) { if (metrics != null) { synchronized (this) { mUserPasswordMetrics.put(userId, metrics); } } else { Slog.wtf(TAG, "Null metrics after credential verification"); } unlockKeystore(sp.deriveKeyStorePassword(), userId); Loading
services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +77 −45 Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; import com.android.internal.widget.ICheckCredentialProgressCallback; import com.android.internal.widget.IWeakEscrowTokenRemovedListener; import com.android.internal.widget.LockPatternUtils; Loading Loading @@ -97,9 +98,11 @@ import java.util.Set; * 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. * PASSWORD_DATA_NAME: Data used for LSKF verification, such as the scrypt salt and * parameters. Only exists for LSKF-based protectors. * parameters. Only exists for LSKF-based protectors. Doesn't exist when * the LSKF is empty, except in old protectors. * PASSWORD_METRICS_NAME: Metrics about the LSKF, encrypted by a key derived from the SP. * Only exists for LSKF-based protectors. * Only exists for LSKF-based protectors. Doesn't exist when the LSKF * is empty, except in old protectors. * SECDISCARDABLE_NAME: A large number of random bytes that all need to be known in order to * decrypt SP_BLOB_NAME. When the protector is deleted, this file is * overwritten and deleted as a "best-effort" attempt to support secure Loading Loading @@ -333,24 +336,15 @@ public class SyntheticPasswordManager { byte scryptLogP; public int credentialType; byte[] salt; // This is the Gatekeeper password handle that resulted from enrolling the stretched LSKF, // when applicable. This field isn't used if Weaver is available, or in new protectors when // the LSKF is empty. // When Weaver is unavailable, this is the Gatekeeper password handle that resulted from // enrolling the stretched LSKF. public byte[] passwordHandle; public static PasswordData create(int credentialType) { PasswordData result = new PasswordData(); if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) { // When the LSKF is empty, scrypt provides no security benefit, so just use the // minimum parameters (N=2, r=1, p=1). result.scryptLogN = 1; result.scryptLogR = 0; result.scryptLogP = 0; } else { result.scryptLogN = PASSWORD_SCRYPT_LOG_N; result.scryptLogR = PASSWORD_SCRYPT_LOG_R; result.scryptLogP = PASSWORD_SCRYPT_LOG_P; } result.credentialType = credentialType; result.salt = secureRandom(PASSWORD_SALT_LENGTH); return result; Loading Loading @@ -611,7 +605,6 @@ public class SyntheticPasswordManager { int getCredentialType(long protectorId, int userId) { byte[] passwordData = loadState(PASSWORD_DATA_NAME, protectorId, userId); if (passwordData == null) { Slog.w(TAG, "getCredentialType: encountered empty password data for user " + userId); return LockPatternUtils.CREDENTIAL_TYPE_NONE; } return PasswordData.fromBytes(passwordData).credentialType; Loading Loading @@ -783,7 +776,8 @@ public class SyntheticPasswordManager { public long createLskfBasedProtector(IGateKeeperService gatekeeper, LockscreenCredential credential, SyntheticPassword sp, int userId) { long protectorId = generateProtectorId(); PasswordData pwd = PasswordData.create(credential.getType()); // There's no need to store password data about an empty LSKF. PasswordData pwd = credential.isNone() ? null : PasswordData.create(credential.getType()); byte[] stretchedLskf = stretchLskf(credential, pwd); long sid = GateKeeper.INVALID_SECURE_USER_ID; final byte[] protectorSecret; Loading Loading @@ -837,8 +831,10 @@ public class SyntheticPasswordManager { // No need to pass in quality since the credential type already encodes sufficient info synchronizeFrpPassword(pwd, 0, userId); } if (!credential.isNone()) { saveState(PASSWORD_DATA_NAME, pwd.toBytes(), protectorId, userId); savePasswordMetrics(credential, sp, protectorId, userId); } createSyntheticPasswordBlob(protectorId, PROTECTOR_TYPE_LSKF_BASED, sp, protectorSecret, sid, userId); return protectorId; Loading Loading @@ -883,10 +879,11 @@ public class SyntheticPasswordManager { public void migrateFrpPasswordLocked(long protectorId, UserInfo userInfo, int requestedQuality) { if (mStorage.getPersistentDataBlockManager() != null && LockPatternUtils.userOwnsFrpCredential(mContext, userInfo)) { && LockPatternUtils.userOwnsFrpCredential(mContext, userInfo) && getCredentialType(protectorId, userInfo.id) != LockPatternUtils.CREDENTIAL_TYPE_NONE) { PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, protectorId, userInfo.id)); if (pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) { int weaverSlot = loadWeaverSlot(protectorId, userInfo.id); if (weaverSlot != INVALID_WEAVER_SLOT) { synchronizeWeaverFrpPassword(pwd, requestedQuality, userInfo.id, weaverSlot); Loading @@ -895,14 +892,13 @@ public class SyntheticPasswordManager { } } } } private void synchronizeFrpPassword(PasswordData pwd, int requestedQuality, int userId) { private void synchronizeFrpPassword(@Nullable PasswordData pwd, int requestedQuality, int userId) { if (mStorage.getPersistentDataBlockManager() != null && LockPatternUtils.userOwnsFrpCredential(mContext, mUserManager.getUserInfo(userId))) { if (pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) { if (pwd != null && pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) { mStorage.writePersistentDataBlock(PersistentData.TYPE_SP, userId, requestedQuality, pwd.toBytes()); } else { Loading @@ -911,12 +907,12 @@ public class SyntheticPasswordManager { } } private void synchronizeWeaverFrpPassword(PasswordData pwd, int requestedQuality, int userId, int weaverSlot) { private void synchronizeWeaverFrpPassword(@Nullable PasswordData pwd, int requestedQuality, int userId, int weaverSlot) { if (mStorage.getPersistentDataBlockManager() != null && LockPatternUtils.userOwnsFrpCredential(mContext, mUserManager.getUserInfo(userId))) { if (pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) { if (pwd != null && pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) { mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, weaverSlot, requestedQuality, pwd.toBytes()); } else { Loading Loading @@ -1047,12 +1043,20 @@ public class SyntheticPasswordManager { return result; } PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, protectorId, userId)); if (!credential.checkAgainstStoredType(pwd.credentialType)) { // Load the PasswordData file. If it doesn't exist, then the LSKF is empty (i.e., // CREDENTIAL_TYPE_NONE), and we'll skip the scrypt and Gatekeeper steps. If it exists, // then either the LSKF is nonempty, or it's an old protector that uses scrypt and // Gatekeeper even though the LSKF is empty. byte[] pwdDataBytes = loadState(PASSWORD_DATA_NAME, protectorId, userId); PasswordData pwd = null; int storedType = LockPatternUtils.CREDENTIAL_TYPE_NONE; if (pwdDataBytes != null) { pwd = PasswordData.fromBytes(pwdDataBytes); storedType = pwd.credentialType; } if (!credential.checkAgainstStoredType(storedType)) { Slog.e(TAG, TextUtils.formatSimple("Credential type mismatch: expected %d actual %d", pwd.credentialType, credential.getType())); storedType, credential.getType())); result.gkResponse = VerifyCredentialResponse.ERROR; return result; } Loading @@ -1078,7 +1082,7 @@ public class SyntheticPasswordManager { } else { // Weaver is unavailable, so the protector uses Gatekeeper to verify the LSKF, unless // the LSKF is empty in which case Gatekeeper might not have been used at all. if (pwd.passwordHandle == null) { if (pwd == null || pwd.passwordHandle == null) { if (!credential.isNone()) { Slog.e(TAG, "Missing Gatekeeper password handle for nonempty LSKF"); result.gkResponse = VerifyCredentialResponse.ERROR; Loading Loading @@ -1149,7 +1153,8 @@ public class SyntheticPasswordManager { // Upgrade case: store the metrics if the device did not have stored metrics before, should // only happen once on old protectors. if (result.syntheticPassword != null && !hasPasswordMetrics(protectorId, userId)) { if (result.syntheticPassword != null && !credential.isNone() && !hasPasswordMetrics(protectorId, userId)) { savePasswordMetrics(credential, result.syntheticPassword, protectorId, userId); } return result; Loading Loading @@ -1415,6 +1420,11 @@ public class SyntheticPasswordManager { } } @VisibleForTesting boolean hasPasswordData(long protectorId, int userId) { return hasState(PASSWORD_DATA_NAME, protectorId, userId); } /** * Retrieves a user's saved password metrics from their LSKF-based SP protector. The * SyntheticPassword itself is needed to decrypt the file containing the password metrics. Loading @@ -1422,10 +1432,16 @@ public class SyntheticPasswordManager { public @Nullable PasswordMetrics getPasswordMetrics(SyntheticPassword sp, long protectorId, int userId) { final byte[] encrypted = loadState(PASSWORD_METRICS_NAME, protectorId, userId); if (encrypted == null) return null; if (encrypted == null) { Slogf.e(TAG, "Failed to read password metrics file for user %d", userId); return null; } final byte[] decrypted = SyntheticPasswordCrypto.decrypt(sp.deriveMetricsKey(), /* personalization= */ new byte[0], encrypted); if (decrypted == null) return null; if (decrypted == null) { Slogf.e(TAG, "Failed to decrypt password metrics file for user %d", userId); return null; } return VersionedPasswordMetrics.deserialize(decrypted).getMetrics(); } Loading @@ -1437,7 +1453,8 @@ public class SyntheticPasswordManager { saveState(PASSWORD_METRICS_NAME, encrypted, protectorId, userId); } private boolean hasPasswordMetrics(long protectorId, int userId) { @VisibleForTesting boolean hasPasswordMetrics(long protectorId, int userId) { return hasState(PASSWORD_METRICS_NAME, protectorId, userId); } Loading Loading @@ -1500,8 +1517,23 @@ public class SyntheticPasswordManager { return TextUtils.formatSimple("%s%x", PROTECTOR_KEY_ALIAS_PREFIX, protectorId); } private byte[] stretchLskf(LockscreenCredential credential, PasswordData data) { /** * Stretches <code>credential</code>, if needed, using the parameters from <code>data</code>. * <p> * When the credential is empty, stetching provides no security benefit. Thus, new protectors * for an empty credential use <code>null</code> {@link PasswordData} and skip the stretching. * <p> * However, old protectors always stored {@link PasswordData} and did the stretching, regardless * of whether the credential was empty or not. For this reason, this method also continues to * support stretching of empty credentials so that old protectors can still be unlocked. */ @VisibleForTesting byte[] stretchLskf(LockscreenCredential credential, @Nullable PasswordData data) { final byte[] password = credential.isNone() ? DEFAULT_PASSWORD : credential.getCredential(); if (data == null) { Preconditions.checkArgument(credential.isNone()); return Arrays.copyOf(password, STRETCHED_LSKF_LENGTH); } return scrypt(password, data.salt, 1 << data.scryptLogN, 1 << data.scryptLogR, 1 << data.scryptLogP, STRETCHED_LSKF_LENGTH); } Loading
services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java +45 −11 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; 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.eq; Loading @@ -49,6 +50,8 @@ import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationRe import com.android.server.locksettings.SyntheticPasswordManager.PasswordData; import com.android.server.locksettings.SyntheticPasswordManager.SyntheticPassword; import libcore.util.HexEncoding; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; Loading Loading @@ -84,6 +87,8 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { long protectorId = manager.createLskfBasedProtector(mGateKeeperService, LockscreenCredential.createNone(), sp, USER_ID); assertFalse(lskfGatekeeperHandleExists(USER_ID)); assertFalse(manager.hasPasswordData(protectorId, USER_ID)); assertFalse(manager.hasPasswordMetrics(protectorId, USER_ID)); AuthenticationResult result = manager.unlockLskfBasedProtector(mGateKeeperService, protectorId, LockscreenCredential.createNone(), USER_ID, null); Loading @@ -103,6 +108,8 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { long protectorId = manager.createLskfBasedProtector(mGateKeeperService, password, sp, USER_ID); assertTrue(lskfGatekeeperHandleExists(USER_ID)); assertTrue(manager.hasPasswordData(protectorId, USER_ID)); assertTrue(manager.hasPasswordMetrics(protectorId, USER_ID)); AuthenticationResult result = manager.unlockLskfBasedProtector(mGateKeeperService, protectorId, password, USER_ID, null); Loading Loading @@ -452,19 +459,46 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { mService.getHashFactor(profilePassword, MANAGED_PROFILE_USER_ID)); } // Tests stretching of a nonempty LSKF. @Test public void testPasswordData_scryptParams() { // CREDENTIAL_TYPE_NONE should result in the minimum scrypt params being used. PasswordData data = PasswordData.create(CREDENTIAL_TYPE_NONE); assertEquals(1, data.scryptLogN); assertEquals(0, data.scryptLogR); assertEquals(0, data.scryptLogP); public void testStretchLskf_enabled() { byte[] actual = mSpManager.stretchLskf(newPin("12345"), createTestPasswordData()); String expected = "467986710DE8F0D4F4A3668DFF58C9B7E5DB96A79B7CCF415BBD4D7767F8CFFA"; assertEquals(expected, HexEncoding.encodeToString(actual)); } // Any other credential type should result in the real scrypt params being used. data = PasswordData.create(CREDENTIAL_TYPE_PASSWORD); assertTrue(data.scryptLogN > 1); assertTrue(data.scryptLogR > 0); assertTrue(data.scryptLogP > 0); // Tests the case where stretching is disabled for an empty LSKF. @Test public void testStretchLskf_disabled() { byte[] actual = mSpManager.stretchLskf(nonePassword(), null); // "default-password", zero padded String expected = "64656661756C742D70617373776F726400000000000000000000000000000000"; assertEquals(expected, HexEncoding.encodeToString(actual)); } // Tests the legacy case where stretching is enabled for an empty LSKF. @Test public void testStretchLskf_emptyButEnabled() { byte[] actual = mSpManager.stretchLskf(nonePassword(), createTestPasswordData()); String expected = "9E6DDCC1EC388BB1E1CD54097AF924CA80BCB90993196FA8F6122FF58EB333DE"; assertEquals(expected, HexEncoding.encodeToString(actual)); } // Tests the forbidden case where stretching is disabled for a nonempty LSKF. @Test public void testStretchLskf_nonEmptyButDisabled() { assertThrows(IllegalArgumentException.class, () -> mSpManager.stretchLskf(newPin("12345"), null)); } private PasswordData createTestPasswordData() { PasswordData data = new PasswordData(); // For the unit test, the scrypt parameters have to be constant; the salt can't be random. data.scryptLogN = 11; data.scryptLogR = 3; data.scryptLogP = 1; data.salt = "abcdefghijklmnop".getBytes(); return data; } @Test Loading