summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/com/android/internal/widget/ILockSettings.aidl1
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java105
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java30
-rw-r--r--services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java6
4 files changed, 117 insertions, 25 deletions
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 7e63adc27c9e..591f15fd5676 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -45,6 +45,7 @@ interface ILockSettings {
boolean checkVoldPassword(int userId);
boolean havePattern(int userId);
boolean havePassword(int userId);
+ byte[] getHashFactor(String currentCredential, int userId);
void setSeparateProfileChallengeEnabled(int userId, boolean enabled, String managedUserPassword);
boolean getSeparateProfileChallengeEnabled(int userId);
void registerStrongAuthTracker(in IStrongAuthTracker tracker);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index d4ab4265b3fb..7c339fb6d6b1 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -66,8 +66,10 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.List;
+import java.util.StringJoiner;
/**
* Utilities for the lock pattern and its settings.
*/
@@ -165,6 +167,7 @@ public class LockPatternUtils {
public static final String SYNTHETIC_PASSWORD_HANDLE_KEY = "sp-handle";
public static final String SYNTHETIC_PASSWORD_ENABLED_KEY = "enable-sp";
+ private static final String HISTORY_DELIMITER = ",";
private final Context mContext;
private final ContentResolver mContentResolver;
@@ -507,31 +510,50 @@ public class LockPatternUtils {
}
/**
+ * Returns the password history hash factor, needed to check new password against password
+ * history with {@link #checkPasswordHistory(String, byte[], int)}
+ */
+ public byte[] getPasswordHistoryHashFactor(String currentPassword, int userId) {
+ try {
+ return getLockSettings().getHashFactor(currentPassword, userId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to get hash factor", e);
+ return null;
+ }
+ }
+
+ /**
* Check to see if a password matches any of the passwords stored in the
* password history.
*
- * @param password The password to check.
+ * @param passwordToCheck The password to check.
+ * @param hashFactor Hash factor of the current user returned from
+ * {@link ILockSettings#getHashFactor}
* @return Whether the password matches any in the history.
*/
- public boolean checkPasswordHistory(String password, int userId) {
- String passwordHashString = new String(
- passwordToHash(password, userId), StandardCharsets.UTF_8);
+ public boolean checkPasswordHistory(String passwordToCheck, byte[] hashFactor, int userId) {
+ if (TextUtils.isEmpty(passwordToCheck)) {
+ Log.e(TAG, "checkPasswordHistory: empty password");
+ return false;
+ }
String passwordHistory = getString(PASSWORD_HISTORY_KEY, userId);
- if (passwordHistory == null) {
+ if (TextUtils.isEmpty(passwordHistory)) {
return false;
}
- // Password History may be too long...
- int passwordHashLength = passwordHashString.length();
int passwordHistoryLength = getRequestedPasswordHistoryLength(userId);
if(passwordHistoryLength == 0) {
return false;
}
- int neededPasswordHistoryLength = passwordHashLength * passwordHistoryLength
- + passwordHistoryLength - 1;
- if (passwordHistory.length() > neededPasswordHistoryLength) {
- passwordHistory = passwordHistory.substring(0, neededPasswordHistoryLength);
+ String legacyHash = legacyPasswordToHash(passwordToCheck, userId);
+ String passwordHash = passwordToHistoryHash(passwordToCheck, hashFactor, userId);
+ String[] history = passwordHistory.split(HISTORY_DELIMITER);
+ // Password History may be too long...
+ for (int i = 0; i < Math.min(passwordHistoryLength, history.length); i++) {
+ if (history[i].equals(legacyHash) || history[i].equals(passwordHash)) {
+ return true;
+ }
}
- return passwordHistory.contains(passwordHashString);
+ return false;
}
/**
@@ -830,6 +852,7 @@ public class LockPatternUtils {
updateEncryptionPasswordIfNeeded(password,
PasswordMetrics.computeForPassword(password).quality, userHandle);
updatePasswordHistory(password, userHandle);
+ onAfterChangingPassword(userHandle);
}
/**
@@ -852,8 +875,15 @@ public class LockPatternUtils {
}
}
+ /**
+ * Store the hash of the *current* password in the password history list, if device policy
+ * enforces password history requirement.
+ */
private void updatePasswordHistory(String password, int userHandle) {
-
+ if (TextUtils.isEmpty(password)) {
+ Log.e(TAG, "checkPasswordHistory: empty password");
+ return;
+ }
// 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);
@@ -864,16 +894,25 @@ public class LockPatternUtils {
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()));
+ final byte[] hashFactor = getPasswordHistoryHashFactor(password, userHandle);
+ String hash = passwordToHistoryHash(password, hashFactor, userHandle);
+ if (hash == null) {
+ Log.e(TAG, "Compute new style password hash failed, fallback to legacy style");
+ hash = legacyPasswordToHash(password, userHandle);
+ }
+ if (TextUtils.isEmpty(passwordHistory)) {
+ passwordHistory = hash;
+ } else {
+ String[] history = passwordHistory.split(HISTORY_DELIMITER);
+ StringJoiner joiner = new StringJoiner(HISTORY_DELIMITER);
+ joiner.add(hash);
+ for (int i = 0; i < passwordHistoryLength - 1 && i < history.length; i++) {
+ joiner.add(history[i]);
+ }
+ passwordHistory = joiner.toString();
+ }
}
setString(PASSWORD_HISTORY_KEY, passwordHistory, userHandle);
- onAfterChangingPassword(userHandle);
}
/**
@@ -1098,7 +1137,7 @@ public class LockPatternUtils {
return Long.toHexString(salt);
}
- /*
+ /**
* Generate a hash for the given password. To avoid brute force attacks, we use a salted hash.
* Not the most secure, but it is at least a second level of protection. First level is that
* the file is in a location only readable by the system process.
@@ -1107,7 +1146,7 @@ public class LockPatternUtils {
*
* @return the hash of the pattern in a byte array.
*/
- public byte[] passwordToHash(String password, int userId) {
+ public String legacyPasswordToHash(String password, int userId) {
if (password == null) {
return null;
}
@@ -1122,7 +1161,24 @@ public class LockPatternUtils {
System.arraycopy(md5, 0, combined, sha1.length, md5.length);
final char[] hexEncoded = HexEncoding.encode(combined);
- return new String(hexEncoded).getBytes(StandardCharsets.UTF_8);
+ return new String(hexEncoded);
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError("Missing digest algorithm: ", e);
+ }
+ }
+
+ /**
+ * Hash the password for password history check purpose.
+ */
+ private String passwordToHistoryHash(String passwordToHash, byte[] hashFactor, int userId) {
+ if (TextUtils.isEmpty(passwordToHash) || hashFactor == null) {
+ return null;
+ }
+ try {
+ MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
+ sha256.update(hashFactor);
+ sha256.update((passwordToHash + getSalt(userId)).getBytes());
+ return new String(HexEncoding.encode(sha256.digest()));
} catch (NoSuchAlgorithmException e) {
throw new AssertionError("Missing digest algorithm: ", e);
}
@@ -1571,6 +1627,7 @@ public class LockPatternUtils {
updateEncryptionPasswordIfNeeded(credential, quality, userId);
updatePasswordHistory(credential, userId);
+ onAfterChangingPassword(userId);
} else {
if (!TextUtils.isEmpty(credential)) {
throw new IllegalArgumentException("password must be emtpy for NONE type");
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index f1fd00b4ea2a..1078f6eefa76 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1742,7 +1742,8 @@ public class LockSettingsService extends ILockSettings.Stub {
if (storedHash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
hash = LockPatternUtils.patternToHash(LockPatternUtils.stringToPattern(credential));
} else {
- hash = mLockPatternUtils.passwordToHash(credential, userId);
+ hash = mLockPatternUtils.legacyPasswordToHash(credential, userId)
+ .getBytes(StandardCharsets.UTF_8);
}
if (Arrays.equals(hash, storedHash.hash)) {
if (storedHash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
@@ -2532,6 +2533,33 @@ public class LockSettingsService extends ILockSettings.Stub {
mRecoverableKeyStoreManager.lockScreenSecretChanged(credentialType, credential, userId);
}
+ /**
+ * Returns a fixed pseudorandom byte string derived from the user's synthetic password.
+ * This is used to salt the password history hash to protect the hash against offline
+ * bruteforcing, since rederiving this value requires a successful authentication.
+ */
+ @Override
+ public byte[] getHashFactor(String currentCredential, int userId) throws RemoteException {
+ checkPasswordReadPermission(userId);
+ if (TextUtils.isEmpty(currentCredential)) {
+ currentCredential = null;
+ }
+ synchronized (mSpManager) {
+ if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
+ 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) {
+ Slog.w(TAG, "Current credential is incorrect");
+ return null;
+ }
+ return auth.authToken.derivePasswordHashFactor();
+ }
+ }
+
private long addEscrowToken(byte[] token, int userId) throws RemoteException {
if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId);
synchronized (mSpManager) {
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 0700ab35df1b..596daeb1427b 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -123,6 +123,7 @@ public class SyntheticPasswordManager {
private static final byte[] PERSONALIZATION_FBE_KEY = "fbe-key".getBytes();
private static final byte[] PERSONALIZATION_AUTHSECRET_KEY = "authsecret-hal".getBytes();
private static final byte[] PERSONALIZATION_SP_SPLIT = "sp-split".getBytes();
+ private static final byte[] PERSONALIZATION_PASSWORD_HASH = "pw-hash".getBytes();
private static final byte[] PERSONALIZATION_E0 = "e0-encryption".getBytes();
private static final byte[] PERSONALISATION_WEAVER_PASSWORD = "weaver-pwd".getBytes();
private static final byte[] PERSONALISATION_WEAVER_KEY = "weaver-key".getBytes();
@@ -165,6 +166,11 @@ public class SyntheticPasswordManager {
syntheticPassword.getBytes());
}
+ public byte[] derivePasswordHashFactor() {
+ return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_PASSWORD_HASH,
+ syntheticPassword.getBytes());
+ }
+
private void initialize(byte[] P0, byte[] P1) {
this.P1 = P1;
this.syntheticPassword = String.valueOf(HexEncoding.encode(