Merge "Refactor LockSettingsService to unify the handling of pattern and password"

This commit is contained in:
TreeHugger Robot
2017-01-13 19:30:46 +00:00
committed by Android (Google) Code Review
5 changed files with 408 additions and 540 deletions

View File

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

View File

@@ -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<LockPatternView.Cell> 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

View File

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

View File

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

View File

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