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