Loading core/api/test-current.txt +5 −0 Original line number Diff line number Diff line Loading @@ -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(); } Loading core/java/android/app/KeyguardManager.java +93 −37 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); } return true; private boolean hasPermission(String permission) { return PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( permission); } /** Loading Loading @@ -792,44 +795,97 @@ public class KeyguardManager { Log.e(TAG, "Password is not valid, rejecting call to setLock"); return false; } boolean success = false; boolean success; try { 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; } finally { Arrays.fill(password, (byte) 0); } 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")); lockPatternUtils.setLockCredential( LockscreenCredential.createPassword(passwordStr), /* savedPassword= */ LockscreenCredential.createNone(), userId); success = true; break; return LockscreenCredential.createPassword(passwordStr); case PIN: CharSequence pinStr = new String(password); lockPatternUtils.setLockCredential( LockscreenCredential.createPin(pinStr), /* savedPassword= */ LockscreenCredential.createNone(), userId); success = true; break; return LockscreenCredential.createPin(pinStr); case PATTERN: List<LockPatternView.Cell> pattern = LockPatternUtils.byteArrayToPattern(password); lockPatternUtils.setLockCredential( LockscreenCredential.createPattern(pattern), /* savedPassword= */ LockscreenCredential.createNone(), userId); pattern.clear(); success = true; break; return LockscreenCredential.createPattern(pattern); default: Log.e(TAG, "Unknown lock type, returning a failure"); throw new IllegalArgumentException("Unknown lock type " + lockType); } } catch (Exception e) { Log.e(TAG, "Save lock exception", e); success = false; } finally { Arrays.fill(password, (byte) 0); } return success; } } core/java/com/android/internal/widget/ILockSettings.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -95,4 +95,5 @@ interface ILockSettings { boolean hasSecureLockScreen(); boolean tryUnlockWithCachedUnifiedChallenge(int userId); void removeCachedUnifiedChallenge(int userId); void updateEncryptionPassword(int type, in byte[] password); } core/java/com/android/internal/widget/LockPatternUtils.java +13 −178 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading Loading @@ -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; Loading Loading @@ -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)) { Loading Loading @@ -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; Loading Loading @@ -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 Loading @@ -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"); } } /** Loading Loading @@ -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()); } /** Loading Loading @@ -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."); Loading Loading @@ -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); } /** Loading core/java/com/android/internal/widget/LockscreenCredential.java +80 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading
core/api/test-current.txt +5 −0 Original line number Diff line number Diff line Loading @@ -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(); } Loading
core/java/android/app/KeyguardManager.java +93 −37 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); } return true; private boolean hasPermission(String permission) { return PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( permission); } /** Loading Loading @@ -792,44 +795,97 @@ public class KeyguardManager { Log.e(TAG, "Password is not valid, rejecting call to setLock"); return false; } boolean success = false; boolean success; try { 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; } finally { Arrays.fill(password, (byte) 0); } 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")); lockPatternUtils.setLockCredential( LockscreenCredential.createPassword(passwordStr), /* savedPassword= */ LockscreenCredential.createNone(), userId); success = true; break; return LockscreenCredential.createPassword(passwordStr); case PIN: CharSequence pinStr = new String(password); lockPatternUtils.setLockCredential( LockscreenCredential.createPin(pinStr), /* savedPassword= */ LockscreenCredential.createNone(), userId); success = true; break; return LockscreenCredential.createPin(pinStr); case PATTERN: List<LockPatternView.Cell> pattern = LockPatternUtils.byteArrayToPattern(password); lockPatternUtils.setLockCredential( LockscreenCredential.createPattern(pattern), /* savedPassword= */ LockscreenCredential.createNone(), userId); pattern.clear(); success = true; break; return LockscreenCredential.createPattern(pattern); default: Log.e(TAG, "Unknown lock type, returning a failure"); throw new IllegalArgumentException("Unknown lock type " + lockType); } } catch (Exception e) { Log.e(TAG, "Save lock exception", e); success = false; } finally { Arrays.fill(password, (byte) 0); } return success; } }
core/java/com/android/internal/widget/ILockSettings.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -95,4 +95,5 @@ interface ILockSettings { boolean hasSecureLockScreen(); boolean tryUnlockWithCachedUnifiedChallenge(int userId); void removeCachedUnifiedChallenge(int userId); void updateEncryptionPassword(int type, in byte[] password); }
core/java/com/android/internal/widget/LockPatternUtils.java +13 −178 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading Loading @@ -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; Loading Loading @@ -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)) { Loading Loading @@ -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; Loading Loading @@ -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 Loading @@ -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"); } } /** Loading Loading @@ -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()); } /** Loading Loading @@ -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."); Loading Loading @@ -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); } /** Loading
core/java/com/android/internal/widget/LockscreenCredential.java +80 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading