diff --git a/core/api/test-current.txt b/core/api/test-current.txt index a2634da67a554..9931bf9d78c23 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -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(); } diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 4326c2d855008..dc71a3237b0bc 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -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 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}. + *

If no password is currently set, {@code currentPassword} should be set to {@code null}. + *

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}. + *

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 pattern = + LockPatternUtils.byteArrayToPattern(password); + return LockscreenCredential.createPattern(pattern); + default: + throw new IllegalArgumentException("Unknown lock type " + lockType); + } + } } diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl index 654b46164dcf9..d16d9c6194038 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -95,4 +95,5 @@ interface ILockSettings { boolean hasSecureLockScreen(); boolean tryUnlockWithCachedUnifiedChallenge(int userId); void removeCachedUnifiedChallenge(int userId); + void updateEncryptionPassword(int type, in byte[] password); } diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index a161f18b2aab0..a0e50be934109 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -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() { - @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); } /** diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java index a488449db0199..361ba958f7593 100644 --- a/core/java/com/android/internal/widget/LockscreenCredential.java +++ b/core/java/com/android/internal/widget/LockscreenCredential.java @@ -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); diff --git a/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java b/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java index 05bab1c185dea..9d77d16b4a1df 100644 --- a/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java +++ b/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java @@ -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())); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 71a5d1ce972d2..6b28fbc9b86c2 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -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