summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk1
-rw-r--r--core/java/android/service/gatekeeper/IGateKeeperService.aidl65
-rw-r--r--core/java/com/android/internal/widget/ILockSettings.aidl6
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java64
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java2
-rw-r--r--services/core/java/com/android/server/LockSettingsService.java270
-rw-r--r--services/core/java/com/android/server/LockSettingsStorage.java101
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java44
9 files changed, 480 insertions, 75 deletions
diff --git a/Android.mk b/Android.mk
index 64a8b89..d7e8e4e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -209,6 +209,7 @@ LOCAL_SRC_FILES += \
core/java/android/security/IKeystoreService.aidl \
core/java/android/service/carrier/ICarrierMessagingCallback.aidl \
core/java/android/service/carrier/ICarrierMessagingService.aidl \
+ core/java/android/service/gatekeeper/IGateKeeperService.aidl \
core/java/android/service/notification/INotificationListener.aidl \
core/java/android/service/notification/IStatusBarNotificationHolder.aidl \
core/java/android/service/notification/IConditionListener.aidl \
diff --git a/core/java/android/service/gatekeeper/IGateKeeperService.aidl b/core/java/android/service/gatekeeper/IGateKeeperService.aidl
new file mode 100644
index 0000000..2f3e296
--- /dev/null
+++ b/core/java/android/service/gatekeeper/IGateKeeperService.aidl
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.gatekeeper;
+
+/**
+ * Interface for communication with GateKeeper, the
+ * secure password storage daemon.
+ *
+ * This must be kept manually in sync with system/core/gatekeeperd
+ * until AIDL can generate both C++ and Java bindings.
+ *
+ * @hide
+ */
+interface IGateKeeperService {
+ /**
+ * Enrolls a password, returning the handle to the enrollment to be stored locally.
+ * @param uid The Android user ID associated to this enrollment
+ * @param currentPasswordHandle The previously enrolled handle, or null if none
+ * @param currentPassword The previously enrolled plaintext password, or null if none.
+ * If provided, must verify against the currentPasswordHandle.
+ * @param desiredPassword The new desired password, for which a handle will be returned
+ * upon success.
+ * @return the handle corresponding to desiredPassword, or null
+ */
+ byte[] enroll(int uid, in byte[] currentPasswordHandle, in byte[] currentPassword,
+ in byte[] desiredPassword);
+
+ /**
+ * Verifies an enrolled handle against a provided, plaintext blob.
+ * @param uid The Android user ID associated to this enrollment
+ * @param enrolledPasswordHandle The handle against which the provided password will be
+ * verified.
+ * @param The plaintext blob to verify against enrolledPassword.
+ * @return True if the authentication was successful
+ */
+ boolean verify(int uid, in byte[] enrolledPasswordHandle,
+ in byte[] providedPassword);
+ /**
+ * Verifies an enrolled handle against a provided, plaintext blob.
+ * @param uid The Android user ID associated to this enrollment
+ * @param challenge a challenge to authenticate agaisnt the device credential. If successful
+ * authentication occurs, this value will be written to the returned
+ * authentication attestation.
+ * @param enrolledPasswordHandle The handle against which the provided password will be
+ * verified.
+ * @param The plaintext blob to verify against enrolledPassword.
+ * @return an opaque attestation of authentication on success, or null.
+ */
+ byte[] verifyChallenge(int uid, long challenge, in byte[] enrolledPasswordHandle,
+ in byte[] providedPassword);
+}
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 0cb1f38..bfafff6 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -24,10 +24,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, int userId);
+ void setLockPattern(in String pattern, in String savedPattern, int userId);
boolean checkPattern(in String pattern, int userId);
- void setLockPassword(in String password, int userId);
+ byte[] verifyPattern(in String pattern, long challenge, int userId);
+ void setLockPassword(in String password, in String savedPassword, int userId);
boolean checkPassword(in String password, int userId);
+ byte[] verifyPassword(in String password, 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 2967876..123d1ac 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -280,6 +280,24 @@ public class LockPatternUtils {
}
/**
+ * Check to see if a pattern matches the saved pattern.
+ * If pattern matches, return an opaque attestation that the challenge
+ * was verified.
+ *
+ * @param pattern The pattern to check.
+ * @param challenge The challenge to verify against the pattern
+ * @return the attestation that the challenge was verified, or null.
+ */
+ public byte[] verifyPattern(List<LockPatternView.Cell> pattern, long challenge) {
+ final int userId = getCurrentOrCallingUserId();
+ try {
+ return getLockSettings().verifyPattern(patternToString(pattern), challenge, userId);
+ } catch (RemoteException re) {
+ return null;
+ }
+ }
+
+ /**
* Check to see if a pattern matches the saved pattern. If no pattern exists,
* always returns true.
* @param pattern The pattern to check.
@@ -295,6 +313,24 @@ public class LockPatternUtils {
}
/**
+ * Check to see if a password matches the saved password.
+ * If password matches, return an opaque attestation that the challenge
+ * was verified.
+ *
+ * @param password The password to check.
+ * @param challenge The challenge to verify against the password
+ * @return the attestation that the challenge was verified, or null.
+ */
+ public byte[] verifyPassword(String password, long challenge) {
+ final int userId = getCurrentOrCallingUserId();
+ try {
+ return getLockSettings().verifyPassword(password, challenge, userId);
+ } catch (RemoteException re) {
+ return null;
+ }
+ }
+
+ /**
* Check to see if a password matches the saved password. If no password exists,
* always returns true.
* @param password The password to check.
@@ -425,8 +461,8 @@ public class LockPatternUtils {
setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userHandle);
try {
- getLockSettings().setLockPassword(null, userHandle);
- getLockSettings().setLockPattern(null, userHandle);
+ getLockSettings().setLockPassword(null, null, userHandle);
+ getLockSettings().setLockPattern(null, null, userHandle);
} catch (RemoteException e) {
// well, we tried...
}
@@ -477,24 +513,30 @@ public class LockPatternUtils {
/**
* Save a lock pattern.
* @param pattern The new pattern to save.
+ * @param savedPattern The previously saved pattern, or null if none
*/
- public void saveLockPattern(List<LockPatternView.Cell> pattern) {
- this.saveLockPattern(pattern, getCurrentOrCallingUserId());
+ public void saveLockPattern(List<LockPatternView.Cell> pattern,
+ String savedPattern) {
+ this.saveLockPattern(pattern, savedPattern, getCurrentOrCallingUserId());
}
+ public void saveLockPattern(List<LockPatternView.Cell> pattern, int userId) {
+ this.saveLockPattern(pattern, null, userId);
+ }
/**
* Save a lock pattern.
* @param pattern The new pattern to save.
+ * @param savedPattern The previously saved pattern, converted to String format
* @param userId the user whose pattern is to be saved.
*/
- public void saveLockPattern(List<LockPatternView.Cell> pattern, int userId) {
+ public void saveLockPattern(List<LockPatternView.Cell> pattern, String savedPattern, int userId) {
try {
if (pattern == null || pattern.size() < MIN_LOCK_PATTERN_SIZE) {
throw new IllegalArgumentException("pattern must not be null and at least "
+ MIN_LOCK_PATTERN_SIZE + " dots long.");
}
- getLockSettings().setLockPattern(patternToString(pattern), userId);
+ getLockSettings().setLockPattern(patternToString(pattern), savedPattern, userId);
DevicePolicyManager dpm = getDevicePolicyManager();
// Update the device encryption password.
@@ -685,10 +727,11 @@ public class LockPatternUtils {
* as the requested mode, but will adjust the mode to be as good as the
* pattern.
* @param password The password to save
+ * @param savedPassword The previously saved lock password, or null if none
* @param quality {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
*/
- public void saveLockPassword(String password, int quality) {
- saveLockPassword(password, quality, getCurrentOrCallingUserId());
+ public void saveLockPassword(String password, String savedPassword, int quality) {
+ saveLockPassword(password, savedPassword, quality, getCurrentOrCallingUserId());
}
/**
@@ -699,7 +742,8 @@ public class LockPatternUtils {
* @param quality {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
* @param userHandle The userId of the user to change the password for
*/
- public void saveLockPassword(String password, int quality, int userHandle) {
+ public void saveLockPassword(String password, String savedPassword, int quality,
+ int userHandle) {
try {
DevicePolicyManager dpm = getDevicePolicyManager();
if (password == null || password.length() < MIN_LOCK_PASSWORD_SIZE) {
@@ -707,7 +751,7 @@ public class LockPatternUtils {
+ "of length " + MIN_LOCK_PASSWORD_SIZE);
}
- getLockSettings().setLockPassword(password, userHandle);
+ getLockSettings().setLockPassword(password, savedPassword, userHandle);
int computedQuality = computePasswordQuality(password);
// Update the device encryption password.
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 0b6ab99..870f043 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -2066,7 +2066,7 @@ class DatabaseHelper extends SQLiteOpenHelper {
LockPatternUtils lpu = new LockPatternUtils(mContext);
List<LockPatternView.Cell> cellPattern =
LockPatternUtils.stringToPattern(lockPattern);
- lpu.saveLockPattern(cellPattern);
+ lpu.saveLockPattern(cellPattern, null);
} catch (IllegalArgumentException e) {
// Don't want corrupted lock pattern to hang the reboot process
}
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 2e6d1c1..ee73b1a 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -44,12 +44,14 @@ import android.provider.Settings;
import android.provider.Settings.Secure;
import android.provider.Settings.SettingNotFoundException;
import android.security.KeyStore;
+import android.service.gatekeeper.IGateKeeperService;
import android.text.TextUtils;
import android.util.Slog;
import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockPatternUtils;
+import com.android.server.LockSettingsStorage.CredentialHash;
import java.util.Arrays;
import java.util.List;
@@ -72,6 +74,7 @@ public class LockSettingsService extends ILockSettings.Stub {
private LockPatternUtils mLockPatternUtils;
private boolean mFirstCallToVold;
+ private IGateKeeperService mGateKeeperService;
public LockSettingsService(Context context) {
mContext = context;
@@ -131,6 +134,7 @@ public class LockSettingsService extends ILockSettings.Stub {
public void systemReady() {
migrateOldData();
+ getGateKeeperService();
mStorage.prefetchUser(UserHandle.USER_OWNER);
}
@@ -277,7 +281,6 @@ public class LockSettingsService extends ILockSettings.Stub {
@Override
public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException {
checkReadPermission(key, userId);
-
String value = getStringUnchecked(key, null, userId);
return TextUtils.isEmpty(value) ?
defaultValue : (value.equals("1") || value.equals("true"));
@@ -345,61 +348,251 @@ 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;
+ }
+
+ // sanity check
+ if (currentHandleType != CredentialHash.TYPE_NONE && currentHandle == null) {
+ Slog.e(TAG, "Stored handle type [" + currentHandleType + "] but no handle available");
+ }
+
+ return currentHandle;
+ }
+
+
@Override
- public void setLockPattern(String pattern, int userId) throws RemoteException {
- checkWritePermission(userId);
+ public void setLockPattern(String pattern, String savedCredential, int userId)
+ throws RemoteException {
+ byte[] currentHandle = getCurrentHandle(userId);
- maybeUpdateKeystore(pattern, userId);
+ if (pattern == null) {
+ mStorage.writePatternHash(null, userId);
+ return;
+ }
+
+ if (currentHandle == null) {
+ if (savedCredential != null) {
+ Slog.w(TAG, "Saved credential provided, but none stored");
+ }
+ savedCredential = null;
+ }
- final byte[] hash = LockPatternUtils.patternToHash(
- LockPatternUtils.stringToPattern(pattern));
- mStorage.writePatternHash(hash, userId);
+ byte[] enrolledHandle = enrollCredential(currentHandle, savedCredential, pattern, userId);
+ if (enrolledHandle != null) {
+ mStorage.writePatternHash(enrolledHandle, userId);
+ } else {
+ Slog.e(TAG, "Failed to enroll pattern");
+ }
}
+
@Override
- public void setLockPassword(String password, int userId) throws RemoteException {
- checkWritePermission(userId);
+ public void setLockPassword(String password, String savedCredential, int userId)
+ throws RemoteException {
+ byte[] currentHandle = getCurrentHandle(userId);
- maybeUpdateKeystore(password, userId);
+ if (password == null) {
+ mStorage.writePasswordHash(null, userId);
+ return;
+ }
- mStorage.writePasswordHash(mLockPatternUtils.passwordToHash(password, userId), userId);
+ if (currentHandle == null) {
+ if (savedCredential != null) {
+ Slog.w(TAG, "Saved credential provided, but none stored");
+ }
+ savedCredential = null;
+ }
+
+ byte[] enrolledHandle = enrollCredential(currentHandle, savedCredential, password, userId);
+ if (enrolledHandle != null) {
+ mStorage.writePasswordHash(enrolledHandle, userId);
+ } else {
+ Slog.e(TAG, "Failed to enroll password");
+ }
+ }
+
+ private byte[] enrollCredential(byte[] enrolledHandle,
+ String enrolledCredential, String toEnroll, int userId)
+ throws RemoteException {
+ checkWritePermission(userId);
+ byte[] enrolledCredentialBytes = enrolledCredential == null
+ ? null
+ : enrolledCredential.getBytes();
+ byte[] toEnrollBytes = toEnroll == null
+ ? null
+ : toEnroll.getBytes();
+ byte[] hash = getGateKeeperService().enroll(userId, enrolledHandle, enrolledCredentialBytes,
+ toEnrollBytes);
+
+ if (hash != null) {
+ maybeUpdateKeystore(toEnroll, userId);
+ }
+
+ return hash;
}
@Override
public boolean checkPattern(String pattern, int userId) throws RemoteException {
- checkPasswordReadPermission(userId);
- byte[] hash = LockPatternUtils.patternToHash(LockPatternUtils.stringToPattern(pattern));
- byte[] storedHash = mStorage.readPatternHash(userId);
+ try {
+ doVerifyPattern(pattern, false, 0, userId);
+ } catch (VerificationFailedException ex) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public byte[] verifyPattern(String pattern, long challenge, int userId)
+ throws RemoteException {
+ try {
+ return doVerifyPattern(pattern, true, challenge, userId);
+ } catch (VerificationFailedException ex) {
+ return null;
+ }
+ }
+
+ private byte[] doVerifyPattern(String pattern, boolean hasChallenge, long challenge,
+ int userId) throws VerificationFailedException, RemoteException {
+ checkPasswordReadPermission(userId);
+
+ CredentialHash storedHash = mStorage.readPatternHash(userId);
- if (storedHash == null) {
- return true;
+ if ((storedHash == null || storedHash.hash.length == 0) && TextUtils.isEmpty(pattern)) {
+ // don't need to pass empty passwords to GateKeeper
+ return null;
}
- boolean matched = Arrays.equals(hash, storedHash);
- if (matched && !TextUtils.isEmpty(pattern)) {
- maybeUpdateKeystore(pattern, userId);
+ if (TextUtils.isEmpty(pattern)) {
+ throw new VerificationFailedException();
}
- return matched;
+
+ if (storedHash.version == CredentialHash.VERSION_LEGACY) {
+ byte[] hash = mLockPatternUtils.patternToHash(
+ mLockPatternUtils.stringToPattern(pattern));
+ if (Arrays.equals(hash, storedHash.hash)) {
+ maybeUpdateKeystore(pattern, userId);
+ // migrate password to GateKeeper
+ setLockPattern(pattern, null, userId);
+ if (!hasChallenge) {
+ return null;
+ }
+ // Fall through to get the auth token. Technically this should never happen,
+ // as a user that had a legacy pattern would have to unlock their device
+ // before getting to a flow with a challenge, but supporting for consistency.
+ } else {
+ throw new VerificationFailedException();
+ }
+ }
+
+ byte[] token = null;
+ if (hasChallenge) {
+ token = getGateKeeperService()
+ .verifyChallenge(userId, challenge, storedHash.hash, pattern.getBytes());
+ if (token == null) {
+ throw new VerificationFailedException();
+ }
+ } else if (!getGateKeeperService().verify(userId, storedHash.hash, pattern.getBytes())) {
+ throw new VerificationFailedException();
+ }
+
+ // pattern has matched
+ maybeUpdateKeystore(pattern, userId);
+ return token;
+
}
@Override
public boolean checkPassword(String password, int userId) throws RemoteException {
- checkPasswordReadPermission(userId);
+ try {
+ doVerifyPassword(password, false, 0, userId);
+ } catch (VerificationFailedException ex) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public byte[] verifyPassword(String password, long challenge, int userId)
+ throws RemoteException {
+ try {
+ return doVerifyPassword(password, true, challenge, userId);
+ } catch (VerificationFailedException ex) {
+ return null;
+ }
+ }
+
+ private byte[] doVerifyPassword(String password, boolean hasChallenge, long challenge,
+ int userId) throws VerificationFailedException, RemoteException {
+ checkPasswordReadPermission(userId);
- byte[] hash = mLockPatternUtils.passwordToHash(password, userId);
- byte[] storedHash = mStorage.readPasswordHash(userId);
+ CredentialHash storedHash = mStorage.readPasswordHash(userId);
- if (storedHash == null) {
- return true;
+ if ((storedHash == null || storedHash.hash.length == 0) && TextUtils.isEmpty(password)) {
+ // don't need to pass empty passwords to GateKeeper
+ return null;
}
- boolean matched = Arrays.equals(hash, storedHash);
- if (matched && !TextUtils.isEmpty(password)) {
- maybeUpdateKeystore(password, userId);
+ if (TextUtils.isEmpty(password)) {
+ throw new VerificationFailedException();
}
- return matched;
+
+ if (storedHash.version == CredentialHash.VERSION_LEGACY) {
+ byte[] hash = mLockPatternUtils.passwordToHash(password, userId);
+ if (Arrays.equals(hash, storedHash.hash)) {
+ maybeUpdateKeystore(password, userId);
+ // migrate password to GateKeeper
+ setLockPassword(password, null, userId);
+ if (!hasChallenge) {
+ return null;
+ }
+ // Fall through to get the auth token. Technically this should never happen,
+ // as a user that had a legacy password would have to unlock their device
+ // before getting to a flow with a challenge, but supporting for consistency.
+ } else {
+ throw new VerificationFailedException();
+ }
+ }
+
+ byte[] token = null;
+ if (hasChallenge) {
+ token = getGateKeeperService()
+ .verifyChallenge(userId, challenge, storedHash.hash, password.getBytes());
+ if (token == null) {
+ throw new VerificationFailedException();
+ }
+ } else if (!getGateKeeperService().verify(userId, storedHash.hash, password.getBytes())) {
+ throw new VerificationFailedException();
+ }
+
+ // password has matched
+ maybeUpdateKeystore(password, userId);
+ return token;
}
+
@Override
public boolean checkVoldPassword(int userId) throws RemoteException {
if (!mFirstCallToVold) {
@@ -497,4 +690,23 @@ public class LockSettingsService extends ILockSettings.Stub {
}
return null;
}
+
+ private synchronized IGateKeeperService getGateKeeperService() {
+ if (mGateKeeperService != null) {
+ return mGateKeeperService;
+ }
+
+ final IBinder service =
+ ServiceManager.getService("android.service.gatekeeper.IGateKeeperService");
+ if (service != null) {
+ mGateKeeperService = IGateKeeperService.Stub.asInterface(service);
+ return mGateKeeperService;
+ }
+
+ Slog.e(TAG, "Unable to acquire GateKeeperService");
+ return null;
+ }
+
+ private class VerificationFailedException extends Exception {}
+
}
diff --git a/services/core/java/com/android/server/LockSettingsStorage.java b/services/core/java/com/android/server/LockSettingsStorage.java
index d81daa9..f202c36 100644
--- a/services/core/java/com/android/server/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/LockSettingsStorage.java
@@ -56,8 +56,10 @@ class LockSettingsStorage {
};
private static final String SYSTEM_DIRECTORY = "/system/";
- private static final String LOCK_PATTERN_FILE = "gesture.key";
- private static final String LOCK_PASSWORD_FILE = "password.key";
+ private static final String LOCK_PATTERN_FILE = "gatekeeper.gesture.key";
+ private static final String LEGACY_LOCK_PATTERN_FILE = "gesture.key";
+ private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key";
+ private static final String LEGACY_LOCK_PASSWORD_FILE = "password.key";
private static final Object DEFAULT = new Object();
@@ -66,6 +68,25 @@ class LockSettingsStorage {
private final Cache mCache = new Cache();
private final Object mFileWriteLock = new Object();
+ private int mStoredCredentialType;
+
+ class CredentialHash {
+ static final int TYPE_NONE = -1;
+ static final int TYPE_PATTERN = 1;
+ static final int TYPE_PASSWORD = 2;
+
+ static final int VERSION_LEGACY = 0;
+ static final int VERSION_GATEKEEPER = 1;
+
+ CredentialHash(byte[] hash, int version) {
+ this.hash = hash;
+ this.version = version;
+ }
+
+ byte[] hash;
+ int version;
+ }
+
public LockSettingsStorage(Context context, Callback callback) {
mContext = context;
mOpenHelper = new DatabaseHelper(context, callback);
@@ -148,28 +169,72 @@ class LockSettingsStorage {
readPatternHash(userId);
}
- public byte[] readPasswordHash(int userId) {
- final byte[] stored = readFile(getLockPasswordFilename(userId));
+ public int getStoredCredentialType(int userId) {
+ if (mStoredCredentialType != 0) {
+ return mStoredCredentialType;
+ }
+
+ CredentialHash pattern = readPatternHash(userId);
+ if (pattern == null) {
+ if (readPasswordHash(userId) != null) {
+ mStoredCredentialType = CredentialHash.TYPE_PASSWORD;
+ } else {
+ mStoredCredentialType = CredentialHash.TYPE_NONE;
+ }
+ } else {
+ CredentialHash password = readPasswordHash(userId);
+ if (password != null) {
+ // Both will never be GateKeeper
+ if (password.version == CredentialHash.VERSION_GATEKEEPER) {
+ mStoredCredentialType = CredentialHash.TYPE_PASSWORD;
+ } else {
+ mStoredCredentialType = CredentialHash.TYPE_PATTERN;
+ }
+ } else {
+ mStoredCredentialType = CredentialHash.TYPE_PATTERN;
+ }
+ }
+
+ return mStoredCredentialType;
+ }
+
+
+ public CredentialHash readPasswordHash(int userId) {
+ byte[] stored = readFile(getLockPasswordFilename(userId));
if (stored != null && stored.length > 0) {
- return stored;
+ return new CredentialHash(stored, CredentialHash.VERSION_GATEKEEPER);
}
+
+ stored = readFile(getLegacyLockPasswordFilename(userId));
+ if (stored != null && stored.length > 0) {
+ return new CredentialHash(stored, CredentialHash.VERSION_LEGACY);
+ }
+
return null;
}
- public byte[] readPatternHash(int userId) {
- final byte[] stored = readFile(getLockPatternFilename(userId));
+ public CredentialHash readPatternHash(int userId) {
+ byte[] stored = readFile(getLockPatternFilename(userId));
if (stored != null && stored.length > 0) {
- return stored;
+ return new CredentialHash(stored, CredentialHash.VERSION_GATEKEEPER);
}
+
+ stored = readFile(getLegacyLockPatternFilename(userId));
+ if (stored != null && stored.length > 0) {
+ return new CredentialHash(stored, CredentialHash.VERSION_LEGACY);
+ }
+
return null;
}
public boolean hasPassword(int userId) {
- return hasFile(getLockPasswordFilename(userId));
+ return hasFile(getLockPasswordFilename(userId)) ||
+ hasFile(getLegacyLockPasswordFilename(userId));
}
public boolean hasPattern(int userId) {
- return hasFile(getLockPatternFilename(userId));
+ return hasFile(getLockPatternFilename(userId)) ||
+ hasFile(getLegacyLockPatternFilename(userId));
}
private boolean hasFile(String name) {
@@ -237,6 +302,9 @@ class LockSettingsStorage {
}
public void writePatternHash(byte[] hash, int userId) {
+ mStoredCredentialType = hash == null
+ ? CredentialHash.TYPE_NONE
+ : CredentialHash.TYPE_PATTERN;
writeFile(getLockPatternFilename(userId), hash);
clearPasswordHash(userId);
}
@@ -246,6 +314,9 @@ class LockSettingsStorage {
}
public void writePasswordHash(byte[] hash, int userId) {
+ mStoredCredentialType = hash == null
+ ? CredentialHash.TYPE_NONE
+ : CredentialHash.TYPE_PASSWORD;
writeFile(getLockPasswordFilename(userId), hash);
clearPatternHash(userId);
}
@@ -264,6 +335,16 @@ class LockSettingsStorage {
return getLockCredentialFilePathForUser(userId, LOCK_PASSWORD_FILE);
}
+ @VisibleForTesting
+ String getLegacyLockPatternFilename(int userId) {
+ return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PATTERN_FILE);
+ }
+
+ @VisibleForTesting
+ String getLegacyLockPasswordFilename(int userId) {
+ return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PASSWORD_FILE);
+ }
+
private String getLockCredentialFilePathForUser(int userId, String basename) {
userId = getUserParentOrSelfId(userId);
String dataSystemDirectory =
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c7d2b86..c5e1933 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2813,7 +2813,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
try {
LockPatternUtils utils = new LockPatternUtils(mContext);
if (!TextUtils.isEmpty(password)) {
- utils.saveLockPassword(password, quality, userHandle);
+ utils.saveLockPassword(password, null, quality, userHandle);
} else {
utils.clearLock(userHandle);
}
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
index bf0e75d..dae8447 100644
--- a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
@@ -219,31 +219,31 @@ public class LockSettingsStorageTests extends AndroidTestCase {
public void testPassword_Write() {
mStorage.writePasswordHash("thepassword".getBytes(), 0);
- assertArrayEquals("thepassword".getBytes(), mStorage.readPasswordHash(0));
+ assertArrayEquals("thepassword".getBytes(), mStorage.readPasswordHash(0).hash);
mStorage.clearCache();
- assertArrayEquals("thepassword".getBytes(), mStorage.readPasswordHash(0));
+ assertArrayEquals("thepassword".getBytes(), mStorage.readPasswordHash(0).hash);
}
public void testPassword_WriteProfileWritesParent() {
mStorage.writePasswordHash("parentpasswordd".getBytes(), 1);
mStorage.writePasswordHash("profilepassword".getBytes(), 2);
- assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(1));
- assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(2));
+ assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(1).hash);
+ assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(2).hash);
mStorage.clearCache();
- assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(1));
- assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(2));
+ assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(1).hash);
+ assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(2).hash);
}
public void testPassword_WriteParentWritesProfile() {
mStorage.writePasswordHash("profilepassword".getBytes(), 2);
mStorage.writePasswordHash("parentpasswordd".getBytes(), 1);
- assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(1));
- assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(2));
+ assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(1).hash);
+ assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(2).hash);
mStorage.clearCache();
- assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(1));
- assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(2));
+ assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(1).hash);
+ assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(2).hash);
}
public void testPattern_Default() {
@@ -253,31 +253,31 @@ public class LockSettingsStorageTests extends AndroidTestCase {
public void testPattern_Write() {
mStorage.writePatternHash("thepattern".getBytes(), 0);
- assertArrayEquals("thepattern".getBytes(), mStorage.readPatternHash(0));
+ assertArrayEquals("thepattern".getBytes(), mStorage.readPatternHash(0).hash);
mStorage.clearCache();
- assertArrayEquals("thepattern".getBytes(), mStorage.readPatternHash(0));
+ assertArrayEquals("thepattern".getBytes(), mStorage.readPatternHash(0).hash);
}
public void testPattern_WriteProfileWritesParent() {
mStorage.writePatternHash("parentpatternn".getBytes(), 1);
mStorage.writePatternHash("profilepattern".getBytes(), 2);
- assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(1));
- assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(2));
+ assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(1).hash);
+ assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(2).hash);
mStorage.clearCache();
- assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(1));
- assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(2));
+ assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(1).hash);
+ assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(2).hash);
}
public void testPattern_WriteParentWritesProfile() {
mStorage.writePatternHash("profilepattern".getBytes(), 2);
mStorage.writePatternHash("parentpatternn".getBytes(), 1);
- assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(1));
- assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(2));
+ assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(1).hash);
+ assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(2).hash);
mStorage.clearCache();
- assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(1));
- assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(2));
+ assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(1).hash);
+ assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(2).hash);
}
public void testPrefetch() {
@@ -289,8 +289,8 @@ public class LockSettingsStorageTests extends AndroidTestCase {
mStorage.prefetchUser(0);
assertEquals("toBeFetched", mStorage.readKeyValue("key", "default", 0));
- assertArrayEquals("pattern".getBytes(), mStorage.readPatternHash(0));
- assertArrayEquals("password".getBytes(), mStorage.readPasswordHash(0));
+ assertArrayEquals("pattern".getBytes(), mStorage.readPatternHash(0).hash);
+ assertArrayEquals("password".getBytes(), mStorage.readPasswordHash(0).hash);
}
public void testFileLocation_Owner() {