diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 24a3c16fb0d32..cc076ab95ebef 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -170,7 +170,7 @@ public class LockPatternUtils {
public static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_";
public static final String SYNTHETIC_PASSWORD_KEY_PREFIX = "synthetic_password_";
- public static final String SYNTHETIC_PASSWORD_HANDLE_KEY = "sp-handle";
+ public static final String CURRENT_LSKF_BASED_PROTECTOR_ID_KEY = "sp-handle";
public static final String PASSWORD_HISTORY_DELIMITER = ",";
@UnsupportedAppUsage
diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java
index 1074004b4c33e..40164a45516e6 100644
--- a/core/java/com/android/internal/widget/LockscreenCredential.java
+++ b/core/java/com/android/internal/widget/LockscreenCredential.java
@@ -40,8 +40,8 @@ import java.util.List;
import java.util.Objects;
/**
- * A class representing a lockscreen credential. It can be either an empty password, a pattern
- * or a password (or PIN).
+ * A class representing a lockscreen credential, also called a Lock Screen Knowledge Factor (LSKF).
+ * It can be a PIN, pattern, password, or none (a.k.a. empty).
*
*
As required by some security certification, the framework tries its best to
* remove copies of the lockscreen credential bytes from memory. In this regard, this class
@@ -52,10 +52,10 @@ import java.util.Objects;
* // Process the credential in some way
* }
*
- * With this construct, we can guarantee that there will be no copies of the password left in
- * memory when the credential goes out of scope. This should help mitigate certain class of
- * attacks where the attcker gains read-only access to full device memory (cold boot attack,
- * unsecured software/hardware memory dumping interfaces such as JTAG).
+ * With this construct, we can guarantee that there will be no copies of the credential left in
+ * memory when the object goes out of scope. This should help mitigate certain class of attacks
+ * where the attacker gains read-only access to full device memory (cold boot attack, unsecured
+ * software/hardware memory dumping interfaces such as JTAG).
*/
public class LockscreenCredential implements Parcelable, AutoCloseable {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index f9dd7a9e3da4c..884ae171ba5f2 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -33,10 +33,10 @@ import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSW
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
+import static com.android.internal.widget.LockPatternUtils.CURRENT_LSKF_BASED_PROTECTOR_ID_KEY;
import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
import static com.android.internal.widget.LockPatternUtils.PROFILE_KEY_NAME_DECRYPT;
import static com.android.internal.widget.LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT;
-import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
import static com.android.internal.widget.LockPatternUtils.USER_FRP;
@@ -139,7 +139,7 @@ import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
-import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
+import com.android.server.locksettings.SyntheticPasswordManager.SyntheticPassword;
import com.android.server.locksettings.SyntheticPasswordManager.TokenType;
import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager;
import com.android.server.pm.UserManagerInternal;
@@ -199,8 +199,8 @@ public class LockSettingsService extends ILockSettings.Stub {
private static final int PROFILE_KEY_IV_SIZE = 12;
private static final String SEPARATE_PROFILE_CHALLENGE_KEY = "lockscreen.profilechallenge";
- private static final String PREV_SYNTHETIC_PASSWORD_HANDLE_KEY = "prev-sp-handle";
- private static final String SYNTHETIC_PASSWORD_UPDATE_TIME_KEY = "sp-handle-ts";
+ private static final String PREV_LSKF_BASED_PROTECTOR_ID_KEY = "prev-sp-handle";
+ private static final String LSKF_LAST_CHANGED_TIME_KEY = "sp-handle-ts";
private static final String USER_SERIAL_NUMBER_KEY = "serial-number";
// Duration that LockSettingsService will store the gatekeeper password for. This allows
@@ -787,28 +787,28 @@ public class LockSettingsService extends ILockSettings.Stub {
// credential and still needs to be passed to the HAL once that credential is
// removed.
if (mUserManager.getUserInfo(userId).isPrimary() && !isUserSecure(userId)) {
- tryDeriveAuthTokenForUnsecuredPrimaryUser(userId);
+ tryDeriveVendorAuthSecretForUnsecuredPrimaryUser(userId);
}
}
});
}
- private void tryDeriveAuthTokenForUnsecuredPrimaryUser(@UserIdInt int userId) {
+ private void tryDeriveVendorAuthSecretForUnsecuredPrimaryUser(@UserIdInt int userId) {
synchronized (mSpManager) {
- // Make sure the user has a synthetic password to derive
+ // If there is no SP, then there is no vendor auth secret.
if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
return;
}
- final long handle = getSyntheticPasswordHandleLocked(userId);
+ final long protectorId = getCurrentLskfBasedProtectorId(userId);
AuthenticationResult result =
- mSpManager.unwrapPasswordBasedSyntheticPassword(getGateKeeperService(),
- handle, LockscreenCredential.createNone(), userId, null);
- if (result.authToken != null) {
- Slog.i(TAG, "Retrieved auth token for user " + userId);
- onAuthTokenKnownForUser(userId, result.authToken);
+ mSpManager.unlockLskfBasedProtector(getGateKeeperService(), protectorId,
+ LockscreenCredential.createNone(), userId, null);
+ if (result.syntheticPassword != null) {
+ Slog.i(TAG, "Unwrapped SP for unsecured primary user " + userId);
+ onSyntheticPasswordKnown(userId, result.syntheticPassword);
} else {
- Slog.e(TAG, "Auth token not available for user " + userId);
+ Slog.e(TAG, "Failed to unwrap SP for unsecured primary user " + userId);
}
}
}
@@ -912,7 +912,7 @@ public class LockSettingsService extends ILockSettings.Stub {
DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userInfo.id);
mSpManager.migrateFrpPasswordLocked(
- getSyntheticPasswordHandleLocked(userInfo.id),
+ getCurrentLskfBasedProtectorId(userInfo.id),
userInfo,
redactActualQualityToMostLenientEquivalentQuality(actualQuality));
}
@@ -1168,8 +1168,8 @@ public class LockSettingsService extends ILockSettings.Stub {
}
synchronized (mSpManager) {
if (isSyntheticPasswordBasedCredentialLocked(userId)) {
- final long handle = getSyntheticPasswordHandleLocked(userId);
- int rawType = mSpManager.getCredentialType(handle, userId);
+ final long protectorId = getCurrentLskfBasedProtectorId(userId);
+ int rawType = mSpManager.getCredentialType(protectorId, userId);
if (rawType != CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
return rawType;
}
@@ -1604,13 +1604,13 @@ public class LockSettingsService extends ILockSettings.Stub {
Slog.e(TAG, "Failed to decrypt child profile key", e);
}
}
- final long origHandle = getSyntheticPasswordHandleLocked(userId);
- AuthenticationResult authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
- getGateKeeperService(), origHandle, savedCredential, userId, null);
+ final long oldProtectorId = getCurrentLskfBasedProtectorId(userId);
+ AuthenticationResult authResult = mSpManager.unlockLskfBasedProtector(
+ getGateKeeperService(), oldProtectorId, savedCredential, userId, null);
VerifyCredentialResponse response = authResult.gkResponse;
- AuthenticationToken auth = authResult.authToken;
+ SyntheticPassword sp = authResult.syntheticPassword;
- if (auth == null) {
+ if (sp == null) {
if (response == null
|| response.getResponseCode() == VerifyCredentialResponse.RESPONSE_ERROR) {
Slog.w(TAG, "Failed to enroll: incorrect credential.");
@@ -1624,9 +1624,9 @@ public class LockSettingsService extends ILockSettings.Stub {
throw new IllegalStateException("password change failed");
}
- onAuthTokenKnownForUser(userId, auth);
- setLockCredentialWithAuthTokenLocked(credential, auth, userId);
- mSpManager.destroyPasswordBasedSyntheticPassword(origHandle, userId);
+ onSyntheticPasswordKnown(userId, sp);
+ setLockCredentialWithSpLocked(credential, sp, userId);
+ mSpManager.destroyLskfBasedProtector(oldProtectorId, userId);
sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
return true;
}
@@ -1832,9 +1832,9 @@ public class LockSettingsService extends ILockSettings.Stub {
Slog.w(TAG, "Escrow token is disabled on the current user");
return false;
}
- AuthenticationResult authResult = mSpManager.unwrapWeakTokenBasedSyntheticPassword(
+ AuthenticationResult authResult = mSpManager.unlockWeakTokenBasedProtector(
getGateKeeperService(), handle, token, userId);
- if (authResult.authToken == null) {
+ if (authResult.syntheticPassword == null) {
Slog.w(TAG, "Invalid escrow token supplied");
return false;
}
@@ -1929,7 +1929,7 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
- /** Unlock disk encryption */
+ /** Unlock file-based encryption */
private void unlockUserKey(int userId, byte[] secret) {
final UserInfo userInfo = mUserManager.getUserInfo(userId);
try {
@@ -2112,20 +2112,20 @@ public class LockSettingsService extends ILockSettings.Stub {
progressCallback);
}
- long handle = getSyntheticPasswordHandleLocked(userId);
- authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
- getGateKeeperService(), handle, credential, userId, progressCallback);
+ long protectorId = getCurrentLskfBasedProtectorId(userId);
+ authResult = mSpManager.unlockLskfBasedProtector(
+ getGateKeeperService(), protectorId, credential, userId, progressCallback);
response = authResult.gkResponse;
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
// credential has matched
mBiometricDeferredQueue.addPendingLockoutResetForUser(userId,
- authResult.authToken.deriveGkPassword());
+ authResult.syntheticPassword.deriveGkPassword());
// perform verifyChallenge with synthetic password which generates the real GK auth
// token and response for the current user
- response = mSpManager.verifyChallenge(getGateKeeperService(), authResult.authToken,
- 0L /* challenge */, userId);
+ response = mSpManager.verifyChallenge(getGateKeeperService(),
+ authResult.syntheticPassword, 0L /* challenge */, userId);
if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
// This shouldn't really happen: the unwrapping of SP succeeds, but SP doesn't
// match the recorded GK password handle.
@@ -2135,11 +2135,11 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
- onCredentialVerified(authResult.authToken,
+ onCredentialVerified(authResult.syntheticPassword,
PasswordMetrics.computeForCredential(credential), userId);
if ((flags & VERIFY_FLAG_REQUEST_GK_PW_HANDLE) != 0) {
final long gkHandle = storeGatekeeperPasswordTemporarily(
- authResult.authToken.deriveGkPassword());
+ authResult.syntheticPassword.deriveGkPassword());
response = new VerifyCredentialResponse.Builder()
.setGatekeeperPasswordHandle(gkHandle)
.build();
@@ -2213,9 +2213,9 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
- private PasswordMetrics loadPasswordMetrics(AuthenticationToken auth, int userHandle) {
+ private PasswordMetrics loadPasswordMetrics(SyntheticPassword sp, int userHandle) {
synchronized (mSpManager) {
- return mSpManager.getPasswordMetrics(auth, getSyntheticPasswordHandleLocked(userHandle),
+ return mSpManager.getPasswordMetrics(sp, getCurrentLskfBasedProtectorId(userHandle),
userHandle);
}
}
@@ -2489,24 +2489,23 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
- private void onAuthTokenKnownForUser(@UserIdInt int userId, AuthenticationToken auth) {
+ private void onSyntheticPasswordKnown(@UserIdInt int userId, SyntheticPassword sp) {
if (mInjector.isGsiRunning()) {
Slog.w(TAG, "Running in GSI; skipping calls to AuthSecret and RebootEscrow");
return;
}
- mRebootEscrowManager.callToRebootEscrowIfNeeded(userId, auth.getVersion(),
- auth.getSyntheticPassword());
+ mRebootEscrowManager.callToRebootEscrowIfNeeded(userId, sp.getVersion(),
+ sp.getSyntheticPassword());
- callToAuthSecretIfNeeded(userId, auth);
+ callToAuthSecretIfNeeded(userId, sp);
}
- private void callToAuthSecretIfNeeded(@UserIdInt int userId,
- AuthenticationToken auth) {
+ private void callToAuthSecretIfNeeded(@UserIdInt int userId, SyntheticPassword sp) {
// Pass the primary user's auth secret to the HAL
if (mAuthSecretService != null && mUserManager.getUserInfo(userId).isPrimary()) {
try {
- final byte[] rawSecret = auth.deriveVendorAuthSecret();
+ final byte[] rawSecret = sp.deriveVendorAuthSecret();
final ArrayList secret = new ArrayList<>(rawSecret.length);
for (int i = 0; i < rawSecret.length; ++i) {
secret.add(rawSecret[i]);
@@ -2519,66 +2518,51 @@ public class LockSettingsService extends ILockSettings.Stub {
}
/**
- * Precondition: vold and keystore unlocked.
+ * Creates the synthetic password (SP) for the given user and protects it with the user's LSKF.
+ * This is called just once in the lifetime of the user: the first time a nonempty LSKF is set,
+ * or when an escrow token is activated on a device with an empty LSKF.
*
- * Create new synthetic password, set up synthetic password blob protected by the supplied
- * user credential, and make the newly-created SP blob active. This is called just once in the
- * lifetime of the user: the first time that a user credential is set (!credential.isNone()), or
- * when an escrow token is activated on an unsecured device (credential.isNone()).
- *
- * The invariant under a synthetic password is:
- * 1. If user credential exists, then both vold and keystore and protected with keys derived
- * from the synthetic password.
- * 2. If user credential does not exist, vold and keystore protection are cleared. This is to
- * make it consistent with current behaviour. It also allows ActivityManager to call
- * unlockUser() with empty secret.
- * 3. Once a user is migrated to have synthetic password, its value will never change, no matter
- * whether the user changes their lockscreen PIN or clear/reset it. When the user clears its
- * lockscreen PIN, we still maintain the existing synthetic password in a password blob
- * protected by a default PIN.
- * 4. The user SID is linked with synthetic password, but its cleared/re-created when the user
- * clears/re-creates their lockscreen PIN.
+ * Maintains the SP invariants described in {@link SyntheticPasswordManager}.
*/
@GuardedBy("mSpManager")
@VisibleForTesting
- AuthenticationToken initializeSyntheticPasswordLocked(LockscreenCredential credential,
+ SyntheticPassword initializeSyntheticPasswordLocked(LockscreenCredential credential,
int userId) {
Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId);
- Preconditions.checkState(
- getSyntheticPasswordHandleLocked(userId) == SyntheticPasswordManager.DEFAULT_HANDLE,
+ Preconditions.checkState(getCurrentLskfBasedProtectorId(userId) ==
+ SyntheticPasswordManager.NULL_PROTECTOR_ID,
"Cannot reinitialize SP");
- final AuthenticationToken auth = mSpManager.newSyntheticPassword(userId);
- long handle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(),
- credential, auth, userId);
+ final SyntheticPassword sp = mSpManager.newSyntheticPassword(userId);
+ long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(), credential,
+ sp, userId);
if (!credential.isNone()) {
- mSpManager.newSidForUser(getGateKeeperService(), auth, userId);
- mSpManager.verifyChallenge(getGateKeeperService(), auth, 0L, userId);
- setUserKeyProtection(userId, auth.deriveDiskEncryptionKey());
- setKeystorePassword(auth.deriveKeyStorePassword(), userId);
+ mSpManager.newSidForUser(getGateKeeperService(), sp, userId);
+ mSpManager.verifyChallenge(getGateKeeperService(), sp, 0L, userId);
+ setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
+ setKeystorePassword(sp.deriveKeyStorePassword(), userId);
} else {
clearUserKeyProtection(userId, null);
setKeystorePassword(null, userId);
gateKeeperClearSecureUserId(userId);
}
fixateNewestUserKeyAuth(userId);
- setSyntheticPasswordHandleLocked(handle, userId);
- onAuthTokenKnownForUser(userId, auth);
- return auth;
+ setCurrentLskfBasedProtectorId(protectorId, userId);
+ onSyntheticPasswordKnown(userId, sp);
+ return sp;
}
@VisibleForTesting
- long getSyntheticPasswordHandleLocked(int userId) {
- return getLong(SYNTHETIC_PASSWORD_HANDLE_KEY,
- SyntheticPasswordManager.DEFAULT_HANDLE, userId);
+ long getCurrentLskfBasedProtectorId(int userId) {
+ return getLong(CURRENT_LSKF_BASED_PROTECTOR_ID_KEY,
+ SyntheticPasswordManager.NULL_PROTECTOR_ID, userId);
}
- private void setSyntheticPasswordHandleLocked(long handle, int userId) {
- final long oldHandle = getSyntheticPasswordHandleLocked(userId);
- setLong(SYNTHETIC_PASSWORD_HANDLE_KEY, handle, userId);
- setLong(PREV_SYNTHETIC_PASSWORD_HANDLE_KEY, oldHandle, userId);
- setLong(SYNTHETIC_PASSWORD_UPDATE_TIME_KEY, System.currentTimeMillis(), userId);
-
+ private void setCurrentLskfBasedProtectorId(long newProtectorId, int userId) {
+ final long oldProtectorId = getCurrentLskfBasedProtectorId(userId);
+ setLong(CURRENT_LSKF_BASED_PROTECTOR_ID_KEY, newProtectorId, userId);
+ setLong(PREV_LSKF_BASED_PROTECTOR_ID_KEY, oldProtectorId, userId);
+ setLong(LSKF_LAST_CHANGED_TIME_KEY, System.currentTimeMillis(), userId);
}
@VisibleForTesting
@@ -2593,8 +2577,8 @@ public class LockSettingsService extends ILockSettings.Stub {
final int type = mStorage.readPersistentDataBlock().type;
return type == PersistentData.TYPE_SP || type == PersistentData.TYPE_SP_WEAVER;
}
- long handle = getSyntheticPasswordHandleLocked(userId);
- return handle != SyntheticPasswordManager.DEFAULT_HANDLE;
+ long protectorId = getCurrentLskfBasedProtectorId(userId);
+ return protectorId != SyntheticPasswordManager.NULL_PROTECTOR_ID;
}
/**
@@ -2624,8 +2608,7 @@ public class LockSettingsService extends ILockSettings.Stub {
return handle;
}
- private void onCredentialVerified(AuthenticationToken authToken, PasswordMetrics metrics,
- int userId) {
+ private void onCredentialVerified(SyntheticPassword sp, PasswordMetrics metrics, int userId) {
if (metrics != null) {
synchronized (this) {
@@ -2635,21 +2618,21 @@ public class LockSettingsService extends ILockSettings.Stub {
Slog.wtf(TAG, "Null metrics after credential verification");
}
- unlockKeystore(authToken.deriveKeyStorePassword(), userId);
+ unlockKeystore(sp.deriveKeyStorePassword(), userId);
{
- final byte[] secret = authToken.deriveDiskEncryptionKey();
+ final byte[] secret = sp.deriveFileBasedEncryptionKey();
unlockUser(userId, secret);
Arrays.fill(secret, (byte) 0);
}
- activateEscrowTokens(authToken, userId);
+ activateEscrowTokens(sp, userId);
if (isProfileWithSeparatedLock(userId)) {
setDeviceUnlockedForUser(userId);
}
mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
- onAuthTokenKnownForUser(userId, authToken);
+ onSyntheticPasswordKnown(userId, sp);
}
private void setDeviceUnlockedForUser(int userId) {
@@ -2658,68 +2641,58 @@ public class LockSettingsService extends ILockSettings.Stub {
}
/**
- * Change the user's lockscreen password by creating a new SP blob and update the handle, based
- * on an existing authentication token. Even though a new SP blob is created, the underlying
- * synthetic password is never changed.
+ * Changes the user's LSKF by creating an LSKF-based protector that uses the new LSKF (which may
+ * be empty) and setting the new protector as the user's current LSKF-based protector. The old
+ * LSKF-based protector is not destroyed, and the SP itself is not changed.
*
- * When clearing credential, we keep the SP unchanged, but clear its password handle so its
- * SID is gone. We also clear password from (software-based) keystore and vold, which will be
- * added back when new password is set in future.
+ * Also maintains the invariants described in {@link SyntheticPasswordManager} by
+ * setting/clearing the protection (by the SP) on the user's file-based encryption key and
+ * auth-bound Keystore keys when the LSKF is added/removed, respectively. If the new LSKF is
+ * nonempty, then the Gatekeeper auth token is also refreshed.
*/
@GuardedBy("mSpManager")
- private long setLockCredentialWithAuthTokenLocked(LockscreenCredential credential,
- AuthenticationToken auth, int userId) {
- if (DEBUG) Slog.d(TAG, "setLockCredentialWithAuthTokenLocked: user=" + userId);
+ private long setLockCredentialWithSpLocked(LockscreenCredential credential,
+ SyntheticPassword sp, int userId) {
+ if (DEBUG) Slog.d(TAG, "setLockCredentialWithSpLocked: user=" + userId);
final int savedCredentialType = getCredentialTypeInternal(userId);
- long newHandle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(),
- credential, auth, userId);
+ final long newProtectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(),
+ credential, sp, userId);
final Map profilePasswords;
if (!credential.isNone()) {
// not needed by synchronizeUnifiedWorkChallengeForProfiles()
profilePasswords = null;
if (mSpManager.hasSidForUser(userId)) {
- // We are changing password of a secured device, nothing more needed as
- // createPasswordBasedSyntheticPassword has already taken care of maintaining
- // the password handle and SID unchanged.
-
- //refresh auth token
- mSpManager.verifyChallenge(getGateKeeperService(), auth, 0L, userId);
+ mSpManager.verifyChallenge(getGateKeeperService(), sp, 0L, userId);
} else {
- // A new password is set on a previously-unsecured device, we need to generate
- // a new SID, and re-add keys to vold and keystore.
- mSpManager.newSidForUser(getGateKeeperService(), auth, userId);
- mSpManager.verifyChallenge(getGateKeeperService(), auth, 0L, userId);
- setUserKeyProtection(userId, auth.deriveDiskEncryptionKey());
+ mSpManager.newSidForUser(getGateKeeperService(), sp, userId);
+ mSpManager.verifyChallenge(getGateKeeperService(), sp, 0L, userId);
+ setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
fixateNewestUserKeyAuth(userId);
- setKeystorePassword(auth.deriveKeyStorePassword(), userId);
+ setKeystorePassword(sp.deriveKeyStorePassword(), userId);
}
} else {
// Cache all profile password if they use unified work challenge. This will later be
// used to clear the profile's password in synchronizeUnifiedWorkChallengeForProfiles()
profilePasswords = getDecryptedPasswordsForAllTiedProfiles(userId);
- // we are clearing password of a secured device, so need to nuke SID as well.
mSpManager.clearSidForUser(userId);
gateKeeperClearSecureUserId(userId);
- // Clear key from vold so ActivityManager can just unlock the user with empty secret
- // during boot. Vold storage needs to be unlocked before manipulation of the keys can
- // succeed.
- unlockUserKey(userId, auth.deriveDiskEncryptionKey());
- clearUserKeyProtection(userId, auth.deriveDiskEncryptionKey());
+ unlockUserKey(userId, sp.deriveFileBasedEncryptionKey());
+ clearUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
fixateNewestUserKeyAuth(userId);
- unlockKeystore(auth.deriveKeyStorePassword(), userId);
+ unlockKeystore(sp.deriveKeyStorePassword(), userId);
setKeystorePassword(null, userId);
removeBiometricsForUser(userId);
}
- setSyntheticPasswordHandleLocked(newHandle, userId);
+ setCurrentLskfBasedProtectorId(newProtectorId, userId);
LockPatternUtils.invalidateCredentialTypeCache();
synchronizeUnifiedWorkChallengeForProfiles(userId, profilePasswords);
setUserPasswordMetrics(credential, userId);
mManagedProfilePasswordCache.removePassword(userId);
if (savedCredentialType != CREDENTIAL_TYPE_NONE) {
- mSpManager.destroyAllWeakTokenBasedSyntheticPasswords(userId);
+ mSpManager.destroyAllWeakTokenBasedProtectors(userId);
}
if (profilePasswords != null) {
@@ -2728,7 +2701,7 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
- return newHandle;
+ return newProtectorId;
}
private void removeBiometricsForUser(int userId) {
@@ -2825,14 +2798,14 @@ public class LockSettingsService extends ILockSettings.Stub {
Slog.w(TAG, "Synthetic password not enabled");
return null;
}
- long handle = getSyntheticPasswordHandleLocked(userId);
- AuthenticationResult auth = mSpManager.unwrapPasswordBasedSyntheticPassword(
- getGateKeeperService(), handle, currentCredential, userId, null);
- if (auth.authToken == null) {
+ long protectorId = getCurrentLskfBasedProtectorId(userId);
+ AuthenticationResult auth = mSpManager.unlockLskfBasedProtector(
+ getGateKeeperService(), protectorId, currentCredential, userId, null);
+ if (auth.syntheticPassword == null) {
Slog.w(TAG, "Current credential is incorrect");
return null;
}
- return auth.authToken.derivePasswordHashFactor();
+ return auth.syntheticPassword.derivePasswordHashFactor();
}
} finally {
scheduleGc();
@@ -2843,46 +2816,47 @@ public class LockSettingsService extends ILockSettings.Stub {
@NonNull EscrowTokenStateChangeCallback callback) {
if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId + ", type=" + type);
synchronized (mSpManager) {
- // Migrate to synthetic password based credentials if the user has no password,
- // the token can then be activated immediately.
- AuthenticationToken auth = null;
+ // If the user has no LSKF, then the token can be activated immediately, after creating
+ // the user's SP if it doesn't already exist. Otherwise, the token can't be activated
+ // until the SP is unlocked by another protector (normally the LSKF-based one).
+ SyntheticPassword sp = null;
if (!isUserSecure(userId)) {
- long handle = getSyntheticPasswordHandleLocked(userId);
- if (handle == SyntheticPasswordManager.DEFAULT_HANDLE) {
- auth = initializeSyntheticPasswordLocked(LockscreenCredential.createNone(),
+ long protectorId = getCurrentLskfBasedProtectorId(userId);
+ if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
+ sp = initializeSyntheticPasswordLocked(LockscreenCredential.createNone(),
userId);
} else {
- auth = mSpManager.unwrapPasswordBasedSyntheticPassword(getGateKeeperService(),
- handle, LockscreenCredential.createNone(), userId, null).authToken;
+ sp = mSpManager.unlockLskfBasedProtector(getGateKeeperService(), protectorId,
+ LockscreenCredential.createNone(), userId, null).syntheticPassword;
}
}
disableEscrowTokenOnNonManagedDevicesIfNeeded(userId);
if (!mSpManager.hasEscrowData(userId)) {
throw new SecurityException("Escrow token is disabled on the current user");
}
- long handle = mSpManager.createTokenBasedSyntheticPassword(token, type, userId,
- callback);
- if (auth != null) {
- mSpManager.activateTokenBasedSyntheticPassword(handle, auth, userId);
+ long handle = mSpManager.addPendingToken(token, type, userId, callback);
+ if (sp != null) {
+ // Activate the token immediately
+ mSpManager.createTokenBasedProtector(handle, sp, userId);
}
return handle;
}
}
- private void activateEscrowTokens(AuthenticationToken auth, int userId) {
+ private void activateEscrowTokens(SyntheticPassword sp, int userId) {
if (DEBUG) Slog.d(TAG, "activateEscrowTokens: user=" + userId);
synchronized (mSpManager) {
disableEscrowTokenOnNonManagedDevicesIfNeeded(userId);
for (long handle : mSpManager.getPendingTokensForUser(userId)) {
Slog.i(TAG, String.format("activateEscrowTokens: %x %d ", handle, userId));
- mSpManager.activateTokenBasedSyntheticPassword(handle, auth, userId);
+ mSpManager.createTokenBasedProtector(handle, sp, userId);
}
}
}
private boolean isEscrowTokenActive(long handle, int userId) {
synchronized (mSpManager) {
- return mSpManager.existsHandle(handle, userId);
+ return mSpManager.protectorExists(handle, userId);
}
}
@@ -2896,15 +2870,15 @@ public class LockSettingsService extends ILockSettings.Stub {
private boolean removeEscrowToken(long handle, int userId) {
synchronized (mSpManager) {
- if (handle == getSyntheticPasswordHandleLocked(userId)) {
- Slog.w(TAG, "Cannot remove password handle");
+ if (handle == getCurrentLskfBasedProtectorId(userId)) {
+ Slog.w(TAG, "Escrow token handle equals LSKF-based protector ID");
return false;
}
if (mSpManager.removePendingToken(handle, userId)) {
return true;
}
- if (mSpManager.existsHandle(handle, userId)) {
- mSpManager.destroyTokenBasedSyntheticPassword(handle, userId);
+ if (mSpManager.protectorExists(handle, userId)) {
+ mSpManager.destroyTokenBasedProtector(handle, userId);
return true;
} else {
return false;
@@ -2946,23 +2920,23 @@ public class LockSettingsService extends ILockSettings.Stub {
private boolean setLockCredentialWithTokenInternalLocked(LockscreenCredential credential,
long tokenHandle, byte[] token, int userId) {
final AuthenticationResult result;
- result = mSpManager.unwrapTokenBasedSyntheticPassword(getGateKeeperService(), tokenHandle,
- token, userId);
- if (result.authToken == null) {
+ result = mSpManager.unlockTokenBasedProtector(getGateKeeperService(), tokenHandle, token,
+ userId);
+ if (result.syntheticPassword == null) {
Slog.w(TAG, "Invalid escrow token supplied");
return false;
}
if (result.gkResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
// Most likely, an untrusted credential reset happened in the past which
// changed the synthetic password
- Slog.e(TAG, "Obsolete token: synthetic password derived but it fails GK "
+ Slog.e(TAG, "Obsolete token: synthetic password decrypted but it fails GK "
+ "verification.");
return false;
}
- onAuthTokenKnownForUser(userId, result.authToken);
- long oldHandle = getSyntheticPasswordHandleLocked(userId);
- setLockCredentialWithAuthTokenLocked(credential, result.authToken, userId);
- mSpManager.destroyPasswordBasedSyntheticPassword(oldHandle, userId);
+ onSyntheticPasswordKnown(userId, result.syntheticPassword);
+ final long oldProtectorId = getCurrentLskfBasedProtectorId(userId);
+ setLockCredentialWithSpLocked(credential, result.syntheticPassword, userId);
+ mSpManager.destroyLskfBasedProtector(oldProtectorId, userId);
return true;
}
@@ -2973,16 +2947,16 @@ public class LockSettingsService extends ILockSettings.Stub {
Slog.w(TAG, "Escrow token is disabled on the current user");
return false;
}
- authResult = mSpManager.unwrapTokenBasedSyntheticPassword(getGateKeeperService(),
- tokenHandle, token, userId);
- if (authResult.authToken == null) {
+ authResult = mSpManager.unlockTokenBasedProtector(getGateKeeperService(), tokenHandle,
+ token, userId);
+ if (authResult.syntheticPassword == null) {
Slog.w(TAG, "Invalid escrow token supplied");
return false;
}
}
- onCredentialVerified(authResult.authToken,
- loadPasswordMetrics(authResult.authToken, userId), userId);
+ onCredentialVerified(authResult.syntheticPassword,
+ loadPasswordMetrics(authResult.syntheticPassword, userId), userId);
return true;
}
@@ -3037,11 +3011,11 @@ public class LockSettingsService extends ILockSettings.Stub {
pw.println("User " + userId);
pw.increaseIndent();
synchronized (mSpManager) {
- pw.println(String.format("SP Handle: %x",
- getSyntheticPasswordHandleLocked(userId)));
- pw.println(String.format("Last changed: %s (%x)",
- timestampToString(getLong(SYNTHETIC_PASSWORD_UPDATE_TIME_KEY, 0, userId)),
- getLong(PREV_SYNTHETIC_PASSWORD_HANDLE_KEY, 0, userId)));
+ pw.println(String.format("LSKF-based SP protector ID: %x",
+ getCurrentLskfBasedProtectorId(userId)));
+ pw.println(String.format("LSKF last changed: %s (previous protector: %x)",
+ timestampToString(getLong(LSKF_LAST_CHANGED_TIME_KEY, 0, userId)),
+ getLong(PREV_LSKF_BASED_PROTECTOR_ID_KEY, 0, userId)));
}
try {
pw.println(String.format("SID: %x",
@@ -3340,14 +3314,15 @@ public class LockSettingsService extends ILockSettings.Stub {
}
@Override
- public void onRebootEscrowRestored(byte spVersion, byte[] syntheticPassword, int userId) {
- SyntheticPasswordManager.AuthenticationToken
- authToken = new SyntheticPasswordManager.AuthenticationToken(spVersion);
- authToken.recreateDirectly(syntheticPassword);
+ public void onRebootEscrowRestored(byte spVersion, byte[] rawSyntheticPassword,
+ int userId) {
+ SyntheticPasswordManager.SyntheticPassword
+ sp = new SyntheticPasswordManager.SyntheticPassword(spVersion);
+ sp.recreateDirectly(rawSyntheticPassword);
synchronized (mSpManager) {
- mSpManager.verifyChallenge(getGateKeeperService(), authToken, 0L, userId);
+ mSpManager.verifyChallenge(getGateKeeperService(), sp, 0L, userId);
}
- onCredentialVerified(authToken, loadPasswordMetrics(authToken, userId), userId);
+ onCredentialVerified(sp, loadPasswordMetrics(sp, userId), userId);
}
}
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 92bb26a60f7b2..e5b50362b03d0 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -380,29 +380,30 @@ class LockSettingsStorage {
}
}
- public void writeSyntheticPasswordState(int userId, long handle, String name, byte[] data) {
+ public void writeSyntheticPasswordState(int userId, long protectorId, String name,
+ byte[] data) {
ensureSyntheticPasswordDirectoryForUser(userId);
- writeFile(getSyntheticPasswordStateFileForUser(userId, handle, name), data);
+ writeFile(getSyntheticPasswordStateFileForUser(userId, protectorId, name), data);
}
- public byte[] readSyntheticPasswordState(int userId, long handle, String name) {
- return readFile(getSyntheticPasswordStateFileForUser(userId, handle, name));
+ public byte[] readSyntheticPasswordState(int userId, long protectorId, String name) {
+ return readFile(getSyntheticPasswordStateFileForUser(userId, protectorId, name));
}
- public void deleteSyntheticPasswordState(int userId, long handle, String name) {
- deleteFile(getSyntheticPasswordStateFileForUser(userId, handle, name));
+ public void deleteSyntheticPasswordState(int userId, long protectorId, String name) {
+ deleteFile(getSyntheticPasswordStateFileForUser(userId, protectorId, name));
}
- public Map> listSyntheticPasswordHandlesForAllUsers(String stateName) {
+ public Map> listSyntheticPasswordProtectorsForAllUsers(String stateName) {
Map> result = new ArrayMap<>();
final UserManager um = UserManager.get(mContext);
for (UserInfo user : um.getUsers()) {
- result.put(user.id, listSyntheticPasswordHandlesForUser(stateName, user.id));
+ result.put(user.id, listSyntheticPasswordProtectorsForUser(stateName, user.id));
}
return result;
}
- public List listSyntheticPasswordHandlesForUser(String stateName, int userId) {
+ public List listSyntheticPasswordProtectorsForUser(String stateName, int userId) {
File baseDir = getSyntheticPasswordDirectoryForUser(userId);
List result = new ArrayList<>();
File[] files = baseDir.listFiles();
@@ -415,7 +416,7 @@ class LockSettingsStorage {
try {
result.add(Long.parseUnsignedLong(parts[0], 16));
} catch (NumberFormatException e) {
- Slog.e(TAG, "Failed to parse handle " + parts[0]);
+ Slog.e(TAG, "Failed to parse protector ID " + parts[0]);
}
}
}
@@ -435,8 +436,8 @@ class LockSettingsStorage {
}
}
- private File getSyntheticPasswordStateFileForUser(int userId, long handle, String name) {
- String fileName = formatSimple("%016x.%s", handle, name);
+ private File getSyntheticPasswordStateFileForUser(int userId, long protectorId, String name) {
+ String fileName = formatSimple("%016x.%s", protectorId, name);
return new File(getSyntheticPasswordDirectoryForUser(userId), fileName);
}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
index c8f1cb29d826e..371ef76b1ba6c 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
@@ -52,7 +52,7 @@ public class SyntheticPasswordCrypto {
private static final int PROFILE_KEY_IV_SIZE = 12;
private static final int DEFAULT_TAG_LENGTH_BITS = 128;
private static final int AES_KEY_LENGTH = 32; // 256-bit AES key
- private static final byte[] APPLICATION_ID_PERSONALIZATION = "application-id".getBytes();
+ private static final byte[] PROTECTOR_SECRET_PERSONALIZATION = "application-id".getBytes();
// Time between the user credential is verified with GK and the decryption of synthetic password
// under the auth-bound key. This should always happen one after the other, but give it 15
// seconds just to be sure.
@@ -127,15 +127,19 @@ public class SyntheticPasswordCrypto {
}
}
- public static byte[] decryptBlobV1(String keyAlias, byte[] blob, byte[] applicationId) {
+ /**
+ * Decrypt a legacy SP blob which did the Keystore and software encryption layers in the wrong
+ * order.
+ */
+ public static byte[] decryptBlobV1(String keyAlias, byte[] blob, byte[] protectorSecret) {
try {
KeyStore keyStore = getKeyStore();
- SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
- if (decryptionKey == null) {
+ SecretKey keyStoreKey = (SecretKey) keyStore.getKey(keyAlias, null);
+ if (keyStoreKey == null) {
throw new IllegalStateException("SP key is missing: " + keyAlias);
}
- byte[] intermediate = decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, blob);
- return decrypt(decryptionKey, intermediate);
+ byte[] intermediate = decrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, blob);
+ return decrypt(keyStoreKey, intermediate);
} catch (Exception e) {
Slog.e(TAG, "Failed to decrypt V1 blob", e);
throw new IllegalStateException("Failed to decrypt blob", e);
@@ -157,16 +161,19 @@ public class SyntheticPasswordCrypto {
return keyStore;
}
- public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) {
+ /**
+ * Decrypts an SP blob that was created by {@link #createBlob}.
+ */
+ public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] protectorSecret) {
try {
final KeyStore keyStore = getKeyStore();
- SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
- if (decryptionKey == null) {
+ SecretKey keyStoreKey = (SecretKey) keyStore.getKey(keyAlias, null);
+ if (keyStoreKey == null) {
throw new IllegalStateException("SP key is missing: " + keyAlias);
}
- byte[] intermediate = decrypt(decryptionKey, blob);
- return decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate);
+ byte[] intermediate = decrypt(keyStoreKey, blob);
+ return decrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, intermediate);
} catch (CertificateException | IOException | BadPaddingException
| IllegalBlockSizeException
| KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
@@ -177,11 +184,22 @@ public class SyntheticPasswordCrypto {
}
}
- public static byte[] createBlob(String keyAlias, byte[] data, byte[] applicationId, long sid) {
+ /**
+ * Creates a new SP blob by encrypting the given data. Two encryption layers are applied: an
+ * inner layer using a hash of protectorSecret as the key, and an outer layer using a new
+ * Keystore key with the given alias and optionally bound to a SID.
+ *
+ * The reason we use a layer of software encryption, instead of using protectorSecret as the
+ * applicationId of the Keystore key, is to work around buggy KeyMint implementations that don't
+ * cryptographically bind the applicationId to the key. The Keystore layer has to be the outer
+ * layer, so that LSKF verification is ratelimited by Gatekeeper when Weaver is unavailable.
+ */
+ public static byte[] createBlob(String keyAlias, byte[] data, byte[] protectorSecret,
+ long sid) {
try {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
keyGenerator.init(AES_KEY_LENGTH * 8, new SecureRandom());
- SecretKey secretKey = keyGenerator.generateKey();
+ SecretKey keyStoreKey = keyGenerator.generateKey();
final KeyStore keyStore = getKeyStore();
KeyProtection.Builder builder = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
@@ -194,10 +212,10 @@ public class SyntheticPasswordCrypto {
}
keyStore.setEntry(keyAlias,
- new KeyStore.SecretKeyEntry(secretKey),
+ new KeyStore.SecretKeyEntry(keyStoreKey),
builder.build());
- byte[] intermediate = encrypt(applicationId, APPLICATION_ID_PERSONALIZATION, data);
- return encrypt(secretKey, intermediate);
+ byte[] intermediate = encrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, data);
+ return encrypt(keyStoreKey, intermediate);
} catch (CertificateException | IOException | BadPaddingException
| IllegalBlockSizeException
| KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index f5151c4ace19d..2d0143abb93f7 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -68,32 +68,42 @@ import java.util.Set;
/**
- * A class that maintains the wrapping of synthetic password by user credentials or escrow tokens.
- * It's (mostly) a pure storage for synthetic passwords, providing APIs to creating and destroying
- * synthetic password blobs which are wrapped by user credentials or escrow tokens.
+ * A class that manages a user's synthetic password (SP) ({@link #SyntheticPassword}), along with a
+ * set of SP protectors that are independent ways that the SP is protected.
*
- * Here is the assumptions it makes:
- * Each user has one single synthetic password at any time.
- * The SP has an associated password handle, which binds to the SID for that user. The password
- * handle is persisted by SyntheticPasswordManager internally.
- * If the user credential is null, it's treated as if the credential is DEFAULT_PASSWORD
+ * Invariants for SPs:
*
- * Information persisted on disk:
- * for each user (stored under DEFAULT_HANDLE):
- * SP_HANDLE_NAME: GateKeeper password handle of synthetic password. Only available if user
- * credential exists, cleared when user clears their credential.
- * SP_E0_NAME, SP_P1_NAME: Secret to derive synthetic password when combined with escrow
- * tokens. Destroyed when escrow support is turned off for the given user.
+ * - A user's SP never changes, but SP protectors can be added and removed. There is always a
+ * protector that protects the SP with the user's Lock Screen Knowledge Factor (LSKF), a.k.a.
+ * LockscreenCredential. The LSKF may be empty (none). There may be escrow token-based
+ * protectors as well, only for specific use cases such as enterprise-managed users.
*
- * for each SP blob under the user (stored under the corresponding handle):
- * SP_BLOB_NAME: The encrypted synthetic password. Always exists.
- * PASSWORD_DATA_NAME: Metadata about user credential. Only exists for password based SP.
- * SECDISCARDABLE_NAME: Part of the necessary ingredient to decrypt SP_BLOB_NAME for the
- * purpose of secure deletion. Exists if this is a non-weaver SP
- * (both password and token based), or it's a token-based SP under weaver.
- * WEAVER_SLOT: Metadata about the weaver slot used. Only exists if this is a SP under weaver.
+ * - While the user's LSKF is nonempty, the SP protects the user's CE (credential encrypted)
+ * storage and auth-bound Keystore keys: the user's CE key is encrypted by an SP-derived secret,
+ * and the user's Keystore and Gatekeeper passwords are other SP-derived secrets. However, while
+ * the user's LSKF is empty, these protections are cleared; this is needed to invalidate the
+ * auth-bound keys and make UserController.unlockUser() work with an empty secret.
*
+ * Files stored on disk for each user:
+ * For the SP itself, stored under NULL_PROTECTOR_ID:
+ * SP_HANDLE_NAME: GateKeeper password handle of a password derived from the SP. Only exists
+ * 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.
*
+ * 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.
+ * PASSWORD_METRICS_NAME: Metrics about the LSKF, encrypted by a key derived from the SP.
+ * Only exists for LSKF-based 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
+ * deletion when hardware support for secure deletion is unavailable.
+ * Doesn't exist for LSKF-based protectors that use Weaver.
+ * WEAVER_SLOT: Contains the Weaver slot number used by this protector. Only exists if the
+ * protector uses Weaver.
*/
public class SyntheticPasswordManager {
private static final String SP_BLOB_NAME = "spblob";
@@ -106,18 +116,24 @@ public class SyntheticPasswordManager {
private static final String WEAVER_SLOT_NAME = "weaver";
private static final String PASSWORD_METRICS_NAME = "metrics";
- public static final long DEFAULT_HANDLE = 0L;
+ // used for files associated with the SP itself, not with a particular protector
+ public static final long NULL_PROTECTOR_ID = 0L;
+
private static final byte[] DEFAULT_PASSWORD = "default-password".getBytes();
private static final byte WEAVER_VERSION = 1;
private static final int INVALID_WEAVER_SLOT = -1;
+ // Careful: the SYNTHETIC_PASSWORD_* version numbers are overloaded to identify both the version
+ // of the protector and the version of the synthetic password itself. All a user's protectors
+ // must use a version that treats the synthetic password itself in a compatible way.
private static final byte SYNTHETIC_PASSWORD_VERSION_V1 = 1;
private static final byte SYNTHETIC_PASSWORD_VERSION_V2 = 2;
private static final byte SYNTHETIC_PASSWORD_VERSION_V3 = 3;
- private static final byte SYNTHETIC_PASSWORD_PASSWORD_BASED = 0;
- private static final byte SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED = 1;
- private static final byte SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED = 2;
+
+ private static final byte PROTECTOR_TYPE_LSKF_BASED = 0;
+ private static final byte PROTECTOR_TYPE_STRONG_TOKEN_BASED = 1;
+ private static final byte PROTECTOR_TYPE_WEAK_TOKEN_BASED = 2;
// 256-bit synthetic password
private static final byte SYNTHETIC_PASSWORD_LENGTH = 256 / 8;
@@ -147,7 +163,7 @@ public class SyntheticPasswordManager {
static class AuthenticationResult {
// Non-null if password/token passes verification, null otherwise
- @Nullable public AuthenticationToken authToken;
+ @Nullable public SyntheticPassword syntheticPassword;
// OK: password / token passes verification, user has a lockscreen
// null: user does not have a lockscreen (but password / token passes verification)
// ERROR: password / token fails verification
@@ -156,39 +172,34 @@ public class SyntheticPasswordManager {
}
/**
- * This class represents the main cryptographic secret for a given user (a.k.a synthietic
- * password). This secret is derived from the user's lockscreen credential or password escrow
- * token. All other cryptograhic keys related to the user, including disk encryption key,
- * keystore encryption key, gatekeeper auth key, vendor auth secret and others are directly
- * derived from this token.
- *
- * The main secret associated with an authentication token is retrievable from
- * {@link AuthenticationToken#getSyntheticPassword()} and the authentication token can be
- * reconsturcted from the main secret later with
- * {@link AuthenticationToken#recreateDirectly(byte[])}. The first time an authentication token
- * is needed, it should be created with {@link AuthenticationToken#create()} so that the
- * necessary escrow data ({@link #mEncryptedEscrowSplit0} and {@link #mEscrowSplit1}) is
- * properly initialized. The caller can either persist the (non-secret) esscrow data if escrow
- * is required, or discard it to cryptograhically disable escrow. To support escrow, the caller
- * needs to securely store the secret returned from
- * {@link AuthenticationToken#getEscrowSecret()}, and at the time of use, load the escrow data
- * back with {@link AuthenticationToken#setEscrowData(byte[], byte[])} and then re-create the
- * main secret from the escrow secret via
- * {@link AuthenticationToken#recreateFromEscrow(byte[])}.
+ * A synthetic password (SP) is the main cryptographic secret for a user. The SP is used only
+ * as input to a Key Derivation Function (KDF) to derive other keys.
+ *
+ * SPs are created by {@link SyntheticPassword#create()} as the hash of two random values P0 and
+ * P1. E0 (P0 encrypted by an SP-derived key) and P1 can then be stored on-disk. This approach
+ * is used instead of direct random generation of the SP so that escrow token-based protectors
+ * can protect P0 instead of the SP itself. This makes it possible to cryptographically disable
+ * the ability to create and use such protectors by deleting (or never storing) E0 and P1.
+ *
+ * When protecting the SP directly, use {@link SyntheticPassword#getSyntheticPassword()} to get
+ * the raw SP, and later {@link SyntheticPassword#recreateDirectly(byte[])} to re-create the SP.
+ * When protecting P0, use {@link SyntheticPassword#getEscrowSecret()} to get P0, and later
+ * {@link SyntheticPassword#setEscrowData(byte[], byte[])} followed by
+ * {@link SyntheticPassword#recreateFromEscrow()} to re-create the SP.
*/
- static class AuthenticationToken {
+ static class SyntheticPassword {
private final byte mVersion;
/**
* Here is the relationship between these fields:
* Generate two random block P0 and P1. P1 is recorded in mEscrowSplit1 but P0 is not.
* mSyntheticPassword = hash(P0 || P1)
- * E0 = P0 encrypted under syntheticPassword, recoreded in mEncryptedEscrowSplit0.
+ * E0 = P0 encrypted under syntheticPassword, recorded in mEncryptedEscrowSplit0.
*/
private @NonNull byte[] mSyntheticPassword;
private @Nullable byte[] mEncryptedEscrowSplit0;
private @Nullable byte[] mEscrowSplit1;
- AuthenticationToken(byte version) {
+ SyntheticPassword(byte version) {
mVersion = version;
}
@@ -214,7 +225,7 @@ public class SyntheticPasswordManager {
return deriveSubkey(PERSONALIZATION_SP_GK_AUTH);
}
- public byte[] deriveDiskEncryptionKey() {
+ public byte[] deriveFileBasedEncryptionKey() {
return deriveSubkey(PERSONALIZATION_FBE_KEY);
}
@@ -232,8 +243,8 @@ public class SyntheticPasswordManager {
}
/**
- * Assign escrow data to this auth token. This is a prerequisite to call
- * {@link AuthenticationToken#recreateFromEscrow}.
+ * Assigns escrow data to this synthetic password. This is a prerequisite to call
+ * {@link SyntheticPassword#recreateFromEscrow}.
*/
public void setEscrowData(@Nullable byte[] encryptedEscrowSplit0,
@Nullable byte[] escrowSplit1) {
@@ -242,8 +253,8 @@ public class SyntheticPasswordManager {
}
/**
- * Re-creates authentication token from escrow secret (escrowSplit0, returned from
- * {@link AuthenticationToken#getEscrowSecret}). Escrow data needs to be loaded
+ * Re-creates a synthetic password from the escrow secret (escrowSplit0, returned from
+ * {@link SyntheticPassword#getEscrowSecret}). Escrow data needs to be loaded
* by {@link #setEscrowData} before calling this.
*/
public void recreateFromEscrow(byte[] escrowSplit0) {
@@ -253,7 +264,7 @@ public class SyntheticPasswordManager {
}
/**
- * Re-creates authentication token from synthetic password directly.
+ * Re-creates a synthetic password from its raw bytes.
*/
public void recreateDirectly(byte[] syntheticPassword) {
this.mSyntheticPassword = Arrays.copyOf(syntheticPassword, syntheticPassword.length);
@@ -262,8 +273,8 @@ public class SyntheticPasswordManager {
/**
* Generates a new random synthetic password with escrow data.
*/
- static AuthenticationToken create() {
- AuthenticationToken result = new AuthenticationToken(SYNTHETIC_PASSWORD_VERSION_V3);
+ static SyntheticPassword create() {
+ SyntheticPassword result = new SyntheticPassword(SYNTHETIC_PASSWORD_VERSION_V3);
byte[] escrowSplit0 = secureRandom(SYNTHETIC_PASSWORD_LENGTH);
byte[] escrowSplit1 = secureRandom(SYNTHETIC_PASSWORD_LENGTH);
result.recreate(escrowSplit0, escrowSplit1);
@@ -275,7 +286,7 @@ public class SyntheticPasswordManager {
/**
* Re-creates synthetic password from both escrow splits. See javadoc for
- * AuthenticationToken.mSyntheticPassword for details on what each block means.
+ * SyntheticPassword.mSyntheticPassword for details on what each block means.
*/
private void recreate(byte[] escrowSplit0, byte[] escrowSplit1) {
mSyntheticPassword = bytesToHex(SyntheticPasswordCrypto.personalizedHash(
@@ -283,8 +294,8 @@ public class SyntheticPasswordManager {
}
/**
- * Returns the escrow secret that can be used later to reconstruct this authentication
- * token from {@link #recreateFromEscrow(byte[])}. Only possible if escrow is not disabled
+ * Returns the escrow secret that can be used later to reconstruct this synthetic password
+ * from {@link #recreateFromEscrow(byte[])}. Only possible if escrow is not disabled
* (encryptedEscrowSplit0 known).
*/
public byte[] getEscrowSecret() {
@@ -296,16 +307,15 @@ public class SyntheticPasswordManager {
}
/**
- * Returns the raw synthetic password that can be used later to reconstruct this
- * authentication token from {@link #recreateDirectly(byte[])}
+ * Returns the raw synthetic password, for later use with {@link #recreateDirectly(byte[])}.
*/
public byte[] getSyntheticPassword() {
return mSyntheticPassword;
}
/**
- * Returns the version of this AuthenticationToken for use with reconstructing
- * this with a synthetic password version.
+ * Returns the version number of this synthetic password. This version number determines
+ * the algorithm used to derive subkeys.
*/
public byte getVersion() {
return mVersion;
@@ -318,8 +328,8 @@ public class SyntheticPasswordManager {
byte scryptLogP;
public int credentialType;
byte[] salt;
- // For GateKeeper-based credential, this is the password handle returned by GK,
- // for weaver-based credential, this is empty.
+ // If Weaver is available, then this field is empty. Otherwise, it is the Gatekeeper
+ // password handle that resulted from enrolling the hashed LSKF.
public byte[] passwordHandle;
public static PasswordData create(int passwordType) {
@@ -377,13 +387,14 @@ public class SyntheticPasswordManager {
static class SyntheticPasswordBlob {
byte mVersion;
- byte mType;
+ byte mProtectorType;
byte[] mContent;
- public static SyntheticPasswordBlob create(byte version, byte type, byte[] content) {
+ public static SyntheticPasswordBlob create(byte version, byte protectorType,
+ byte[] content) {
SyntheticPasswordBlob result = new SyntheticPasswordBlob();
result.mVersion = version;
- result.mType = type;
+ result.mProtectorType = protectorType;
result.mContent = content;
return result;
}
@@ -391,7 +402,7 @@ public class SyntheticPasswordManager {
public static SyntheticPasswordBlob fromBytes(byte[] data) {
SyntheticPasswordBlob result = new SyntheticPasswordBlob();
result.mVersion = data[0];
- result.mType = data[1];
+ result.mProtectorType = data[1];
result.mContent = Arrays.copyOfRange(data, 2, data.length);
return result;
}
@@ -399,7 +410,7 @@ public class SyntheticPasswordManager {
public byte[] toByte() {
byte[] blob = new byte[mContent.length + 1 + 1];
blob[0] = mVersion;
- blob[1] = mType;
+ blob[1] = mProtectorType;
System.arraycopy(mContent, 0, blob, 2, mContent.length);
return blob;
}
@@ -569,9 +580,10 @@ public class SyntheticPasswordManager {
}
public void removeUser(IGateKeeperService gatekeeper, int userId) {
- for (long handle : mStorage.listSyntheticPasswordHandlesForUser(SP_BLOB_NAME, userId)) {
- destroyWeaverSlot(handle, userId);
- destroySPBlobKey(getKeyName(handle));
+ for (long protectorId : mStorage.listSyntheticPasswordProtectorsForUser(SP_BLOB_NAME,
+ userId)) {
+ destroyWeaverSlot(protectorId, userId);
+ destroySPBlobKey(getKeyName(protectorId));
}
// Remove potential persistent state (in RPMB), to prevent them from accumulating and
// causing problems.
@@ -582,8 +594,8 @@ public class SyntheticPasswordManager {
}
}
- int getCredentialType(long handle, int userId) {
- byte[] passwordData = loadState(PASSWORD_DATA_NAME, handle, userId);
+ 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;
@@ -599,9 +611,7 @@ public class SyntheticPasswordManager {
}
/**
- * Initializes a new Authentication token for the given user.
- *
- * The authentication token will bear a randomly-generated synthetic password.
+ * Creates a new synthetic password (SP) for the given user.
*
* Any existing SID for the user is cleared.
*
@@ -609,22 +619,21 @@ public class SyntheticPasswordManager {
* an escrow scheme. This information can be removed with {@link #destroyEscrowData} if
* password escrow should be disabled completely on the given user.
*/
- AuthenticationToken newSyntheticPassword(int userId) {
+ SyntheticPassword newSyntheticPassword(int userId) {
clearSidForUser(userId);
- AuthenticationToken result = AuthenticationToken.create();
+ SyntheticPassword result = SyntheticPassword.create();
saveEscrowData(result, userId);
return result;
}
/**
* Enroll a new password handle and SID for the given synthetic password and persist it on disk.
- * Used when adding password to previously-unsecured devices.
+ * Used when the LSKF is changed from empty to nonempty.
*/
- public void newSidForUser(IGateKeeperService gatekeeper, AuthenticationToken authToken,
- int userId) {
+ public void newSidForUser(IGateKeeperService gatekeeper, SyntheticPassword sp, int userId) {
GateKeeperResponse response;
try {
- response = gatekeeper.enroll(userId, null, null, authToken.deriveGkPassword());
+ response = gatekeeper.enroll(userId, null, null, sp.deriveGkPassword());
} catch (RemoteException e) {
throw new IllegalStateException("Failed to create new SID for user", e);
}
@@ -637,49 +646,48 @@ public class SyntheticPasswordManager {
// Nuke the SP handle (and as a result, its SID) for the given user.
public void clearSidForUser(int userId) {
- destroyState(SP_HANDLE_NAME, DEFAULT_HANDLE, userId);
+ destroyState(SP_HANDLE_NAME, NULL_PROTECTOR_ID, userId);
}
public boolean hasSidForUser(int userId) {
- return hasState(SP_HANDLE_NAME, DEFAULT_HANDLE, userId);
+ return hasState(SP_HANDLE_NAME, NULL_PROTECTOR_ID, userId);
}
- // if null, it means there is no SID associated with the user
- // This can happen if the user is migrated to SP but currently
- // do not have a lockscreen password.
+ // If this returns null, it means there is no SID associated with the user. This happens if the
+ // user has an empty LSKF, but does have an SP.
private byte[] loadSyntheticPasswordHandle(int userId) {
- return loadState(SP_HANDLE_NAME, DEFAULT_HANDLE, userId);
+ return loadState(SP_HANDLE_NAME, NULL_PROTECTOR_ID, userId);
}
private void saveSyntheticPasswordHandle(byte[] spHandle, int userId) {
- saveState(SP_HANDLE_NAME, spHandle, DEFAULT_HANDLE, userId);
+ saveState(SP_HANDLE_NAME, spHandle, NULL_PROTECTOR_ID, userId);
}
- private boolean loadEscrowData(AuthenticationToken authToken, int userId) {
- byte[] e0 = loadState(SP_E0_NAME, DEFAULT_HANDLE, userId);
- byte[] p1 = loadState(SP_P1_NAME, DEFAULT_HANDLE, userId);
- authToken.setEscrowData(e0, p1);
+ private boolean loadEscrowData(SyntheticPassword sp, int userId) {
+ byte[] e0 = loadState(SP_E0_NAME, NULL_PROTECTOR_ID, userId);
+ byte[] p1 = loadState(SP_P1_NAME, NULL_PROTECTOR_ID, userId);
+ sp.setEscrowData(e0, p1);
return e0 != null && p1 != null;
}
- private void saveEscrowData(AuthenticationToken authToken, int userId) {
- saveState(SP_E0_NAME, authToken.mEncryptedEscrowSplit0, DEFAULT_HANDLE, userId);
- saveState(SP_P1_NAME, authToken.mEscrowSplit1, DEFAULT_HANDLE, userId);
+ private void saveEscrowData(SyntheticPassword sp, int userId) {
+ saveState(SP_E0_NAME, sp.mEncryptedEscrowSplit0, NULL_PROTECTOR_ID, userId);
+ saveState(SP_P1_NAME, sp.mEscrowSplit1, NULL_PROTECTOR_ID, userId);
}
public boolean hasEscrowData(int userId) {
- return hasState(SP_E0_NAME, DEFAULT_HANDLE, userId)
- && hasState(SP_P1_NAME, DEFAULT_HANDLE, userId);
+ return hasState(SP_E0_NAME, NULL_PROTECTOR_ID, userId)
+ && hasState(SP_P1_NAME, NULL_PROTECTOR_ID, userId);
}
public void destroyEscrowData(int userId) {
- destroyState(SP_E0_NAME, DEFAULT_HANDLE, userId);
- destroyState(SP_P1_NAME, DEFAULT_HANDLE, userId);
+ destroyState(SP_E0_NAME, NULL_PROTECTOR_ID, userId);
+ destroyState(SP_P1_NAME, NULL_PROTECTOR_ID, userId);
}
- private int loadWeaverSlot(long handle, int userId) {
+ private int loadWeaverSlot(long protectorId, int userId) {
final int LENGTH = Byte.BYTES + Integer.BYTES;
- byte[] data = loadState(WEAVER_SLOT_NAME, handle, userId);
+ byte[] data = loadState(WEAVER_SLOT_NAME, protectorId, userId);
if (data == null || data.length != LENGTH) {
return INVALID_WEAVER_SLOT;
}
@@ -687,22 +695,22 @@ public class SyntheticPasswordManager {
buffer.put(data, 0, data.length);
buffer.flip();
if (buffer.get() != WEAVER_VERSION) {
- Slog.e(TAG, "Invalid weaver slot version of handle " + handle);
+ Slog.e(TAG, "Invalid weaver slot version for protector " + protectorId);
return INVALID_WEAVER_SLOT;
}
return buffer.getInt();
}
- private void saveWeaverSlot(int slot, long handle, int userId) {
+ private void saveWeaverSlot(int slot, long protectorId, int userId) {
ByteBuffer buffer = ByteBuffer.allocate(Byte.BYTES + Integer.BYTES);
buffer.put(WEAVER_VERSION);
buffer.putInt(slot);
- saveState(WEAVER_SLOT_NAME, buffer.array(), handle, userId);
+ saveState(WEAVER_SLOT_NAME, buffer.array(), protectorId, userId);
}
- private void destroyWeaverSlot(long handle, int userId) {
- int slot = loadWeaverSlot(handle, userId);
- destroyState(WEAVER_SLOT_NAME, handle, userId);
+ private void destroyWeaverSlot(long protectorId, int userId) {
+ int slot = loadWeaverSlot(protectorId, userId);
+ destroyState(WEAVER_SLOT_NAME, protectorId, userId);
if (slot != INVALID_WEAVER_SLOT) {
Set usedSlots = getUsedWeaverSlots();
if (!usedSlots.contains(slot)) {
@@ -726,12 +734,12 @@ public class SyntheticPasswordManager {
* unintentionally.
*/
private Set getUsedWeaverSlots() {
- Map> slotHandles = mStorage.listSyntheticPasswordHandlesForAllUsers(
- WEAVER_SLOT_NAME);
+ Map> protectorIds =
+ mStorage.listSyntheticPasswordProtectorsForAllUsers(WEAVER_SLOT_NAME);
HashSet slots = new HashSet<>();
- for (Map.Entry> entry : slotHandles.entrySet()) {
- for (Long handle : entry.getValue()) {
- int slot = loadWeaverSlot(handle, entry.getKey());
+ for (Map.Entry> entry : protectorIds.entrySet()) {
+ for (Long protectorId : entry.getValue()) {
+ int slot = loadWeaverSlot(protectorId, entry.getKey());
slots.add(slot);
}
}
@@ -750,29 +758,24 @@ public class SyntheticPasswordManager {
}
/**
- * Create a new password based SP blob based on the supplied authentication token, such that
- * a future successful authentication with unwrapPasswordBasedSyntheticPassword() would result
- * in the same authentication token.
+ * Creates a protector that protects the user's SP with the given LSKF (which may be empty).
*
- * This method only creates SP blob wrapping around the given synthetic password and does not
- * handle logic around SID or SP handle. The caller should separately ensure that the user's SID
- * is consistent with the device state by calling other APIs in this class.
+ * This method only creates a new protector that isn't referenced by anything; it doesn't handle
+ * any higher-level tasks involved in changing the LSKF.
*
- * @see #newSidForUser
- * @see #clearSidForUser
- * @return a new password handle for the wrapped SP blob
- * @throw IllegalStateException if creation fails.
+ * @return the ID of the new protector
+ * @throws IllegalStateException on failure
*/
- public long createPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
- LockscreenCredential credential, AuthenticationToken authToken, int userId) {
- long handle = generateHandle();
+ public long createLskfBasedProtector(IGateKeeperService gatekeeper,
+ LockscreenCredential credential, SyntheticPassword sp, int userId) {
+ long protectorId = generateProtectorId();
PasswordData pwd = PasswordData.create(credential.getType());
byte[] pwdToken = computePasswordToken(credential, pwd);
final long sid;
- final byte[] applicationId;
+ final byte[] protectorSecret;
if (isWeaverAvailable()) {
- // Weaver based user password
+ // Protector uses Weaver to verify the LSKF
int weaverSlot = getNextAvailableWeaverSlot();
Slog.i(TAG, "Weaver enroll password to slot " + weaverSlot + " for user " + userId);
byte[] weaverSecret = weaverEnroll(weaverSlot, passwordTokenToWeaverKey(pwdToken),
@@ -781,15 +784,17 @@ public class SyntheticPasswordManager {
throw new IllegalStateException(
"Fail to enroll user password under weaver " + userId);
}
- saveWeaverSlot(weaverSlot, handle, userId);
+ saveWeaverSlot(weaverSlot, protectorId, userId);
mPasswordSlotManager.markSlotInUse(weaverSlot);
// 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;
- applicationId = transformUnderWeaverSecret(pwdToken, weaverSecret);
+ protectorSecret = transformUnderWeaverSecret(pwdToken, 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 {
@@ -797,30 +802,30 @@ public class SyntheticPasswordManager {
} catch (RemoteException ignore) {
Slog.w(TAG, "Failed to clear SID from gatekeeper");
}
- // GateKeeper based user password
GateKeeperResponse response;
try {
response = gatekeeper.enroll(fakeUid(userId), null, null,
passwordTokenToGkInput(pwdToken));
} catch (RemoteException e) {
- throw new IllegalStateException("Failed to enroll password for new SP blob", 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(
- "Fail to enroll user password when creating SP 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);
- applicationId = transformUnderSecdiscardable(pwdToken,
- createSecdiscardable(handle, userId));
+ protectorSecret = transformUnderSecdiscardable(pwdToken,
+ createSecdiscardable(protectorId, userId));
// No need to pass in quality since the credential type already encodes sufficient info
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;
+ 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;
}
public VerifyCredentialResponse verifyFrpCredential(IGateKeeperService gatekeeper,
@@ -858,13 +863,14 @@ public class SyntheticPasswordManager {
}
- public void migrateFrpPasswordLocked(long handle, UserInfo userInfo, int requestedQuality) {
+ public void migrateFrpPasswordLocked(long protectorId, UserInfo userInfo,
+ int requestedQuality) {
if (mStorage.getPersistentDataBlockManager() != null
&& LockPatternUtils.userOwnsFrpCredential(mContext, userInfo)) {
- PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, handle,
+ PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, protectorId,
userInfo.id));
if (pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
- int weaverSlot = loadWeaverSlot(handle, userInfo.id);
+ int weaverSlot = loadWeaverSlot(protectorId, userInfo.id);
if (weaverSlot != INVALID_WEAVER_SLOT) {
synchronizeWeaverFrpPassword(pwd, requestedQuality, userInfo.id, weaverSlot);
} else {
@@ -905,12 +911,17 @@ public class SyntheticPasswordManager {
private ArrayMap> tokenMap = new ArrayMap<>();
/**
- * Create a token based Synthetic password of the given type for the given user.
- * @return the handle of the token
+ * Caches a pending escrow token in memory and pre-allocates an ID for a new SP protector. This
+ * ID also serves as a handle for the pending token.
+ *
+ * This method doesn't persist any data, and it doesn't require access to the SP.
+ * {@link #createTokenBasedProtector} can be called later to actually create the protector.
+ *
+ * @return the token handle
*/
- public long createTokenBasedSyntheticPassword(byte[] token, @TokenType int type, int userId,
+ public long addPendingToken(byte[] token, @TokenType int type, int userId,
@Nullable EscrowTokenStateChangeCallback changeCallback) {
- long handle = generateHandle();
+ long tokenHandle = generateProtectorId(); // tokenHandle is reused as protectorId later
if (!tokenMap.containsKey(userId)) {
tokenMap.put(userId, new ArrayMap<>());
}
@@ -928,8 +939,8 @@ public class SyntheticPasswordManager {
tokenData.aggregatedSecret = transformUnderSecdiscardable(token, secdiscardable);
tokenData.mCallback = changeCallback;
- tokenMap.get(userId).put(handle, tokenData);
- return handle;
+ tokenMap.get(userId).put(tokenHandle, tokenData);
+ return tokenHandle;
}
public Set getPendingTokensForUser(int userId) {
@@ -940,23 +951,22 @@ public class SyntheticPasswordManager {
}
/** Remove the given pending token. */
- public boolean removePendingToken(long handle, int userId) {
+ public boolean removePendingToken(long tokenHandle, int userId) {
if (!tokenMap.containsKey(userId)) {
return false;
}
- return tokenMap.get(userId).remove(handle) != null;
+ return tokenMap.get(userId).remove(tokenHandle) != null;
}
- public boolean activateTokenBasedSyntheticPassword(long handle, AuthenticationToken authToken,
- int userId) {
+ public boolean createTokenBasedProtector(long tokenHandle, SyntheticPassword sp, int userId) {
if (!tokenMap.containsKey(userId)) {
return false;
}
- TokenData tokenData = tokenMap.get(userId).get(handle);
+ TokenData tokenData = tokenMap.get(userId).get(tokenHandle);
if (tokenData == null) {
return false;
}
- if (!loadEscrowData(authToken, userId)) {
+ if (!loadEscrowData(sp, userId)) {
Slog.w(TAG, "User is not escrowable");
return false;
}
@@ -967,51 +977,52 @@ public class SyntheticPasswordManager {
Slog.e(TAG, "Failed to enroll weaver secret when activating token");
return false;
}
- saveWeaverSlot(slot, handle, userId);
+ saveWeaverSlot(slot, tokenHandle, userId);
mPasswordSlotManager.markSlotInUse(slot);
}
- saveSecdiscardable(handle, tokenData.secdiscardableOnDisk, userId);
- createSyntheticPasswordBlob(handle, getTokenBasedBlobType(tokenData.mType), authToken,
+ saveSecdiscardable(tokenHandle, tokenData.secdiscardableOnDisk, userId);
+ createSyntheticPasswordBlob(tokenHandle, getTokenBasedProtectorType(tokenData.mType), sp,
tokenData.aggregatedSecret, 0L, userId);
- tokenMap.get(userId).remove(handle);
+ tokenMap.get(userId).remove(tokenHandle);
if (tokenData.mCallback != null) {
- tokenData.mCallback.onEscrowTokenActivated(handle, userId);
+ tokenData.mCallback.onEscrowTokenActivated(tokenHandle, userId);
}
return true;
}
- private void createSyntheticPasswordBlob(long handle, byte type, AuthenticationToken authToken,
- byte[] applicationId, long sid, int userId) {
- final byte[] secret;
- if (type == SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED
- || type == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) {
- secret = authToken.getEscrowSecret();
+ private void createSyntheticPasswordBlob(long protectorId, byte protectorType,
+ SyntheticPassword sp, byte[] protectorSecret, long sid, int userId) {
+ final byte[] spSecret;
+ if (protectorType == PROTECTOR_TYPE_STRONG_TOKEN_BASED
+ || protectorType == PROTECTOR_TYPE_WEAK_TOKEN_BASED) {
+ spSecret = sp.getEscrowSecret();
} else {
- secret = authToken.getSyntheticPassword();
+ spSecret = sp.getSyntheticPassword();
}
- byte[] content = createSPBlob(getKeyName(handle), secret, applicationId, sid);
+ byte[] content = createSPBlob(getKeyName(protectorId), spSecret, protectorSecret, sid);
/*
* We can upgrade from v1 to v2 because that's just a change in the way that
* the SP is stored. However, we can't upgrade to v3 because that is a change
* in the way that passwords are derived from the SP.
*/
- byte version = authToken.mVersion == SYNTHETIC_PASSWORD_VERSION_V3
+ byte version = sp.mVersion == SYNTHETIC_PASSWORD_VERSION_V3
? SYNTHETIC_PASSWORD_VERSION_V3 : SYNTHETIC_PASSWORD_VERSION_V2;
- SyntheticPasswordBlob blob = SyntheticPasswordBlob.create(version, type, content);
- saveState(SP_BLOB_NAME, blob.toByte(), handle, userId);
+ SyntheticPasswordBlob blob = SyntheticPasswordBlob.create(version, protectorType, content);
+ saveState(SP_BLOB_NAME, blob.toByte(), protectorId, userId);
}
/**
- * Decrypt a synthetic password by supplying the user credential and corresponding password
- * blob handle generated previously. If the decryption is successful, initiate a GateKeeper
- * verification to referesh the SID & Auth token maintained by the system.
+ * Tries to unlock a user's LSKF-based SP protector, given its ID and the claimed LSKF (which
+ * may be empty). On success, returns the user's synthetic password, and also does a Gatekeeper
+ * verification to refresh the SID and HardwareAuthToken maintained by the system.
*/
- public AuthenticationResult unwrapPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
- long handle, @NonNull LockscreenCredential credential, int userId,
+ public AuthenticationResult unlockLskfBasedProtector(IGateKeeperService gatekeeper,
+ long protectorId, @NonNull LockscreenCredential credential, int userId,
ICheckCredentialProgressCallback progressCallback) {
AuthenticationResult result = new AuthenticationResult();
- PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, handle, userId));
+ PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, protectorId,
+ userId));
if (!credential.checkAgainstStoredType(pwd.credentialType)) {
Slog.e(TAG, String.format("Credential type mismatch: expected %d actual %d",
@@ -1022,13 +1033,13 @@ public class SyntheticPasswordManager {
byte[] pwdToken = computePasswordToken(credential, pwd);
- final byte[] applicationId;
+ final byte[] protectorSecret;
final long sid;
- int weaverSlot = loadWeaverSlot(handle, userId);
+ int weaverSlot = loadWeaverSlot(protectorId, userId);
if (weaverSlot != INVALID_WEAVER_SLOT) {
- // Weaver based user password
+ // Protector uses Weaver to verify the LSKF
if (!isWeaverAvailable()) {
- Slog.e(TAG, "No weaver service to unwrap password based SP");
+ Slog.e(TAG, "Protector uses Weaver, but Weaver is unavailable");
result.gkResponse = VerifyCredentialResponse.ERROR;
return result;
}
@@ -1037,9 +1048,10 @@ public class SyntheticPasswordManager {
return result;
}
sid = GateKeeper.INVALID_SECURE_USER_ID;
- applicationId = transformUnderWeaverSecret(pwdToken,
+ protectorSecret = transformUnderWeaverSecret(pwdToken,
result.gkResponse.getGatekeeperHAT());
} else {
+ // Protector uses Gatekeeper to verify the LSKF
byte[] gkPwdToken = passwordTokenToGkInput(pwdToken);
GateKeeperResponse response;
try {
@@ -1068,7 +1080,7 @@ public class SyntheticPasswordManager {
// 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(), handle, userId);
+ 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);
@@ -1083,8 +1095,8 @@ public class SyntheticPasswordManager {
return result;
}
sid = sidFromPasswordHandle(pwd.passwordHandle);
- applicationId = transformUnderSecdiscardable(pwdToken,
- loadSecdiscardable(handle, userId));
+ protectorSecret = transformUnderSecdiscardable(pwdToken,
+ loadSecdiscardable(protectorId, userId));
}
// Supplied credential passes first stage weaver/gatekeeper check so it should be correct.
// Notify the callback so the keyguard UI can proceed immediately.
@@ -1095,80 +1107,81 @@ public class SyntheticPasswordManager {
Slog.w(TAG, "progressCallback throws exception", e);
}
}
- result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED,
- applicationId, sid, userId);
+ result.syntheticPassword = unwrapSyntheticPasswordBlob(protectorId,
+ PROTECTOR_TYPE_LSKF_BASED, protectorSecret, sid, userId);
// Perform verifyChallenge to refresh auth tokens for GK if user password exists.
- result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
+ result.gkResponse = verifyChallenge(gatekeeper, result.syntheticPassword, 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);
+ // only happen once on old protectors.
+ if (result.syntheticPassword != null && !hasPasswordMetrics(protectorId, userId)) {
+ savePasswordMetrics(credential, result.syntheticPassword, protectorId, userId);
}
return result;
}
/**
- * Decrypt a synthetic password by supplying an escrow token and corresponding token
- * blob handle generated previously. If the decryption is successful, initiate a GateKeeper
- * verification to referesh the SID & Auth token maintained by the system.
+ * Tries to unlock a token-based SP protector (weak or strong), given its ID and the claimed
+ * token. On success, returns the user's synthetic password, and also does a Gatekeeper
+ * verification to refresh the SID and HardwareAuthToken maintained by the system.
*/
- public @NonNull AuthenticationResult unwrapTokenBasedSyntheticPassword(
- IGateKeeperService gatekeeper, long handle, byte[] token, int userId) {
- SyntheticPasswordBlob blob = SyntheticPasswordBlob
- .fromBytes(loadState(SP_BLOB_NAME, handle, userId));
- return unwrapTokenBasedSyntheticPasswordInternal(gatekeeper, handle,
- blob.mType, token, userId);
+ public @NonNull AuthenticationResult unlockTokenBasedProtector(
+ IGateKeeperService gatekeeper, long protectorId, byte[] token, int userId) {
+ SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(loadState(SP_BLOB_NAME,
+ protectorId, userId));
+ return unlockTokenBasedProtectorInternal(gatekeeper, protectorId, blob.mProtectorType,
+ token, userId);
}
/**
- * Decrypt a synthetic password by supplying an strong escrow token and corresponding token
- * blob handle generated previously. If the decryption is successful, initiate a GateKeeper
- * verification to referesh the SID & Auth token maintained by the system.
+ * Like {@link #unlockTokenBasedProtector}, but throws an exception if the protector is not for
+ * a strong token specifically.
*/
- public @NonNull AuthenticationResult unwrapStrongTokenBasedSyntheticPassword(
- IGateKeeperService gatekeeper, long handle, byte[] token, int userId) {
- return unwrapTokenBasedSyntheticPasswordInternal(gatekeeper, handle,
- SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED, token, userId);
+ public @NonNull AuthenticationResult unlockStrongTokenBasedProtector(
+ IGateKeeperService gatekeeper, long protectorId, byte[] token, int userId) {
+ return unlockTokenBasedProtectorInternal(gatekeeper, protectorId,
+ PROTECTOR_TYPE_STRONG_TOKEN_BASED, token, userId);
}
/**
- * Decrypt a synthetic password by supplying a weak escrow token and corresponding token
- * blob handle generated previously. If the decryption is successful, initiate a GateKeeper
- * verification to referesh the SID & Auth token maintained by the system.
+ * Like {@link #unlockTokenBasedProtector}, but throws an exception if the protector is not for
+ * a weak token specifically.
*/
- public @NonNull AuthenticationResult unwrapWeakTokenBasedSyntheticPassword(
- IGateKeeperService gatekeeper, long handle, byte[] token, int userId) {
- return unwrapTokenBasedSyntheticPasswordInternal(gatekeeper, handle,
- SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED, token, userId);
+ public @NonNull AuthenticationResult unlockWeakTokenBasedProtector(
+ IGateKeeperService gatekeeper, long protectorId, byte[] token, int userId) {
+ return unlockTokenBasedProtectorInternal(gatekeeper, protectorId,
+ PROTECTOR_TYPE_WEAK_TOKEN_BASED, token, userId);
}
- private @NonNull AuthenticationResult unwrapTokenBasedSyntheticPasswordInternal(
- IGateKeeperService gatekeeper, long handle, byte type, byte[] token, int userId) {
+ private @NonNull AuthenticationResult unlockTokenBasedProtectorInternal(
+ IGateKeeperService gatekeeper, long protectorId, byte expectedProtectorType,
+ byte[] token, int userId) {
AuthenticationResult result = new AuthenticationResult();
- byte[] secdiscardable = loadSecdiscardable(handle, userId);
- int slotId = loadWeaverSlot(handle, userId);
+ byte[] secdiscardable = loadSecdiscardable(protectorId, userId);
+ int slotId = loadWeaverSlot(protectorId, userId);
if (slotId != INVALID_WEAVER_SLOT) {
if (!isWeaverAvailable()) {
- Slog.e(TAG, "No weaver service to unwrap token based SP");
+ Slog.e(TAG, "Protector uses Weaver, but Weaver is unavailable");
result.gkResponse = VerifyCredentialResponse.ERROR;
return result;
}
VerifyCredentialResponse response = weaverVerify(slotId, null);
if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK ||
response.getGatekeeperHAT() == null) {
- Slog.e(TAG, "Failed to retrieve weaver secret when unwrapping token");
+ Slog.e(TAG,
+ "Failed to retrieve Weaver secret when unlocking token-based protector");
result.gkResponse = VerifyCredentialResponse.ERROR;
return result;
}
secdiscardable = SyntheticPasswordCrypto.decrypt(response.getGatekeeperHAT(),
PERSONALIZATION_WEAVER_TOKEN, secdiscardable);
}
- byte[] applicationId = transformUnderSecdiscardable(token, secdiscardable);
- result.authToken = unwrapSyntheticPasswordBlob(handle, type, applicationId, 0L, userId);
- if (result.authToken != null) {
- result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
+ byte[] protectorSecret = transformUnderSecdiscardable(token, secdiscardable);
+ result.syntheticPassword = unwrapSyntheticPasswordBlob(protectorId, expectedProtectorType,
+ protectorSecret, 0L, userId);
+ if (result.syntheticPassword != null) {
+ result.gkResponse = verifyChallenge(gatekeeper, result.syntheticPassword, 0L, userId);
if (result.gkResponse == null) {
// The user currently has no password. return OK with null payload so null
// is propagated to unlockUser()
@@ -1180,9 +1193,9 @@ public class SyntheticPasswordManager {
return result;
}
- private AuthenticationToken unwrapSyntheticPasswordBlob(long handle, byte type,
- byte[] applicationId, long sid, int userId) {
- byte[] data = loadState(SP_BLOB_NAME, handle, userId);
+ private SyntheticPassword unwrapSyntheticPasswordBlob(long protectorId,
+ byte expectedProtectorType, byte[] protectorSecret, long sid, int userId) {
+ byte[] data = loadState(SP_BLOB_NAME, protectorId, userId);
if (data == null) {
return null;
}
@@ -1190,36 +1203,38 @@ public class SyntheticPasswordManager {
if (blob.mVersion != SYNTHETIC_PASSWORD_VERSION_V3
&& blob.mVersion != SYNTHETIC_PASSWORD_VERSION_V2
&& blob.mVersion != SYNTHETIC_PASSWORD_VERSION_V1) {
- throw new IllegalArgumentException("Unknown blob version");
+ throw new IllegalArgumentException("Unknown blob version: " + blob.mVersion);
}
- if (blob.mType != type) {
- throw new IllegalArgumentException("Invalid blob type");
+ if (blob.mProtectorType != expectedProtectorType) {
+ throw new IllegalArgumentException("Invalid protector type: " + blob.mProtectorType);
}
- final byte[] secret;
+ final byte[] spSecret;
if (blob.mVersion == SYNTHETIC_PASSWORD_VERSION_V1) {
- secret = SyntheticPasswordCrypto.decryptBlobV1(getKeyName(handle), blob.mContent,
- applicationId);
+ spSecret = SyntheticPasswordCrypto.decryptBlobV1(getKeyName(protectorId), blob.mContent,
+ protectorSecret);
} else {
- secret = decryptSPBlob(getKeyName(handle), blob.mContent, applicationId);
+ spSecret = decryptSPBlob(getKeyName(protectorId), blob.mContent, protectorSecret);
}
- if (secret == null) {
+ if (spSecret == null) {
Slog.e(TAG, "Fail to decrypt SP for user " + userId);
return null;
}
- AuthenticationToken result = new AuthenticationToken(blob.mVersion);
- if (type == SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED
- || type == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) {
+ SyntheticPassword result = new SyntheticPassword(blob.mVersion);
+ if (blob.mProtectorType == PROTECTOR_TYPE_STRONG_TOKEN_BASED
+ || blob.mProtectorType == PROTECTOR_TYPE_WEAK_TOKEN_BASED) {
if (!loadEscrowData(result, userId)) {
Slog.e(TAG, "User is not escrowable: " + userId);
return null;
}
- result.recreateFromEscrow(secret);
+ result.recreateFromEscrow(spSecret);
} else {
- result.recreateDirectly(secret);
+ result.recreateDirectly(spSecret);
}
if (blob.mVersion == SYNTHETIC_PASSWORD_VERSION_V1) {
- Slog.i(TAG, "Upgrade v1 SP blob for user " + userId + ", type = " + type);
- createSyntheticPasswordBlob(handle, type, result, applicationId, sid, userId);
+ Slog.i(TAG, "Upgrading v1 SP blob for user " + userId + ", protectorType = "
+ + blob.mProtectorType);
+ createSyntheticPasswordBlob(protectorId, blob.mProtectorType, result, protectorSecret,
+ sid, userId);
}
return result;
}
@@ -1228,13 +1243,12 @@ public class SyntheticPasswordManager {
* performs GK verifyChallenge and returns auth token, re-enrolling SP password handle
* if required.
*
- * Normally performing verifyChallenge with an AuthenticationToken should always return
- * RESPONSE_OK, since user authentication failures are detected earlier when trying to
- * decrypt SP.
+ * Normally performing verifyChallenge with an SP should always return RESPONSE_OK, since user
+ * authentication failures are detected earlier when trying to decrypt the SP.
*/
public @Nullable VerifyCredentialResponse verifyChallenge(IGateKeeperService gatekeeper,
- @NonNull AuthenticationToken auth, long challenge, int userId) {
- return verifyChallengeInternal(gatekeeper, auth.deriveGkPassword(), challenge, userId);
+ @NonNull SyntheticPassword sp, long challenge, int userId) {
+ return verifyChallengeInternal(gatekeeper, sp.deriveGkPassword(), challenge, userId);
}
protected @Nullable VerifyCredentialResponse verifyChallengeInternal(
@@ -1285,46 +1299,49 @@ public class SyntheticPasswordManager {
}
}
- public boolean existsHandle(long handle, int userId) {
- return hasState(SP_BLOB_NAME, handle, userId);
+ public boolean protectorExists(long protectorId, int userId) {
+ return hasState(SP_BLOB_NAME, protectorId, userId);
}
- /** Destroy the escrow token with the given handle for the given user. */
- public void destroyTokenBasedSyntheticPassword(long handle, int userId) {
- SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(loadState(SP_BLOB_NAME, handle,
- userId));
- destroySyntheticPassword(handle, userId);
- destroyState(SECDISCARDABLE_NAME, handle, userId);
- if (blob.mType == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) {
- notifyWeakEscrowTokenRemovedListeners(handle, userId);
+ /** Destroy a token-based SP protector. */
+ public void destroyTokenBasedProtector(long protectorId, int userId) {
+ SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(loadState(SP_BLOB_NAME,
+ protectorId, userId));
+ destroyProtectorCommon(protectorId, userId);
+ destroyState(SECDISCARDABLE_NAME, protectorId, userId);
+ if (blob.mProtectorType == PROTECTOR_TYPE_WEAK_TOKEN_BASED) {
+ notifyWeakEscrowTokenRemovedListeners(protectorId, userId);
}
}
- /** Destroy all weak escrow tokens for the given user. */
- public void destroyAllWeakTokenBasedSyntheticPasswords(int userId) {
- List handles = mStorage.listSyntheticPasswordHandlesForUser(SECDISCARDABLE_NAME,
- userId);
- for (long handle: handles) {
+ /** Destroy all weak token-based SP protectors for the given user. */
+ public void destroyAllWeakTokenBasedProtectors(int userId) {
+ List protectorIds =
+ mStorage.listSyntheticPasswordProtectorsForUser(SECDISCARDABLE_NAME, userId);
+ for (long protectorId : protectorIds) {
SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(loadState(SP_BLOB_NAME,
- handle, userId));
- if (blob.mType == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) {
- destroyTokenBasedSyntheticPassword(handle, userId);
+ protectorId, userId));
+ if (blob.mProtectorType == PROTECTOR_TYPE_WEAK_TOKEN_BASED) {
+ destroyTokenBasedProtector(protectorId, userId);
}
}
}
- public void destroyPasswordBasedSyntheticPassword(long handle, int userId) {
- destroySyntheticPassword(handle, userId);
- destroyState(SECDISCARDABLE_NAME, handle, userId);
- destroyState(PASSWORD_DATA_NAME, handle, userId);
- destroyState(PASSWORD_METRICS_NAME, handle, userId);
+ /**
+ * Destroy an LSKF-based SP protector. This is used when the user's LSKF is changed.
+ */
+ public void destroyLskfBasedProtector(long protectorId, int userId) {
+ destroyProtectorCommon(protectorId, userId);
+ destroyState(SECDISCARDABLE_NAME, protectorId, userId);
+ destroyState(PASSWORD_DATA_NAME, protectorId, userId);
+ destroyState(PASSWORD_METRICS_NAME, protectorId, userId);
}
- private void destroySyntheticPassword(long handle, int userId) {
- destroyState(SP_BLOB_NAME, handle, userId);
- destroySPBlobKey(getKeyName(handle));
- if (hasState(WEAVER_SLOT_NAME, handle, userId)) {
- destroyWeaverSlot(handle, userId);
+ private void destroyProtectorCommon(long protectorId, int userId) {
+ destroyState(SP_BLOB_NAME, protectorId, userId);
+ destroySPBlobKey(getKeyName(protectorId));
+ if (hasState(WEAVER_SLOT_NAME, protectorId, userId)) {
+ destroyWeaverSlot(protectorId, userId);
}
}
@@ -1346,92 +1363,91 @@ public class SyntheticPasswordManager {
return result;
}
- private byte[] createSecdiscardable(long handle, int userId) {
+ private byte[] createSecdiscardable(long protectorId, int userId) {
byte[] data = secureRandom(SECDISCARDABLE_LENGTH);
- saveSecdiscardable(handle, data, userId);
+ saveSecdiscardable(protectorId, data, userId);
return data;
}
- private void saveSecdiscardable(long handle, byte[] secdiscardable, int userId) {
- saveState(SECDISCARDABLE_NAME, secdiscardable, handle, userId);
+ private void saveSecdiscardable(long protectorId, byte[] secdiscardable, int userId) {
+ saveState(SECDISCARDABLE_NAME, secdiscardable, protectorId, userId);
}
- private byte[] loadSecdiscardable(long handle, int userId) {
- return loadState(SECDISCARDABLE_NAME, handle, userId);
+ private byte[] loadSecdiscardable(long protectorId, int userId) {
+ return loadState(SECDISCARDABLE_NAME, protectorId, userId);
}
- private byte getTokenBasedBlobType(@TokenType int type) {
+ private byte getTokenBasedProtectorType(@TokenType int type) {
switch (type) {
case TOKEN_TYPE_WEAK:
- return SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED;
+ return PROTECTOR_TYPE_WEAK_TOKEN_BASED;
case TOKEN_TYPE_STRONG:
default:
- return SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED;
+ return PROTECTOR_TYPE_STRONG_TOKEN_BASED;
}
}
/**
- * 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.
+ * 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.
*/
- public @Nullable PasswordMetrics getPasswordMetrics(AuthenticationToken authToken, long handle,
+ public @Nullable PasswordMetrics getPasswordMetrics(SyntheticPassword sp, long protectorId,
int userId) {
- final byte[] encrypted = loadState(PASSWORD_METRICS_NAME, handle, userId);
+ final byte[] encrypted = loadState(PASSWORD_METRICS_NAME, protectorId, userId);
if (encrypted == null) return null;
- final byte[] decrypted = SyntheticPasswordCrypto.decrypt(authToken.deriveMetricsKey(),
+ final byte[] decrypted = SyntheticPasswordCrypto.decrypt(sp.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(),
+ private void savePasswordMetrics(LockscreenCredential credential, SyntheticPassword sp,
+ long protectorId, int userId) {
+ final byte[] encrypted = SyntheticPasswordCrypto.encrypt(sp.deriveMetricsKey(),
/* personalization= */ new byte[0],
new VersionedPasswordMetrics(credential).serialize());
- saveState(PASSWORD_METRICS_NAME, encrypted, handle, userId);
+ saveState(PASSWORD_METRICS_NAME, encrypted, protectorId, userId);
}
- private boolean hasPasswordMetrics(long handle, int userId) {
- return hasState(PASSWORD_METRICS_NAME, handle, userId);
+ private boolean hasPasswordMetrics(long protectorId, int userId) {
+ return hasState(PASSWORD_METRICS_NAME, protectorId, userId);
}
- private boolean hasState(String stateName, long handle, int userId) {
- return !ArrayUtils.isEmpty(loadState(stateName, handle, userId));
+ private boolean hasState(String stateName, long protectorId, int userId) {
+ return !ArrayUtils.isEmpty(loadState(stateName, protectorId, userId));
}
- private byte[] loadState(String stateName, long handle, int userId) {
- return mStorage.readSyntheticPasswordState(userId, handle, stateName);
+ private byte[] loadState(String stateName, long protectorId, int userId) {
+ return mStorage.readSyntheticPasswordState(userId, protectorId, stateName);
}
- private void saveState(String stateName, byte[] data, long handle, int userId) {
- mStorage.writeSyntheticPasswordState(userId, handle, stateName, data);
+ private void saveState(String stateName, byte[] data, long protectorId, int userId) {
+ mStorage.writeSyntheticPasswordState(userId, protectorId, stateName, data);
}
- private void destroyState(String stateName, long handle, int userId) {
- mStorage.deleteSyntheticPasswordState(userId, handle, stateName);
+ private void destroyState(String stateName, long protectorId, int userId) {
+ mStorage.deleteSyntheticPasswordState(userId, protectorId, stateName);
}
- protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] applicationId) {
- return SyntheticPasswordCrypto.decryptBlob(blobKeyName, blob, applicationId);
+ protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] protectorSecret) {
+ return SyntheticPasswordCrypto.decryptBlob(blobKeyName, blob, protectorSecret);
}
- protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] applicationId, long sid) {
- return SyntheticPasswordCrypto.createBlob(blobKeyName, data, applicationId, sid);
+ protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] protectorSecret,
+ long sid) {
+ return SyntheticPasswordCrypto.createBlob(blobKeyName, data, protectorSecret, sid);
}
protected void destroySPBlobKey(String keyAlias) {
SyntheticPasswordCrypto.destroyBlobKey(keyAlias);
}
- public static long generateHandle() {
+ public static long generateProtectorId() {
SecureRandom rng = new SecureRandom();
long result;
do {
result = rng.nextLong();
- } while (result == DEFAULT_HANDLE);
+ } while (result == NULL_PROTECTOR_ID);
return result;
}
@@ -1448,8 +1464,8 @@ public class SyntheticPasswordManager {
}
}
- private String getKeyName(long handle) {
- return String.format("%s%x", LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX, handle);
+ private String getKeyName(long protectorId) {
+ return String.format("%s%x", LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX, protectorId);
}
private byte[] computePasswordToken(LockscreenCredential credential, PasswordData data) {
@@ -1506,11 +1522,11 @@ public class SyntheticPasswordManager {
*/
public boolean migrateKeyNamespace() {
boolean success = true;
- final Map> allHandles =
- mStorage.listSyntheticPasswordHandlesForAllUsers(SP_BLOB_NAME);
- for (List userHandles : allHandles.values()) {
- for (long handle : userHandles) {
- success &= SyntheticPasswordCrypto.migrateLockSettingsKey(getKeyName(handle));
+ final Map> allProtectors =
+ mStorage.listSyntheticPasswordProtectorsForAllUsers(SP_BLOB_NAME);
+ for (List userProtectors : allProtectors.values()) {
+ for (long protectorId : userProtectors) {
+ success &= SyntheticPasswordCrypto.migrateLockSettingsKey(getKeyName(protectorId));
}
}
return success;
@@ -1528,13 +1544,13 @@ public class SyntheticPasswordManager {
return mListeners.unregister(listener);
}
- private void notifyWeakEscrowTokenRemovedListeners(long handle, int userId) {
+ private void notifyWeakEscrowTokenRemovedListeners(long protectorId, int userId) {
int i = mListeners.beginBroadcast();
try {
while (i > 0) {
i--;
try {
- mListeners.getBroadcastItem(i).onWeakEscrowTokenRemoved(handle, userId);
+ mListeners.getBroadcastItem(i).onWeakEscrowTokenRemoved(protectorId, userId);
} catch (RemoteException e) {
Slog.e(TAG, "Exception while notifying WeakEscrowTokenRemovedListener.",
e);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
index 5a3f12c5b4ed9..0bb20215111b9 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
@@ -47,7 +47,7 @@ public class MockSyntheticPasswordManager extends SyntheticPasswordManager {
private ArrayMap mBlobs = new ArrayMap<>();
@Override
- protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] applicationId) {
+ protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] protectorSecret) {
if (mBlobs.containsKey(blobKeyName) && !Arrays.equals(mBlobs.get(blobKeyName), blob)) {
throw new AssertionFailedError("blobKeyName content is overwritten: " + blobKeyName);
}
@@ -59,11 +59,11 @@ public class MockSyntheticPasswordManager extends SyntheticPasswordManager {
byte[] data = new byte[len];
buffer.get(data);
len = buffer.getInt();
- byte[] appId = new byte[len];
- buffer.get(appId);
+ byte[] storedProtectorSecret = new byte[len];
+ buffer.get(storedProtectorSecret);
long sid = buffer.getLong();
- if (!Arrays.equals(appId, applicationId)) {
- throw new AssertionFailedError("Invalid application id");
+ if (!Arrays.equals(storedProtectorSecret, protectorSecret)) {
+ throw new AssertionFailedError("Invalid protector secret");
}
if (sid != 0 && mGateKeeper.getAuthTokenForSid(sid) == null) {
throw new AssertionFailedError("No valid auth token");
@@ -72,13 +72,14 @@ public class MockSyntheticPasswordManager extends SyntheticPasswordManager {
}
@Override
- protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] applicationId, long sid) {
+ protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] protectorSecret,
+ long sid) {
ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + data.length + Integer.BYTES
- + applicationId.length + Long.BYTES);
+ + protectorSecret.length + Long.BYTES);
buffer.putInt(data.length);
buffer.put(data);
- buffer.putInt(applicationId.length);
- buffer.put(applicationId);
+ buffer.putInt(protectorSecret.length);
+ buffer.put(protectorSecret);
buffer.putLong(sid);
byte[] result = buffer.array();
mBlobs.put(blobKeyName, result);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 6d1df2c2f2bf8..87beece5b4143 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -19,7 +19,7 @@ package com.android.server.locksettings;
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;
-import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
+import static com.android.internal.widget.LockPatternUtils.CURRENT_LSKF_BASED_PROTECTOR_ID_KEY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -45,8 +45,8 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
-import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
import com.android.server.locksettings.SyntheticPasswordManager.PasswordData;
+import com.android.server.locksettings.SyntheticPasswordManager.SyntheticPassword;
import org.junit.Before;
import org.junit.Test;
@@ -74,28 +74,28 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
}
@Test
- public void testPasswordBasedSyntheticPassword() throws RemoteException {
+ public void testLskfBasedProtector() 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);
- AuthenticationToken authToken = manager.newSyntheticPassword(USER_ID);
- long handle = manager.createPasswordBasedSyntheticPassword(mGateKeeperService,
- password, authToken, USER_ID);
+ SyntheticPassword sp = manager.newSyntheticPassword(USER_ID);
+ long protectorId = manager.createLskfBasedProtector(mGateKeeperService, password, sp,
+ USER_ID);
- AuthenticationResult result = manager.unwrapPasswordBasedSyntheticPassword(
- mGateKeeperService, handle, password, USER_ID, null);
- assertArrayEquals(result.authToken.deriveKeyStorePassword(),
- authToken.deriveKeyStorePassword());
+ AuthenticationResult result = manager.unlockLskfBasedProtector(mGateKeeperService,
+ protectorId, password, USER_ID, null);
+ assertArrayEquals(result.syntheticPassword.deriveKeyStorePassword(),
+ sp.deriveKeyStorePassword());
- result = manager.unwrapPasswordBasedSyntheticPassword(mGateKeeperService, handle,
- badPassword, USER_ID, null);
- assertNull(result.authToken);
+ result = manager.unlockLskfBasedProtector(mGateKeeperService, protectorId, badPassword,
+ USER_ID, null);
+ assertNull(result.syntheticPassword);
}
private boolean hasSyntheticPassword(int userId) throws RemoteException {
- return mService.getLong(SYNTHETIC_PASSWORD_HANDLE_KEY, 0, userId) != 0;
+ return mService.getLong(CURRENT_LSKF_BASED_PROTECTOR_ID_KEY, 0, userId) != 0;
}
private void initializeCredential(LockscreenCredential password, int userId)
@@ -544,12 +544,12 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
}
private void assertNoOrphanedFilesLeft(int userId) {
- String handleString = String.format("%016x",
- mService.getSyntheticPasswordHandleLocked(userId));
+ String lskfProtectorPrefix = String.format("%016x",
+ mService.getCurrentLskfBasedProtectorId(userId));
File directory = mStorage.getSyntheticPasswordDirectoryForUser(userId);
for (File file : directory.listFiles()) {
String[] parts = file.getName().split("\\.");
- if (!parts[0].equals(handleString) && !parts[0].equals("0000000000000000")) {
+ if (!parts[0].equals(lskfProtectorPrefix) && !parts[0].equals("0000000000000000")) {
fail("Orphaned state left: " + file.getName());
}
}