Enforce minimum pattern length in PasswordMetrics

For patterns, currently PasswordMetrics#validateCredential() does
nothing except validate that the pattern credential type is allowed.
Pattern length is supposed to be >= MIN_LOCK_PATTERN_SIZE, but that is
hardcoded into the ChooseLockPattern activity in Settings.

Since there are other places that can set a pattern lockscreen
credential, such as LockSettingsShellCommand, let's add the pattern
length validation to PasswordMetrics#validatePasswordMetrics() so that
it gets done in the same place as PIN and password validation.

Note that this required starting to populate the length field of
PasswordMetrics created for patterns.

Bug: 219511761
Bug: 232900169
Bug: 243881358
Test: atest PasswordMetricsTest
Change-Id: I9f33f86045ea5f674b8ab79f9fda62c988436c53
This commit is contained in:
Eric Biggers
2023-06-27 22:27:26 +00:00
parent 14c9b2a587
commit e4c9d2de89
2 changed files with 34 additions and 11 deletions

View File

@@ -30,6 +30,7 @@ import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSW
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
import static com.android.internal.widget.LockPatternUtils.MIN_LOCK_PASSWORD_SIZE;
import static com.android.internal.widget.LockPatternUtils.MIN_LOCK_PATTERN_SIZE;
import static com.android.internal.widget.PasswordValidationError.CONTAINS_INVALID_CHARACTERS;
import static com.android.internal.widget.PasswordValidationError.CONTAINS_SEQUENCE;
import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_DIGITS;
@@ -201,7 +202,9 @@ public final class PasswordMetrics implements Parcelable {
return PasswordMetrics.computeForPasswordOrPin(credential.getCredential(),
credential.isPin());
} else if (credential.isPattern()) {
return new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
PasswordMetrics metrics = new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
metrics.length = credential.size();
return metrics;
} else if (credential.isNone()) {
return new PasswordMetrics(CREDENTIAL_TYPE_NONE);
} else {
@@ -529,13 +532,8 @@ public final class PasswordMetrics implements Parcelable {
return Collections.singletonList(
new PasswordValidationError(CONTAINS_INVALID_CHARACTERS, 0));
}
if (credential.isPassword() || credential.isPin()) {
return validatePassword(adminMetrics, minComplexity, credential.isPin(),
credential.getCredential());
} else {
return validatePasswordMetrics(adminMetrics, minComplexity,
new PasswordMetrics(credential.getType()));
}
PasswordMetrics actualMetrics = computeForCredential(credential);
return validatePasswordMetrics(adminMetrics, minComplexity, actualMetrics);
}
/**
@@ -584,9 +582,18 @@ public final class PasswordMetrics implements Parcelable {
|| !bucket.allowsCredType(actualMetrics.credType)) {
return Collections.singletonList(new PasswordValidationError(WEAK_CREDENTIAL_TYPE, 0));
}
if (actualMetrics.credType != CREDENTIAL_TYPE_PASSWORD
&& actualMetrics.credType != CREDENTIAL_TYPE_PIN) {
return Collections.emptyList(); // Nothing to check for pattern or none.
if (actualMetrics.credType == CREDENTIAL_TYPE_PATTERN) {
// For pattern, only need to check the length against the hardcoded minimum. If the
// pattern length is unavailable (e.g., PasswordMetrics that was stored on-disk before
// the pattern length started being included in it), assume it is okay.
if (actualMetrics.length != 0 && actualMetrics.length < MIN_LOCK_PATTERN_SIZE) {
return Collections.singletonList(new PasswordValidationError(TOO_SHORT,
MIN_LOCK_PATTERN_SIZE));
}
return Collections.emptyList();
}
if (actualMetrics.credType == CREDENTIAL_TYPE_NONE) {
return Collections.emptyList(); // Nothing to check for none.
}
if (actualMetrics.credType == CREDENTIAL_TYPE_PIN && actualMetrics.nonNumeric > 0) {

View File

@@ -42,6 +42,7 @@ import android.platform.test.annotations.Presubmit;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.PasswordValidationError;
@@ -419,6 +420,21 @@ public class PasswordMetricsTest {
PasswordValidationError.TOO_SHORT, 6);
}
private LockscreenCredential createPattern(String patternString) {
return LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(
patternString.getBytes()));
}
@Test
public void testValidateCredential_pattern() {
PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_NONE);
assertValidationErrors(
validateCredential(adminMetrics, PASSWORD_COMPLEXITY_NONE, createPattern("123")),
PasswordValidationError.TOO_SHORT, 4);
assertValidationErrors(
validateCredential(adminMetrics, PASSWORD_COMPLEXITY_NONE, createPattern("1234")));
}
/**
* @param expected sequence of validation error codes followed by requirement values, must have
* even number of elements. Empty means no errors.