Merge "Update synthetic password terminology to match new design doc"
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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).
|
||||
*
|
||||
* <p> 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
|
||||
* }
|
||||
* </pre>
|
||||
* 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 {
|
||||
|
||||
|
||||
@@ -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<Byte> 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<Integer, LockscreenCredential> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Integer, List<Long>> listSyntheticPasswordHandlesForAllUsers(String stateName) {
|
||||
public Map<Integer, List<Long>> listSyntheticPasswordProtectorsForAllUsers(String stateName) {
|
||||
Map<Integer, List<Long>> 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<Long> listSyntheticPasswordHandlesForUser(String stateName, int userId) {
|
||||
public List<Long> listSyntheticPasswordProtectorsForUser(String stateName, int userId) {
|
||||
File baseDir = getSyntheticPasswordDirectoryForUser(userId);
|
||||
List<Long> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -47,7 +47,7 @@ public class MockSyntheticPasswordManager extends SyntheticPasswordManager {
|
||||
private ArrayMap<String, byte[]> 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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user