diff options
author | Andres Morales <anmorales@google.com> | 2015-04-09 19:14:42 -0700 |
---|---|---|
committer | Andres Morales <anmorales@google.com> | 2015-04-13 18:38:45 -0700 |
commit | d9fc85ac27742adbe89e54fd35f3cb2469e94b91 (patch) | |
tree | 730af15f0d98b06e957c608bdf5667299e9d3508 | |
parent | 8fa5665f0e757cec0063fb4cf1354f1596f93a91 (diff) | |
download | frameworks_base-d9fc85ac27742adbe89e54fd35f3cb2469e94b91.zip frameworks_base-d9fc85ac27742adbe89e54fd35f3cb2469e94b91.tar.gz frameworks_base-d9fc85ac27742adbe89e54fd35f3cb2469e94b91.tar.bz2 |
Add challenge to IGateKeeperService
required for enrolling secondary auth form-factors
Change-Id: Id5a1eb1ed22f01fbaabe8e4ebddfc42d58322625
4 files changed, 171 insertions, 34 deletions
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 @@ interface IGateKeeperService { * @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 @@ interface ILockSettings { 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 @@ 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. 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 @@ public class LockSettingsService extends ILockSettings.Stub { 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 @@ public class LockSettingsService extends ILockSettings.Stub { @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 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public boolean checkPattern(String pattern, int userId) throws RemoteException { - checkPasswordReadPermission(userId); + try { + doVerifyPattern(pattern, false, 0, userId); + } catch (VerificationFailedException ex) { + return false; + } + + return true; + } - CredentialHash storedHash = mStorage.readPatternHash(userId); + @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); - if (storedHash == null) { - return true; + 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 @@ public class LockSettingsService extends ILockSettings.Stub { return null; } + private class VerificationFailedException extends Exception {} + } |