diff options
author | Andres Morales <anmorales@google.com> | 2015-05-27 18:37:21 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2015-05-27 18:37:32 +0000 |
commit | 5ccfe51d8b9ae4f73a3b0fdb553b807cf5691582 (patch) | |
tree | 93187b35ac868eec08498685ad91c2a5ee5272cd | |
parent | 77aa9afe0c67ca7285ed81664571f3d6f567eb9b (diff) | |
parent | 2397427cb1a0bad8a42e6a342dcf29b31e40a234 (diff) | |
download | frameworks_base-5ccfe51d8b9ae4f73a3b0fdb553b807cf5691582.zip frameworks_base-5ccfe51d8b9ae4f73a3b0fdb553b807cf5691582.tar.gz frameworks_base-5ccfe51d8b9ae4f73a3b0fdb553b807cf5691582.tar.bz2 |
Merge "[LockSettings] migrate password attempt throttling to hardware" into mnc-dev
13 files changed, 592 insertions, 199 deletions
diff --git a/core/java/android/service/gatekeeper/GateKeeperResponse.aidl b/core/java/android/service/gatekeeper/GateKeeperResponse.aidl new file mode 100644 index 0000000..966606e --- /dev/null +++ b/core/java/android/service/gatekeeper/GateKeeperResponse.aidl @@ -0,0 +1,24 @@ +/* + * 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; + +/** + * Response object for a GateKeeper verification request. + * @hide + */ +parcelable GateKeeperResponse; + diff --git a/core/java/android/service/gatekeeper/GateKeeperResponse.java b/core/java/android/service/gatekeeper/GateKeeperResponse.java new file mode 100644 index 0000000..a512957 --- /dev/null +++ b/core/java/android/service/gatekeeper/GateKeeperResponse.java @@ -0,0 +1,120 @@ +/* + * 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; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Response object for a GateKeeper verification request. + * @hide + */ +public final class GateKeeperResponse implements Parcelable { + + public static final int RESPONSE_ERROR = -1; + public static final int RESPONSE_OK = 0; + public static final int RESPONSE_RETRY = 1; + + private final int mResponseCode; + + private int mTimeout; + private byte[] mPayload; + private boolean mShouldReEnroll; + + private GateKeeperResponse(int responseCode) { + mResponseCode = responseCode; + } + + private GateKeeperResponse(int responseCode, int timeout) { + mResponseCode = responseCode; + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<GateKeeperResponse> CREATOR + = new Parcelable.Creator<GateKeeperResponse>() { + @Override + public GateKeeperResponse createFromParcel(Parcel source) { + int responseCode = source.readInt(); + GateKeeperResponse response = new GateKeeperResponse(responseCode); + if (responseCode == RESPONSE_RETRY) { + response.setTimeout(source.readInt()); + } else if (responseCode == RESPONSE_OK) { + response.setShouldReEnroll(source.readInt() == 1); + int size = source.readInt(); + if (size > 0) { + byte[] payload = new byte[size]; + source.readByteArray(payload); + response.setPayload(payload); + } + } + return response; + } + + @Override + public GateKeeperResponse[] newArray(int size) { + return new GateKeeperResponse[size]; + } + + }; + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mResponseCode); + if (mResponseCode == RESPONSE_RETRY) { + dest.writeInt(mTimeout); + } else if (mResponseCode == RESPONSE_OK) { + dest.writeInt(mShouldReEnroll ? 1 : 0); + if (mPayload != null) { + dest.writeInt(mPayload.length); + dest.writeByteArray(mPayload); + } + } + } + + public byte[] getPayload() { + return mPayload; + } + + public int getTimeout() { + return mTimeout; + } + + public boolean getShouldReEnroll() { + return mShouldReEnroll; + } + + public int getResponseCode() { + return mResponseCode; + } + + private void setTimeout(int timeout) { + mTimeout = timeout; + } + + private void setShouldReEnroll(boolean shouldReEnroll) { + mShouldReEnroll = shouldReEnroll; + } + + private void setPayload(byte[] payload) { + mPayload = payload; + } + +} diff --git a/core/java/android/service/gatekeeper/IGateKeeperService.aidl b/core/java/android/service/gatekeeper/IGateKeeperService.aidl index 4f46701..6db2110 100644 --- a/core/java/android/service/gatekeeper/IGateKeeperService.aidl +++ b/core/java/android/service/gatekeeper/IGateKeeperService.aidl @@ -16,6 +16,8 @@ package android.service.gatekeeper; +import android.service.gatekeeper.GateKeeperResponse; + /** * Interface for communication with GateKeeper, the * secure password storage daemon. @@ -34,9 +36,9 @@ interface IGateKeeperService { * 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 + * @return an EnrollResponse or null on failure */ - byte[] enroll(int uid, in byte[] currentPasswordHandle, in byte[] currentPassword, + GateKeeperResponse enroll(int uid, in byte[] currentPasswordHandle, in byte[] currentPassword, in byte[] desiredPassword); /** @@ -45,10 +47,10 @@ 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 the authentication was successful + * @return a VerifyResponse, or null on failure. */ - boolean verify(int uid, in byte[] enrolledPasswordHandle, - in byte[] providedPassword); + GateKeeperResponse 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 @@ -58,9 +60,9 @@ interface IGateKeeperService { * @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. + * @return a VerifyResponse with an attestation, or null on failure. */ - byte[] verifyChallenge(int uid, long challenge, in byte[] enrolledPasswordHandle, + GateKeeperResponse 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 bfafff6..dfb7c50 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -16,6 +16,8 @@ package com.android.internal.widget; +import com.android.internal.widget.VerifyCredentialResponse; + /** {@hide} */ interface ILockSettings { void setBoolean(in String key, in boolean value, in int userId); @@ -25,11 +27,11 @@ interface ILockSettings { 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); - boolean checkPattern(in String pattern, int userId); - byte[] verifyPattern(in String pattern, long challenge, int userId); + VerifyCredentialResponse checkPattern(in String pattern, int userId); + VerifyCredentialResponse 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); + VerifyCredentialResponse checkPassword(in String password, int userId); + VerifyCredentialResponse 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/LockPatternChecker.java b/core/java/com/android/internal/widget/LockPatternChecker.java index ac0f5fe..4880664 100644 --- a/core/java/com/android/internal/widget/LockPatternChecker.java +++ b/core/java/com/android/internal/widget/LockPatternChecker.java @@ -2,6 +2,8 @@ package com.android.internal.widget; import android.os.AsyncTask; +import com.android.internal.widget.LockPatternUtils.RequestThrottledException; + import java.util.List; /** @@ -16,8 +18,10 @@ public final class LockPatternChecker { * Invoked when a security check is finished. * * @param matched Whether the PIN/Password/Pattern matches the stored one. + * @param throttleTimeoutMs The amount of time in ms to wait before reattempting + * the call. Only non-0 if matched is false. */ - void onChecked(boolean matched); + void onChecked(boolean matched, int throttleTimeoutMs); } /** @@ -28,8 +32,10 @@ public final class LockPatternChecker { * Invoked when a security verification is finished. * * @param attestation The attestation that the challenge was verified, or null. + * @param throttleTimeoutMs The amount of time in ms to wait before reattempting + * the call. Only non-0 if attestation is null. */ - void onVerified(byte[] attestation); + void onVerified(byte[] attestation, int throttleTimeoutMs); } /** @@ -47,14 +53,21 @@ public final class LockPatternChecker { final int userId, final OnVerifyCallback callback) { AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() { + private int mThrottleTimeout; + @Override protected byte[] doInBackground(Void... args) { - return utils.verifyPattern(pattern, challenge, userId); + try { + return utils.verifyPattern(pattern, challenge, userId); + } catch (RequestThrottledException ex) { + mThrottleTimeout = ex.getTimeoutMs(); + return null; + } } @Override protected void onPostExecute(byte[] result) { - callback.onVerified(result); + callback.onVerified(result, mThrottleTimeout); } }; task.execute(); @@ -74,14 +87,21 @@ public final class LockPatternChecker { final int userId, final OnCheckCallback callback) { AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() { + private int mThrottleTimeout; + @Override protected Boolean doInBackground(Void... args) { - return utils.checkPattern(pattern, userId); + try { + return utils.checkPattern(pattern, userId); + } catch (RequestThrottledException ex) { + mThrottleTimeout = ex.getTimeoutMs(); + return false; + } } @Override protected void onPostExecute(Boolean result) { - callback.onChecked(result); + callback.onChecked(result, mThrottleTimeout); } }; task.execute(); @@ -103,14 +123,21 @@ public final class LockPatternChecker { final int userId, final OnVerifyCallback callback) { AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() { + private int mThrottleTimeout; + @Override protected byte[] doInBackground(Void... args) { - return utils.verifyPassword(password, challenge, userId); + try { + return utils.verifyPassword(password, challenge, userId); + } catch (RequestThrottledException ex) { + mThrottleTimeout = ex.getTimeoutMs(); + return null; + } } @Override protected void onPostExecute(byte[] result) { - callback.onVerified(result); + callback.onVerified(result, mThrottleTimeout); } }; task.execute(); @@ -130,14 +157,21 @@ public final class LockPatternChecker { final int userId, final OnCheckCallback callback) { AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() { + private int mThrottleTimeout; + @Override protected Boolean doInBackground(Void... args) { - return utils.checkPassword(password, userId); + try { + return utils.checkPassword(password, userId); + } catch (RequestThrottledException ex) { + mThrottleTimeout = ex.getTimeoutMs(); + return false; + } } @Override protected void onPostExecute(Boolean result) { - callback.onChecked(result); + callback.onChecked(result, mThrottleTimeout); } }; task.execute(); diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 55b058c..aee0ff6 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -60,24 +60,12 @@ public class LockPatternUtils { private static final boolean DEBUG = false; /** - * The maximum number of incorrect attempts before the user is prevented - * from trying again for {@link #FAILED_ATTEMPT_TIMEOUT_MS}. - */ - public static final int FAILED_ATTEMPTS_BEFORE_TIMEOUT = 5; - - /** * The number of incorrect attempts before which we fall back on an alternative * method of verifying the user, and resetting their lock pattern. */ public static final int FAILED_ATTEMPTS_BEFORE_RESET = 20; /** - * How long the user is prevented from trying again after entering the - * wrong pattern too many times. - */ - public static final long FAILED_ATTEMPT_TIMEOUT_MS = 30000L; - - /** * The interval of the countdown for showing progress of the lockout. */ public static final long FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS = 1000L; @@ -109,6 +97,7 @@ public class LockPatternUtils { @Deprecated public final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently"; public final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline"; + public final static String LOCKOUT_ATTEMPT_TIMEOUT_MS = "lockscreen.lockoutattempttimeoutmss"; public final static String PATTERN_EVER_CHOSEN_KEY = "lockscreen.patterneverchosen"; public final static String PASSWORD_TYPE_KEY = "lockscreen.password_type"; @Deprecated @@ -144,6 +133,23 @@ public class LockPatternUtils { private DevicePolicyManager mDevicePolicyManager; private ILockSettings mLockSettingsService; + + public static final class RequestThrottledException extends Exception { + private int mTimeoutMs; + public RequestThrottledException(int timeoutMs) { + mTimeoutMs = timeoutMs; + } + + /** + * @return The amount of time in ms before another request may + * be executed + */ + public int getTimeoutMs() { + return mTimeoutMs; + } + + } + public DevicePolicyManager getDevicePolicyManager() { if (mDevicePolicyManager == null) { mDevicePolicyManager = @@ -239,9 +245,23 @@ public class LockPatternUtils { * @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, int userId) { + public byte[] verifyPattern(List<LockPatternView.Cell> pattern, long challenge, int userId) + throws RequestThrottledException { try { - return getLockSettings().verifyPattern(patternToString(pattern), challenge, userId); + 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; } @@ -253,9 +273,19 @@ public class LockPatternUtils { * @param pattern The pattern to check. * @return Whether the pattern matches the stored one. */ - public boolean checkPattern(List<LockPatternView.Cell> pattern, int userId) { + public boolean checkPattern(List<LockPatternView.Cell> pattern, int userId) + throws RequestThrottledException { try { - return getLockSettings().checkPattern(patternToString(pattern), userId); + VerifyCredentialResponse response = + getLockSettings().checkPattern(patternToString(pattern), userId); + + 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 true; } @@ -270,9 +300,19 @@ public class LockPatternUtils { * @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, int userId) { + public byte[] verifyPassword(String password, long challenge, int userId) + throws RequestThrottledException { try { - return getLockSettings().verifyPassword(password, challenge, userId); + 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; } @@ -284,9 +324,17 @@ public class LockPatternUtils { * @param password The password to check. * @return Whether the password matches the stored one. */ - public boolean checkPassword(String password, int userId) { + public boolean checkPassword(String password, int userId) throws RequestThrottledException { try { - return getLockSettings().checkPassword(password, userId); + VerifyCredentialResponse response = + getLockSettings().checkPassword(password, userId); + 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 true; } @@ -992,9 +1040,10 @@ public class LockPatternUtils { * pattern until the deadline has passed. * @return the chosen deadline. */ - public long setLockoutAttemptDeadline(int userId) { - final long deadline = SystemClock.elapsedRealtime() + FAILED_ATTEMPT_TIMEOUT_MS; + public long setLockoutAttemptDeadline(int userId, int timeoutMs) { + final long deadline = SystemClock.elapsedRealtime() + timeoutMs; setLong(LOCKOUT_ATTEMPT_DEADLINE, deadline, userId); + setLong(LOCKOUT_ATTEMPT_TIMEOUT_MS, timeoutMs, userId); return deadline; } @@ -1005,8 +1054,9 @@ public class LockPatternUtils { */ public long getLockoutAttemptDeadline(int userId) { final long deadline = getLong(LOCKOUT_ATTEMPT_DEADLINE, 0L, userId); + final long timeoutMs = getLong(LOCKOUT_ATTEMPT_TIMEOUT_MS, 0L, userId); final long now = SystemClock.elapsedRealtime(); - if (deadline < now || deadline > (now + FAILED_ATTEMPT_TIMEOUT_MS)) { + if (deadline < now || deadline > (now + timeoutMs)) { return 0L; } return deadline; diff --git a/core/java/com/android/internal/widget/VerifyCredentialResponse.aidl b/core/java/com/android/internal/widget/VerifyCredentialResponse.aidl new file mode 100644 index 0000000..59a4bba --- /dev/null +++ b/core/java/com/android/internal/widget/VerifyCredentialResponse.aidl @@ -0,0 +1,24 @@ +/* + * 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 com.android.internal.widget; + +/** + * Response object for an ILockSettings verification request. + * @hide + */ +parcelable VerifyCredentialResponse; + diff --git a/core/java/com/android/internal/widget/VerifyCredentialResponse.java b/core/java/com/android/internal/widget/VerifyCredentialResponse.java new file mode 100644 index 0000000..48109ca --- /dev/null +++ b/core/java/com/android/internal/widget/VerifyCredentialResponse.java @@ -0,0 +1,126 @@ +/* + * 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 com.android.internal.widget; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Response object for a ILockSettings credential verification request. + * @hide + */ +public final class VerifyCredentialResponse implements Parcelable { + + public static final int RESPONSE_ERROR = -1; + public static final int RESPONSE_OK = 0; + public static final int RESPONSE_RETRY = 1; + + public static final VerifyCredentialResponse OK = new VerifyCredentialResponse(); + public static final VerifyCredentialResponse ERROR + = new VerifyCredentialResponse(RESPONSE_ERROR, 0, null); + + private int mResponseCode; + private byte[] mPayload; + private int mTimeout; + + public static final Parcelable.Creator<VerifyCredentialResponse> CREATOR + = new Parcelable.Creator<VerifyCredentialResponse>() { + @Override + public VerifyCredentialResponse createFromParcel(Parcel source) { + int responseCode = source.readInt(); + VerifyCredentialResponse response = new VerifyCredentialResponse(responseCode, 0, null); + if (responseCode == RESPONSE_RETRY) { + response.setTimeout(source.readInt()); + } else if (responseCode == RESPONSE_OK) { + int size = source.readInt(); + if (size > 0) { + byte[] payload = new byte[size]; + source.readByteArray(payload); + response.setPayload(payload); + } + } + return response; + } + + @Override + public VerifyCredentialResponse[] newArray(int size) { + return new VerifyCredentialResponse[size]; + } + + }; + + public VerifyCredentialResponse() { + mResponseCode = RESPONSE_OK; + mPayload = null; + } + + + public VerifyCredentialResponse(byte[] payload) { + mPayload = payload; + mResponseCode = RESPONSE_OK; + } + + public VerifyCredentialResponse(int timeout) { + mTimeout = timeout; + mResponseCode = RESPONSE_RETRY; + mPayload = null; + } + + private VerifyCredentialResponse(int responseCode, int timeout, byte[] payload) { + mResponseCode = responseCode; + mTimeout = timeout; + mPayload = payload; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mResponseCode); + if (mResponseCode == RESPONSE_RETRY) { + dest.writeInt(mTimeout); + } else if (mResponseCode == RESPONSE_OK) { + if (mPayload != null) { + dest.writeInt(mPayload.length); + dest.writeByteArray(mPayload); + } + } + } + + @Override + public int describeContents() { + return 0; + } + + public byte[] getPayload() { + return mPayload; + } + + public int getTimeout() { + return mTimeout; + } + + public int getResponseCode() { + return mResponseCode; + } + + private void setTimeout(int timeout) { + mTimeout = timeout; + } + + private void setPayload(byte[] payload) { + mPayload = payload; + } +} diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java index 6295de4..4edc1c9 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java @@ -114,35 +114,40 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout if (mPendingLockCheck != null) { mPendingLockCheck.cancel(false); } + + if (entry.length() < MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) { + // to avoid accidental lockout, only count attempts that are long enough to be a + // real password. This may require some tweaking. + setPasswordEntryInputEnabled(true); + onPasswordChecked(entry, false, 0); + return; + } + mPendingLockCheck = LockPatternChecker.checkPassword( mLockPatternUtils, entry, KeyguardUpdateMonitor.getCurrentUser(), new LockPatternChecker.OnCheckCallback() { @Override - public void onChecked(boolean matched) { + public void onChecked(boolean matched, int timeoutMs) { setPasswordEntryInputEnabled(true); mPendingLockCheck = null; - onPasswordChecked(entry, matched); + onPasswordChecked(entry, matched, timeoutMs); } }); } - private void onPasswordChecked(String entry, boolean matched) { + private void onPasswordChecked(String entry, boolean matched, int timeoutMs) { if (matched) { - mCallback.reportUnlockAttempt(true); + mCallback.reportUnlockAttempt(true, 0); mCallback.dismiss(true); } else { - if (entry.length() > MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT ) { - // to avoid accidental lockout, only count attempts that are long enough to be a - // real password. This may require some tweaking. - mCallback.reportUnlockAttempt(false); - int attempts = KeyguardUpdateMonitor.getInstance(mContext).getFailedUnlockAttempts(); - if (0 == (attempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { - long deadline = mLockPatternUtils.setLockoutAttemptDeadline( - KeyguardUpdateMonitor.getCurrentUser()); - handleAttemptLockout(deadline); - } + mCallback.reportUnlockAttempt(false, timeoutMs); + int attempts = KeyguardUpdateMonitor.getInstance(mContext).getFailedUnlockAttempts(); + if (timeoutMs > 0) { + long deadline = mLockPatternUtils.setLockoutAttemptDeadline( + KeyguardUpdateMonitor.getCurrentUser(), timeoutMs); + handleAttemptLockout(deadline); } mSecurityMessageDisplay.setMessage(getWrongPasswordStringId(), true); } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java index 35c6873..ed595c0 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java @@ -224,23 +224,30 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit mPendingLockCheck.cancel(false); } + if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { + mLockPatternView.enableInput(); + onPatternChecked(pattern, false, 0); + return; + } + mPendingLockCheck = LockPatternChecker.checkPattern( mLockPatternUtils, pattern, KeyguardUpdateMonitor.getCurrentUser(), new LockPatternChecker.OnCheckCallback() { @Override - public void onChecked(boolean matched) { + public void onChecked(boolean matched, int timeoutMs) { mLockPatternView.enableInput(); mPendingLockCheck = null; - onPatternChecked(pattern, matched); + onPatternChecked(pattern, matched, timeoutMs); } }); } - private void onPatternChecked(List<LockPatternView.Cell> pattern, boolean matched) { + private void onPatternChecked(List<LockPatternView.Cell> pattern, boolean matched, + int timeoutMs) { if (matched) { - mCallback.reportUnlockAttempt(true); + mCallback.reportUnlockAttempt(true, 0); mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct); mCallback.dismiss(true); } else { @@ -248,16 +255,11 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit mCallback.userActivity(); } mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); - boolean registeredAttempt = - pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL; - if (registeredAttempt) { - mCallback.reportUnlockAttempt(false); - } + mCallback.reportUnlockAttempt(false, timeoutMs); int attempts = mKeyguardUpdateMonitor.getFailedUnlockAttempts(); - if (registeredAttempt && - 0 == (attempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { + if (timeoutMs > 0) { long deadline = mLockPatternUtils.setLockoutAttemptDeadline( - KeyguardUpdateMonitor.getCurrentUser()); + KeyguardUpdateMonitor.getCurrentUser(), timeoutMs); handleAttemptLockout(deadline); } else { mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern, true); diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityCallback.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityCallback.java index 5877bc8..836c195 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityCallback.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityCallback.java @@ -37,8 +37,10 @@ public interface KeyguardSecurityCallback { /** * Call to report an unlock attempt. * @param success set to 'true' if user correctly entered security credentials. + * @param timeoutMs timeout in milliseconds to wait before reattempting an unlock. + * Only nonzero if 'success' is false */ - void reportUnlockAttempt(boolean success); + void reportUnlockAttempt(boolean success, int timeoutMs); /** * Resets the keyguard view. diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java index 4d89a8d..d17b25a 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -176,8 +176,8 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe dialog.show(); } - private void showTimeoutDialog() { - int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000; + private void showTimeoutDialog(int timeoutMs) { + int timeoutInSeconds = (int) timeoutMs / 1000; int messageId = 0; switch (mSecurityModel.getSecurityMode()) { @@ -244,16 +244,7 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe showDialog(null, message); } - private void showAlmostAtAccountLoginDialog() { - final int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000; - final int count = LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET - - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT; - String message = mContext.getString(R.string.kg_failed_attempts_almost_at_login, - count, LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT, timeoutInSeconds); - showDialog(null, message); - } - - private void reportFailedUnlockAttempt() { + private void reportFailedUnlockAttempt(int timeoutMs) { final KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext); final int failedAttempts = monitor.getFailedUnlockAttempts() + 1; // +1 for this time @@ -290,14 +281,11 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe Slog.i(TAG, "Too many unlock attempts; user " + expiringUser + " will be wiped!"); showWipeDialog(failedAttempts, userType); } - } else { - showTimeout = - (failedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) == 0; } monitor.reportFailedUnlockAttempt(); mLockPatternUtils.reportFailedPasswordAttempt(KeyguardUpdateMonitor.getCurrentUser()); - if (showTimeout) { - showTimeoutDialog(); + if (timeoutMs > 0) { + showTimeoutDialog(timeoutMs); } } @@ -425,14 +413,14 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe return mIsVerifyUnlockOnly; } - public void reportUnlockAttempt(boolean success) { + public void reportUnlockAttempt(boolean success, int timeoutMs) { KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext); if (success) { monitor.clearFailedUnlockAttempts(); mLockPatternUtils.reportSuccessfulPasswordAttempt( KeyguardUpdateMonitor.getCurrentUser()); } else { - KeyguardSecurityContainer.this.reportFailedUnlockAttempt(); + KeyguardSecurityContainer.this.reportFailedUnlockAttempt(timeoutMs); } } @@ -448,7 +436,7 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe @Override public void userActivity() { } @Override - public void reportUnlockAttempt(boolean success) { } + public void reportUnlockAttempt(boolean success, int timeoutMs) { } @Override public boolean isVerifyUnlockOnly() { return false; } @Override diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java index 2df7f79..f6ca0d7 100644 --- a/services/core/java/com/android/server/LockSettingsService.java +++ b/services/core/java/com/android/server/LockSettingsService.java @@ -25,11 +25,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.UserInfo; - import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE; import static android.content.Context.USER_SERVICE; import static android.Manifest.permission.READ_PROFILE; - import android.database.sqlite.SQLiteDatabase; import android.os.Binder; import android.os.IBinder; @@ -44,6 +42,7 @@ import android.provider.Settings; import android.provider.Settings.Secure; import android.provider.Settings.SettingNotFoundException; import android.security.KeyStore; +import android.service.gatekeeper.GateKeeperResponse; import android.service.gatekeeper.IGateKeeperService; import android.text.TextUtils; import android.util.Slog; @@ -51,6 +50,7 @@ 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.internal.widget.VerifyCredentialResponse; import com.android.server.LockSettingsStorage.CredentialHash; import java.util.Arrays; @@ -76,6 +76,12 @@ public class LockSettingsService extends ILockSettings.Stub { private boolean mFirstCallToVold; private IGateKeeperService mGateKeeperService; + private interface CredentialUtil { + void setCredential(String credential, String savedCredential, int userId) + throws RemoteException; + byte[] toHash(String credential, int userId); + } + public LockSettingsService(Context context) { mContext = context; // Open the database @@ -468,155 +474,163 @@ public class LockSettingsService extends ILockSettings.Stub { byte[] toEnrollBytes = toEnroll == null ? null : toEnroll.getBytes(); - byte[] hash = getGateKeeperService().enroll(userId, enrolledHandle, enrolledCredentialBytes, - toEnrollBytes); + GateKeeperResponse response = getGateKeeperService().enroll(userId, enrolledHandle, + enrolledCredentialBytes, toEnrollBytes); + if (response == null) { + return null; + } + + byte[] hash = response.getPayload(); if (hash != null) { setKeystorePassword(toEnroll, userId); + } else { + // Should not happen + Slog.e(TAG, "Throttled while enrolling a password"); } - return hash; } @Override - public boolean checkPattern(String pattern, int userId) throws RemoteException { - try { - doVerifyPattern(pattern, false, 0, userId); - } catch (VerificationFailedException ex) { - return false; - } - - return true; + public VerifyCredentialResponse checkPattern(String pattern, int userId) throws RemoteException { + return doVerifyPattern(pattern, false, 0, userId); } @Override - public byte[] verifyPattern(String pattern, long challenge, int userId) + public VerifyCredentialResponse verifyPattern(String pattern, long challenge, int userId) throws RemoteException { - try { - return doVerifyPattern(pattern, true, challenge, userId); - } catch (VerificationFailedException ex) { - return null; - } + return doVerifyPattern(pattern, true, challenge, userId); } - private byte[] doVerifyPattern(String pattern, boolean hasChallenge, long challenge, - int userId) throws VerificationFailedException, RemoteException { + private VerifyCredentialResponse doVerifyPattern(String pattern, boolean hasChallenge, long challenge, + int userId) throws 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) { - byte[] hash = mLockPatternUtils.patternToHash( - mLockPatternUtils.stringToPattern(pattern)); - if (Arrays.equals(hash, storedHash.hash)) { - unlockKeystore(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 - unlockKeystore(pattern, userId); - return token; - + return verifyCredential(userId, storedHash, pattern, hasChallenge, + challenge, + new CredentialUtil() { + @Override + public void setCredential(String pattern, String oldPattern, int userId) + throws RemoteException { + setLockPattern(pattern, oldPattern, userId); + } + + @Override + public byte[] toHash(String pattern, int userId) { + return mLockPatternUtils.patternToHash( + mLockPatternUtils.stringToPattern(pattern)); + } + } + ); } @Override - public boolean checkPassword(String password, int userId) throws RemoteException { - try { - doVerifyPassword(password, false, 0, userId); - } catch (VerificationFailedException ex) { - return false; - } - return true; + public VerifyCredentialResponse checkPassword(String password, int userId) + throws RemoteException { + return doVerifyPassword(password, false, 0, userId); } @Override - public byte[] verifyPassword(String password, long challenge, int userId) + public VerifyCredentialResponse verifyPassword(String password, long challenge, int userId) throws RemoteException { - try { - return doVerifyPassword(password, true, challenge, userId); - } catch (VerificationFailedException ex) { - return null; - } + return doVerifyPassword(password, true, challenge, userId); } - private byte[] doVerifyPassword(String password, boolean hasChallenge, long challenge, - int userId) throws VerificationFailedException, RemoteException { + private VerifyCredentialResponse doVerifyPassword(String password, boolean hasChallenge, + long challenge, int userId) throws RemoteException { checkPasswordReadPermission(userId); - CredentialHash storedHash = mStorage.readPasswordHash(userId); + return verifyCredential(userId, storedHash, password, hasChallenge, challenge, + new CredentialUtil() { + @Override + public void setCredential(String password, String oldPassword, int userId) + throws RemoteException { + setLockPassword(password, oldPassword, userId); + } - if ((storedHash == null || storedHash.hash.length == 0) && TextUtils.isEmpty(password)) { - // don't need to pass empty passwords to GateKeeper - return null; + @Override + public byte[] toHash(String password, int userId) { + return mLockPatternUtils.passwordToHash(password, userId); + } + } + ); + } + + private VerifyCredentialResponse verifyCredential(int userId, CredentialHash storedHash, + String credential, boolean hasChallenge, long challenge, CredentialUtil credentialUtil) + throws RemoteException { + if ((storedHash == null || storedHash.hash.length == 0) && TextUtils.isEmpty(credential)) { + // don't need to pass empty credentials to GateKeeper + return VerifyCredentialResponse.OK; } - if (TextUtils.isEmpty(password)) { - throw new VerificationFailedException(); + if (TextUtils.isEmpty(credential)) { + return VerifyCredentialResponse.ERROR; } if (storedHash.version == CredentialHash.VERSION_LEGACY) { - byte[] hash = mLockPatternUtils.passwordToHash(password, userId); + byte[] hash = credentialUtil.toHash(credential, userId); if (Arrays.equals(hash, storedHash.hash)) { - unlockKeystore(password, userId); - // migrate password to GateKeeper - setLockPassword(password, null, userId); + unlockKeystore(credential, userId); + // migrate credential to GateKeeper + credentialUtil.setCredential(credential, null, userId); if (!hasChallenge) { - return null; + return VerifyCredentialResponse.OK; } // 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 + // as a user that had a legacy credential would have to unlock their device // before getting to a flow with a challenge, but supporting for consistency. } else { - throw new VerificationFailedException(); + return VerifyCredentialResponse.ERROR; } } - byte[] token = null; + VerifyCredentialResponse response; + boolean shouldReEnroll = false;; if (hasChallenge) { - token = getGateKeeperService() - .verifyChallenge(userId, challenge, storedHash.hash, password.getBytes()); - if (token == null) { - throw new VerificationFailedException(); + byte[] token = null; + GateKeeperResponse gateKeeperResponse = getGateKeeperService() + .verifyChallenge(userId, challenge, storedHash.hash, credential.getBytes()); + int responseCode = gateKeeperResponse.getResponseCode(); + if (responseCode == GateKeeperResponse.RESPONSE_RETRY) { + response = new VerifyCredentialResponse(gateKeeperResponse.getTimeout()); + } else if (responseCode == GateKeeperResponse.RESPONSE_OK) { + token = gateKeeperResponse.getPayload(); + if (token == null) { + // something's wrong if there's no payload with a challenge + Slog.e(TAG, "verifyChallenge response had no associated payload"); + response = VerifyCredentialResponse.ERROR; + } else { + shouldReEnroll = gateKeeperResponse.getShouldReEnroll(); + response = new VerifyCredentialResponse(token); + } + } else { + response = VerifyCredentialResponse.ERROR; + } + } else { + GateKeeperResponse gateKeeperResponse = getGateKeeperService().verify( + userId, storedHash.hash, credential.getBytes()); + int responseCode = gateKeeperResponse.getResponseCode(); + if (responseCode == GateKeeperResponse.RESPONSE_RETRY) { + response = new VerifyCredentialResponse(gateKeeperResponse.getTimeout()); + } else if (responseCode == GateKeeperResponse.RESPONSE_OK) { + shouldReEnroll = gateKeeperResponse.getShouldReEnroll(); + response = VerifyCredentialResponse.OK; + } else { + response = VerifyCredentialResponse.ERROR; } - } else if (!getGateKeeperService().verify(userId, storedHash.hash, password.getBytes())) { - throw new VerificationFailedException(); } - // password has matched - unlockKeystore(password, userId); - return token; - } + if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) { + // credential has matched + unlockKeystore(credential, userId); + if (shouldReEnroll) { + credentialUtil.setCredential(credential, credential, userId); + } + } + return response; + } @Override public boolean checkVoldPassword(int userId) throws RemoteException { @@ -644,7 +658,8 @@ public class LockSettingsService extends ILockSettings.Stub { try { if (mLockPatternUtils.isLockPatternEnabled(userId)) { - if (checkPattern(password, userId)) { + if (checkPattern(password, userId).getResponseCode() + == GateKeeperResponse.RESPONSE_OK) { return true; } } @@ -653,7 +668,8 @@ public class LockSettingsService extends ILockSettings.Stub { try { if (mLockPatternUtils.isLockPasswordEnabled(userId)) { - if (checkPassword(password, userId)) { + if (checkPassword(password, userId).getResponseCode() + == GateKeeperResponse.RESPONSE_OK) { return true; } } @@ -741,6 +757,4 @@ public class LockSettingsService extends ILockSettings.Stub { return null; } - private class VerificationFailedException extends Exception {} - } |