From 1de89b3bec2f296763f3ecde9a36ecbca2110f3d Mon Sep 17 00:00:00 2001 From: Rubin Xu Date: Wed, 30 Nov 2016 20:03:13 +0000 Subject: [PATCH] Refactor LockSettingsService to unify the handling of pattern and password Also fix LockSettingsStorageTests. More unit tests on LockSettingsService to be added in the next CL. Bug: 33126408 Test: runtest frameworks-services -c com.android.server.LockSettingsStorageTests Change-Id: I0f143b26fed1d5ae122fba3b57bd39c7793ad8d9 --- .../internal/widget/ILockSettings.aidl | 12 +- .../internal/widget/LockPatternUtils.java | 127 +++-- .../android/server/LockSettingsService.java | 458 ++++++------------ .../android/server/LockSettingsStorage.java | 157 +++--- .../server/LockSettingsStorageTests.java | 194 ++++---- 5 files changed, 408 insertions(+), 540 deletions(-) diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl index 9fa558e89ae79..b380b13402327 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -28,16 +28,12 @@ interface ILockSettings { boolean getBoolean(in String key, in boolean defaultValue, in int userId); long getLong(in String key, in long defaultValue, in int userId); String getString(in String key, in String defaultValue, in int userId); - void setLockPattern(in String pattern, in String savedPattern, int userId); + void setLockCredential(in String credential, int type, in String savedCredential, int userId); void resetKeyStore(int userId); - VerifyCredentialResponse checkPattern(in String pattern, int userId, + VerifyCredentialResponse checkCredential(in String credential, int type, int userId, in ICheckCredentialProgressCallback progressCallback); - VerifyCredentialResponse verifyPattern(in String pattern, long challenge, int userId); - void setLockPassword(in String password, in String savedPassword, int userId); - VerifyCredentialResponse checkPassword(in String password, int userId, - in ICheckCredentialProgressCallback progressCallback); - VerifyCredentialResponse verifyPassword(in String password, long challenge, int userId); - VerifyCredentialResponse verifyTiedProfileChallenge(String password, boolean isPattern, long challenge, int userId); + VerifyCredentialResponse verifyCredential(in String credential, int type, long challenge, int userId); + VerifyCredentialResponse verifyTiedProfileChallenge(String credential, int type, long challenge, int userId); boolean checkVoldPassword(int userId); boolean havePattern(int userId); boolean havePassword(int userId); diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 2a8077cf3f701..a43f3a72782ca 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -45,6 +45,8 @@ import android.util.SparseIntArray; import com.google.android.collect.Lists; +import libcore.util.HexEncoding; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.charset.StandardCharsets; @@ -55,8 +57,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import libcore.util.HexEncoding; - /** * Utilities for the lock pattern and its settings. */ @@ -66,7 +66,7 @@ public class LockPatternUtils { private static final boolean DEBUG = false; /** - * The key to identify when the lock pattern enabled flag is being acccessed for legacy reasons. + * The key to identify when the lock pattern enabled flag is being accessed for legacy reasons. */ public static final String LEGACY_LOCK_PATTERN_ENABLED = "legacy_lock_pattern_enabled"; @@ -105,6 +105,12 @@ public class LockPatternUtils { */ public static final int MIN_PATTERN_REGISTER_FAIL = MIN_LOCK_PATTERN_SIZE; + public static final int CREDENTIAL_TYPE_NONE = -1; + + public static final int CREDENTIAL_TYPE_PATTERN = 1; + + public static final int CREDENTIAL_TYPE_PASSWORD = 2; + @Deprecated public final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently"; public final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline"; @@ -301,6 +307,42 @@ public class LockPatternUtils { null /* componentName */, userId); } + private byte[] verifyCredential(String credential, int type, long challenge, int userId) + throws RequestThrottledException { + try { + VerifyCredentialResponse response = getLockSettings().verifyCredential(credential, + type, challenge, userId); + if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) { + return response.getPayload(); + } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) { + throw new RequestThrottledException(response.getTimeout()); + } else { + return null; + } + } catch (RemoteException re) { + return null; + } + } + + private boolean checkCredential(String credential, int type, int userId, + @Nullable CheckCredentialProgressCallback progressCallback) + throws RequestThrottledException { + try { + VerifyCredentialResponse response = getLockSettings().checkCredential(credential, type, + userId, wrapCallback(progressCallback)); + + if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) { + return true; + } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) { + throw new RequestThrottledException(response.getTimeout()); + } else { + return false; + } + } catch (RemoteException re) { + return false; + } + } + /** * Check to see if a pattern matches the saved pattern. * If pattern matches, return an opaque attestation that the challenge @@ -313,24 +355,8 @@ public class LockPatternUtils { public byte[] verifyPattern(List pattern, long challenge, int userId) throws RequestThrottledException { throwIfCalledOnMainThread(); - try { - VerifyCredentialResponse response = - getLockSettings().verifyPattern(patternToString(pattern), challenge, userId); - if (response == null) { - // Shouldn't happen - return null; - } - - if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) { - return response.getPayload(); - } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) { - throw new RequestThrottledException(response.getTimeout()); - } else { - return null; - } - } catch (RemoteException re) { - return null; - } + return verifyCredential(patternToString(pattern), CREDENTIAL_TYPE_PATTERN, challenge, + userId); } /** @@ -354,21 +380,8 @@ public class LockPatternUtils { @Nullable CheckCredentialProgressCallback progressCallback) throws RequestThrottledException { throwIfCalledOnMainThread(); - try { - VerifyCredentialResponse response = - getLockSettings().checkPattern(patternToString(pattern), userId, - wrapCallback(progressCallback)); - - if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) { - return true; - } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) { - throw new RequestThrottledException(response.getTimeout()); - } else { - return false; - } - } catch (RemoteException re) { - return false; - } + return checkCredential(patternToString(pattern), CREDENTIAL_TYPE_PATTERN, userId, + progressCallback); } /** @@ -383,20 +396,7 @@ public class LockPatternUtils { public byte[] verifyPassword(String password, long challenge, int userId) throws RequestThrottledException { throwIfCalledOnMainThread(); - try { - VerifyCredentialResponse response = - getLockSettings().verifyPassword(password, challenge, userId); - - if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) { - return response.getPayload(); - } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) { - throw new RequestThrottledException(response.getTimeout()); - } else { - return null; - } - } catch (RemoteException re) { - return null; - } + return verifyCredential(password, CREDENTIAL_TYPE_PASSWORD, challenge, userId); } @@ -414,7 +414,8 @@ public class LockPatternUtils { throwIfCalledOnMainThread(); try { VerifyCredentialResponse response = - getLockSettings().verifyTiedProfileChallenge(password, isPattern, challenge, + getLockSettings().verifyTiedProfileChallenge(password, + isPattern ? CREDENTIAL_TYPE_PATTERN : CREDENTIAL_TYPE_PASSWORD, challenge, userId); if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) { @@ -449,19 +450,7 @@ public class LockPatternUtils { @Nullable CheckCredentialProgressCallback progressCallback) throws RequestThrottledException { throwIfCalledOnMainThread(); - try { - VerifyCredentialResponse response = - getLockSettings().checkPassword(password, userId, wrapCallback(progressCallback)); - if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) { - return true; - } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) { - throw new RequestThrottledException(response.getTimeout()); - } else { - return false; - } - } catch (RemoteException re) { - return false; - } + return checkCredential(password, CREDENTIAL_TYPE_PASSWORD, userId, progressCallback); } /** @@ -578,8 +567,7 @@ public class LockPatternUtils { setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userHandle); try { - getLockSettings().setLockPassword(null, null, userHandle); - getLockSettings().setLockPattern(null, null, userHandle); + getLockSettings().setLockCredential(null, CREDENTIAL_TYPE_NONE, null, userHandle); } catch (RemoteException e) { // well, we tried... } @@ -643,8 +631,8 @@ public class LockPatternUtils { } setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId); - getLockSettings().setLockPattern(patternToString(pattern), savedPattern, userId); - DevicePolicyManager dpm = getDevicePolicyManager(); + getLockSettings().setLockCredential(patternToString(pattern), CREDENTIAL_TYPE_PATTERN, + savedPattern, userId); // Update the device encryption password. if (userId == UserHandle.USER_SYSTEM @@ -772,7 +760,8 @@ public class LockPatternUtils { final int computedQuality = PasswordMetrics.computeForPassword(password).quality; setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle); - getLockSettings().setLockPassword(password, savedPassword, userHandle); + getLockSettings().setLockCredential(password, CREDENTIAL_TYPE_PASSWORD, savedPassword, + userHandle); // Update the device encryption password. if (userHandle == UserHandle.USER_SYSTEM diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java index a5552b8365641..51503c0ac91d7 100644 --- a/services/core/java/com/android/server/LockSettingsService.java +++ b/services/core/java/com/android/server/LockSettingsService.java @@ -16,6 +16,11 @@ package com.android.server; +import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE; +import static android.Manifest.permission.READ_CONTACTS; +import static android.content.Context.KEYGUARD_SERVICE; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; + import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.KeyguardManager; @@ -35,31 +40,23 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.content.res.Resources; - -import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE; -import static android.content.Context.KEYGUARD_SERVICE; -import static android.content.Context.USER_SERVICE; -import static android.Manifest.permission.READ_CONTACTS; -import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; - import android.database.sqlite.SQLiteDatabase; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.IProgressListener; -import android.os.Parcel; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; -import android.os.ShellCallback; -import android.os.storage.IStorageManager; -import android.os.storage.StorageManager; import android.os.ServiceManager; +import android.os.ShellCallback; import android.os.StrictMode; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.os.storage.IStorageManager; +import android.os.storage.StorageManager; import android.provider.Settings; import android.provider.Settings.Secure; import android.provider.Settings.SettingNotFoundException; @@ -109,20 +106,21 @@ import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; /** - * Keeps the lock pattern/password data and related settings for each user. - * Used by LockPatternUtils. Needs to be a service because Settings app also needs - * to be able to save lockscreen information for secondary users. + * Keeps the lock pattern/password data and related settings for each user. Used by + * LockPatternUtils. Needs to be a service because Settings app also needs to be able to save + * lockscreen information for secondary users. + * * @hide */ public class LockSettingsService extends ILockSettings.Stub { private static final String TAG = "LockSettingsService"; private static final String PERMISSION = ACCESS_KEYGUARD_SECURE_STORAGE; - private static final Intent ACTION_NULL; // hack to ensure notification shows the bouncer private static final int FBE_ENCRYPTED_NOTIFICATION = 0; private static final boolean DEBUG = false; private static final int PROFILE_KEY_IV_SIZE = 12; private static final String SEPARATE_PROFILE_CHALLENGE_KEY = "lockscreen.profilechallenge"; + private final Object mSeparateChallengeLock = new Object(); private final Context mContext; @@ -142,21 +140,9 @@ public class LockSettingsService extends ILockSettings.Stub { /** * The UIDs that are used for system credential storage in keystore. */ - private static final int[] SYSTEM_CREDENTIAL_UIDS = {Process.WIFI_UID, Process.VPN_UID, - Process.ROOT_UID, Process.SYSTEM_UID}; - - static { - // Just launch the home screen, which happens anyway - ACTION_NULL = new Intent(Intent.ACTION_MAIN); - ACTION_NULL.addCategory(Intent.CATEGORY_HOME); - } - - private interface CredentialUtil { - void setCredential(String credential, String savedCredential, int userId) - throws RemoteException; - byte[] toHash(String credential, int userId); - String adjustForKeystore(String credential); - } + private static final int[] SYSTEM_CREDENTIAL_UIDS = { + Process.WIFI_UID, Process.VPN_UID, + Process.ROOT_UID, Process.SYSTEM_UID }; // This class manages life cycle events for encrypted users on File Based Encryption (FBE) // devices. The most basic of these is to show/hide notifications about missing features until @@ -258,7 +244,8 @@ public class LockSettingsService extends ILockSettings.Stub { try { randomLockSeed = SecureRandom.getInstance("SHA1PRNG").generateSeed(40); String newPassword = String.valueOf(HexEncoding.encode(randomLockSeed)); - setLockPasswordInternal(newPassword, managedUserPassword, managedUserId); + setLockCredentialInternal(newPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, + managedUserPassword, managedUserId); // We store a private credential for the managed user that's unlocked by the primary // account holder's credential. As such, the user will never be prompted to enter this // password directly, so we always store a password. @@ -306,8 +293,8 @@ public class LockSettingsService extends ILockSettings.Stub { } /** - * If the account is credential-encrypted, show notification requesting the user to unlock - * the device. + * If the account is credential-encrypted, show notification requesting the user to unlock the + * device. */ private void maybeShowEncryptionNotificationForUser(@UserIdInt int userId) { final UserInfo user = mUserManager.getUserInfo(userId); @@ -342,19 +329,21 @@ public class LockSettingsService extends ILockSettings.Stub { com.android.internal.R.string.profile_encrypted_detail); final KeyguardManager km = (KeyguardManager) mContext.getSystemService(KEYGUARD_SERVICE); - final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null, user.getIdentifier()); + final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null, + user.getIdentifier()); if (unlockIntent == null) { return; } - unlockIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + unlockIntent.setFlags( + Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); PendingIntent intent = PendingIntent.getActivity(mContext, 0, unlockIntent, PendingIntent.FLAG_UPDATE_CURRENT); showEncryptionNotification(user, title, message, detail, intent); } - private void showEncryptionNotification(UserHandle user, CharSequence title, CharSequence message, - CharSequence detail, PendingIntent intent) { + private void showEncryptionNotification(UserHandle user, CharSequence title, + CharSequence message, CharSequence detail, PendingIntent intent) { if (DEBUG) Slog.v(TAG, "showing encryption notification, user: " + user.getIdentifier()); // Suppress all notifications on non-FBE devices for now @@ -365,7 +354,7 @@ public class LockSettingsService extends ILockSettings.Stub { .setWhen(0) .setOngoing(true) .setTicker(title) - .setDefaults(0) // please be quiet + .setDefaults(0) // please be quiet .setPriority(Notification.PRIORITY_MAX) .setColor(mContext.getColor( com.android.internal.R.color.system_notification_accent_color)) @@ -379,7 +368,7 @@ public class LockSettingsService extends ILockSettings.Stub { } private void hideEncryptionNotification(UserHandle userHandle) { - if (DEBUG) Slog.v(TAG, "hide encryption notification, user: "+ userHandle.getIdentifier()); + if (DEBUG) Slog.v(TAG, "hide encryption notification, user: " + userHandle.getIdentifier()); mNotificationManager.cancelAsUser(null, FBE_ENCRYPTED_NOTIFICATION, userHandle); } @@ -491,7 +480,7 @@ public class LockSettingsService extends ILockSettings.Stub { Settings.Secure.putStringForUser(cr, OWNER_INFO, "", userId); } - // Migrate owner info enabled. Note there was a bug where older platforms only + // Migrate owner info enabled. Note there was a bug where older platforms only // stored this value if the checkbox was toggled at least once. The code detects // this case by handling the exception. final String OWNER_INFO_ENABLED = Secure.LOCK_SCREEN_OWNER_INFO_ENABLED; @@ -788,8 +777,9 @@ public class LockSettingsService extends ILockSettings.Stub { private void unlockChildProfile(int profileHandle) throws RemoteException { try { - doVerifyPassword(getDecryptedPasswordForTiedProfile(profileHandle), false, - 0 /* no challenge */, profileHandle, null /* progressCallback */); + doVerifyCredential(getDecryptedPasswordForTiedProfile(profileHandle), + LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, + false, 0 /* no challenge */, profileHandle, null /* progressCallback */); } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException | IllegalBlockSizeException @@ -852,35 +842,13 @@ public class LockSettingsService extends ILockSettings.Stub { } private byte[] getCurrentHandle(int userId) { - CredentialHash credential; - byte[] currentHandle; - - int currentHandleType = mStorage.getStoredCredentialType(userId); - switch (currentHandleType) { - case CredentialHash.TYPE_PATTERN: - credential = mStorage.readPatternHash(userId); - currentHandle = credential != null - ? credential.hash - : null; - break; - case CredentialHash.TYPE_PASSWORD: - credential = mStorage.readPasswordHash(userId); - currentHandle = credential != null - ? credential.hash - : null; - break; - case CredentialHash.TYPE_NONE: - default: - currentHandle = null; - break; - } + CredentialHash credential = mStorage.readCredentialHash(userId); // sanity check - if (currentHandleType != CredentialHash.TYPE_NONE && currentHandle == null) { - Slog.e(TAG, "Stored handle type [" + currentHandleType + "] but no handle available"); + if (credential.type != LockPatternUtils.CREDENTIAL_TYPE_NONE && credential.hash == null) { + Slog.e(TAG, "Stored handle type [" + credential.type + "] but no handle available"); } - - return currentHandle; + return credential.hash; } private void onUserLockChanged(int userId) throws RemoteException { @@ -902,7 +870,7 @@ public class LockSettingsService extends ILockSettings.Stub { } else { clearUserKeyProtection(managedUserId); getGateKeeperService().clearSecureUserId(managedUserId); - mStorage.writePatternHash(null, managedUserId); + mStorage.writeCredentialHash(CredentialHash.createEmptyHash(), managedUserId); setKeystorePassword(null, managedUserId); fixateNewestUserKeyAuth(managedUserId); mStorage.removeChildProfileLock(managedUserId); @@ -923,97 +891,37 @@ public class LockSettingsService extends ILockSettings.Stub { } // This method should be called by LockPatternUtil only, all internal methods in this class - // should call setLockPatternInternal. + // should call setLockCredentialInternal. @Override - public void setLockPattern(String pattern, String savedCredential, int userId) + public void setLockCredential(String credential, int type, String savedCredential, int userId) throws RemoteException { checkWritePermission(userId); synchronized (mSeparateChallengeLock) { - setLockPatternInternal(pattern, savedCredential, userId); + setLockCredentialInternal(credential, type, savedCredential, userId); setSeparateProfileChallengeEnabled(userId, true, null); notifyPasswordChanged(userId); } } - private void setLockPatternInternal(String pattern, String savedCredential, int userId) - throws RemoteException { + private void setLockCredentialInternal(String credential, int credentialType, + String savedCredential, int userId) throws RemoteException { byte[] currentHandle = getCurrentHandle(userId); - - if (pattern == null) { + if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) { + if (credential != null) { + Slog.wtf(TAG, "CredentialType is none, but credential is non-null."); + } clearUserKeyProtection(userId); getGateKeeperService().clearSecureUserId(userId); - mStorage.writePatternHash(null, userId); + mStorage.writeCredentialHash(CredentialHash.createEmptyHash(), userId); setKeystorePassword(null, userId); fixateNewestUserKeyAuth(userId); onUserLockChanged(userId); notifyActivePasswordMetricsAvailable(null, userId); return; } - - if (isManagedProfileWithUnifiedLock(userId)) { - // get credential from keystore when managed profile has unified lock - try { - savedCredential = getDecryptedPasswordForTiedProfile(userId); - } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException - | NoSuchAlgorithmException | NoSuchPaddingException - | InvalidAlgorithmParameterException | IllegalBlockSizeException - | BadPaddingException | CertificateException | IOException e) { - if (e instanceof FileNotFoundException) { - Slog.i(TAG, "Child profile key not found"); - } else { - Slog.e(TAG, "Failed to decrypt child profile key", e); - } - } - } else { - if (currentHandle == null) { - if (savedCredential != null) { - Slog.w(TAG, "Saved credential provided, but none stored"); - } - savedCredential = null; - } + if (credential == null) { + throw new RemoteException("Null credential with mismatched credential type"); } - - byte[] enrolledHandle = enrollCredential(currentHandle, savedCredential, pattern, userId); - if (enrolledHandle != null) { - CredentialHash willStore - = new CredentialHash(enrolledHandle, CredentialHash.VERSION_GATEKEEPER); - setUserKeyProtection(userId, pattern, - doVerifyPattern(pattern, willStore, true, 0, userId, null /* progressCallback */)); - mStorage.writePatternHash(enrolledHandle, userId); - fixateNewestUserKeyAuth(userId); - onUserLockChanged(userId); - } else { - throw new RemoteException("Failed to enroll pattern"); - } - } - - // This method should be called by LockPatternUtil only, all internal methods in this class - // should call setLockPasswordInternal. - @Override - public void setLockPassword(String password, String savedCredential, int userId) - throws RemoteException { - checkWritePermission(userId); - synchronized (mSeparateChallengeLock) { - setLockPasswordInternal(password, savedCredential, userId); - setSeparateProfileChallengeEnabled(userId, true, null); - notifyPasswordChanged(userId); - } - } - - private void setLockPasswordInternal(String password, String savedCredential, int userId) - throws RemoteException { - byte[] currentHandle = getCurrentHandle(userId); - if (password == null) { - clearUserKeyProtection(userId); - getGateKeeperService().clearSecureUserId(userId); - mStorage.writePasswordHash(null, userId); - setKeystorePassword(null, userId); - fixateNewestUserKeyAuth(userId); - onUserLockChanged(userId); - notifyActivePasswordMetricsAvailable(null, userId); - return; - } - if (isManagedProfileWithUnifiedLock(userId)) { // get credential from keystore when managed profile has unified lock try { @@ -1035,18 +943,21 @@ public class LockSettingsService extends ILockSettings.Stub { } } - byte[] enrolledHandle = enrollCredential(currentHandle, savedCredential, password, userId); + byte[] enrolledHandle = enrollCredential(currentHandle, savedCredential, credential, + userId); if (enrolledHandle != null) { - CredentialHash willStore - = new CredentialHash(enrolledHandle, CredentialHash.VERSION_GATEKEEPER); - setUserKeyProtection(userId, password, - doVerifyPassword(password, willStore, true, 0, userId, - null /* progressCallback */)); - mStorage.writePasswordHash(enrolledHandle, userId); + CredentialHash willStore = CredentialHash.create(enrolledHandle, credentialType); + mStorage.writeCredentialHash(willStore, userId); + // Refresh the auth token + setUserKeyProtection(userId, credential, + doVerifyCredential(credential, credentialType, true, 0, userId, + null /* progressCallback */)); fixateNewestUserKeyAuth(userId); onUserLockChanged(userId); } else { - throw new RemoteException("Failed to enroll password"); + throw new RemoteException("Failed to enroll " + + (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD ? "password" + : "pattern")); } } @@ -1143,7 +1054,7 @@ public class LockSettingsService extends ILockSettings.Stub { } if (vcr.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) { throw new RemoteException("Non-OK response verifying a credential we just set: " - + vcr.getResponseCode()); + + vcr.getResponseCode()); } byte[] token = vcr.getPayload(); if (token == null) { @@ -1240,90 +1151,61 @@ public class LockSettingsService extends ILockSettings.Stub { } @Override - public VerifyCredentialResponse checkPattern(String pattern, int userId, + public VerifyCredentialResponse checkCredential(String credential, int type, int userId, ICheckCredentialProgressCallback progressCallback) throws RemoteException { - return doVerifyPattern(pattern, false, 0, userId, progressCallback); + checkPasswordReadPermission(userId); + return doVerifyCredential(credential, type, false, 0, userId, progressCallback); } @Override - public VerifyCredentialResponse verifyPattern(String pattern, long challenge, int userId) - throws RemoteException { - return doVerifyPattern(pattern, true, challenge, userId, null /* progressCallback */); + public VerifyCredentialResponse verifyCredential(String credential, int type, long challenge, + int userId) throws RemoteException { + checkPasswordReadPermission(userId); + return doVerifyCredential(credential, type, true, challenge, userId, + null /* progressCallback */); } - private VerifyCredentialResponse doVerifyPattern(String pattern, boolean hasChallenge, - long challenge, int userId, ICheckCredentialProgressCallback progressCallback) - throws RemoteException { - checkPasswordReadPermission(userId); - if (TextUtils.isEmpty(pattern)) { - throw new IllegalArgumentException("Pattern can't be null or empty"); - } - CredentialHash storedHash = mStorage.readPatternHash(userId); - return doVerifyPattern(pattern, storedHash, hasChallenge, challenge, userId, - progressCallback); - } - - private VerifyCredentialResponse doVerifyPattern(String pattern, CredentialHash storedHash, + /** + * Verify user credential and unlock the user. Fix pattern bug by deprecating the old base zero + * format. + */ + private VerifyCredentialResponse doVerifyCredential(String credential, int credentialType, boolean hasChallenge, long challenge, int userId, ICheckCredentialProgressCallback progressCallback) throws RemoteException { + if (TextUtils.isEmpty(credential)) { + throw new IllegalArgumentException("Credential can't be null or empty"); + } - if (TextUtils.isEmpty(pattern)) { - throw new IllegalArgumentException("Pattern can't be null or empty"); - } - boolean shouldReEnrollBaseZero = storedHash != null && storedHash.isBaseZeroPattern; + CredentialHash storedHash = mStorage.readCredentialHash(userId); + if (storedHash.type != credentialType) { + Slog.wtf(TAG, "doVerifyCredential type mismatch with stored credential??" + + " stored: " + storedHash.type + " passed in: " + credentialType); + return VerifyCredentialResponse.ERROR; + } - String patternToVerify; - if (shouldReEnrollBaseZero) { - patternToVerify = LockPatternUtils.patternStringToBaseZero(pattern); - } else { - patternToVerify = pattern; - } + boolean shouldReEnrollBaseZero = storedHash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN + && storedHash.isBaseZeroPattern; - VerifyCredentialResponse response = verifyCredential(userId, storedHash, patternToVerify, - hasChallenge, challenge, - new CredentialUtil() { - @Override - public void setCredential(String pattern, String oldPattern, int userId) - throws RemoteException { - setLockPatternInternal(pattern, oldPattern, userId); - } + String credentialToVerify; + if (shouldReEnrollBaseZero) { + credentialToVerify = LockPatternUtils.patternStringToBaseZero(credential); + } else { + credentialToVerify = credential; + } - @Override - public byte[] toHash(String pattern, int userId) { - return LockPatternUtils.patternToHash( - LockPatternUtils.stringToPattern(pattern)); - } + VerifyCredentialResponse response = verifyCredential(userId, storedHash, credentialToVerify, + hasChallenge, challenge, progressCallback); - @Override - public String adjustForKeystore(String pattern) { - return LockPatternUtils.patternStringToBaseZero(pattern); - } - }, - progressCallback - ); + if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK + && shouldReEnrollBaseZero) { + setLockCredentialInternal(credential, storedHash.type, credentialToVerify, userId); + } - if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK - && shouldReEnrollBaseZero) { - setLockPatternInternal(pattern, patternToVerify, userId); - } - - return response; + return response; } @Override - public VerifyCredentialResponse checkPassword(String password, int userId, - ICheckCredentialProgressCallback progressCallback) throws RemoteException { - return doVerifyPassword(password, false, 0, userId, progressCallback); - } - - @Override - public VerifyCredentialResponse verifyPassword(String password, long challenge, int userId) - throws RemoteException { - return doVerifyPassword(password, true, challenge, userId, null /* progressCallback */); - } - - @Override - public VerifyCredentialResponse verifyTiedProfileChallenge(String password, boolean isPattern, + public VerifyCredentialResponse verifyTiedProfileChallenge(String credential, int type, long challenge, int userId) throws RemoteException { checkPasswordReadPermission(userId); if (!isManagedProfileWithUnifiedLock(userId)) { @@ -1331,11 +1213,13 @@ public class LockSettingsService extends ILockSettings.Stub { } final int parentProfileId = mUserManager.getProfileParent(userId).id; // Unlock parent by using parent's challenge - final VerifyCredentialResponse parentResponse = isPattern - ? doVerifyPattern(password, true, challenge, parentProfileId, - null /* progressCallback */) - : doVerifyPassword(password, true, challenge, parentProfileId, - null /* progressCallback */); + final VerifyCredentialResponse parentResponse = doVerifyCredential( + credential, + type, + true /* hasChallenge */, + challenge, + parentProfileId, + null /* progressCallback */); if (parentResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) { // Failed, just return parent's response return parentResponse; @@ -1343,7 +1227,9 @@ public class LockSettingsService extends ILockSettings.Stub { try { // Unlock work profile, and work profile with unified lock must use password only - return doVerifyPassword(getDecryptedPasswordForTiedProfile(userId), true, + return doVerifyCredential(getDecryptedPasswordForTiedProfile(userId), + LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, + true, challenge, userId, null /* progressCallback */); } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException @@ -1355,48 +1241,14 @@ public class LockSettingsService extends ILockSettings.Stub { } } - private VerifyCredentialResponse doVerifyPassword(String password, boolean hasChallenge, - long challenge, int userId, ICheckCredentialProgressCallback progressCallback) - throws RemoteException { - checkPasswordReadPermission(userId); - if (TextUtils.isEmpty(password)) { - throw new IllegalArgumentException("Password can't be null or empty"); - } - CredentialHash storedHash = mStorage.readPasswordHash(userId); - return doVerifyPassword(password, storedHash, hasChallenge, challenge, userId, - progressCallback); - } - - private VerifyCredentialResponse doVerifyPassword(String password, CredentialHash storedHash, - boolean hasChallenge, long challenge, int userId, - ICheckCredentialProgressCallback progressCallback) throws RemoteException { - if (TextUtils.isEmpty(password)) { - throw new IllegalArgumentException("Password can't be null or empty"); - } - return verifyCredential(userId, storedHash, password, hasChallenge, challenge, - new CredentialUtil() { - @Override - public void setCredential(String password, String oldPassword, int userId) - throws RemoteException { - setLockPasswordInternal(password, oldPassword, userId); - } - - @Override - public byte[] toHash(String password, int userId) { - return mLockPatternUtils.passwordToHash(password, userId); - } - - @Override - public String adjustForKeystore(String password) { - return password; - } - }, progressCallback); - } - + /** + * Lowest-level credential verification routine that talks to GateKeeper. If verification + * passes, unlock the corresponding user and keystore. Also handles the migration from legacy + * hash to GK. + */ private VerifyCredentialResponse verifyCredential(int userId, CredentialHash storedHash, - String credential, boolean hasChallenge, long challenge, CredentialUtil credentialUtil, - ICheckCredentialProgressCallback progressCallback) - throws RemoteException { + String credential, boolean hasChallenge, long challenge, + ICheckCredentialProgressCallback progressCallback) throws RemoteException { if ((storedHash == null || storedHash.hash.length == 0) && TextUtils.isEmpty(credential)) { // don't need to pass empty credentials to GateKeeper return VerifyCredentialResponse.OK; @@ -1411,10 +1263,18 @@ public class LockSettingsService extends ILockSettings.Stub { StrictMode.noteDiskRead(); if (storedHash.version == CredentialHash.VERSION_LEGACY) { - byte[] hash = credentialUtil.toHash(credential, userId); + final byte[] hash; + if (storedHash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) { + hash = LockPatternUtils.patternToHash(LockPatternUtils.stringToPattern(credential)); + } else { + hash = mLockPatternUtils.passwordToHash(credential, userId); + } if (Arrays.equals(hash, storedHash.hash)) { - unlockKeystore(credentialUtil.adjustForKeystore(credential), userId); - + if (storedHash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) { + unlockKeystore(LockPatternUtils.patternStringToBaseZero(credential), userId); + } else { + unlockKeystore(credential, userId); + } // Users with legacy credentials don't have credential-backed // FBE keys, so just pass through a fake token/secret Slog.i(TAG, "Unlocking user with fake token: " + userId); @@ -1422,7 +1282,7 @@ public class LockSettingsService extends ILockSettings.Stub { unlockUser(userId, fakeToken, fakeToken); // migrate credential to GateKeeper - credentialUtil.setCredential(credential, null, userId); + setLockCredentialInternal(credential, storedHash.type, null, userId); if (!hasChallenge) { notifyActivePasswordMetricsAvailable(credential, userId); return VerifyCredentialResponse.OK; @@ -1441,7 +1301,7 @@ public class LockSettingsService extends ILockSettings.Stub { .verifyChallenge(userId, challenge, storedHash.hash, credential.getBytes()); int responseCode = gateKeeperResponse.getResponseCode(); if (responseCode == GateKeeperResponse.RESPONSE_RETRY) { - response = new VerifyCredentialResponse(gateKeeperResponse.getTimeout()); + response = new VerifyCredentialResponse(gateKeeperResponse.getTimeout()); } else if (responseCode == GateKeeperResponse.RESPONSE_OK) { byte[] token = gateKeeperResponse.getPayload(); if (token == null) { @@ -1458,7 +1318,6 @@ public class LockSettingsService extends ILockSettings.Stub { if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) { - // credential has matched if (progressCallback != null) { @@ -1468,7 +1327,7 @@ public class LockSettingsService extends ILockSettings.Stub { unlockKeystore(credential, userId); Slog.i(TAG, "Unlocking user " + userId + - " with token length " + response.getPayload().length); + " with token length " + response.getPayload().length); unlockUser(userId, response.getPayload(), secretFromCredential(credential)); if (isManagedProfileWithSeparatedLock(userId)) { @@ -1477,7 +1336,7 @@ public class LockSettingsService extends ILockSettings.Stub { trustManager.setDeviceLockedForUser(userId, false); } if (shouldReEnroll) { - credentialUtil.setCredential(credential, credential, userId); + setLockCredentialInternal(credential, storedHash.type, credential, userId); } } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) { if (response.getTimeout() > 0) { @@ -1550,8 +1409,9 @@ public class LockSettingsService extends ILockSettings.Stub { try { if (mLockPatternUtils.isLockPatternEnabled(userId)) { - if (checkPattern(password, userId, null /* progressCallback */).getResponseCode() - == GateKeeperResponse.RESPONSE_OK) { + if (checkCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, userId, + null /* progressCallback */) + .getResponseCode() == GateKeeperResponse.RESPONSE_OK) { return true; } } @@ -1560,8 +1420,9 @@ public class LockSettingsService extends ILockSettings.Stub { try { if (mLockPatternUtils.isLockPasswordEnabled(userId)) { - if (checkPassword(password, userId, null /* progressCallback */).getResponseCode() - == GateKeeperResponse.RESPONSE_OK) { + if (checkCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, userId, + null /* progressCallback */) + .getResponseCode() == GateKeeperResponse.RESPONSE_OK) { return true; } } @@ -1581,7 +1442,7 @@ public class LockSettingsService extends ILockSettings.Stub { try { final IGateKeeperService gk = getGateKeeperService(); if (gk != null) { - gk.clearSecureUserId(userId); + gk.clearSecureUserId(userId); } } catch (RemoteException ex) { Slog.w(TAG, "unable to clear GK secure user id"); @@ -1661,28 +1522,28 @@ public class LockSettingsService extends ILockSettings.Stub { } private static final String[] VALID_SETTINGS = new String[] { - LockPatternUtils.LOCKOUT_PERMANENT_KEY, - LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE, - LockPatternUtils.PATTERN_EVER_CHOSEN_KEY, - LockPatternUtils.PASSWORD_TYPE_KEY, - LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY, - LockPatternUtils.LOCK_PASSWORD_SALT_KEY, - LockPatternUtils.DISABLE_LOCKSCREEN_KEY, - LockPatternUtils.LOCKSCREEN_OPTIONS, - LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, - LockPatternUtils.BIOMETRIC_WEAK_EVER_CHOSEN_KEY, - LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS, - LockPatternUtils.PASSWORD_HISTORY_KEY, - Secure.LOCK_PATTERN_ENABLED, - Secure.LOCK_BIOMETRIC_WEAK_FLAGS, - Secure.LOCK_PATTERN_VISIBLE, - Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED + LockPatternUtils.LOCKOUT_PERMANENT_KEY, + LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE, + LockPatternUtils.PATTERN_EVER_CHOSEN_KEY, + LockPatternUtils.PASSWORD_TYPE_KEY, + LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY, + LockPatternUtils.LOCK_PASSWORD_SALT_KEY, + LockPatternUtils.DISABLE_LOCKSCREEN_KEY, + LockPatternUtils.LOCKSCREEN_OPTIONS, + LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, + LockPatternUtils.BIOMETRIC_WEAK_EVER_CHOSEN_KEY, + LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS, + LockPatternUtils.PASSWORD_HISTORY_KEY, + Secure.LOCK_PATTERN_ENABLED, + Secure.LOCK_BIOMETRIC_WEAK_FLAGS, + Secure.LOCK_PATTERN_VISIBLE, + Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED }; // Reading these settings needs the contacts permission private static final String[] READ_CONTACTS_PROTECTED_SETTINGS = new String[] { - Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, - Secure.LOCK_SCREEN_OWNER_INFO + Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, + Secure.LOCK_SCREEN_OWNER_INFO }; // Reading these settings needs the same permission as checking the password @@ -1694,8 +1555,8 @@ public class LockSettingsService extends ILockSettings.Stub { }; private static final String[] SETTINGS_TO_BACKUP = new String[] { - Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, - Secure.LOCK_SCREEN_OWNER_INFO + Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, + Secure.LOCK_SCREEN_OWNER_INFO }; private IStorageManager getStorageManager() { @@ -1720,8 +1581,7 @@ public class LockSettingsService extends ILockSettings.Stub { return mGateKeeperService; } - final IBinder service = - ServiceManager.getService(Context.GATEKEEPER_SERVICE); + final IBinder service = ServiceManager.getService(Context.GATEKEEPER_SERVICE); if (service != null) { service.linkToDeath(new GateKeeperDiedRecipient(), 0); mGateKeeperService = IGateKeeperService.Stub.asInterface(service); diff --git a/services/core/java/com/android/server/LockSettingsStorage.java b/services/core/java/com/android/server/LockSettingsStorage.java index ab91a732f3ccf..3d973a04c2124 100644 --- a/services/core/java/com/android/server/LockSettingsStorage.java +++ b/services/core/java/com/android/server/LockSettingsStorage.java @@ -16,7 +16,7 @@ package com.android.server; -import com.android.internal.annotations.VisibleForTesting; +import static android.content.Context.USER_SERVICE; import android.content.ContentValues; import android.content.Context; @@ -29,14 +29,15 @@ import android.os.UserManager; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; -import android.util.SparseArray; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; +import com.android.internal.widget.LockPatternUtils; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; -import static android.content.Context.USER_SERVICE; - /** * Storage for the lock settings service. */ @@ -72,29 +73,48 @@ class LockSettingsStorage { private final Cache mCache = new Cache(); private final Object mFileWriteLock = new Object(); - private SparseArray mStoredCredentialType; - - static class CredentialHash { - static final int TYPE_NONE = -1; - static final int TYPE_PATTERN = 1; - static final int TYPE_PASSWORD = 2; - + @VisibleForTesting + public static class CredentialHash { static final int VERSION_LEGACY = 0; static final int VERSION_GATEKEEPER = 1; - CredentialHash(byte[] hash, int version) { + private CredentialHash(byte[] hash, int type, int version) { + if (type != LockPatternUtils.CREDENTIAL_TYPE_NONE) { + if (hash == null) { + throw new RuntimeException("Empty hash for CredentialHash"); + } + } else /* type == LockPatternUtils.CREDENTIAL_TYPE_NONE */ { + if (hash != null) { + throw new RuntimeException("None type CredentialHash should not have hash"); + } + } this.hash = hash; + this.type = type; this.version = version; this.isBaseZeroPattern = false; } - CredentialHash(byte[] hash, boolean isBaseZeroPattern) { + private CredentialHash(byte[] hash, boolean isBaseZeroPattern) { this.hash = hash; + this.type = LockPatternUtils.CREDENTIAL_TYPE_PATTERN; this.version = VERSION_GATEKEEPER; this.isBaseZeroPattern = isBaseZeroPattern; } + static CredentialHash create(byte[] hash, int type) { + if (type == LockPatternUtils.CREDENTIAL_TYPE_NONE) { + throw new RuntimeException("Bad type for CredentialHash"); + } + return new CredentialHash(hash, type, VERSION_GATEKEEPER); + } + + static CredentialHash createEmptyHash() { + return new CredentialHash(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, + VERSION_GATEKEEPER); + } + byte[] hash; + int type; int version; boolean isBaseZeroPattern; } @@ -102,7 +122,6 @@ class LockSettingsStorage { public LockSettingsStorage(Context context, Callback callback) { mContext = context; mOpenHelper = new DatabaseHelper(context, callback); - mStoredCredentialType = new SparseArray(); } public void writeKeyValue(String key, String value, int userId) { @@ -178,75 +197,62 @@ class LockSettingsStorage { } // Populate cache by reading the password and pattern files. - readPasswordHash(userId); - readPatternHash(userId); + readCredentialHash(userId); } - public int getStoredCredentialType(int userId) { - final Integer cachedStoredCredentialType = mStoredCredentialType.get(userId); - if (cachedStoredCredentialType != null) { - return cachedStoredCredentialType.intValue(); - } - - int storedCredentialType; - CredentialHash pattern = readPatternHash(userId); - if (pattern == null) { - if (readPasswordHash(userId) != null) { - storedCredentialType = CredentialHash.TYPE_PASSWORD; - } else { - storedCredentialType = CredentialHash.TYPE_NONE; - } - } else { - CredentialHash password = readPasswordHash(userId); - if (password != null) { - // Both will never be GateKeeper - if (password.version == CredentialHash.VERSION_GATEKEEPER) { - storedCredentialType = CredentialHash.TYPE_PASSWORD; - } else { - storedCredentialType = CredentialHash.TYPE_PATTERN; - } - } else { - storedCredentialType = CredentialHash.TYPE_PATTERN; - } - } - mStoredCredentialType.put(userId, storedCredentialType); - return storedCredentialType; - } - - - public CredentialHash readPasswordHash(int userId) { + private CredentialHash readPasswordHashIfExists(int userId) { byte[] stored = readFile(getLockPasswordFilename(userId)); - if (stored != null && stored.length > 0) { - return new CredentialHash(stored, CredentialHash.VERSION_GATEKEEPER); + if (!ArrayUtils.isEmpty(stored)) { + return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, + CredentialHash.VERSION_GATEKEEPER); } stored = readFile(getLegacyLockPasswordFilename(userId)); - if (stored != null && stored.length > 0) { - return new CredentialHash(stored, CredentialHash.VERSION_LEGACY); + if (!ArrayUtils.isEmpty(stored)) { + return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, + CredentialHash.VERSION_LEGACY); } - return null; } - public CredentialHash readPatternHash(int userId) { + private CredentialHash readPatternHashIfExists(int userId) { byte[] stored = readFile(getLockPatternFilename(userId)); - if (stored != null && stored.length > 0) { - return new CredentialHash(stored, CredentialHash.VERSION_GATEKEEPER); + if (!ArrayUtils.isEmpty(stored)) { + return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, + CredentialHash.VERSION_GATEKEEPER); } stored = readFile(getBaseZeroLockPatternFilename(userId)); - if (stored != null && stored.length > 0) { + if (!ArrayUtils.isEmpty(stored)) { return new CredentialHash(stored, true); } stored = readFile(getLegacyLockPatternFilename(userId)); - if (stored != null && stored.length > 0) { - return new CredentialHash(stored, CredentialHash.VERSION_LEGACY); + if (!ArrayUtils.isEmpty(stored)) { + return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, + CredentialHash.VERSION_LEGACY); } - return null; } + public CredentialHash readCredentialHash(int userId) { + CredentialHash passwordHash = readPasswordHashIfExists(userId); + CredentialHash patternHash = readPatternHashIfExists(userId); + if (passwordHash != null && patternHash != null) { + if (passwordHash.version == CredentialHash.VERSION_GATEKEEPER) { + return passwordHash; + } else { + return patternHash; + } + } else if (passwordHash != null) { + return passwordHash; + } else if (patternHash != null) { + return patternHash; + } else { + return CredentialHash.createEmptyHash(); + } + } + public void removeChildProfileLock(int userId) { if (DEBUG) Slog.e(TAG, "Remove child profile lock for user: " + userId); @@ -355,26 +361,17 @@ class LockSettingsStorage { } } - public void writePatternHash(byte[] hash, int userId) { - mStoredCredentialType.put(userId, hash == null ? CredentialHash.TYPE_NONE - : CredentialHash.TYPE_PATTERN); - writeFile(getLockPatternFilename(userId), hash); - clearPasswordHash(userId); - } + public void writeCredentialHash(CredentialHash hash, int userId) { + byte[] patternHash = null; + byte[] passwordHash = null; - private void clearPatternHash(int userId) { - writeFile(getLockPatternFilename(userId), null); - } - - public void writePasswordHash(byte[] hash, int userId) { - mStoredCredentialType.put(userId, hash == null ? CredentialHash.TYPE_NONE - : CredentialHash.TYPE_PASSWORD); - writeFile(getLockPasswordFilename(userId), hash); - clearPatternHash(userId); - } - - private void clearPasswordHash(int userId) { - writeFile(getLockPasswordFilename(userId), null); + if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) { + passwordHash = hash.hash; + } else if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) { + patternHash = hash.hash; + } + writeFile(getLockPasswordFilename(userId), passwordHash); + writeFile(getLockPatternFilename(userId), patternHash); } @VisibleForTesting diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java index 7d28e3964f8ed..9d521533d7736 100644 --- a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java +++ b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java @@ -16,6 +16,10 @@ package com.android.server; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import android.content.Context; import android.content.ContextWrapper; import android.content.pm.UserInfo; @@ -24,13 +28,23 @@ import android.os.FileUtils; import android.os.UserManager; import android.test.AndroidTestCase; +import com.android.internal.widget.LockPatternUtils; +import com.android.server.LockSettingsStorage.CredentialHash; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CountDownLatch; +/** + * runtest frameworks-services -c com.android.server.LockSettingsStorageTests + */ public class LockSettingsStorageTests extends AndroidTestCase { + private final byte[] PASSWORD_0 = "thepassword0".getBytes(); + private final byte[] PASSWORD_1 = "password1".getBytes(); + private final byte[] PATTERN_0 = "123654".getBytes(); + private final byte[] PATTERN_1 = "147852369".getBytes(); + LockSettingsStorage mStorage; File mStorageDir; @@ -47,24 +61,16 @@ public class LockSettingsStorageTests extends AndroidTestCase { assertTrue(!mDb.exists() || mDb.delete()); final Context ctx = getContext(); + final UserManager mockUserManager = mock(UserManager.class); + // User 2 is a profile of user 1. + when(mockUserManager.getProfileParent(eq(2))).thenReturn(new UserInfo(1, "name", 0)); + // User 3 is a profile of user 0. + when(mockUserManager.getProfileParent(eq(3))).thenReturn(new UserInfo(0, "name", 0)); setContext(new ContextWrapper(ctx) { @Override public Object getSystemService(String name) { if (USER_SERVICE.equals(name)) { - return new UserManager(ctx, null) { - @Override - public UserInfo getProfileParent(int userHandle) { - if (userHandle == 2) { - // User 2 is a profile of user 1. - return new UserInfo(1, "name", 0); - } - if (userHandle == 3) { - // User 3 is a profile of user 0. - return new UserInfo(0, "name", 0); - } - return null; - } - }; + return mockUserManager; } return super.getSystemService(name); } @@ -201,151 +207,149 @@ public class LockSettingsStorageTests extends AndroidTestCase { public void testRemoveUser() { mStorage.writeKeyValue("key", "value", 0); - mStorage.writePasswordHash(new byte[]{1}, 0); - mStorage.writePatternHash(new byte[]{2}, 0); + writePasswordBytes(PASSWORD_0, 0); + writePatternBytes(PATTERN_0, 0); mStorage.writeKeyValue("key", "value", 1); - mStorage.writePasswordHash(new byte[]{1}, 1); - mStorage.writePatternHash(new byte[]{2}, 1); + writePasswordBytes(PASSWORD_1, 1); + writePatternBytes(PATTERN_1, 1); mStorage.removeUser(0); assertEquals("value", mStorage.readKeyValue("key", "default", 1)); assertEquals("default", mStorage.readKeyValue("key", "default", 0)); - assertNotNull(mStorage.readPasswordHash(1)); - assertNull(mStorage.readPasswordHash(0)); - assertNotNull(mStorage.readPatternHash(1)); - assertNull(mStorage.readPatternHash(0)); + assertEquals(LockPatternUtils.CREDENTIAL_TYPE_NONE, mStorage.readCredentialHash(0).type); + assertPatternBytes(PATTERN_1, 1); } - public void testPassword_Default() { - assertNull(mStorage.readPasswordHash(0)); + public void testCredential_Default() { + assertEquals(mStorage.readCredentialHash(0).type, LockPatternUtils.CREDENTIAL_TYPE_NONE); } public void testPassword_Write() { - mStorage.writePasswordHash("thepassword".getBytes(), 0); + writePasswordBytes(PASSWORD_0, 0); - assertArrayEquals("thepassword".getBytes(), mStorage.readPasswordHash(0).hash); + assertPasswordBytes(PASSWORD_0, 0); mStorage.clearCache(); - assertArrayEquals("thepassword".getBytes(), mStorage.readPasswordHash(0).hash); + assertPasswordBytes(PASSWORD_0, 0); } public void testPassword_WriteProfileWritesParent() { - mStorage.writePasswordHash("parentpasswordd".getBytes(), 1); - mStorage.writePasswordHash("profilepassword".getBytes(), 2); + writePasswordBytes(PASSWORD_0, 1); + writePasswordBytes(PASSWORD_1, 2); - assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(1).hash); - assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(2).hash); + assertPasswordBytes(PASSWORD_0, 1); + assertPasswordBytes(PASSWORD_1, 2); mStorage.clearCache(); - assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(1).hash); - assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(2).hash); + assertPasswordBytes(PASSWORD_0, 1); + assertPasswordBytes(PASSWORD_1, 2); } public void testLockType_WriteProfileWritesParent() { - mStorage.writePasswordHash("parentpassword".getBytes(), 10); - mStorage.writePatternHash("12345678".getBytes(), 20); + writePasswordBytes(PASSWORD_0, 10); + writePatternBytes(PATTERN_0, 20); - assertEquals(2, mStorage.getStoredCredentialType(10)); - assertEquals(1, mStorage.getStoredCredentialType(20)); + assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, + mStorage.readCredentialHash(10).type); + assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN, + mStorage.readCredentialHash(20).type); mStorage.clearCache(); - assertEquals(2, mStorage.getStoredCredentialType(10)); - assertEquals(1, mStorage.getStoredCredentialType(20)); + assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, + mStorage.readCredentialHash(10).type); + assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN, + mStorage.readCredentialHash(20).type); + } + + public void testPassword_WriteParentWritesProfile() { + writePasswordBytes(PASSWORD_0, 2); + writePasswordBytes(PASSWORD_1, 1); + + assertPasswordBytes(PASSWORD_1, 1); + assertPasswordBytes(PASSWORD_0, 2); + mStorage.clearCache(); + assertPasswordBytes(PASSWORD_1, 1); + assertPasswordBytes(PASSWORD_0, 2); } public void testProfileLock_ReadWriteChildProfileLock() { assertFalse(mStorage.hasChildProfileLock(20)); - mStorage.writeChildProfileLock(20, "profilepassword".getBytes()); - assertArrayEquals("profilepassword".getBytes(), mStorage.readChildProfileLock(20)); + mStorage.writeChildProfileLock(20, PASSWORD_0); + assertArrayEquals(PASSWORD_0, mStorage.readChildProfileLock(20)); assertTrue(mStorage.hasChildProfileLock(20)); mStorage.clearCache(); - assertArrayEquals("profilepassword".getBytes(), mStorage.readChildProfileLock(20)); + assertArrayEquals(PASSWORD_0, mStorage.readChildProfileLock(20)); assertTrue(mStorage.hasChildProfileLock(20)); } - public void testPassword_WriteParentWritesProfile() { - mStorage.writePasswordHash("profilepassword".getBytes(), 2); - mStorage.writePasswordHash("parentpasswordd".getBytes(), 1); - - assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(1).hash); - assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(2).hash); - mStorage.clearCache(); - assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(1).hash); - assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(2).hash); - } - - public void testPattern_Default() { - assertNull(mStorage.readPasswordHash(0)); - } - public void testPattern_Write() { - mStorage.writePatternHash("thepattern".getBytes(), 0); + writePatternBytes(PATTERN_0, 0); - assertArrayEquals("thepattern".getBytes(), mStorage.readPatternHash(0).hash); + assertPatternBytes(PATTERN_0, 0); mStorage.clearCache(); - assertArrayEquals("thepattern".getBytes(), mStorage.readPatternHash(0).hash); + assertPatternBytes(PATTERN_0, 0); } public void testPattern_WriteProfileWritesParent() { - mStorage.writePatternHash("parentpatternn".getBytes(), 1); - mStorage.writePatternHash("profilepattern".getBytes(), 2); + writePatternBytes(PATTERN_0, 1); + writePatternBytes(PATTERN_1, 2); - assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(1).hash); - assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(2).hash); + assertPatternBytes(PATTERN_0, 1); + assertPatternBytes(PATTERN_1, 2); mStorage.clearCache(); - assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(1).hash); - assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(2).hash); + assertPatternBytes(PATTERN_0, 1); + assertPatternBytes(PATTERN_1, 2); } public void testPattern_WriteParentWritesProfile() { - mStorage.writePatternHash("profilepattern".getBytes(), 2); - mStorage.writePatternHash("parentpatternn".getBytes(), 1); + writePatternBytes(PATTERN_1, 2); + writePatternBytes(PATTERN_0, 1); - assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(1).hash); - assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(2).hash); + assertPatternBytes(PATTERN_0, 1); + assertPatternBytes(PATTERN_1, 2); mStorage.clearCache(); - assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(1).hash); - assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(2).hash); + assertPatternBytes(PATTERN_0, 1); + assertPatternBytes(PATTERN_1, 2); } public void testPrefetch() { mStorage.writeKeyValue("key", "toBeFetched", 0); - mStorage.writePatternHash("pattern".getBytes(), 0); - mStorage.writePasswordHash("password".getBytes(), 0); + writePatternBytes(PATTERN_0, 0); mStorage.clearCache(); mStorage.prefetchUser(0); assertEquals("toBeFetched", mStorage.readKeyValue("key", "default", 0)); - assertArrayEquals("pattern".getBytes(), mStorage.readPatternHash(0).hash); - assertArrayEquals("password".getBytes(), mStorage.readPasswordHash(0).hash); + assertPatternBytes(PATTERN_0, 0); } public void testFileLocation_Owner() { LockSettingsStorage storage = new LockSettingsStorage(getContext(), null); - assertEquals("/data/system/gesture.key", storage.getLockPatternFilename(0)); - assertEquals("/data/system/password.key", storage.getLockPasswordFilename(0)); + assertEquals("/data/system/gesture.key", storage.getLegacyLockPatternFilename(0)); + assertEquals("/data/system/password.key", storage.getLegacyLockPasswordFilename(0)); + assertEquals("/data/system/gatekeeper.pattern.key", storage.getLockPatternFilename(0)); + assertEquals("/data/system/gatekeeper.password.key", storage.getLockPasswordFilename(0)); } public void testFileLocation_SecondaryUser() { LockSettingsStorage storage = new LockSettingsStorage(getContext(), null); - assertEquals("/data/system/users/1/gesture.key", storage.getLockPatternFilename(1)); - assertEquals("/data/system/users/1/password.key", storage.getLockPasswordFilename(1)); + assertEquals("/data/system/users/1/gatekeeper.pattern.key", storage.getLockPatternFilename(1)); + assertEquals("/data/system/users/1/gatekeeper.password.key", storage.getLockPasswordFilename(1)); } public void testFileLocation_ProfileToSecondary() { LockSettingsStorage storage = new LockSettingsStorage(getContext(), null); - assertEquals("/data/system/users/1/gesture.key", storage.getLockPatternFilename(2)); - assertEquals("/data/system/users/1/password.key", storage.getLockPasswordFilename(2)); + assertEquals("/data/system/users/2/gatekeeper.pattern.key", storage.getLockPatternFilename(2)); + assertEquals("/data/system/users/2/gatekeeper.password.key", storage.getLockPasswordFilename(2)); } public void testFileLocation_ProfileToOwner() { LockSettingsStorage storage = new LockSettingsStorage(getContext(), null); - assertEquals("/data/system/gesture.key", storage.getLockPatternFilename(3)); - assertEquals("/data/system/password.key", storage.getLockPasswordFilename(3)); + assertEquals("/data/system/users/3/gatekeeper.pattern.key", storage.getLockPatternFilename(3)); + assertEquals("/data/system/users/3/gatekeeper.password.key", storage.getLockPasswordFilename(3)); } private static void assertArrayEquals(byte[] expected, byte[] actual) { @@ -354,4 +358,26 @@ public class LockSettingsStorageTests extends AndroidTestCase { "> but was:<" + Arrays.toString(actual) + ">"); } } + + private void writePasswordBytes(byte[] password, int userId) { + mStorage.writeCredentialHash(CredentialHash.create( + password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), userId); + } + + private void writePatternBytes(byte[] pattern, int userId) { + mStorage.writeCredentialHash(CredentialHash.create( + pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN), userId); + } + + private void assertPasswordBytes(byte[] password, int userId) { + CredentialHash cred = mStorage.readCredentialHash(userId); + assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, cred.type); + assertArrayEquals(password, cred.hash); + } + + private void assertPatternBytes(byte[] pattern, int userId) { + CredentialHash cred = mStorage.readCredentialHash(userId); + assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN, cred.type); + assertArrayEquals(pattern, cred.hash); + } }