Add challenge to IGateKeeperService
required for enrolling secondary auth form-factors
Change-Id: Id5a1eb1ed22f01fbaabe8e4ebddfc42d58322625
diff --git a/core/java/android/service/gatekeeper/IGateKeeperService.aidl b/core/java/android/service/gatekeeper/IGateKeeperService.aidl
index 675374d..2f3e296 100644
--- a/core/java/android/service/gatekeeper/IGateKeeperService.aidl
+++ b/core/java/android/service/gatekeeper/IGateKeeperService.aidl
@@ -45,7 +45,21 @@
* @param enrolledPasswordHandle The handle against which the provided password will be
* verified.
* @param The plaintext blob to verify against enrolledPassword.
- * @return true if success, false if failure
+ * @return True if the authentication was successful
*/
- boolean verify(int uid, in byte[] enrolledPasswordHandle, in byte[] providedPassword);
+ 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 53a860d..bfafff6 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -26,8 +26,10 @@
String getString(in String key, in String defaultValue, in int userId);
void setLockPattern(in String pattern, in String savedPattern, int userId);
boolean checkPattern(in String pattern, 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 fce57bd..123d1ac 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -280,6 +280,24 @@
}
/**
+ * 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 @@
}
/**
+ * 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.
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 48f4120..ee73b1a 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -387,6 +387,11 @@
throws RemoteException {
byte[] currentHandle = getCurrentHandle(userId);
+ if (pattern == null) {
+ mStorage.writePatternHash(null, userId);
+ return;
+ }
+
if (currentHandle == null) {
if (savedCredential != null) {
Slog.w(TAG, "Saved credential provided, but none stored");
@@ -406,9 +411,13 @@
@Override
public void setLockPassword(String password, String savedCredential, int userId)
throws RemoteException {
-
byte[] currentHandle = getCurrentHandle(userId);
+ if (password == null) {
+ mStorage.writePasswordHash(null, userId);
+ return;
+ }
+
if (currentHandle == null) {
if (savedCredential != null) {
Slog.w(TAG, "Saved credential provided, but none stored");
@@ -446,70 +455,144 @@
@Override
public boolean checkPattern(String pattern, int userId) throws RemoteException {
- checkPasswordReadPermission(userId);
+ try {
+ doVerifyPattern(pattern, false, 0, userId);
+ } catch (VerificationFailedException ex) {
+ return false;
+ }
- CredentialHash storedHash = mStorage.readPatternHash(userId);
+ return true;
+ }
- if (storedHash == null) {
- 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 || storedHash.hash.length == 0) && TextUtils.isEmpty(pattern)) {
+ // don't need to pass empty passwords to GateKeeper
+ return null;
+ }
+
+ if (TextUtils.isEmpty(pattern)) {
+ throw new VerificationFailedException();
}
if (storedHash.version == CredentialHash.VERSION_LEGACY) {
- // Try the old backend and upgrade if a match is found
- byte[] hash = LockPatternUtils.patternToHash(
- LockPatternUtils.stringToPattern(pattern));
- boolean matched = Arrays.equals(hash, storedHash.hash);
- if (matched && !TextUtils.isEmpty(pattern)) {
+ byte[] hash = mLockPatternUtils.patternToHash(
+ mLockPatternUtils.stringToPattern(pattern));
+ if (Arrays.equals(hash, storedHash.hash)) {
maybeUpdateKeystore(pattern, userId);
- // migrate pattern to GateKeeper
+ // 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();
}
-
- return matched;
}
- boolean matched = getGateKeeperService()
- .verify(userId, storedHash.hash, pattern.getBytes());
- if (matched && !TextUtils.isEmpty(pattern)) {
- maybeUpdateKeystore(pattern, userId);
- return true;
+ 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();
}
- return matched;
+ // 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);
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;
+ }
+
+ if (TextUtils.isEmpty(password)) {
+ throw new VerificationFailedException();
}
if (storedHash.version == CredentialHash.VERSION_LEGACY) {
byte[] hash = mLockPatternUtils.passwordToHash(password, userId);
- boolean matched = Arrays.equals(hash, storedHash.hash);
- if (matched && !TextUtils.isEmpty(password)) {
+ 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();
}
- return matched;
}
-
-
- boolean matched = getGateKeeperService()
- .verify(userId, storedHash.hash, password.getBytes());
- if (!TextUtils.isEmpty(password) && matched) {
- maybeUpdateKeystore(password, userId);
+ 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();
}
- return matched;
+ // password has matched
+ maybeUpdateKeystore(password, userId);
+ return token;
}
+
@Override
public boolean checkVoldPassword(int userId) throws RemoteException {
if (!mFirstCallToVold) {
@@ -624,4 +707,6 @@
return null;
}
+ private class VerificationFailedException extends Exception {}
+
}