Loading services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +97 −77 Original line number Diff line number Diff line Loading @@ -333,16 +333,25 @@ public class SyntheticPasswordManager { byte scryptLogP; public int credentialType; byte[] salt; // If Weaver is available, then this field is empty. Otherwise, it is the Gatekeeper // password handle that resulted from enrolling the hashed LSKF. // 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. public byte[] passwordHandle; public static PasswordData create(int passwordType) { 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 = passwordType; } result.credentialType = credentialType; result.salt = secureRandom(PASSWORD_SALT_LENGTH); return result; } Loading Loading @@ -776,11 +785,12 @@ public class SyntheticPasswordManager { long protectorId = generateProtectorId(); PasswordData pwd = PasswordData.create(credential.getType()); byte[] stretchedLskf = stretchLskf(credential, pwd); final long sid; long sid = GateKeeper.INVALID_SECURE_USER_ID; final byte[] protectorSecret; if (isWeaverAvailable()) { // Protector uses Weaver to verify the LSKF // Weaver is available, so make the protector use it to verify the LSKF. Do this even // if the LSKF is empty, as that gives us support for securely deleting the protector. int weaverSlot = getNextAvailableWeaverSlot(); Slog.i(TAG, "Weaver enroll password to slot " + weaverSlot + " for user " + userId); byte[] weaverSecret = weaverEnroll(weaverSlot, stretchedLskfToWeaverKey(stretchedLskf), Loading @@ -794,14 +804,14 @@ public class SyntheticPasswordManager { // No need to pass in quality since the credential type already encodes sufficient info synchronizeWeaverFrpPassword(pwd, 0, userId, weaverSlot); pwd.passwordHandle = null; sid = GateKeeper.INVALID_SECURE_USER_ID; protectorSecret = transformUnderWeaverSecret(stretchedLskf, weaverSecret); } else { // Protector uses Gatekeeper to verify the LSKF // In case GK enrollment leaves persistent state around (in RPMB), this will nuke them // to prevent them from accumulating and causing problems. // Weaver is unavailable, so make the protector use Gatekeeper to verify the LSKF // instead. However, skip Gatekeeper when the LSKF is empty, since it wouldn't give any // benefit in that case as Gatekeeper isn't expected to provide secure deletion. if (!credential.isNone()) { // In case GK enrollment leaves persistent state around (in RPMB), this will nuke // them to prevent them from accumulating and causing problems. try { gatekeeper.clearSecureUserId(fakeUserId(userId)); } catch (RemoteException ignore) { Loading @@ -812,15 +822,16 @@ public class SyntheticPasswordManager { response = gatekeeper.enroll(fakeUserId(userId), null, null, stretchedLskfToGkPassword(stretchedLskf)); } catch (RemoteException e) { throw new IllegalStateException("Failed to enroll LSKF for new SP protector for " + "user " + userId, e); throw new IllegalStateException("Failed to enroll LSKF for new SP protector" + " for user " + userId, e); } if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) { throw new IllegalStateException("Failed to enroll LSKF for new SP protector for " + "user " + userId); throw new IllegalStateException("Failed to enroll LSKF for new SP protector" + " for user " + userId); } pwd.passwordHandle = response.getPayload(); sid = sidFromPasswordHandle(pwd.passwordHandle); } protectorSecret = transformUnderSecdiscardable(stretchedLskf, createSecdiscardable(protectorId, userId)); // No need to pass in quality since the credential type already encodes sufficient info Loading Loading @@ -1049,7 +1060,7 @@ public class SyntheticPasswordManager { byte[] stretchedLskf = stretchLskf(credential, pwd); final byte[] protectorSecret; final long sid; long sid = GateKeeper.INVALID_SECURE_USER_ID; int weaverSlot = loadWeaverSlot(protectorId, userId); if (weaverSlot != INVALID_WEAVER_SLOT) { // Protector uses Weaver to verify the LSKF Loading @@ -1062,11 +1073,18 @@ public class SyntheticPasswordManager { if (result.gkResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) { return result; } sid = GateKeeper.INVALID_SECURE_USER_ID; protectorSecret = transformUnderWeaverSecret(stretchedLskf, result.gkResponse.getGatekeeperHAT()); } else { // Protector uses Gatekeeper to verify the LSKF // 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 (!credential.isNone()) { Slog.e(TAG, "Missing Gatekeeper password handle for nonempty LSKF"); result.gkResponse = VerifyCredentialResponse.ERROR; return result; } } else { byte[] gkPassword = stretchedLskfToGkPassword(stretchedLskf); GateKeeperResponse response; try { Loading Loading @@ -1110,6 +1128,7 @@ public class SyntheticPasswordManager { return result; } sid = sidFromPasswordHandle(pwd.passwordHandle); } protectorSecret = transformUnderSecdiscardable(stretchedLskf, loadSecdiscardable(protectorId, userId)); } Loading Loading @@ -1463,7 +1482,8 @@ public class SyntheticPasswordManager { return result; } private int fakeUserId(int userId) { @VisibleForTesting static int fakeUserId(int userId) { return 100000 + userId; } Loading services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java +39 −1 Original line number Diff line number Diff line Loading @@ -75,15 +75,34 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { } @Test public void testLskfBasedProtector() throws RemoteException { public void testNoneLskfBasedProtector() throws RemoteException { final int USER_ID = 10; MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mContext, mStorage, mGateKeeperService, mUserManager, mPasswordSlotManager); SyntheticPassword sp = manager.newSyntheticPassword(USER_ID); assertFalse(lskfGatekeeperHandleExists(USER_ID)); long protectorId = manager.createLskfBasedProtector(mGateKeeperService, LockscreenCredential.createNone(), sp, USER_ID); assertFalse(lskfGatekeeperHandleExists(USER_ID)); AuthenticationResult result = manager.unlockLskfBasedProtector(mGateKeeperService, protectorId, LockscreenCredential.createNone(), USER_ID, null); assertArrayEquals(result.syntheticPassword.deriveKeyStorePassword(), sp.deriveKeyStorePassword()); } @Test public void testNonNoneLskfBasedProtector() throws RemoteException { final int USER_ID = 10; final LockscreenCredential password = newPassword("user-password"); final LockscreenCredential badPassword = newPassword("bad-password"); MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mContext, mStorage, mGateKeeperService, mUserManager, mPasswordSlotManager); SyntheticPassword sp = manager.newSyntheticPassword(USER_ID); assertFalse(lskfGatekeeperHandleExists(USER_ID)); long protectorId = manager.createLskfBasedProtector(mGateKeeperService, password, sp, USER_ID); assertTrue(lskfGatekeeperHandleExists(USER_ID)); AuthenticationResult result = manager.unlockLskfBasedProtector(mGateKeeperService, protectorId, password, USER_ID, null); Loading @@ -95,6 +114,10 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { assertNull(result.syntheticPassword); } private boolean lskfGatekeeperHandleExists(int userId) throws RemoteException { return mGateKeeperService.getSecureUserId(SyntheticPasswordManager.fakeUserId(userId)) != 0; } private boolean hasSyntheticPassword(int userId) throws RemoteException { return mService.getLong(CURRENT_LSKF_BASED_PROTECTOR_ID_KEY, 0, userId) != 0; } Loading Loading @@ -429,6 +452,21 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { mService.getHashFactor(profilePassword, MANAGED_PROFILE_USER_ID)); } @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); // 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); } @Test public void testPasswordData_serializeDeserialize() { PasswordData data = new PasswordData(); Loading Loading
services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +97 −77 Original line number Diff line number Diff line Loading @@ -333,16 +333,25 @@ public class SyntheticPasswordManager { byte scryptLogP; public int credentialType; byte[] salt; // If Weaver is available, then this field is empty. Otherwise, it is the Gatekeeper // password handle that resulted from enrolling the hashed LSKF. // 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. public byte[] passwordHandle; public static PasswordData create(int passwordType) { 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 = passwordType; } result.credentialType = credentialType; result.salt = secureRandom(PASSWORD_SALT_LENGTH); return result; } Loading Loading @@ -776,11 +785,12 @@ public class SyntheticPasswordManager { long protectorId = generateProtectorId(); PasswordData pwd = PasswordData.create(credential.getType()); byte[] stretchedLskf = stretchLskf(credential, pwd); final long sid; long sid = GateKeeper.INVALID_SECURE_USER_ID; final byte[] protectorSecret; if (isWeaverAvailable()) { // Protector uses Weaver to verify the LSKF // Weaver is available, so make the protector use it to verify the LSKF. Do this even // if the LSKF is empty, as that gives us support for securely deleting the protector. int weaverSlot = getNextAvailableWeaverSlot(); Slog.i(TAG, "Weaver enroll password to slot " + weaverSlot + " for user " + userId); byte[] weaverSecret = weaverEnroll(weaverSlot, stretchedLskfToWeaverKey(stretchedLskf), Loading @@ -794,14 +804,14 @@ public class SyntheticPasswordManager { // No need to pass in quality since the credential type already encodes sufficient info synchronizeWeaverFrpPassword(pwd, 0, userId, weaverSlot); pwd.passwordHandle = null; sid = GateKeeper.INVALID_SECURE_USER_ID; protectorSecret = transformUnderWeaverSecret(stretchedLskf, weaverSecret); } else { // Protector uses Gatekeeper to verify the LSKF // In case GK enrollment leaves persistent state around (in RPMB), this will nuke them // to prevent them from accumulating and causing problems. // Weaver is unavailable, so make the protector use Gatekeeper to verify the LSKF // instead. However, skip Gatekeeper when the LSKF is empty, since it wouldn't give any // benefit in that case as Gatekeeper isn't expected to provide secure deletion. if (!credential.isNone()) { // In case GK enrollment leaves persistent state around (in RPMB), this will nuke // them to prevent them from accumulating and causing problems. try { gatekeeper.clearSecureUserId(fakeUserId(userId)); } catch (RemoteException ignore) { Loading @@ -812,15 +822,16 @@ public class SyntheticPasswordManager { response = gatekeeper.enroll(fakeUserId(userId), null, null, stretchedLskfToGkPassword(stretchedLskf)); } catch (RemoteException e) { throw new IllegalStateException("Failed to enroll LSKF for new SP protector for " + "user " + userId, e); throw new IllegalStateException("Failed to enroll LSKF for new SP protector" + " for user " + userId, e); } if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) { throw new IllegalStateException("Failed to enroll LSKF for new SP protector for " + "user " + userId); throw new IllegalStateException("Failed to enroll LSKF for new SP protector" + " for user " + userId); } pwd.passwordHandle = response.getPayload(); sid = sidFromPasswordHandle(pwd.passwordHandle); } protectorSecret = transformUnderSecdiscardable(stretchedLskf, createSecdiscardable(protectorId, userId)); // No need to pass in quality since the credential type already encodes sufficient info Loading Loading @@ -1049,7 +1060,7 @@ public class SyntheticPasswordManager { byte[] stretchedLskf = stretchLskf(credential, pwd); final byte[] protectorSecret; final long sid; long sid = GateKeeper.INVALID_SECURE_USER_ID; int weaverSlot = loadWeaverSlot(protectorId, userId); if (weaverSlot != INVALID_WEAVER_SLOT) { // Protector uses Weaver to verify the LSKF Loading @@ -1062,11 +1073,18 @@ public class SyntheticPasswordManager { if (result.gkResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) { return result; } sid = GateKeeper.INVALID_SECURE_USER_ID; protectorSecret = transformUnderWeaverSecret(stretchedLskf, result.gkResponse.getGatekeeperHAT()); } else { // Protector uses Gatekeeper to verify the LSKF // 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 (!credential.isNone()) { Slog.e(TAG, "Missing Gatekeeper password handle for nonempty LSKF"); result.gkResponse = VerifyCredentialResponse.ERROR; return result; } } else { byte[] gkPassword = stretchedLskfToGkPassword(stretchedLskf); GateKeeperResponse response; try { Loading Loading @@ -1110,6 +1128,7 @@ public class SyntheticPasswordManager { return result; } sid = sidFromPasswordHandle(pwd.passwordHandle); } protectorSecret = transformUnderSecdiscardable(stretchedLskf, loadSecdiscardable(protectorId, userId)); } Loading Loading @@ -1463,7 +1482,8 @@ public class SyntheticPasswordManager { return result; } private int fakeUserId(int userId) { @VisibleForTesting static int fakeUserId(int userId) { return 100000 + userId; } Loading
services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java +39 −1 Original line number Diff line number Diff line Loading @@ -75,15 +75,34 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { } @Test public void testLskfBasedProtector() throws RemoteException { public void testNoneLskfBasedProtector() throws RemoteException { final int USER_ID = 10; MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mContext, mStorage, mGateKeeperService, mUserManager, mPasswordSlotManager); SyntheticPassword sp = manager.newSyntheticPassword(USER_ID); assertFalse(lskfGatekeeperHandleExists(USER_ID)); long protectorId = manager.createLskfBasedProtector(mGateKeeperService, LockscreenCredential.createNone(), sp, USER_ID); assertFalse(lskfGatekeeperHandleExists(USER_ID)); AuthenticationResult result = manager.unlockLskfBasedProtector(mGateKeeperService, protectorId, LockscreenCredential.createNone(), USER_ID, null); assertArrayEquals(result.syntheticPassword.deriveKeyStorePassword(), sp.deriveKeyStorePassword()); } @Test public void testNonNoneLskfBasedProtector() throws RemoteException { final int USER_ID = 10; final LockscreenCredential password = newPassword("user-password"); final LockscreenCredential badPassword = newPassword("bad-password"); MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mContext, mStorage, mGateKeeperService, mUserManager, mPasswordSlotManager); SyntheticPassword sp = manager.newSyntheticPassword(USER_ID); assertFalse(lskfGatekeeperHandleExists(USER_ID)); long protectorId = manager.createLskfBasedProtector(mGateKeeperService, password, sp, USER_ID); assertTrue(lskfGatekeeperHandleExists(USER_ID)); AuthenticationResult result = manager.unlockLskfBasedProtector(mGateKeeperService, protectorId, password, USER_ID, null); Loading @@ -95,6 +114,10 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { assertNull(result.syntheticPassword); } private boolean lskfGatekeeperHandleExists(int userId) throws RemoteException { return mGateKeeperService.getSecureUserId(SyntheticPasswordManager.fakeUserId(userId)) != 0; } private boolean hasSyntheticPassword(int userId) throws RemoteException { return mService.getLong(CURRENT_LSKF_BASED_PROTECTOR_ID_KEY, 0, userId) != 0; } Loading Loading @@ -429,6 +452,21 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { mService.getHashFactor(profilePassword, MANAGED_PROFILE_USER_ID)); } @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); // 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); } @Test public void testPasswordData_serializeDeserialize() { PasswordData data = new PasswordData(); Loading