Merge "Small tweaks in LockSettingsService"
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 58fb145..ef6e6c2 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -569,11 +569,12 @@
/**
* Clear any lock pattern or password.
*/
- public void clearLock(int userHandle) {
+ public void clearLock(String savedCredential, int userHandle) {
setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userHandle);
- try {
- getLockSettings().setLockCredential(null, CREDENTIAL_TYPE_NONE, null, userHandle);
+ try{
+ getLockSettings().setLockCredential(null, CREDENTIAL_TYPE_NONE, savedCredential,
+ userHandle);
} catch (RemoteException e) {
// well, we tried...
}
@@ -758,7 +759,6 @@
public void saveLockPassword(String password, String savedPassword, int quality,
int userHandle) {
try {
- DevicePolicyManager dpm = getDevicePolicyManager();
if (password == null || password.length() < MIN_LOCK_PASSWORD_SIZE) {
throw new IllegalArgumentException("password must not be null and at least "
+ "of length " + MIN_LOCK_PASSWORD_SIZE);
@@ -769,48 +769,55 @@
getLockSettings().setLockCredential(password, CREDENTIAL_TYPE_PASSWORD, savedPassword,
userHandle);
- // Update the device encryption password.
- if (userHandle == UserHandle.USER_SYSTEM
- && LockPatternUtils.isDeviceEncryptionEnabled()) {
- if (!shouldEncryptWithCredentials(true)) {
- clearEncryptionPassword();
- } else {
- boolean numeric = computedQuality
- == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
- boolean numericComplex = computedQuality
- == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
- int type = numeric || numericComplex ? StorageManager.CRYPT_TYPE_PIN
- : StorageManager.CRYPT_TYPE_PASSWORD;
- updateEncryptionPassword(type, password);
- }
- }
-
- // Add the password to the password history. We assume all
- // password hashes have the same length for simplicity of implementation.
- String passwordHistory = getString(PASSWORD_HISTORY_KEY, userHandle);
- if (passwordHistory == null) {
- passwordHistory = "";
- }
- int passwordHistoryLength = getRequestedPasswordHistoryLength(userHandle);
- if (passwordHistoryLength == 0) {
- passwordHistory = "";
- } else {
- byte[] hash = passwordToHash(password, userHandle);
- passwordHistory = new String(hash, StandardCharsets.UTF_8) + "," + passwordHistory;
- // Cut it to contain passwordHistoryLength hashes
- // and passwordHistoryLength -1 commas.
- passwordHistory = passwordHistory.substring(0, Math.min(hash.length
- * passwordHistoryLength + passwordHistoryLength - 1, passwordHistory
- .length()));
- }
- setString(PASSWORD_HISTORY_KEY, passwordHistory, userHandle);
- onAfterChangingPassword(userHandle);
+ addEncryptionPassword(password, computedQuality, userHandle);
+ updatePasswordHistory(password, userHandle);
} catch (RemoteException re) {
// Cant do much
Log.e(TAG, "Unable to save lock password " + re);
}
}
+ private void addEncryptionPassword(String password, int quality, int userHandle) {
+ // Update the device encryption password.
+ if (userHandle == UserHandle.USER_SYSTEM
+ && LockPatternUtils.isDeviceEncryptionEnabled()) {
+ if (!shouldEncryptWithCredentials(true)) {
+ clearEncryptionPassword();
+ } else {
+ boolean numeric = quality == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+ boolean numericComplex = quality
+ == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
+ int type = numeric || numericComplex ? StorageManager.CRYPT_TYPE_PIN
+ : StorageManager.CRYPT_TYPE_PASSWORD;
+ updateEncryptionPassword(type, password);
+ }
+ }
+ }
+
+ private void updatePasswordHistory(String password, int userHandle) {
+
+ // Add the password to the password history. We assume all
+ // password hashes have the same length for simplicity of implementation.
+ String passwordHistory = getString(PASSWORD_HISTORY_KEY, userHandle);
+ if (passwordHistory == null) {
+ passwordHistory = "";
+ }
+ int passwordHistoryLength = getRequestedPasswordHistoryLength(userHandle);
+ if (passwordHistoryLength == 0) {
+ passwordHistory = "";
+ } else {
+ byte[] hash = passwordToHash(password, userHandle);
+ passwordHistory = new String(hash, StandardCharsets.UTF_8) + "," + passwordHistory;
+ // Cut it to contain passwordHistoryLength hashes
+ // and passwordHistoryLength -1 commas.
+ passwordHistory = passwordHistory.substring(0, Math.min(hash.length
+ * passwordHistoryLength + passwordHistoryLength - 1, passwordHistory
+ .length()));
+ }
+ setString(PASSWORD_HISTORY_KEY, passwordHistory, userHandle);
+ onAfterChangingPassword(userHandle);
+ }
+
/**
* Determine if the device supports encryption, even if it's set to default. This
* differs from isDeviceEncrypted() in that it returns true even if the device is
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 8ef34dc..a073d8e 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -68,6 +68,7 @@
import android.service.gatekeeper.GateKeeperResponse;
import android.service.gatekeeper.IGateKeeperService;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
@@ -94,8 +95,10 @@
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -231,7 +234,7 @@
}
// Do not tie it to parent when parent does not have a screen lock
final int parentId = mUserManager.getProfileParent(managedUserId).id;
- if (!mStorage.hasPassword(parentId) && !mStorage.hasPattern(parentId)) {
+ if (!isUserSecure(parentId)) {
if (DEBUG) Slog.v(TAG, "Parent does not have a screen lock");
return;
}
@@ -378,7 +381,7 @@
}
final UserHandle userHandle = user.getUserHandle();
- final boolean isSecure = mStorage.hasPassword(userId) || mStorage.hasPattern(userId);
+ final boolean isSecure = isUserSecure(userId);
if (isSecure && !mUserManager.isUserUnlockingOrUnlocked(userHandle)) {
UserInfo parent = mUserManager.getProfileParent(userId);
if (parent != null &&
@@ -453,35 +456,34 @@
}
public void onUnlockUser(final int userId) {
- // Hide notification first, as tie managed profile lock takes time
- hideEncryptionNotification(new UserHandle(userId));
+ // Perform tasks which require locks in LSS on a handler, as we are callbacks from
+ // ActivityManager.unlockUser()
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // Hide notification first, as tie managed profile lock takes time
+ hideEncryptionNotification(new UserHandle(userId));
- if (mUserManager.getUserInfo(userId).isManagedProfile()) {
- // As tieManagedProfileLockIfNecessary() may try to unlock user, we should not do it
- // in onUnlockUser() synchronously, otherwise it may cause a deadlock
- mHandler.post(new Runnable() {
- @Override
- public void run() {
+ // Now we have unlocked the parent user we should show notifications
+ // about any profiles that exist.
+ List<UserInfo> profiles = mUserManager.getProfiles(userId);
+ for (int i = 0; i < profiles.size(); i++) {
+ UserInfo profile = profiles.get(i);
+ final boolean isSecure = isUserSecure(profile.id);
+ if (isSecure && profile.isManagedProfile()) {
+ UserHandle userHandle = profile.getUserHandle();
+ if (!mUserManager.isUserUnlockingOrUnlocked(userHandle) &&
+ !mUserManager.isQuietModeEnabled(userHandle)) {
+ showEncryptionNotificationForProfile(userHandle);
+ }
+ }
+ }
+
+ if (mUserManager.getUserInfo(userId).isManagedProfile()) {
tieManagedProfileLockIfNecessary(userId, null);
}
- });
- }
-
- // Now we have unlocked the parent user we should show notifications
- // about any profiles that exist.
- List<UserInfo> profiles = mUserManager.getProfiles(userId);
- for (int i = 0; i < profiles.size(); i++) {
- UserInfo profile = profiles.get(i);
- final boolean isSecure =
- mStorage.hasPassword(profile.id) || mStorage.hasPattern(profile.id);
- if (isSecure && profile.isManagedProfile()) {
- UserHandle userHandle = profile.getUserHandle();
- if (!mUserManager.isUserUnlockingOrUnlocked(userHandle) &&
- !mUserManager.isQuietModeEnabled(userHandle)) {
- showEncryptionNotificationForProfile(userHandle);
- }
}
- }
+ });
}
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -809,6 +811,10 @@
return mStorage.hasPattern(userId);
}
+ private boolean isUserSecure(int userId) {
+ return mStorage.hasCredential(userId);
+ }
+
private void setKeystorePassword(String password, int userHandle) {
final KeyStore ks = KeyStore.getInstance();
ks.onUserPasswordChanged(userHandle, password);
@@ -914,21 +920,52 @@
}
}
- private byte[] getCurrentHandle(int userId) {
- CredentialHash credential = mStorage.readCredentialHash(userId);
-
- // sanity check
- if (credential.type != LockPatternUtils.CREDENTIAL_TYPE_NONE && credential.hash == null) {
- Slog.e(TAG, "Stored handle type [" + credential.type + "] but no handle available");
+ private Map<Integer, String> getDecryptedPasswordsForAllTiedProfiles(int userId) {
+ if (mUserManager.getUserInfo(userId).isManagedProfile()) {
+ return null;
}
- return credential.hash;
+ Map<Integer, String> result = new ArrayMap<Integer, String>();
+ final List<UserInfo> profiles = mUserManager.getProfiles(userId);
+ final int size = profiles.size();
+ for (int i = 0; i < size; i++) {
+ final UserInfo profile = profiles.get(i);
+ if (!profile.isManagedProfile()) {
+ continue;
+ }
+ final int managedUserId = profile.id;
+ if (mLockPatternUtils.isSeparateProfileChallengeEnabled(managedUserId)) {
+ continue;
+ }
+ try {
+ result.put(userId, getDecryptedPasswordForTiedProfile(userId));
+ } catch (KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException
+ | NoSuchPaddingException | InvalidKeyException
+ | InvalidAlgorithmParameterException | IllegalBlockSizeException
+ | BadPaddingException | CertificateException | IOException e) {
+ // ignore
+ }
+ }
+ return result;
}
- private void onUserLockChanged(int userId) throws RemoteException {
+ /**
+ * Synchronize all profile's work challenge of the given user if it's unified: tie or clear them
+ * depending on the parent user's secure state.
+ *
+ * When clearing tied work challenges, a pre-computed password table for profiles are required,
+ * since changing password for profiles requires existing password, and existing passwords can
+ * only be computed before the parent user's password is cleared.
+ *
+ * Strictly this is a recursive function, since setLockCredentialInternal ends up calling this
+ * method again on profiles. However the recursion is guaranteed to terminate as this method
+ * terminates when the user is a managed profile.
+ */
+ private void synchronizeUnifiedWorkChallengeForProfiles(int userId,
+ Map<Integer, String> profilePasswordMap) throws RemoteException {
if (mUserManager.getUserInfo(userId).isManagedProfile()) {
return;
}
- final boolean isSecure = mStorage.hasPassword(userId) || mStorage.hasPattern(userId);
+ final boolean isSecure = isUserSecure(userId);
final List<UserInfo> profiles = mUserManager.getProfiles(userId);
final int size = profiles.size();
for (int i = 0; i < size; i++) {
@@ -941,11 +978,17 @@
if (isSecure) {
tieManagedProfileLockIfNecessary(managedUserId, null);
} else {
- clearUserKeyProtection(managedUserId);
- getGateKeeperService().clearSecureUserId(managedUserId);
- mStorage.writeCredentialHash(CredentialHash.createEmptyHash(), managedUserId);
- setKeystorePassword(null, managedUserId);
- fixateNewestUserKeyAuth(managedUserId);
+ // We use cached work profile password computed before clearing the parent's
+ // credential, otherwise they get lost
+ if (profilePasswordMap != null && profilePasswordMap.containsKey(managedUserId)) {
+ setLockCredentialInternal(null, LockPatternUtils.CREDENTIAL_TYPE_NONE,
+ profilePasswordMap.get(managedUserId), managedUserId);
+ } else {
+ Slog.wtf(TAG, "clear tied profile challenges, but no password supplied.");
+ // Supplying null here would lead to untrusted credential change
+ setLockCredentialInternal(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, null,
+ managedUserId);
+ }
mStorage.removeChildProfileLock(managedUserId);
removeKeystoreProfileKey(managedUserId);
}
@@ -978,7 +1021,6 @@
private void setLockCredentialInternal(String credential, int credentialType,
String savedCredential, int userId) throws RemoteException {
- byte[] currentHandle = getCurrentHandle(userId);
if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
if (credential != null) {
Slog.wtf(TAG, "CredentialType is none, but credential is non-null.");
@@ -988,27 +1030,31 @@
mStorage.writeCredentialHash(CredentialHash.createEmptyHash(), userId);
setKeystorePassword(null, userId);
fixateNewestUserKeyAuth(userId);
- onUserLockChanged(userId);
+ synchronizeUnifiedWorkChallengeForProfiles(userId, null);
notifyActivePasswordMetricsAvailable(null, userId);
return;
}
if (credential == null) {
throw new RemoteException("Null credential with mismatched credential type");
}
+
+ CredentialHash currentHandle = mStorage.readCredentialHash(userId);
if (isManagedProfileWithUnifiedLock(userId)) {
// get credential from keystore when managed profile has unified lock
- try {
- savedCredential = getDecryptedPasswordForTiedProfile(userId);
- } catch (FileNotFoundException e) {
- Slog.i(TAG, "Child profile key not found");
- } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
- | NoSuchAlgorithmException | NoSuchPaddingException
- | InvalidAlgorithmParameterException | IllegalBlockSizeException
- | BadPaddingException | CertificateException | IOException e) {
- Slog.e(TAG, "Failed to decrypt child profile key", e);
+ if (savedCredential == null) {
+ try {
+ savedCredential = getDecryptedPasswordForTiedProfile(userId);
+ } catch (FileNotFoundException e) {
+ Slog.i(TAG, "Child profile key not found");
+ } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
+ | NoSuchAlgorithmException | NoSuchPaddingException
+ | InvalidAlgorithmParameterException | IllegalBlockSizeException
+ | BadPaddingException | CertificateException | IOException e) {
+ Slog.e(TAG, "Failed to decrypt child profile key", e);
+ }
}
} else {
- if (currentHandle == null) {
+ if (currentHandle.hash == null) {
if (savedCredential != null) {
Slog.w(TAG, "Saved credential provided, but none stored");
}
@@ -1016,17 +1062,19 @@
}
}
- byte[] enrolledHandle = enrollCredential(currentHandle, savedCredential, credential,
+ byte[] enrolledHandle = enrollCredential(currentHandle.hash, savedCredential, credential,
userId);
if (enrolledHandle != null) {
CredentialHash willStore = CredentialHash.create(enrolledHandle, credentialType);
mStorage.writeCredentialHash(willStore, userId);
- // Refresh the auth token
- setUserKeyProtection(userId, credential,
- doVerifyCredential(credential, credentialType, true, 0, userId,
- null /* progressCallback */));
+ // push new secret and auth token to vold
+ GateKeeperResponse gkResponse = getGateKeeperService()
+ .verifyChallenge(userId, 0, willStore.hash, credential.getBytes());
+ setUserKeyProtection(userId, credential, convertResponse(gkResponse));
fixateNewestUserKeyAuth(userId);
- onUserLockChanged(userId);
+ // Refresh the auth token
+ doVerifyCredential(credential, credentialType, true, 0, userId, null /* progressCallback */);
+ synchronizeUnifiedWorkChallengeForProfiles(userId, null);
} else {
throw new RemoteException("Failed to enroll " +
(credentialType == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD ? "password"
@@ -1034,6 +1082,26 @@
}
}
+ private VerifyCredentialResponse convertResponse(GateKeeperResponse gateKeeperResponse) {
+ VerifyCredentialResponse response;
+ int responseCode = gateKeeperResponse.getResponseCode();
+ if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
+ response = new VerifyCredentialResponse(gateKeeperResponse.getTimeout());
+ } else if (responseCode == GateKeeperResponse.RESPONSE_OK) {
+ byte[] token = gateKeeperResponse.getPayload();
+ if (token == null) {
+ // something's wrong if there's no payload with a challenge
+ Slog.e(TAG, "verifyChallenge response had no associated payload");
+ response = VerifyCredentialResponse.ERROR;
+ } else {
+ response = new VerifyCredentialResponse(token);
+ }
+ } else {
+ response = VerifyCredentialResponse.ERROR;
+ }
+ return response;
+ }
+
@VisibleForTesting
protected void tieProfileLockToParent(int userId, String password) {
if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
@@ -1123,6 +1191,7 @@
private void setUserKeyProtection(int userId, String credential, VerifyCredentialResponse vcr)
throws RemoteException {
+ if (DEBUG) Slog.d(TAG, "setUserKeyProtection: user=" + userId);
if (vcr == null) {
throw new RemoteException("Null response verifying a credential we just set");
}
@@ -1138,6 +1207,7 @@
}
private void clearUserKeyProtection(int userId) throws RemoteException {
+ if (DEBUG) Slog.d(TAG, "clearUserKeyProtection user=" + userId);
addUserKeyAuth(userId, null, null);
}
@@ -1171,6 +1241,7 @@
private void fixateNewestUserKeyAuth(int userId)
throws RemoteException {
+ if (DEBUG) Slog.d(TAG, "fixateNewestUserKeyAuth: user=" + userId);
final IStorageManager storageManager = mInjector.getStorageManager();
final long callingId = Binder.clearCallingIdentity();
try {
@@ -1370,27 +1441,10 @@
return VerifyCredentialResponse.ERROR;
}
}
-
- VerifyCredentialResponse response;
- boolean shouldReEnroll = false;
GateKeeperResponse gateKeeperResponse = getGateKeeperService()
.verifyChallenge(userId, challenge, storedHash.hash, credential.getBytes());
- int responseCode = gateKeeperResponse.getResponseCode();
- if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
- response = new VerifyCredentialResponse(gateKeeperResponse.getTimeout());
- } else if (responseCode == GateKeeperResponse.RESPONSE_OK) {
- byte[] token = gateKeeperResponse.getPayload();
- if (token == null) {
- // something's wrong if there's no payload with a challenge
- Slog.e(TAG, "verifyChallenge response had no associated payload");
- response = VerifyCredentialResponse.ERROR;
- } else {
- shouldReEnroll = gateKeeperResponse.getShouldReEnroll();
- response = new VerifyCredentialResponse(token);
- }
- } else {
- response = VerifyCredentialResponse.ERROR;
- }
+ VerifyCredentialResponse response = convertResponse(gateKeeperResponse);
+ boolean shouldReEnroll = gateKeeperResponse.getShouldReEnroll();
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
diff --git a/services/core/java/com/android/server/LockSettingsShellCommand.java b/services/core/java/com/android/server/LockSettingsShellCommand.java
index e131251..1ab5303 100644
--- a/services/core/java/com/android/server/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/LockSettingsShellCommand.java
@@ -116,7 +116,7 @@
}
private void runClear() throws RemoteException {
- mLockPatternUtils.clearLock(mCurrentUserId);
+ mLockPatternUtils.clearLock(mOld, mCurrentUserId);
getOutPrintWriter().println("Lock credential cleared");
}
diff --git a/services/core/java/com/android/server/LockSettingsStorage.java b/services/core/java/com/android/server/LockSettingsStorage.java
index c858036..385b1cf 100644
--- a/services/core/java/com/android/server/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/LockSettingsStorage.java
@@ -290,6 +290,10 @@
hasFile(getLegacyLockPatternFilename(userId));
}
+ public boolean hasCredential(int userId) {
+ return hasPassword(userId) || hasPattern(userId);
+ }
+
private boolean hasFile(String name) {
byte[] contents = readFile(name);
return contents != null && contents.length > 0;
@@ -650,4 +654,5 @@
}
}
}
+
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 003b6d0..9d8372a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4297,7 +4297,7 @@
if (!TextUtils.isEmpty(password)) {
mLockPatternUtils.saveLockPassword(password, null, quality, userHandle);
} else {
- mLockPatternUtils.clearLock(userHandle);
+ mLockPatternUtils.clearLock(null, userHandle);
}
boolean requireEntry = (flags & DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY) != 0;
if (requireEntry) {
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/LockSettingsShellCommandTest.java
index d6ee367..84ebb19 100644
--- a/services/tests/servicestests/src/com/android/server/LockSettingsShellCommandTest.java
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsShellCommandTest.java
@@ -137,6 +137,6 @@
assertEquals(0, mCommand.exec(new Binder(), in, out, err,
new String[] { "clear", "--old", "1234" },
mShellCallback, mResultReceiver));
- verify(mLockPatternUtils).clearLock(mUserId);
+ verify(mLockPatternUtils).clearLock("1234", mUserId);
}
}