Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6d5531ff authored by Eric Biggers's avatar Eric Biggers
Browse files

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
Merged-In: I9f33f86045ea5f674b8ab79f9fda62c988436c53
(cherry picked from commit e4c9d2de)
parent 27a062bc
Loading
Loading
Loading
Loading
+18 −11
Original line number Diff line number Diff line
@@ -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) {
+16 −0
Original line number Diff line number Diff line
@@ -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.