Merge "Add set/verify lock credentials testAPIs in KeygyardManager" into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
45dc22e11c
@@ -281,6 +281,11 @@ package android.app {
|
||||
method public abstract void onHomeVisibilityChanged(boolean);
|
||||
}
|
||||
|
||||
public class KeyguardManager {
|
||||
method @RequiresPermission(anyOf={android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS, "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"}) public boolean checkLock(int, @Nullable byte[]);
|
||||
method @RequiresPermission(anyOf={android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS, "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"}) public boolean setLock(int, @Nullable byte[], int, @Nullable byte[]);
|
||||
}
|
||||
|
||||
public class Notification implements android.os.Parcelable {
|
||||
method public boolean shouldShowForegroundImmediately();
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.annotation.RequiresFeature;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.SystemService;
|
||||
import android.annotation.TestApi;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.app.admin.DevicePolicyManager.PasswordComplexity;
|
||||
import android.app.admin.PasswordMetrics;
|
||||
@@ -51,6 +52,7 @@ import com.android.internal.policy.IKeyguardDismissCallback;
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.internal.widget.LockPatternView;
|
||||
import com.android.internal.widget.LockscreenCredential;
|
||||
import com.android.internal.widget.VerifyCredentialResponse;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
@@ -696,14 +698,15 @@ public class KeyguardManager {
|
||||
}
|
||||
|
||||
private boolean checkInitialLockMethodUsage() {
|
||||
if (mContext.checkCallingOrSelfPermission(Manifest.permission.SET_INITIAL_LOCK)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
if (!hasPermission(Manifest.permission.SET_INITIAL_LOCK)) {
|
||||
throw new SecurityException("Requires SET_INITIAL_LOCK permission.");
|
||||
}
|
||||
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
|
||||
}
|
||||
|
||||
private boolean hasPermission(String permission) {
|
||||
return PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
|
||||
permission);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -792,38 +795,14 @@ public class KeyguardManager {
|
||||
Log.e(TAG, "Password is not valid, rejecting call to setLock");
|
||||
return false;
|
||||
}
|
||||
boolean success = false;
|
||||
boolean success;
|
||||
try {
|
||||
switch (lockType) {
|
||||
case PASSWORD:
|
||||
CharSequence passwordStr = new String(password, Charset.forName("UTF-8"));
|
||||
lockPatternUtils.setLockCredential(
|
||||
LockscreenCredential.createPassword(passwordStr),
|
||||
/* savedPassword= */ LockscreenCredential.createNone(),
|
||||
userId);
|
||||
success = true;
|
||||
break;
|
||||
case PIN:
|
||||
CharSequence pinStr = new String(password);
|
||||
lockPatternUtils.setLockCredential(
|
||||
LockscreenCredential.createPin(pinStr),
|
||||
/* savedPassword= */ LockscreenCredential.createNone(),
|
||||
userId);
|
||||
success = true;
|
||||
break;
|
||||
case PATTERN:
|
||||
List<LockPatternView.Cell> pattern =
|
||||
LockPatternUtils.byteArrayToPattern(password);
|
||||
lockPatternUtils.setLockCredential(
|
||||
LockscreenCredential.createPattern(pattern),
|
||||
/* savedPassword= */ LockscreenCredential.createNone(),
|
||||
userId);
|
||||
pattern.clear();
|
||||
success = true;
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "Unknown lock type, returning a failure");
|
||||
}
|
||||
LockscreenCredential credential = createLockscreenCredential(
|
||||
lockType, password);
|
||||
success = lockPatternUtils.setLockCredential(
|
||||
credential,
|
||||
/* savedPassword= */ LockscreenCredential.createNone(),
|
||||
userId);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Save lock exception", e);
|
||||
success = false;
|
||||
@@ -832,4 +811,81 @@ public class KeyguardManager {
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the lockscreen password to {@code newPassword} after validating the current password
|
||||
* against {@code currentPassword}.
|
||||
* <p>If no password is currently set, {@code currentPassword} should be set to {@code null}.
|
||||
* <p>To clear the current password, {@code newPassword} should be set to {@code null}.
|
||||
*
|
||||
* @return {@code true} if password successfully set.
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code newLockType} or {@code currentLockType}
|
||||
* is invalid.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@TestApi
|
||||
@RequiresPermission(anyOf = {
|
||||
Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS,
|
||||
Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE
|
||||
})
|
||||
public boolean setLock(@LockTypes int newLockType, @Nullable byte[] newPassword,
|
||||
@LockTypes int currentLockType, @Nullable byte[] currentPassword) {
|
||||
final LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
|
||||
final int userId = mContext.getUserId();
|
||||
LockscreenCredential currentCredential = createLockscreenCredential(
|
||||
currentLockType, currentPassword);
|
||||
LockscreenCredential newCredential = createLockscreenCredential(
|
||||
newLockType, newPassword);
|
||||
return lockPatternUtils.setLockCredential(newCredential, currentCredential, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the current lock credentials against {@code password}.
|
||||
* <p>To check if no password is set, {@code password} should be set to {@code null}.
|
||||
*
|
||||
* @return {@code true} if credentials match
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code lockType} is invalid.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@TestApi
|
||||
@RequiresPermission(anyOf = {
|
||||
Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS,
|
||||
Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE
|
||||
})
|
||||
public boolean checkLock(@LockTypes int lockType, @Nullable byte[] password) {
|
||||
final LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
|
||||
final LockscreenCredential credential = createLockscreenCredential(
|
||||
lockType, password);
|
||||
final VerifyCredentialResponse response = lockPatternUtils.verifyCredential(
|
||||
credential, mContext.getUserId(), /* flags= */ 0);
|
||||
if (response == null) {
|
||||
return false;
|
||||
}
|
||||
return response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK;
|
||||
}
|
||||
|
||||
private LockscreenCredential createLockscreenCredential(
|
||||
@LockTypes int lockType, @Nullable byte[] password) {
|
||||
if (password == null) {
|
||||
return LockscreenCredential.createNone();
|
||||
}
|
||||
switch (lockType) {
|
||||
case PASSWORD:
|
||||
CharSequence passwordStr = new String(password, Charset.forName("UTF-8"));
|
||||
return LockscreenCredential.createPassword(passwordStr);
|
||||
case PIN:
|
||||
CharSequence pinStr = new String(password);
|
||||
return LockscreenCredential.createPin(pinStr);
|
||||
case PATTERN:
|
||||
List<LockPatternView.Cell> pattern =
|
||||
LockPatternUtils.byteArrayToPattern(password);
|
||||
return LockscreenCredential.createPattern(pattern);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown lock type " + lockType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,4 +95,5 @@ interface ILockSettings {
|
||||
boolean hasSecureLockScreen();
|
||||
boolean tryUnlockWithCachedUnifiedChallenge(int userId);
|
||||
void removeCachedUnifiedChallenge(int userId);
|
||||
void updateEncryptionPassword(int type, in byte[] password);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
@@ -59,18 +58,13 @@ import com.android.server.LocalServices;
|
||||
|
||||
import com.google.android.collect.Lists;
|
||||
|
||||
import libcore.util.HexEncoding;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
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.
|
||||
@@ -186,7 +180,7 @@ public class LockPatternUtils {
|
||||
public static final String SYNTHETIC_PASSWORD_HANDLE_KEY = "sp-handle";
|
||||
public static final String SYNTHETIC_PASSWORD_ENABLED_KEY = "enable-sp";
|
||||
public static final int SYNTHETIC_PASSWORD_ENABLED_BY_DEFAULT = 1;
|
||||
private static final String HISTORY_DELIMITER = ",";
|
||||
public static final String PASSWORD_HISTORY_DELIMITER = ",";
|
||||
|
||||
@UnsupportedAppUsage
|
||||
private final Context mContext;
|
||||
@@ -559,9 +553,11 @@ public class LockPatternUtils {
|
||||
if(passwordHistoryLength == 0) {
|
||||
return false;
|
||||
}
|
||||
String legacyHash = legacyPasswordToHash(passwordToCheck, userId);
|
||||
String passwordHash = passwordToHistoryHash(passwordToCheck, hashFactor, userId);
|
||||
String[] history = passwordHistory.split(HISTORY_DELIMITER);
|
||||
byte[] salt = getSalt(userId).getBytes();
|
||||
String legacyHash = LockscreenCredential.legacyPasswordToHash(passwordToCheck, salt);
|
||||
String passwordHash = LockscreenCredential.passwordToHistoryHash(
|
||||
passwordToCheck, salt, hashFactor);
|
||||
String[] history = passwordHistory.split(PASSWORD_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)) {
|
||||
@@ -701,20 +697,9 @@ public class LockPatternUtils {
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException("Unable to save lock password", e);
|
||||
}
|
||||
|
||||
onPostPasswordChanged(newCredential, userHandle);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onPostPasswordChanged(LockscreenCredential newCredential, int userHandle) {
|
||||
updateEncryptionPasswordIfNeeded(newCredential, userHandle);
|
||||
if (newCredential.isPattern()) {
|
||||
reportPatternWasChosen(userHandle);
|
||||
}
|
||||
updatePasswordHistory(newCredential, userHandle);
|
||||
reportEnabledTrustAgentsChanged(userHandle);
|
||||
}
|
||||
|
||||
private void updateCryptoUserInfo(int userId) {
|
||||
if (userId != UserHandle.USER_SYSTEM) {
|
||||
return;
|
||||
@@ -781,100 +766,6 @@ public class LockPatternUtils {
|
||||
return getDeviceOwnerInfo() != null;
|
||||
}
|
||||
|
||||
/** Update the encryption password if it is enabled **/
|
||||
private void updateEncryptionPassword(final int type, final byte[] password) {
|
||||
if (!hasSecureLockScreen() && password != null && password.length != 0) {
|
||||
throw new UnsupportedOperationException(
|
||||
"This operation requires the lock screen feature.");
|
||||
}
|
||||
if (!isDeviceEncryptionEnabled()) {
|
||||
return;
|
||||
}
|
||||
final IBinder service = ServiceManager.getService("mount");
|
||||
if (service == null) {
|
||||
Log.e(TAG, "Could not find the mount service to update the encryption password");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(b/120484642): This is a location where we still use a String for vold
|
||||
String passwordString = password != null ? new String(password) : null;
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... dummy) {
|
||||
IStorageManager storageManager = IStorageManager.Stub.asInterface(service);
|
||||
try {
|
||||
storageManager.changeEncryptionPassword(type, passwordString);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Error changing encryption password", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update device encryption password if calling user is USER_SYSTEM and device supports
|
||||
* encryption.
|
||||
*/
|
||||
private void updateEncryptionPasswordIfNeeded(LockscreenCredential credential, int userHandle) {
|
||||
// Update the device encryption password.
|
||||
if (userHandle != UserHandle.USER_SYSTEM || !isDeviceEncryptionEnabled()) {
|
||||
return;
|
||||
}
|
||||
if (!shouldEncryptWithCredentials(true)) {
|
||||
updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
|
||||
return;
|
||||
}
|
||||
if (credential.isNone()) {
|
||||
// Set the encryption password to default.
|
||||
setCredentialRequiredToDecrypt(false);
|
||||
}
|
||||
updateEncryptionPassword(credential.getStorageCryptType(), credential.getCredential());
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the hash of the *current* password in the password history list, if device policy
|
||||
* enforces password history requirement.
|
||||
*/
|
||||
private void updatePasswordHistory(LockscreenCredential password, int userHandle) {
|
||||
if (password.isNone()) {
|
||||
return;
|
||||
}
|
||||
if (password.isPattern()) {
|
||||
// Do not keep track of historical patterns
|
||||
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);
|
||||
if (passwordHistory == null) {
|
||||
passwordHistory = "";
|
||||
}
|
||||
int passwordHistoryLength = getRequestedPasswordHistoryLength(userHandle);
|
||||
if (passwordHistoryLength == 0) {
|
||||
passwordHistory = "";
|
||||
} else {
|
||||
final byte[] hashFactor = getPasswordHistoryHashFactor(password, userHandle);
|
||||
String hash = passwordToHistoryHash(password.getCredential(), hashFactor, userHandle);
|
||||
if (hash == null) {
|
||||
Log.e(TAG, "Compute new style password hash failed, fallback to legacy style");
|
||||
hash = legacyPasswordToHash(password.getCredential(), 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -898,7 +789,11 @@ public class LockPatternUtils {
|
||||
* Clears the encryption password.
|
||||
*/
|
||||
public void clearEncryptionPassword() {
|
||||
updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
|
||||
try {
|
||||
getLockSettings().updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Couldn't clear encryption password");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1045,56 +940,9 @@ public class LockPatternUtils {
|
||||
* @param password the gesture pattern.
|
||||
*
|
||||
* @return the hash of the pattern in a byte array.
|
||||
* TODO: move to LockscreenCredential class
|
||||
*/
|
||||
public String legacyPasswordToHash(byte[] password, int userId) {
|
||||
if (password == null || password.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Previously the password was passed as a String with the following code:
|
||||
// byte[] saltedPassword = (password + getSalt(userId)).getBytes();
|
||||
// The code below creates the identical digest preimage using byte arrays:
|
||||
byte[] salt = getSalt(userId).getBytes();
|
||||
byte[] saltedPassword = Arrays.copyOf(password, password.length + salt.length);
|
||||
System.arraycopy(salt, 0, saltedPassword, password.length, salt.length);
|
||||
byte[] sha1 = MessageDigest.getInstance("SHA-1").digest(saltedPassword);
|
||||
byte[] md5 = MessageDigest.getInstance("MD5").digest(saltedPassword);
|
||||
|
||||
byte[] combined = new byte[sha1.length + md5.length];
|
||||
System.arraycopy(sha1, 0, combined, 0, sha1.length);
|
||||
System.arraycopy(md5, 0, combined, sha1.length, md5.length);
|
||||
|
||||
final char[] hexEncoded = HexEncoding.encode(combined);
|
||||
Arrays.fill(saltedPassword, (byte) 0);
|
||||
return new String(hexEncoded);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError("Missing digest algorithm: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash the password for password history check purpose.
|
||||
* TODO: move to LockscreenCredential class
|
||||
*/
|
||||
private String passwordToHistoryHash(byte[] passwordToHash, byte[] hashFactor, int userId) {
|
||||
if (passwordToHash == null || passwordToHash.length == 0 || hashFactor == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
||||
sha256.update(hashFactor);
|
||||
byte[] salt = getSalt(userId).getBytes();
|
||||
byte[] saltedPassword = Arrays.copyOf(passwordToHash, passwordToHash.length
|
||||
+ salt.length);
|
||||
System.arraycopy(salt, 0, saltedPassword, passwordToHash.length, salt.length);
|
||||
sha256.update(saltedPassword);
|
||||
Arrays.fill(saltedPassword, (byte) 0);
|
||||
return new String(HexEncoding.encode(sha256.digest()));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError("Missing digest algorithm: ", e);
|
||||
}
|
||||
return LockscreenCredential.legacyPasswordToHash(password, getSalt(userId).getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1396,14 +1244,6 @@ public class LockPatternUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isDoNotAskCredentialsOnBootSet() {
|
||||
return getDevicePolicyManager().getDoNotAskCredentialsOnBoot();
|
||||
}
|
||||
|
||||
private boolean shouldEncryptWithCredentials(boolean defaultValue) {
|
||||
return isCredentialRequiredToDecrypt(defaultValue) && !isDoNotAskCredentialsOnBootSet();
|
||||
}
|
||||
|
||||
private void throwIfCalledOnMainThread() {
|
||||
if (Looper.getMainLooper().isCurrentThread()) {
|
||||
throw new IllegalStateException("should not be called from the main thread.");
|
||||
@@ -1590,12 +1430,7 @@ public class LockPatternUtils {
|
||||
credential.checkLength();
|
||||
LockSettingsInternal localService = getLockSettingsInternal();
|
||||
|
||||
if (!localService.setLockCredentialWithToken(credential, tokenHandle, token, userHandle)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
onPostPasswordChanged(credential, userHandle);
|
||||
return true;
|
||||
return localService.setLockCredentialWithToken(credential, tokenHandle, token, userHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,6 +31,10 @@ import android.text.TextUtils;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import libcore.util.HexEncoding;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@@ -276,6 +280,82 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
|
||||
return getType() == storedCredentialType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash the password for password history check purpose.
|
||||
*/
|
||||
public String passwordToHistoryHash(byte[] salt, byte[] hashFactor) {
|
||||
return passwordToHistoryHash(mCredential, salt, hashFactor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash the password for password history check purpose.
|
||||
*/
|
||||
public static String passwordToHistoryHash(
|
||||
byte[] passwordToHash, byte[] salt, byte[] hashFactor) {
|
||||
if (passwordToHash == null || passwordToHash.length == 0
|
||||
|| hashFactor == null || salt == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
||||
sha256.update(hashFactor);
|
||||
byte[] saltedPassword = Arrays.copyOf(passwordToHash, passwordToHash.length
|
||||
+ salt.length);
|
||||
System.arraycopy(salt, 0, saltedPassword, passwordToHash.length, salt.length);
|
||||
sha256.update(saltedPassword);
|
||||
Arrays.fill(saltedPassword, (byte) 0);
|
||||
return new String(HexEncoding.encode(sha256.digest()));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError("Missing digest algorithm: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return the hash of the pattern in a byte array.
|
||||
*/
|
||||
public String legacyPasswordToHash(byte[] salt) {
|
||||
return legacyPasswordToHash(mCredential, 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.
|
||||
*
|
||||
* @param password the gesture pattern.
|
||||
*
|
||||
* @return the hash of the pattern in a byte array.
|
||||
*/
|
||||
public static String legacyPasswordToHash(byte[] password, byte[] salt) {
|
||||
if (password == null || password.length == 0 || salt == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Previously the password was passed as a String with the following code:
|
||||
// byte[] saltedPassword = (password + salt).getBytes();
|
||||
// The code below creates the identical digest preimage using byte arrays:
|
||||
byte[] saltedPassword = Arrays.copyOf(password, password.length + salt.length);
|
||||
System.arraycopy(salt, 0, saltedPassword, password.length, salt.length);
|
||||
byte[] sha1 = MessageDigest.getInstance("SHA-1").digest(saltedPassword);
|
||||
byte[] md5 = MessageDigest.getInstance("MD5").digest(saltedPassword);
|
||||
|
||||
byte[] combined = new byte[sha1.length + md5.length];
|
||||
System.arraycopy(sha1, 0, combined, 0, sha1.length);
|
||||
System.arraycopy(md5, 0, combined, sha1.length, md5.length);
|
||||
|
||||
final char[] hexEncoded = HexEncoding.encode(combined);
|
||||
Arrays.fill(saltedPassword, (byte) 0);
|
||||
return new String(hexEncoded);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError("Missing digest algorithm: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(mType);
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package com.android.internal.widget;
|
||||
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -175,6 +177,81 @@ public class LockscreenCredentialTest extends AndroidTestCase {
|
||||
assertEquals(credential, credential.duplicate());
|
||||
}
|
||||
|
||||
public void testPasswordToHistoryHash() {
|
||||
String password = "1234";
|
||||
LockscreenCredential credential = LockscreenCredential.createPassword(password);
|
||||
String hashFactor = "6637D20C0798382D9F1304861C81DE222BC6CB7183623C67DA99B115A7AF702D";
|
||||
String salt = "6d5331dd120077a0";
|
||||
String expectedHash = "BCFB17409F2CD0A00D8627F76D080FB547B0B6A30CB7A375A34720D2312EDAC7";
|
||||
|
||||
assertThat(
|
||||
credential.passwordToHistoryHash(salt.getBytes(), hashFactor.getBytes()))
|
||||
.isEqualTo(expectedHash);
|
||||
assertThat(
|
||||
LockscreenCredential.passwordToHistoryHash(
|
||||
password.getBytes(), salt.getBytes(), hashFactor.getBytes()))
|
||||
.isEqualTo(expectedHash);
|
||||
}
|
||||
|
||||
public void testPasswordToHistoryHashInvalidInput() {
|
||||
String password = "1234";
|
||||
LockscreenCredential credential = LockscreenCredential.createPassword(password);
|
||||
String hashFactor = "6637D20C0798382D9F1304861C81DE222BC6CB7183623C67DA99B115A7AF702D";
|
||||
String salt = "6d5331dd120077a0";
|
||||
|
||||
assertThat(
|
||||
credential.passwordToHistoryHash(/* salt= */ null, hashFactor.getBytes()))
|
||||
.isNull();
|
||||
assertThat(
|
||||
LockscreenCredential.passwordToHistoryHash(
|
||||
password.getBytes(), /* salt= */ null, hashFactor.getBytes()))
|
||||
.isNull();
|
||||
|
||||
assertThat(
|
||||
credential.passwordToHistoryHash(salt.getBytes(), /* hashFactor= */ null))
|
||||
.isNull();
|
||||
assertThat(
|
||||
LockscreenCredential.passwordToHistoryHash(
|
||||
password.getBytes(), salt.getBytes(), /* hashFactor= */ null))
|
||||
.isNull();
|
||||
|
||||
assertThat(
|
||||
LockscreenCredential.passwordToHistoryHash(
|
||||
/* password= */ null, salt.getBytes(), hashFactor.getBytes()))
|
||||
.isNull();
|
||||
}
|
||||
|
||||
public void testLegacyPasswordToHash() {
|
||||
String password = "1234";
|
||||
LockscreenCredential credential = LockscreenCredential.createPassword(password);
|
||||
String salt = "6d5331dd120077a0";
|
||||
String expectedHash =
|
||||
"2DD04348ADBF8F4CABD7F722DC2E2887FAD4B6020A0C3E02C831E09946F0554FDC13B155";
|
||||
|
||||
assertThat(
|
||||
credential.legacyPasswordToHash(salt.getBytes()))
|
||||
.isEqualTo(expectedHash);
|
||||
assertThat(
|
||||
LockscreenCredential.legacyPasswordToHash(
|
||||
password.getBytes(), salt.getBytes()))
|
||||
.isEqualTo(expectedHash);
|
||||
}
|
||||
|
||||
public void testLegacyPasswordToHashInvalidInput() {
|
||||
String password = "1234";
|
||||
LockscreenCredential credential = LockscreenCredential.createPassword(password);
|
||||
String salt = "6d5331dd120077a0";
|
||||
|
||||
assertThat(credential.legacyPasswordToHash(/* salt= */ null)).isNull();
|
||||
assertThat(LockscreenCredential.legacyPasswordToHash(
|
||||
password.getBytes(), /* salt= */ null)).isNull();
|
||||
|
||||
assertThat(
|
||||
LockscreenCredential.legacyPasswordToHash(
|
||||
/* password= */ null, salt.getBytes()))
|
||||
.isNull();
|
||||
}
|
||||
|
||||
private LockscreenCredential createPattern(String patternString) {
|
||||
return LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(
|
||||
patternString.getBytes()));
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.server.locksettings;
|
||||
import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE;
|
||||
import static android.Manifest.permission.MANAGE_BIOMETRIC;
|
||||
import static android.Manifest.permission.READ_CONTACTS;
|
||||
import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS;
|
||||
import static android.content.Context.KEYGUARD_SERVICE;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static android.os.UserHandle.USER_ALL;
|
||||
@@ -160,6 +161,7 @@ import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -1074,6 +1076,10 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
mContext.enforceCallingOrSelfPermission(BIOMETRIC_PERMISSION, "LockSettingsBiometric");
|
||||
}
|
||||
|
||||
private boolean hasPermission(String permission) {
|
||||
return mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSecureLockScreen() {
|
||||
return mHasSecureLockScreen;
|
||||
@@ -1567,42 +1573,52 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
throw new UnsupportedOperationException(
|
||||
"This operation requires secure lock screen feature");
|
||||
}
|
||||
checkWritePermission(userId);
|
||||
enforceFrpResolved();
|
||||
if (!hasPermission(PERMISSION) && !hasPermission(SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS)) {
|
||||
throw new SecurityException(
|
||||
"setLockCredential requires SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS or "
|
||||
+ PERMISSION);
|
||||
}
|
||||
|
||||
// When changing credential for profiles with unified challenge, some callers
|
||||
// will pass in empty credential while others will pass in the credential of
|
||||
// the parent user. setLockCredentialInternal() handles the formal case (empty
|
||||
// credential) correctly but not the latter. As a stopgap fix, convert the latter
|
||||
// case to the formal. The long-term fix would be fixing LSS such that it should
|
||||
// accept only the parent user credential on its public API interfaces, swap it
|
||||
// with the profile's random credential at that API boundary (i.e. here) and make
|
||||
// sure LSS internally does not special case profile with unififed challenge: b/80170828.
|
||||
if (!savedCredential.isNone() && isManagedProfileWithUnifiedLock(userId)) {
|
||||
// Verify the parent credential again, to make sure we have a fresh enough
|
||||
// auth token such that getDecryptedPasswordForTiedProfile() inside
|
||||
// setLockCredentialInternal() can function correctly.
|
||||
verifyCredential(savedCredential, mUserManager.getProfileParent(userId).id,
|
||||
0 /* flags */);
|
||||
savedCredential.zeroize();
|
||||
savedCredential = LockscreenCredential.createNone();
|
||||
}
|
||||
synchronized (mSeparateChallengeLock) {
|
||||
if (!setLockCredentialInternal(credential, savedCredential,
|
||||
userId, /* isLockTiedToParent= */ false)) {
|
||||
scheduleGc();
|
||||
return false;
|
||||
long identity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
enforceFrpResolved();
|
||||
// When changing credential for profiles with unified challenge, some callers
|
||||
// will pass in empty credential while others will pass in the credential of
|
||||
// the parent user. setLockCredentialInternal() handles the formal case (empty
|
||||
// credential) correctly but not the latter. As a stopgap fix, convert the latter
|
||||
// case to the formal. The long-term fix would be fixing LSS such that it should
|
||||
// accept only the parent user credential on its public API interfaces, swap it
|
||||
// with the profile's random credential at that API boundary (i.e. here) and make
|
||||
// sure LSS internally does not special case profile with unififed challenge: b/80170828
|
||||
if (!savedCredential.isNone() && isManagedProfileWithUnifiedLock(userId)) {
|
||||
// Verify the parent credential again, to make sure we have a fresh enough
|
||||
// auth token such that getDecryptedPasswordForTiedProfile() inside
|
||||
// setLockCredentialInternal() can function correctly.
|
||||
verifyCredential(savedCredential, mUserManager.getProfileParent(userId).id,
|
||||
0 /* flags */);
|
||||
savedCredential.zeroize();
|
||||
savedCredential = LockscreenCredential.createNone();
|
||||
}
|
||||
setSeparateProfileChallengeEnabledLocked(userId, true, /* unused */ null);
|
||||
notifyPasswordChanged(userId);
|
||||
synchronized (mSeparateChallengeLock) {
|
||||
if (!setLockCredentialInternal(credential, savedCredential,
|
||||
userId, /* isLockTiedToParent= */ false)) {
|
||||
scheduleGc();
|
||||
return false;
|
||||
}
|
||||
setSeparateProfileChallengeEnabledLocked(userId, true, /* unused */ null);
|
||||
notifyPasswordChanged(userId);
|
||||
}
|
||||
if (mUserManager.getUserInfo(userId).isManagedProfile()) {
|
||||
// Make sure the profile doesn't get locked straight after setting work challenge.
|
||||
setDeviceUnlockedForUser(userId);
|
||||
}
|
||||
notifySeparateProfileChallengeChanged(userId);
|
||||
onPostPasswordChanged(credential, userId);
|
||||
scheduleGc();
|
||||
return true;
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identity);
|
||||
}
|
||||
if (mUserManager.getUserInfo(userId).isManagedProfile()) {
|
||||
// Make sure the profile doesn't get locked straight after setting work challenge.
|
||||
setDeviceUnlockedForUser(userId);
|
||||
}
|
||||
notifySeparateProfileChallengeChanged(userId);
|
||||
scheduleGc();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1703,10 +1719,157 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onPostPasswordChanged(LockscreenCredential newCredential, int userHandle) {
|
||||
updateEncryptionPasswordIfNeeded(newCredential, userHandle);
|
||||
if (newCredential.isPattern()) {
|
||||
setBoolean(LockPatternUtils.PATTERN_EVER_CHOSEN_KEY, true, userHandle);
|
||||
}
|
||||
updatePasswordHistory(newCredential, userHandle);
|
||||
mContext.getSystemService(TrustManager.class).reportEnabledTrustAgentsChanged(userHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update device encryption password if calling user is USER_SYSTEM and device supports
|
||||
* encryption.
|
||||
*/
|
||||
private void updateEncryptionPasswordIfNeeded(LockscreenCredential credential, int userHandle) {
|
||||
// Update the device encryption password.
|
||||
if (userHandle != UserHandle.USER_SYSTEM || !isDeviceEncryptionEnabled()) {
|
||||
return;
|
||||
}
|
||||
if (!shouldEncryptWithCredentials()) {
|
||||
updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
|
||||
return;
|
||||
}
|
||||
if (credential.isNone()) {
|
||||
// Set the encryption password to default.
|
||||
setCredentialRequiredToDecrypt(false);
|
||||
}
|
||||
updateEncryptionPassword(credential.getStorageCryptType(), credential.getCredential());
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the hash of the *current* password in the password history list, if device policy
|
||||
* enforces password history requirement.
|
||||
*/
|
||||
private void updatePasswordHistory(LockscreenCredential password, int userHandle) {
|
||||
if (password.isNone()) {
|
||||
return;
|
||||
}
|
||||
if (password.isPattern()) {
|
||||
// Do not keep track of historical patterns
|
||||
return;
|
||||
}
|
||||
// Add the password to the password history. We assume all
|
||||
// password hashes have the same length for simplicity of implementation.
|
||||
String passwordHistory = getString(
|
||||
LockPatternUtils.PASSWORD_HISTORY_KEY, /* defaultValue= */ null, userHandle);
|
||||
if (passwordHistory == null) {
|
||||
passwordHistory = "";
|
||||
}
|
||||
int passwordHistoryLength = getRequestedPasswordHistoryLength(userHandle);
|
||||
if (passwordHistoryLength == 0) {
|
||||
passwordHistory = "";
|
||||
} else {
|
||||
final byte[] hashFactor = getHashFactor(password, userHandle);
|
||||
final byte[] salt = getSalt(userHandle).getBytes();
|
||||
String hash = password.passwordToHistoryHash(hashFactor, salt);
|
||||
if (hash == null) {
|
||||
Slog.e(TAG, "Compute new style password hash failed, fallback to legacy style");
|
||||
hash = password.legacyPasswordToHash(salt);
|
||||
}
|
||||
if (TextUtils.isEmpty(passwordHistory)) {
|
||||
passwordHistory = hash;
|
||||
} else {
|
||||
String[] history = passwordHistory.split(
|
||||
LockPatternUtils.PASSWORD_HISTORY_DELIMITER);
|
||||
StringJoiner joiner = new StringJoiner(LockPatternUtils.PASSWORD_HISTORY_DELIMITER);
|
||||
joiner.add(hash);
|
||||
for (int i = 0; i < passwordHistoryLength - 1 && i < history.length; i++) {
|
||||
joiner.add(history[i]);
|
||||
}
|
||||
passwordHistory = joiner.toString();
|
||||
}
|
||||
}
|
||||
setString(LockPatternUtils.PASSWORD_HISTORY_KEY, passwordHistory, userHandle);
|
||||
}
|
||||
|
||||
private String getSalt(int userId) {
|
||||
long salt = getLong(LockPatternUtils.LOCK_PASSWORD_SALT_KEY, 0, userId);
|
||||
if (salt == 0) {
|
||||
try {
|
||||
salt = SecureRandom.getInstance("SHA1PRNG").nextLong();
|
||||
setLong(LockPatternUtils.LOCK_PASSWORD_SALT_KEY, salt, userId);
|
||||
Slog.v(TAG, "Initialized lock password salt for user: " + userId);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// Throw an exception rather than storing a password we'll never be able to recover
|
||||
throw new IllegalStateException("Couldn't get SecureRandom number", e);
|
||||
}
|
||||
}
|
||||
return Long.toHexString(salt);
|
||||
}
|
||||
|
||||
private int getRequestedPasswordHistoryLength(int userId) {
|
||||
return mInjector.getDevicePolicyManager().getPasswordHistoryLength(null, userId);
|
||||
}
|
||||
|
||||
private static boolean isDeviceEncryptionEnabled() {
|
||||
return StorageManager.isEncrypted();
|
||||
}
|
||||
|
||||
private boolean shouldEncryptWithCredentials() {
|
||||
return isCredentialRequiredToDecrypt() && !isDoNotAskCredentialsOnBootSet();
|
||||
}
|
||||
|
||||
private boolean isDoNotAskCredentialsOnBootSet() {
|
||||
return mInjector.getDevicePolicyManager().getDoNotAskCredentialsOnBoot();
|
||||
}
|
||||
|
||||
private boolean isCredentialRequiredToDecrypt() {
|
||||
final int value = Settings.Global.getInt(mContext.getContentResolver(),
|
||||
Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT, -1);
|
||||
return value != 0;
|
||||
}
|
||||
|
||||
private VerifyCredentialResponse convertResponse(GateKeeperResponse gateKeeperResponse) {
|
||||
return VerifyCredentialResponse.fromGateKeeperResponse(gateKeeperResponse);
|
||||
}
|
||||
|
||||
private void setCredentialRequiredToDecrypt(boolean required) {
|
||||
if (isDeviceEncryptionEnabled()) {
|
||||
Settings.Global.putInt(mContext.getContentResolver(),
|
||||
Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT, required ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
/** Update the encryption password if it is enabled **/
|
||||
@Override
|
||||
public void updateEncryptionPassword(final int type, final byte[] password) {
|
||||
if (!hasSecureLockScreen() && password != null && password.length != 0) {
|
||||
throw new UnsupportedOperationException(
|
||||
"This operation requires the lock screen feature.");
|
||||
}
|
||||
if (!isDeviceEncryptionEnabled()) {
|
||||
return;
|
||||
}
|
||||
final IBinder service = ServiceManager.getService("mount");
|
||||
if (service == null) {
|
||||
Slog.e(TAG, "Could not find the mount service to update the encryption password");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(b/120484642): This is a location where we still use a String for vold
|
||||
String passwordString = password != null ? new String(password) : null;
|
||||
mHandler.post(() -> {
|
||||
IStorageManager storageManager = mInjector.getStorageManager();
|
||||
try {
|
||||
storageManager.changeEncryptionPassword(type, passwordString);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Error changing encryption password", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@VisibleForTesting /** Note: this method is overridden in unit tests */
|
||||
protected void tieProfileLockToParent(int userId, LockscreenCredential password) {
|
||||
if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
|
||||
@@ -1952,10 +2115,16 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
@Nullable
|
||||
public VerifyCredentialResponse verifyCredential(LockscreenCredential credential,
|
||||
int userId, int flags) {
|
||||
checkPasswordReadPermission();
|
||||
if (!hasPermission(PERMISSION) && !hasPermission(SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS)) {
|
||||
throw new SecurityException(
|
||||
"verifyCredential requires SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS or "
|
||||
+ PERMISSION);
|
||||
}
|
||||
final long identity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
return doVerifyCredential(credential, userId, null /* progressCallback */, flags);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identity);
|
||||
scheduleGc();
|
||||
}
|
||||
}
|
||||
@@ -3436,8 +3605,12 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
throw new UnsupportedOperationException(
|
||||
"This operation requires secure lock screen feature.");
|
||||
}
|
||||
return LockSettingsService.this.setLockCredentialWithToken(
|
||||
credential, tokenHandle, token, userId);
|
||||
if (!LockSettingsService.this.setLockCredentialWithToken(
|
||||
credential, tokenHandle, token, userId)) {
|
||||
return false;
|
||||
}
|
||||
onPostPasswordChanged(credential, userId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user