Merge "Add set/verify lock credentials testAPIs in KeygyardManager" into sc-dev

This commit is contained in:
Kholoud Mohamed
2021-04-08 00:38:36 +00:00
committed by Android (Google) Code Review
7 changed files with 478 additions and 251 deletions

View File

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

View File

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

View File

@@ -95,4 +95,5 @@ interface ILockSettings {
boolean hasSecureLockScreen();
boolean tryUnlockWithCachedUnifiedChallenge(int userId);
void removeCachedUnifiedChallenge(int userId);
void updateEncryptionPassword(int type, in byte[] password);
}

View File

@@ -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);
}
/**

View File

@@ -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);

View File

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

View File

@@ -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