Merge "Eliminate unnecessary work for empty LSKFs"

This commit is contained in:
Robin Lee
2022-10-12 07:37:37 +00:00
committed by Android (Google) Code Review
2 changed files with 137 additions and 79 deletions

View File

@@ -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();
result.scryptLogN = PASSWORD_SCRYPT_LOG_N;
result.scryptLogR = PASSWORD_SCRYPT_LOG_R;
result.scryptLogP = PASSWORD_SCRYPT_LOG_P;
result.credentialType = passwordType;
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;
}
@@ -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),
@@ -794,33 +804,34 @@ 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.
try {
gatekeeper.clearSecureUserId(fakeUserId(userId));
} catch (RemoteException ignore) {
Slog.w(TAG, "Failed to clear SID from gatekeeper");
// 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) {
Slog.w(TAG, "Failed to clear SID from gatekeeper");
}
GateKeeperResponse response;
try {
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);
}
if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
throw new IllegalStateException("Failed to enroll LSKF for new SP protector"
+ " for user " + userId);
}
pwd.passwordHandle = response.getPayload();
sid = sidFromPasswordHandle(pwd.passwordHandle);
}
GateKeeperResponse response;
try {
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);
}
if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
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
@@ -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
@@ -1062,54 +1073,62 @@ 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
byte[] gkPassword = stretchedLskfToGkPassword(stretchedLskf);
GateKeeperResponse response;
try {
response = gatekeeper.verifyChallenge(fakeUserId(userId), 0L,
pwd.passwordHandle, gkPassword);
} catch (RemoteException e) {
Slog.e(TAG, "gatekeeper verify failed", e);
result.gkResponse = VerifyCredentialResponse.ERROR;
return result;
}
int responseCode = response.getResponseCode();
if (responseCode == GateKeeperResponse.RESPONSE_OK) {
result.gkResponse = VerifyCredentialResponse.OK;
if (response.getShouldReEnroll()) {
GateKeeperResponse reenrollResponse;
try {
reenrollResponse = gatekeeper.enroll(fakeUserId(userId),
pwd.passwordHandle, gkPassword, gkPassword);
} catch (RemoteException e) {
Slog.w(TAG, "Fail to invoke gatekeeper.enroll", e);
reenrollResponse = GateKeeperResponse.ERROR;
// continue the flow anyway
}
if (reenrollResponse.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
pwd.passwordHandle = reenrollResponse.getPayload();
// Use the reenrollment opportunity to update credential type
// (getting rid of CREDENTIAL_TYPE_PASSWORD_OR_PIN)
pwd.credentialType = credential.getType();
saveState(PASSWORD_DATA_NAME, pwd.toBytes(), protectorId, userId);
synchronizeFrpPassword(pwd, 0, userId);
} else {
Slog.w(TAG, "Fail to re-enroll user password for user " + userId);
// continue the flow anyway
}
// 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 if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
result.gkResponse = VerifyCredentialResponse.fromTimeout(response.getTimeout());
return result;
} else {
result.gkResponse = VerifyCredentialResponse.ERROR;
return result;
} else {
byte[] gkPassword = stretchedLskfToGkPassword(stretchedLskf);
GateKeeperResponse response;
try {
response = gatekeeper.verifyChallenge(fakeUserId(userId), 0L,
pwd.passwordHandle, gkPassword);
} catch (RemoteException e) {
Slog.e(TAG, "gatekeeper verify failed", e);
result.gkResponse = VerifyCredentialResponse.ERROR;
return result;
}
int responseCode = response.getResponseCode();
if (responseCode == GateKeeperResponse.RESPONSE_OK) {
result.gkResponse = VerifyCredentialResponse.OK;
if (response.getShouldReEnroll()) {
GateKeeperResponse reenrollResponse;
try {
reenrollResponse = gatekeeper.enroll(fakeUserId(userId),
pwd.passwordHandle, gkPassword, gkPassword);
} catch (RemoteException e) {
Slog.w(TAG, "Fail to invoke gatekeeper.enroll", e);
reenrollResponse = GateKeeperResponse.ERROR;
// continue the flow anyway
}
if (reenrollResponse.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
pwd.passwordHandle = reenrollResponse.getPayload();
// Use the reenrollment opportunity to update credential type
// (getting rid of CREDENTIAL_TYPE_PASSWORD_OR_PIN)
pwd.credentialType = credential.getType();
saveState(PASSWORD_DATA_NAME, pwd.toBytes(), protectorId, userId);
synchronizeFrpPassword(pwd, 0, userId);
} else {
Slog.w(TAG, "Fail to re-enroll user password for user " + userId);
// continue the flow anyway
}
}
} else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
result.gkResponse = VerifyCredentialResponse.fromTimeout(response.getTimeout());
return result;
} else {
result.gkResponse = VerifyCredentialResponse.ERROR;
return result;
}
sid = sidFromPasswordHandle(pwd.passwordHandle);
}
sid = sidFromPasswordHandle(pwd.passwordHandle);
protectorSecret = transformUnderSecdiscardable(stretchedLskf,
loadSecdiscardable(protectorId, userId));
}
@@ -1463,7 +1482,8 @@ public class SyntheticPasswordManager {
return result;
}
private int fakeUserId(int userId) {
@VisibleForTesting
static int fakeUserId(int userId) {
return 100000 + userId;
}

View File

@@ -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);
@@ -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;
}
@@ -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();