Loading services/core/java/com/android/server/locksettings/LockSettingsService.java +25 −10 Original line number Diff line number Diff line Loading @@ -2161,6 +2161,13 @@ public class LockSettingsService extends ILockSettings.Stub { } } private PasswordMetrics loadPasswordMetrics(AuthenticationToken auth, int userHandle) { synchronized (mSpManager) { return mSpManager.getPasswordMetrics(auth, getSyntheticPasswordHandleLocked(userHandle), userHandle); } } /** * Call after {@link #setUserPasswordMetrics} so metrics are updated before * reporting the password changed. Loading Loading @@ -2611,7 +2618,8 @@ public class LockSettingsService extends ILockSettings.Stub { return auth; } private long getSyntheticPasswordHandleLocked(int userId) { @VisibleForTesting long getSyntheticPasswordHandleLocked(int userId) { return getLong(SYNTHETIC_PASSWORD_HANDLE_KEY, SyntheticPasswordManager.DEFAULT_HANDLE, userId); } Loading Loading @@ -2706,13 +2714,8 @@ public class LockSettingsService extends ILockSettings.Stub { resetLockouts.add(new PendingResetLockout(userId, response.getPayload())); } // TODO: Move setUserPasswordMetrics() inside onCredentialVerified(): this will require // LSS to store an encrypted version of the latest password metric for every user, // because user credential is not known when onCredentialVerified() is called during // a token-based unlock. setUserPasswordMetrics(userCredential, userId); onCredentialVerified(authResult.authToken, challengeType, challenge, resetLockouts, userId); PasswordMetrics.computeForCredential(userCredential), userId); } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) { if (response.getTimeout() > 0) { requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId); Loading @@ -2724,7 +2727,16 @@ public class LockSettingsService extends ILockSettings.Stub { private void onCredentialVerified(AuthenticationToken authToken, @ChallengeType int challengeType, long challenge, @Nullable ArrayList<PendingResetLockout> resetLockouts, int userId) { @Nullable ArrayList<PendingResetLockout> resetLockouts, PasswordMetrics metrics, int userId) { if (metrics != null) { synchronized (this) { mUserPasswordMetrics.put(userId, metrics); } } else { Slog.wtf(TAG, "Null metrics after credential verification"); } unlockKeystore(authToken.deriveKeyStorePassword(), userId); Loading Loading @@ -3118,7 +3130,9 @@ public class LockSettingsService extends ILockSettings.Stub { // TODO: Reset biometrics lockout here. Ideally that should be self-contained inside // onCredentialVerified(), which will require some refactoring on the current lockout // reset logic. onCredentialVerified(authResult.authToken, CHALLENGE_NONE, 0, null, userId); onCredentialVerified(authResult.authToken, CHALLENGE_NONE, 0, null, loadPasswordMetrics(authResult.authToken, userId), userId); return true; } Loading Loading @@ -3414,7 +3428,8 @@ public class LockSettingsService extends ILockSettings.Stub { SyntheticPasswordManager.AuthenticationToken authToken = new SyntheticPasswordManager.AuthenticationToken(spVersion); authToken.recreateDirectly(syntheticPassword); onCredentialVerified(authToken, CHALLENGE_NONE, 0, null, userId); onCredentialVerified(authToken, CHALLENGE_NONE, 0, null, loadPasswordMetrics(authToken, userId), userId); } } } services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +44 −1 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChang import android.annotation.NonNull; import android.annotation.Nullable; import android.app.admin.PasswordMetrics; import android.content.Context; import android.content.pm.UserInfo; import android.hardware.weaver.V1_0.IWeaver; Loading Loading @@ -97,6 +98,7 @@ public class SyntheticPasswordManager { private static final int SECDISCARDABLE_LENGTH = 16 * 1024; private static final String PASSWORD_DATA_NAME = "pwd"; private static final String WEAVER_SLOT_NAME = "weaver"; private static final String PASSWORD_METRICS_NAME = "metrics"; public static final long DEFAULT_HANDLE = 0L; private static final byte[] DEFAULT_PASSWORD = "default-password".getBytes(); Loading Loading @@ -132,6 +134,7 @@ public class SyntheticPasswordManager { private static final byte[] PERSONALISATION_WEAVER_PASSWORD = "weaver-pwd".getBytes(); private static final byte[] PERSONALISATION_WEAVER_KEY = "weaver-key".getBytes(); private static final byte[] PERSONALISATION_WEAVER_TOKEN = "weaver-token".getBytes(); private static final byte[] PERSONALIZATION_PASSWORD_METRICS = "password-metrics".getBytes(); private static final byte[] PERSONALISATION_CONTEXT = "android-synthetic-password-personalization-context".getBytes(); Loading Loading @@ -212,6 +215,11 @@ public class SyntheticPasswordManager { return derivePassword(PERSONALIZATION_PASSWORD_HASH); } /** Derives key used to encrypt password metrics */ public byte[] deriveMetricsKey() { return derivePassword(PERSONALIZATION_PASSWORD_METRICS); } /** * Assign escrow data to this auth token. This is a prerequisite to call * {@link AuthenticationToken#recreateFromEscrow}. Loading Loading @@ -778,7 +786,7 @@ public class SyntheticPasswordManager { synchronizeFrpPassword(pwd, 0, userId); } saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId); savePasswordMetrics(credential, authToken, handle, userId); createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED, authToken, applicationId, sid, userId); return handle; Loading Loading @@ -1061,6 +1069,12 @@ public class SyntheticPasswordManager { // Perform verifyChallenge to refresh auth tokens for GK if user password exists. result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId); // Upgrade case: store the metrics if the device did not have stored metrics before, should // only happen once on old synthetic password blobs. if (result.authToken != null && !hasPasswordMetrics(handle, userId)) { savePasswordMetrics(credential, result.authToken, handle, userId); } return result; } Loading Loading @@ -1216,6 +1230,7 @@ public class SyntheticPasswordManager { destroySyntheticPassword(handle, userId); destroyState(SECDISCARDABLE_NAME, handle, userId); destroyState(PASSWORD_DATA_NAME, handle, userId); destroyState(PASSWORD_METRICS_NAME, handle, userId); } private void destroySyntheticPassword(long handle, int userId) { Loading Loading @@ -1258,6 +1273,34 @@ public class SyntheticPasswordManager { return loadState(SECDISCARDABLE_NAME, handle, userId); } /** * Retrieves the saved password metrics associated with a SP handle. Only meaningful to be * called on the handle of a password-based synthetic password. A valid AuthenticationToken for * the target user is required in order to be able to decrypt the encrypted password metrics on * disk. */ public @Nullable PasswordMetrics getPasswordMetrics(AuthenticationToken authToken, long handle, int userId) { final byte[] encrypted = loadState(PASSWORD_METRICS_NAME, handle, userId); if (encrypted == null) return null; final byte[] decrypted = SyntheticPasswordCrypto.decrypt(authToken.deriveMetricsKey(), /* personalization= */ new byte[0], encrypted); if (decrypted == null) return null; return VersionedPasswordMetrics.deserialize(decrypted).getMetrics(); } private void savePasswordMetrics(LockscreenCredential credential, AuthenticationToken authToken, long handle, int userId) { final byte[] encrypted = SyntheticPasswordCrypto.encrypt(authToken.deriveMetricsKey(), /* personalization= */ new byte[0], new VersionedPasswordMetrics(credential).serialize()); saveState(PASSWORD_METRICS_NAME, encrypted, handle, userId); } private boolean hasPasswordMetrics(long handle, int userId) { return hasState(PASSWORD_METRICS_NAME, handle, userId); } private boolean hasState(String stateName, long handle, int userId) { return !ArrayUtils.isEmpty(loadState(stateName, handle, userId)); } Loading services/core/java/com/android/server/locksettings/VersionedPasswordMetrics.java 0 → 100644 +79 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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 android.app.admin.PasswordMetrics; import com.android.internal.widget.LockscreenCredential; import java.nio.ByteBuffer; /** * A versioned and serializable wrapper around {@link PasswordMetrics}, * for long-term persistence on disk. */ public class VersionedPasswordMetrics { private static final int VERSION_1 = 1; private final PasswordMetrics mMetrics; private final int mVersion; private VersionedPasswordMetrics(int version, PasswordMetrics metrics) { mMetrics = metrics; mVersion = version; } public VersionedPasswordMetrics(LockscreenCredential credential) { this(VERSION_1, PasswordMetrics.computeForCredential(credential)); } public int getVersion() { return mVersion; } public PasswordMetrics getMetrics() { return mMetrics; } /** Serialize object to a byte array. */ public byte[] serialize() { final ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES * 11); buffer.putInt(mVersion); buffer.putInt(mMetrics.credType); buffer.putInt(mMetrics.length); buffer.putInt(mMetrics.letters); buffer.putInt(mMetrics.upperCase); buffer.putInt(mMetrics.lowerCase); buffer.putInt(mMetrics.numeric); buffer.putInt(mMetrics.symbols); buffer.putInt(mMetrics.nonLetter); buffer.putInt(mMetrics.nonNumeric); buffer.putInt(mMetrics.seqLength); return buffer.array(); } /** Deserialize byte array to an object */ public static VersionedPasswordMetrics deserialize(byte[] data) { final ByteBuffer buffer = ByteBuffer.allocate(data.length); buffer.put(data, 0, data.length); buffer.flip(); final int version = buffer.getInt(); PasswordMetrics metrics = new PasswordMetrics(buffer.getInt(), buffer.getInt(), buffer.getInt(), buffer.getInt(), buffer.getInt(), buffer.getInt(), buffer.getInt(), buffer.getInt(), buffer.getInt(), buffer.getInt()); return new VersionedPasswordMetrics(version, metrics); } } services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java +40 −1 Original line number Diff line number Diff line Loading @@ -53,6 +53,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import java.io.File; import java.util.ArrayList; Loading Loading @@ -104,7 +105,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { protected void initializeCredentialUnderSP(LockscreenCredential password, int userId) throws RemoteException { enableSyntheticPassword(); mService.setLockCredential(password, nonePassword(), userId); assertTrue(mService.setLockCredential(password, nonePassword(), userId)); assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(userId)); assertTrue(mService.isSyntheticPasswordBasedCredential(userId)); } Loading Loading @@ -492,6 +493,44 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class)); } @Test public void testUnlockUserWithToken() throws Exception { LockscreenCredential password = newPassword("password"); byte[] token = "some-high-entropy-secure-token".getBytes(); initializeCredentialUnderSP(password, PRIMARY_USER_ID); // Disregard any reportPasswordChanged() invocations as part of credential setup. flushHandlerTasks(); reset(mDevicePolicyManager); long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null); mService.verifyCredential(password, 0, PRIMARY_USER_ID).getResponseCode(); assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); mService.onCleanupUser(PRIMARY_USER_ID); assertNull(mLocalService.getUserPasswordMetrics(PRIMARY_USER_ID)); assertTrue(mLocalService.unlockUserWithToken(handle, token, PRIMARY_USER_ID)); assertEquals(PasswordMetrics.computeForCredential(password), mLocalService.getUserPasswordMetrics(PRIMARY_USER_ID)); } @Test public void testPasswordChange_NoOrphanedFilesLeft() throws Exception { LockscreenCredential password = newPassword("password"); initializeCredentialUnderSP(password, PRIMARY_USER_ID); assertTrue(mService.setLockCredential(password, password, PRIMARY_USER_ID)); String handleString = String.format("%016x", mService.getSyntheticPasswordHandleLocked(PRIMARY_USER_ID)); File directory = mStorage.getSyntheticPasswordDirectoryForUser(PRIMARY_USER_ID); for (File file : directory.listFiles()) { String[] parts = file.getName().split("\\."); if (!parts[0].equals(handleString) && !parts[0].equals("0000000000000000")) { fail("Orphaned state left: " + file.getName()); } } } // b/62213311 //TODO: add non-migration work profile case, and unify/un-unify transition. //TODO: test token after user resets password Loading Loading
services/core/java/com/android/server/locksettings/LockSettingsService.java +25 −10 Original line number Diff line number Diff line Loading @@ -2161,6 +2161,13 @@ public class LockSettingsService extends ILockSettings.Stub { } } private PasswordMetrics loadPasswordMetrics(AuthenticationToken auth, int userHandle) { synchronized (mSpManager) { return mSpManager.getPasswordMetrics(auth, getSyntheticPasswordHandleLocked(userHandle), userHandle); } } /** * Call after {@link #setUserPasswordMetrics} so metrics are updated before * reporting the password changed. Loading Loading @@ -2611,7 +2618,8 @@ public class LockSettingsService extends ILockSettings.Stub { return auth; } private long getSyntheticPasswordHandleLocked(int userId) { @VisibleForTesting long getSyntheticPasswordHandleLocked(int userId) { return getLong(SYNTHETIC_PASSWORD_HANDLE_KEY, SyntheticPasswordManager.DEFAULT_HANDLE, userId); } Loading Loading @@ -2706,13 +2714,8 @@ public class LockSettingsService extends ILockSettings.Stub { resetLockouts.add(new PendingResetLockout(userId, response.getPayload())); } // TODO: Move setUserPasswordMetrics() inside onCredentialVerified(): this will require // LSS to store an encrypted version of the latest password metric for every user, // because user credential is not known when onCredentialVerified() is called during // a token-based unlock. setUserPasswordMetrics(userCredential, userId); onCredentialVerified(authResult.authToken, challengeType, challenge, resetLockouts, userId); PasswordMetrics.computeForCredential(userCredential), userId); } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) { if (response.getTimeout() > 0) { requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId); Loading @@ -2724,7 +2727,16 @@ public class LockSettingsService extends ILockSettings.Stub { private void onCredentialVerified(AuthenticationToken authToken, @ChallengeType int challengeType, long challenge, @Nullable ArrayList<PendingResetLockout> resetLockouts, int userId) { @Nullable ArrayList<PendingResetLockout> resetLockouts, PasswordMetrics metrics, int userId) { if (metrics != null) { synchronized (this) { mUserPasswordMetrics.put(userId, metrics); } } else { Slog.wtf(TAG, "Null metrics after credential verification"); } unlockKeystore(authToken.deriveKeyStorePassword(), userId); Loading Loading @@ -3118,7 +3130,9 @@ public class LockSettingsService extends ILockSettings.Stub { // TODO: Reset biometrics lockout here. Ideally that should be self-contained inside // onCredentialVerified(), which will require some refactoring on the current lockout // reset logic. onCredentialVerified(authResult.authToken, CHALLENGE_NONE, 0, null, userId); onCredentialVerified(authResult.authToken, CHALLENGE_NONE, 0, null, loadPasswordMetrics(authResult.authToken, userId), userId); return true; } Loading Loading @@ -3414,7 +3428,8 @@ public class LockSettingsService extends ILockSettings.Stub { SyntheticPasswordManager.AuthenticationToken authToken = new SyntheticPasswordManager.AuthenticationToken(spVersion); authToken.recreateDirectly(syntheticPassword); onCredentialVerified(authToken, CHALLENGE_NONE, 0, null, userId); onCredentialVerified(authToken, CHALLENGE_NONE, 0, null, loadPasswordMetrics(authToken, userId), userId); } } }
services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +44 −1 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChang import android.annotation.NonNull; import android.annotation.Nullable; import android.app.admin.PasswordMetrics; import android.content.Context; import android.content.pm.UserInfo; import android.hardware.weaver.V1_0.IWeaver; Loading Loading @@ -97,6 +98,7 @@ public class SyntheticPasswordManager { private static final int SECDISCARDABLE_LENGTH = 16 * 1024; private static final String PASSWORD_DATA_NAME = "pwd"; private static final String WEAVER_SLOT_NAME = "weaver"; private static final String PASSWORD_METRICS_NAME = "metrics"; public static final long DEFAULT_HANDLE = 0L; private static final byte[] DEFAULT_PASSWORD = "default-password".getBytes(); Loading Loading @@ -132,6 +134,7 @@ public class SyntheticPasswordManager { private static final byte[] PERSONALISATION_WEAVER_PASSWORD = "weaver-pwd".getBytes(); private static final byte[] PERSONALISATION_WEAVER_KEY = "weaver-key".getBytes(); private static final byte[] PERSONALISATION_WEAVER_TOKEN = "weaver-token".getBytes(); private static final byte[] PERSONALIZATION_PASSWORD_METRICS = "password-metrics".getBytes(); private static final byte[] PERSONALISATION_CONTEXT = "android-synthetic-password-personalization-context".getBytes(); Loading Loading @@ -212,6 +215,11 @@ public class SyntheticPasswordManager { return derivePassword(PERSONALIZATION_PASSWORD_HASH); } /** Derives key used to encrypt password metrics */ public byte[] deriveMetricsKey() { return derivePassword(PERSONALIZATION_PASSWORD_METRICS); } /** * Assign escrow data to this auth token. This is a prerequisite to call * {@link AuthenticationToken#recreateFromEscrow}. Loading Loading @@ -778,7 +786,7 @@ public class SyntheticPasswordManager { synchronizeFrpPassword(pwd, 0, userId); } saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId); savePasswordMetrics(credential, authToken, handle, userId); createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED, authToken, applicationId, sid, userId); return handle; Loading Loading @@ -1061,6 +1069,12 @@ public class SyntheticPasswordManager { // Perform verifyChallenge to refresh auth tokens for GK if user password exists. result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId); // Upgrade case: store the metrics if the device did not have stored metrics before, should // only happen once on old synthetic password blobs. if (result.authToken != null && !hasPasswordMetrics(handle, userId)) { savePasswordMetrics(credential, result.authToken, handle, userId); } return result; } Loading Loading @@ -1216,6 +1230,7 @@ public class SyntheticPasswordManager { destroySyntheticPassword(handle, userId); destroyState(SECDISCARDABLE_NAME, handle, userId); destroyState(PASSWORD_DATA_NAME, handle, userId); destroyState(PASSWORD_METRICS_NAME, handle, userId); } private void destroySyntheticPassword(long handle, int userId) { Loading Loading @@ -1258,6 +1273,34 @@ public class SyntheticPasswordManager { return loadState(SECDISCARDABLE_NAME, handle, userId); } /** * Retrieves the saved password metrics associated with a SP handle. Only meaningful to be * called on the handle of a password-based synthetic password. A valid AuthenticationToken for * the target user is required in order to be able to decrypt the encrypted password metrics on * disk. */ public @Nullable PasswordMetrics getPasswordMetrics(AuthenticationToken authToken, long handle, int userId) { final byte[] encrypted = loadState(PASSWORD_METRICS_NAME, handle, userId); if (encrypted == null) return null; final byte[] decrypted = SyntheticPasswordCrypto.decrypt(authToken.deriveMetricsKey(), /* personalization= */ new byte[0], encrypted); if (decrypted == null) return null; return VersionedPasswordMetrics.deserialize(decrypted).getMetrics(); } private void savePasswordMetrics(LockscreenCredential credential, AuthenticationToken authToken, long handle, int userId) { final byte[] encrypted = SyntheticPasswordCrypto.encrypt(authToken.deriveMetricsKey(), /* personalization= */ new byte[0], new VersionedPasswordMetrics(credential).serialize()); saveState(PASSWORD_METRICS_NAME, encrypted, handle, userId); } private boolean hasPasswordMetrics(long handle, int userId) { return hasState(PASSWORD_METRICS_NAME, handle, userId); } private boolean hasState(String stateName, long handle, int userId) { return !ArrayUtils.isEmpty(loadState(stateName, handle, userId)); } Loading
services/core/java/com/android/server/locksettings/VersionedPasswordMetrics.java 0 → 100644 +79 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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 android.app.admin.PasswordMetrics; import com.android.internal.widget.LockscreenCredential; import java.nio.ByteBuffer; /** * A versioned and serializable wrapper around {@link PasswordMetrics}, * for long-term persistence on disk. */ public class VersionedPasswordMetrics { private static final int VERSION_1 = 1; private final PasswordMetrics mMetrics; private final int mVersion; private VersionedPasswordMetrics(int version, PasswordMetrics metrics) { mMetrics = metrics; mVersion = version; } public VersionedPasswordMetrics(LockscreenCredential credential) { this(VERSION_1, PasswordMetrics.computeForCredential(credential)); } public int getVersion() { return mVersion; } public PasswordMetrics getMetrics() { return mMetrics; } /** Serialize object to a byte array. */ public byte[] serialize() { final ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES * 11); buffer.putInt(mVersion); buffer.putInt(mMetrics.credType); buffer.putInt(mMetrics.length); buffer.putInt(mMetrics.letters); buffer.putInt(mMetrics.upperCase); buffer.putInt(mMetrics.lowerCase); buffer.putInt(mMetrics.numeric); buffer.putInt(mMetrics.symbols); buffer.putInt(mMetrics.nonLetter); buffer.putInt(mMetrics.nonNumeric); buffer.putInt(mMetrics.seqLength); return buffer.array(); } /** Deserialize byte array to an object */ public static VersionedPasswordMetrics deserialize(byte[] data) { final ByteBuffer buffer = ByteBuffer.allocate(data.length); buffer.put(data, 0, data.length); buffer.flip(); final int version = buffer.getInt(); PasswordMetrics metrics = new PasswordMetrics(buffer.getInt(), buffer.getInt(), buffer.getInt(), buffer.getInt(), buffer.getInt(), buffer.getInt(), buffer.getInt(), buffer.getInt(), buffer.getInt(), buffer.getInt()); return new VersionedPasswordMetrics(version, metrics); } }
services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java +40 −1 Original line number Diff line number Diff line Loading @@ -53,6 +53,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import java.io.File; import java.util.ArrayList; Loading Loading @@ -104,7 +105,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { protected void initializeCredentialUnderSP(LockscreenCredential password, int userId) throws RemoteException { enableSyntheticPassword(); mService.setLockCredential(password, nonePassword(), userId); assertTrue(mService.setLockCredential(password, nonePassword(), userId)); assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(userId)); assertTrue(mService.isSyntheticPasswordBasedCredential(userId)); } Loading Loading @@ -492,6 +493,44 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class)); } @Test public void testUnlockUserWithToken() throws Exception { LockscreenCredential password = newPassword("password"); byte[] token = "some-high-entropy-secure-token".getBytes(); initializeCredentialUnderSP(password, PRIMARY_USER_ID); // Disregard any reportPasswordChanged() invocations as part of credential setup. flushHandlerTasks(); reset(mDevicePolicyManager); long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null); mService.verifyCredential(password, 0, PRIMARY_USER_ID).getResponseCode(); assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); mService.onCleanupUser(PRIMARY_USER_ID); assertNull(mLocalService.getUserPasswordMetrics(PRIMARY_USER_ID)); assertTrue(mLocalService.unlockUserWithToken(handle, token, PRIMARY_USER_ID)); assertEquals(PasswordMetrics.computeForCredential(password), mLocalService.getUserPasswordMetrics(PRIMARY_USER_ID)); } @Test public void testPasswordChange_NoOrphanedFilesLeft() throws Exception { LockscreenCredential password = newPassword("password"); initializeCredentialUnderSP(password, PRIMARY_USER_ID); assertTrue(mService.setLockCredential(password, password, PRIMARY_USER_ID)); String handleString = String.format("%016x", mService.getSyntheticPasswordHandleLocked(PRIMARY_USER_ID)); File directory = mStorage.getSyntheticPasswordDirectoryForUser(PRIMARY_USER_ID); for (File file : directory.listFiles()) { String[] parts = file.getName().split("\\."); if (!parts[0].equals(handleString) && !parts[0].equals("0000000000000000")) { fail("Orphaned state left: " + file.getName()); } } } // b/62213311 //TODO: add non-migration work profile case, and unify/un-unify transition. //TODO: test token after user resets password Loading