Merge "[LockSettings] migrate password attempt throttling to hardware" into mnc-dev

This commit is contained in:
Andres Morales
2015-05-27 18:37:21 +00:00
committed by Android (Google) Code Review
13 changed files with 595 additions and 202 deletions

View File

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

View File

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

View File

@@ -16,6 +16,8 @@
package android.service.gatekeeper; package android.service.gatekeeper;
import android.service.gatekeeper.GateKeeperResponse;
/** /**
* Interface for communication with GateKeeper, the * Interface for communication with GateKeeper, the
* secure password storage daemon. * secure password storage daemon.
@@ -34,9 +36,9 @@ interface IGateKeeperService {
* If provided, must verify against the currentPasswordHandle. * If provided, must verify against the currentPasswordHandle.
* @param desiredPassword The new desired password, for which a handle will be returned * @param desiredPassword The new desired password, for which a handle will be returned
* upon success. * 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); in byte[] desiredPassword);
/** /**
@@ -45,10 +47,10 @@ interface IGateKeeperService {
* @param enrolledPasswordHandle The handle against which the provided password will be * @param enrolledPasswordHandle The handle against which the provided password will be
* verified. * verified.
* @param The plaintext blob to verify against enrolledPassword. * @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, GateKeeperResponse verify(int uid, in byte[] enrolledPasswordHandle, in byte[] providedPassword);
in byte[] providedPassword);
/** /**
* Verifies an enrolled handle against a provided, plaintext blob. * Verifies an enrolled handle against a provided, plaintext blob.
* @param uid The Android user ID associated to this enrollment * @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 * @param enrolledPasswordHandle The handle against which the provided password will be
* verified. * verified.
* @param The plaintext blob to verify against enrolledPassword. * @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); in byte[] providedPassword);
/** /**

View File

@@ -16,6 +16,8 @@
package com.android.internal.widget; package com.android.internal.widget;
import com.android.internal.widget.VerifyCredentialResponse;
/** {@hide} */ /** {@hide} */
interface ILockSettings { interface ILockSettings {
void setBoolean(in String key, in boolean value, in int userId); 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); long getLong(in String key, in long defaultValue, in int userId);
String getString(in String key, in String defaultValue, in int userId); String getString(in String key, in String defaultValue, in int userId);
void setLockPattern(in String pattern, in String savedPattern, int userId); void setLockPattern(in String pattern, in String savedPattern, int userId);
boolean checkPattern(in String pattern, int userId); VerifyCredentialResponse checkPattern(in String pattern, int userId);
byte[] verifyPattern(in String pattern, long challenge, int userId); VerifyCredentialResponse verifyPattern(in String pattern, long challenge, int userId);
void setLockPassword(in String password, in String savedPassword, int userId); void setLockPassword(in String password, in String savedPassword, int userId);
boolean checkPassword(in String password, int userId); VerifyCredentialResponse checkPassword(in String password, int userId);
byte[] verifyPassword(in String password, long challenge, int userId); VerifyCredentialResponse verifyPassword(in String password, long challenge, int userId);
boolean checkVoldPassword(int userId); boolean checkVoldPassword(int userId);
boolean havePattern(int userId); boolean havePattern(int userId);
boolean havePassword(int userId); boolean havePassword(int userId);

View File

@@ -2,6 +2,8 @@ package com.android.internal.widget;
import android.os.AsyncTask; import android.os.AsyncTask;
import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
import java.util.List; import java.util.List;
/** /**
@@ -16,8 +18,10 @@ public final class LockPatternChecker {
* Invoked when a security check is finished. * Invoked when a security check is finished.
* *
* @param matched Whether the PIN/Password/Pattern matches the stored one. * @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. * Invoked when a security verification is finished.
* *
* @param attestation The attestation that the challenge was verified, or null. * @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 int userId,
final OnVerifyCallback callback) { final OnVerifyCallback callback) {
AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() { AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() {
private int mThrottleTimeout;
@Override @Override
protected byte[] doInBackground(Void... args) { 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 @Override
protected void onPostExecute(byte[] result) { protected void onPostExecute(byte[] result) {
callback.onVerified(result); callback.onVerified(result, mThrottleTimeout);
} }
}; };
task.execute(); task.execute();
@@ -74,14 +87,21 @@ public final class LockPatternChecker {
final int userId, final int userId,
final OnCheckCallback callback) { final OnCheckCallback callback) {
AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() { AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() {
private int mThrottleTimeout;
@Override @Override
protected Boolean doInBackground(Void... args) { 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 @Override
protected void onPostExecute(Boolean result) { protected void onPostExecute(Boolean result) {
callback.onChecked(result); callback.onChecked(result, mThrottleTimeout);
} }
}; };
task.execute(); task.execute();
@@ -103,14 +123,21 @@ public final class LockPatternChecker {
final int userId, final int userId,
final OnVerifyCallback callback) { final OnVerifyCallback callback) {
AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() { AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() {
private int mThrottleTimeout;
@Override @Override
protected byte[] doInBackground(Void... args) { 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 @Override
protected void onPostExecute(byte[] result) { protected void onPostExecute(byte[] result) {
callback.onVerified(result); callback.onVerified(result, mThrottleTimeout);
} }
}; };
task.execute(); task.execute();
@@ -130,14 +157,21 @@ public final class LockPatternChecker {
final int userId, final int userId,
final OnCheckCallback callback) { final OnCheckCallback callback) {
AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() { AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() {
private int mThrottleTimeout;
@Override @Override
protected Boolean doInBackground(Void... args) { 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 @Override
protected void onPostExecute(Boolean result) { protected void onPostExecute(Boolean result) {
callback.onChecked(result); callback.onChecked(result, mThrottleTimeout);
} }
}; };
task.execute(); task.execute();

View File

@@ -59,24 +59,12 @@ public class LockPatternUtils {
private static final String TAG = "LockPatternUtils"; private static final String TAG = "LockPatternUtils";
private static final boolean DEBUG = false; 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 * The number of incorrect attempts before which we fall back on an alternative
* method of verifying the user, and resetting their lock pattern. * method of verifying the user, and resetting their lock pattern.
*/ */
public static final int FAILED_ATTEMPTS_BEFORE_RESET = 20; 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. * The interval of the countdown for showing progress of the lockout.
*/ */
@@ -109,6 +97,7 @@ public class LockPatternUtils {
@Deprecated @Deprecated
public final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently"; public final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently";
public final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline"; 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 PATTERN_EVER_CHOSEN_KEY = "lockscreen.patterneverchosen";
public final static String PASSWORD_TYPE_KEY = "lockscreen.password_type"; public final static String PASSWORD_TYPE_KEY = "lockscreen.password_type";
@Deprecated @Deprecated
@@ -144,6 +133,23 @@ public class LockPatternUtils {
private DevicePolicyManager mDevicePolicyManager; private DevicePolicyManager mDevicePolicyManager;
private ILockSettings mLockSettingsService; 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() { public DevicePolicyManager getDevicePolicyManager() {
if (mDevicePolicyManager == null) { if (mDevicePolicyManager == null) {
mDevicePolicyManager = mDevicePolicyManager =
@@ -239,9 +245,23 @@ public class LockPatternUtils {
* @param challenge The challenge to verify against the pattern * @param challenge The challenge to verify against the pattern
* @return the attestation that the challenge was verified, or null. * @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 { 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) { } catch (RemoteException re) {
return null; return null;
} }
@@ -253,9 +273,19 @@ public class LockPatternUtils {
* @param pattern The pattern to check. * @param pattern The pattern to check.
* @return Whether the pattern matches the stored one. * @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 { 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) { } catch (RemoteException re) {
return true; return true;
} }
@@ -270,9 +300,19 @@ public class LockPatternUtils {
* @param challenge The challenge to verify against the password * @param challenge The challenge to verify against the password
* @return the attestation that the challenge was verified, or null. * @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 { 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) { } catch (RemoteException re) {
return null; return null;
} }
@@ -284,9 +324,17 @@ public class LockPatternUtils {
* @param password The password to check. * @param password The password to check.
* @return Whether the password matches the stored one. * @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 { 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) { } catch (RemoteException re) {
return true; return true;
} }
@@ -992,9 +1040,10 @@ public class LockPatternUtils {
* pattern until the deadline has passed. * pattern until the deadline has passed.
* @return the chosen deadline. * @return the chosen deadline.
*/ */
public long setLockoutAttemptDeadline(int userId) { public long setLockoutAttemptDeadline(int userId, int timeoutMs) {
final long deadline = SystemClock.elapsedRealtime() + FAILED_ATTEMPT_TIMEOUT_MS; final long deadline = SystemClock.elapsedRealtime() + timeoutMs;
setLong(LOCKOUT_ATTEMPT_DEADLINE, deadline, userId); setLong(LOCKOUT_ATTEMPT_DEADLINE, deadline, userId);
setLong(LOCKOUT_ATTEMPT_TIMEOUT_MS, timeoutMs, userId);
return deadline; return deadline;
} }
@@ -1005,8 +1054,9 @@ public class LockPatternUtils {
*/ */
public long getLockoutAttemptDeadline(int userId) { public long getLockoutAttemptDeadline(int userId) {
final long deadline = getLong(LOCKOUT_ATTEMPT_DEADLINE, 0L, 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(); final long now = SystemClock.elapsedRealtime();
if (deadline < now || deadline > (now + FAILED_ATTEMPT_TIMEOUT_MS)) { if (deadline < now || deadline > (now + timeoutMs)) {
return 0L; return 0L;
} }
return deadline; return deadline;

View File

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

View File

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

View File

@@ -114,35 +114,40 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout
if (mPendingLockCheck != null) { if (mPendingLockCheck != null) {
mPendingLockCheck.cancel(false); 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( mPendingLockCheck = LockPatternChecker.checkPassword(
mLockPatternUtils, mLockPatternUtils,
entry, entry,
KeyguardUpdateMonitor.getCurrentUser(), KeyguardUpdateMonitor.getCurrentUser(),
new LockPatternChecker.OnCheckCallback() { new LockPatternChecker.OnCheckCallback() {
@Override @Override
public void onChecked(boolean matched) { public void onChecked(boolean matched, int timeoutMs) {
setPasswordEntryInputEnabled(true); setPasswordEntryInputEnabled(true);
mPendingLockCheck = null; 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) { if (matched) {
mCallback.reportUnlockAttempt(true); mCallback.reportUnlockAttempt(true, 0);
mCallback.dismiss(true); mCallback.dismiss(true);
} else { } else {
if (entry.length() > MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT ) { mCallback.reportUnlockAttempt(false, timeoutMs);
// to avoid accidental lockout, only count attempts that are long enough to be a int attempts = KeyguardUpdateMonitor.getInstance(mContext).getFailedUnlockAttempts();
// real password. This may require some tweaking. if (timeoutMs > 0) {
mCallback.reportUnlockAttempt(false); long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
int attempts = KeyguardUpdateMonitor.getInstance(mContext).getFailedUnlockAttempts(); KeyguardUpdateMonitor.getCurrentUser(), timeoutMs);
if (0 == (attempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { handleAttemptLockout(deadline);
long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
KeyguardUpdateMonitor.getCurrentUser());
handleAttemptLockout(deadline);
}
} }
mSecurityMessageDisplay.setMessage(getWrongPasswordStringId(), true); mSecurityMessageDisplay.setMessage(getWrongPasswordStringId(), true);
} }

View File

@@ -224,23 +224,30 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
mPendingLockCheck.cancel(false); mPendingLockCheck.cancel(false);
} }
if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
mLockPatternView.enableInput();
onPatternChecked(pattern, false, 0);
return;
}
mPendingLockCheck = LockPatternChecker.checkPattern( mPendingLockCheck = LockPatternChecker.checkPattern(
mLockPatternUtils, mLockPatternUtils,
pattern, pattern,
KeyguardUpdateMonitor.getCurrentUser(), KeyguardUpdateMonitor.getCurrentUser(),
new LockPatternChecker.OnCheckCallback() { new LockPatternChecker.OnCheckCallback() {
@Override @Override
public void onChecked(boolean matched) { public void onChecked(boolean matched, int timeoutMs) {
mLockPatternView.enableInput(); mLockPatternView.enableInput();
mPendingLockCheck = null; 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) { if (matched) {
mCallback.reportUnlockAttempt(true); mCallback.reportUnlockAttempt(true, 0);
mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct); mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
mCallback.dismiss(true); mCallback.dismiss(true);
} else { } else {
@@ -248,16 +255,11 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
mCallback.userActivity(); mCallback.userActivity();
} }
mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
boolean registeredAttempt = mCallback.reportUnlockAttempt(false, timeoutMs);
pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL;
if (registeredAttempt) {
mCallback.reportUnlockAttempt(false);
}
int attempts = mKeyguardUpdateMonitor.getFailedUnlockAttempts(); int attempts = mKeyguardUpdateMonitor.getFailedUnlockAttempts();
if (registeredAttempt && if (timeoutMs > 0) {
0 == (attempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
long deadline = mLockPatternUtils.setLockoutAttemptDeadline( long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
KeyguardUpdateMonitor.getCurrentUser()); KeyguardUpdateMonitor.getCurrentUser(), timeoutMs);
handleAttemptLockout(deadline); handleAttemptLockout(deadline);
} else { } else {
mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern, true); mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern, true);

View File

@@ -37,8 +37,10 @@ public interface KeyguardSecurityCallback {
/** /**
* Call to report an unlock attempt. * Call to report an unlock attempt.
* @param success set to 'true' if user correctly entered security credentials. * @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. * Resets the keyguard view.

View File

@@ -176,8 +176,8 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
dialog.show(); dialog.show();
} }
private void showTimeoutDialog() { private void showTimeoutDialog(int timeoutMs) {
int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000; int timeoutInSeconds = (int) timeoutMs / 1000;
int messageId = 0; int messageId = 0;
switch (mSecurityModel.getSecurityMode()) { switch (mSecurityModel.getSecurityMode()) {
@@ -244,16 +244,7 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
showDialog(null, message); showDialog(null, message);
} }
private void showAlmostAtAccountLoginDialog() { private void reportFailedUnlockAttempt(int timeoutMs) {
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() {
final KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext); final KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
final int failedAttempts = monitor.getFailedUnlockAttempts() + 1; // +1 for this time 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!"); Slog.i(TAG, "Too many unlock attempts; user " + expiringUser + " will be wiped!");
showWipeDialog(failedAttempts, userType); showWipeDialog(failedAttempts, userType);
} }
} else {
showTimeout =
(failedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) == 0;
} }
monitor.reportFailedUnlockAttempt(); monitor.reportFailedUnlockAttempt();
mLockPatternUtils.reportFailedPasswordAttempt(KeyguardUpdateMonitor.getCurrentUser()); mLockPatternUtils.reportFailedPasswordAttempt(KeyguardUpdateMonitor.getCurrentUser());
if (showTimeout) { if (timeoutMs > 0) {
showTimeoutDialog(); showTimeoutDialog(timeoutMs);
} }
} }
@@ -425,14 +413,14 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
return mIsVerifyUnlockOnly; return mIsVerifyUnlockOnly;
} }
public void reportUnlockAttempt(boolean success) { public void reportUnlockAttempt(boolean success, int timeoutMs) {
KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext); KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
if (success) { if (success) {
monitor.clearFailedUnlockAttempts(); monitor.clearFailedUnlockAttempts();
mLockPatternUtils.reportSuccessfulPasswordAttempt( mLockPatternUtils.reportSuccessfulPasswordAttempt(
KeyguardUpdateMonitor.getCurrentUser()); KeyguardUpdateMonitor.getCurrentUser());
} else { } else {
KeyguardSecurityContainer.this.reportFailedUnlockAttempt(); KeyguardSecurityContainer.this.reportFailedUnlockAttempt(timeoutMs);
} }
} }
@@ -448,7 +436,7 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
@Override @Override
public void userActivity() { } public void userActivity() { }
@Override @Override
public void reportUnlockAttempt(boolean success) { } public void reportUnlockAttempt(boolean success, int timeoutMs) { }
@Override @Override
public boolean isVerifyUnlockOnly() { return false; } public boolean isVerifyUnlockOnly() { return false; }
@Override @Override

View File

@@ -25,11 +25,9 @@ import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.UserInfo; import android.content.pm.UserInfo;
import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE; import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE;
import static android.content.Context.USER_SERVICE; import static android.content.Context.USER_SERVICE;
import static android.Manifest.permission.READ_PROFILE; import static android.Manifest.permission.READ_PROFILE;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.os.Binder; import android.os.Binder;
import android.os.IBinder; import android.os.IBinder;
@@ -44,6 +42,7 @@ import android.provider.Settings;
import android.provider.Settings.Secure; import android.provider.Settings.Secure;
import android.provider.Settings.SettingNotFoundException; import android.provider.Settings.SettingNotFoundException;
import android.security.KeyStore; import android.security.KeyStore;
import android.service.gatekeeper.GateKeeperResponse;
import android.service.gatekeeper.IGateKeeperService; import android.service.gatekeeper.IGateKeeperService;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Slog; import android.util.Slog;
@@ -51,6 +50,7 @@ import android.util.Slog;
import com.android.internal.util.ArrayUtils; import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.ILockSettings; import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.server.LockSettingsStorage.CredentialHash; import com.android.server.LockSettingsStorage.CredentialHash;
import java.util.Arrays; import java.util.Arrays;
@@ -76,6 +76,12 @@ public class LockSettingsService extends ILockSettings.Stub {
private boolean mFirstCallToVold; private boolean mFirstCallToVold;
private IGateKeeperService mGateKeeperService; 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) { public LockSettingsService(Context context) {
mContext = context; mContext = context;
// Open the database // Open the database
@@ -468,155 +474,163 @@ public class LockSettingsService extends ILockSettings.Stub {
byte[] toEnrollBytes = toEnroll == null byte[] toEnrollBytes = toEnroll == null
? null ? null
: toEnroll.getBytes(); : toEnroll.getBytes();
byte[] hash = getGateKeeperService().enroll(userId, enrolledHandle, enrolledCredentialBytes, GateKeeperResponse response = getGateKeeperService().enroll(userId, enrolledHandle,
toEnrollBytes); enrolledCredentialBytes, toEnrollBytes);
if (hash != null) { if (response == null) {
setKeystorePassword(toEnroll, userId); 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; return hash;
} }
@Override @Override
public boolean checkPattern(String pattern, int userId) throws RemoteException { public VerifyCredentialResponse checkPattern(String pattern, int userId) throws RemoteException {
try { return doVerifyPattern(pattern, false, 0, userId);
doVerifyPattern(pattern, false, 0, userId);
} catch (VerificationFailedException ex) {
return false;
}
return true;
} }
@Override @Override
public byte[] verifyPattern(String pattern, long challenge, int userId) public VerifyCredentialResponse verifyPattern(String pattern, long challenge, int userId)
throws RemoteException { throws RemoteException {
try { return doVerifyPattern(pattern, true, challenge, userId);
return doVerifyPattern(pattern, true, challenge, userId);
} catch (VerificationFailedException ex) {
return null;
}
} }
private byte[] doVerifyPattern(String pattern, boolean hasChallenge, long challenge, private VerifyCredentialResponse doVerifyPattern(String pattern, boolean hasChallenge, long challenge,
int userId) throws VerificationFailedException, RemoteException { int userId) throws RemoteException {
checkPasswordReadPermission(userId); checkPasswordReadPermission(userId);
CredentialHash storedHash = mStorage.readPatternHash(userId); CredentialHash storedHash = mStorage.readPatternHash(userId);
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);
}
if ((storedHash == null || storedHash.hash.length == 0) && TextUtils.isEmpty(pattern)) { @Override
// don't need to pass empty passwords to GateKeeper public byte[] toHash(String pattern, int userId) {
return null; return mLockPatternUtils.patternToHash(
} mLockPatternUtils.stringToPattern(pattern));
}
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;
} }
@Override @Override
public boolean checkPassword(String password, int userId) throws RemoteException { public VerifyCredentialResponse checkPassword(String password, int 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 { throws RemoteException {
try { return doVerifyPassword(password, false, 0, userId);
return doVerifyPassword(password, true, challenge, userId);
} catch (VerificationFailedException ex) {
return null;
}
} }
private byte[] doVerifyPassword(String password, boolean hasChallenge, long challenge, @Override
int userId) throws VerificationFailedException, RemoteException { public VerifyCredentialResponse verifyPassword(String password, long challenge, int userId)
throws RemoteException {
return doVerifyPassword(password, true, challenge, userId);
}
private VerifyCredentialResponse doVerifyPassword(String password, boolean hasChallenge,
long challenge, int userId) throws RemoteException {
checkPasswordReadPermission(userId); checkPasswordReadPermission(userId);
CredentialHash storedHash = mStorage.readPasswordHash(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)) { @Override
// don't need to pass empty passwords to GateKeeper public byte[] toHash(String password, int userId) {
return null; 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)) { if (TextUtils.isEmpty(credential)) {
throw new VerificationFailedException(); return VerifyCredentialResponse.ERROR;
} }
if (storedHash.version == CredentialHash.VERSION_LEGACY) { if (storedHash.version == CredentialHash.VERSION_LEGACY) {
byte[] hash = mLockPatternUtils.passwordToHash(password, userId); byte[] hash = credentialUtil.toHash(credential, userId);
if (Arrays.equals(hash, storedHash.hash)) { if (Arrays.equals(hash, storedHash.hash)) {
unlockKeystore(password, userId); unlockKeystore(credential, userId);
// migrate password to GateKeeper // migrate credential to GateKeeper
setLockPassword(password, null, userId); credentialUtil.setCredential(credential, null, userId);
if (!hasChallenge) { if (!hasChallenge) {
return null; return VerifyCredentialResponse.OK;
} }
// Fall through to get the auth token. Technically this should never happen, // 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. // before getting to a flow with a challenge, but supporting for consistency.
} else { } else {
throw new VerificationFailedException(); return VerifyCredentialResponse.ERROR;
} }
} }
byte[] token = null; VerifyCredentialResponse response;
boolean shouldReEnroll = false;;
if (hasChallenge) { if (hasChallenge) {
token = getGateKeeperService() byte[] token = null;
.verifyChallenge(userId, challenge, storedHash.hash, password.getBytes()); GateKeeperResponse gateKeeperResponse = getGateKeeperService()
if (token == null) { .verifyChallenge(userId, challenge, storedHash.hash, credential.getBytes());
throw new VerificationFailedException(); 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 if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
unlockKeystore(password, userId); // credential has matched
return token; unlockKeystore(credential, userId);
} if (shouldReEnroll) {
credentialUtil.setCredential(credential, credential, userId);
}
}
return response;
}
@Override @Override
public boolean checkVoldPassword(int userId) throws RemoteException { public boolean checkVoldPassword(int userId) throws RemoteException {
@@ -644,7 +658,8 @@ public class LockSettingsService extends ILockSettings.Stub {
try { try {
if (mLockPatternUtils.isLockPatternEnabled(userId)) { if (mLockPatternUtils.isLockPatternEnabled(userId)) {
if (checkPattern(password, userId)) { if (checkPattern(password, userId).getResponseCode()
== GateKeeperResponse.RESPONSE_OK) {
return true; return true;
} }
} }
@@ -653,7 +668,8 @@ public class LockSettingsService extends ILockSettings.Stub {
try { try {
if (mLockPatternUtils.isLockPasswordEnabled(userId)) { if (mLockPatternUtils.isLockPasswordEnabled(userId)) {
if (checkPassword(password, userId)) { if (checkPassword(password, userId).getResponseCode()
== GateKeeperResponse.RESPONSE_OK) {
return true; return true;
} }
} }
@@ -741,6 +757,4 @@ public class LockSettingsService extends ILockSettings.Stub {
return null; return null;
} }
private class VerificationFailedException extends Exception {}
} }