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

Commit 4014800f authored by Eric Biggers's avatar Eric Biggers
Browse files

Return new error codes in VerifyCredentialResponse

If the SoftwareRateLimiter reports that the credential is too short or
was already tried, report this in the VerifyCredentialResponse so that
upper layers such as Keyguard will have access to this information.

Similarly, if Weaver reports INCORRECT_KEY and a timeout of zero, report
this as VerifyCredentialResponse#RESPONSE_CRED_INCORRECT so that
LockSettingsService#reportResultToSoftwareRateLimiter() and upper layers
such as Keyguard will have access to this information.

Note that although these new error codes will now be present in the
VerifyCredentialResponse, ultimately this does not change any other
behavior yet, since no non-test code treats RESPONSE_OTHER_ERROR and
unknown errors differently yet.  (Any such user would have come up in
the rename of RESPONSE_ERROR to RESPONSE_OTHER_ERROR.)  This CL just
makes it possible to handle different errors differently in later CLs.

Test: atest FrameworksServicesTests:com.android.server.locksettings
Bug: 395976735
Flag: android.security.software_ratelimiter
Change-Id: I5fdc2fc54b841dc5889c2f92b1f9f3220ad70d98
parent df165e3e
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -2479,9 +2479,14 @@ public class LockSettingsService extends ILockSettings.Stub {
                    case SoftwareRateLimiterResult.RATE_LIMITED:
                        return VerifyCredentialResponse.fromTimeout(res.remainingDelay);
                    case SoftwareRateLimiterResult.CREDENTIAL_TOO_SHORT:
                        return VerifyCredentialResponse.fromError(
                                VerifyCredentialResponse.RESPONSE_CRED_TOO_SHORT);
                    case SoftwareRateLimiterResult.DUPLICATE_WRONG_GUESS:
                        return VerifyCredentialResponse.fromError(
                                VerifyCredentialResponse.RESPONSE_CRED_ALREADY_TRIED);
                    default:
                        return VerifyCredentialResponse.fromError();
                        return VerifyCredentialResponse.fromError(
                                VerifyCredentialResponse.RESPONSE_OTHER_ERROR);
                }
            }
            if (isSpecialUserId(userId)) {
+17 −0
Original line number Diff line number Diff line
@@ -719,11 +719,28 @@ class SyntheticPasswordManager {
            case WeaverReadStatus.OK:
                return VerifyCredentialResponse.OK;
            case WeaverReadStatus.THROTTLE:
                // Either the credential could not be verified because a timeout is still active, or
                // the credential was incorrect and there is a timeout before the next attempt will
                // be allowed. INCORRECT_KEY is preferred in the latter case to avoid the ambiguity,
                // but we still have to support implementations that use THROTTLE for both cases.
                //
                // TODO(b/395976735): needs unit testing via MockWeaverService
                return responseFromTimeout(weaverResponse);
            case WeaverReadStatus.INCORRECT_KEY:
                if (weaverResponse.timeout != 0) {
                    // The credential was incorrect, and there is a timeout until the next attempt
                    // will be allowed. This is reached if the Weaver implementation returns
                    // INCORRECT_KEY in this case instead of THROTTLE.
                    //
                    // TODO(b/395976735): use RESPONSE_CRED_INCORRECT in this case, and update users
                    // of VerifyCredentialResponse to be compatible with that.
                    // TODO(b/395976735): needs unit testing via MockWeaverService
                    return responseFromTimeout(weaverResponse);
                }
                if (android.security.Flags.softwareRatelimiter()) {
                    return VerifyCredentialResponse.fromError(
                            VerifyCredentialResponse.RESPONSE_CRED_INCORRECT);
                }
                break;
        }
        return VerifyCredentialResponse.OTHER_ERROR;
+26 −0
Original line number Diff line number Diff line
@@ -656,6 +656,11 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
            VerifyCredentialResponse response =
                    mService.verifyCredential(wrongPin, userId, 0 /* flags */);
            assertFalse(response.isMatched());
            assertEquals(
                    i == 0
                            ? VerifyCredentialResponse.RESPONSE_CRED_INCORRECT
                            : VerifyCredentialResponse.RESPONSE_CRED_ALREADY_TRIED,
                    response.getResponseCode());
            assertEquals(0, response.getTimeout());
        }
        // The software and hardware counters should now be 1, for 1 unique guess.
@@ -685,6 +690,7 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
            VerifyCredentialResponse response =
                    mService.verifyCredential(wrongPin, userId, 0 /* flags */);
            assertFalse(response.isMatched());
            assertEquals(VerifyCredentialResponse.RESPONSE_OTHER_ERROR, response.getResponseCode());
        }
        // The software counter should still be 0, since the software rate-limiter is fully disabled
        // and thus it should have never been told about the guesses at all. The hardware counter
@@ -693,6 +699,26 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
        assertEquals(numGuesses, mSpManager.getSumOfWeaverFailureCounters());
    }

    @Test
    @EnableFlags(android.security.Flags.FLAG_SOFTWARE_RATELIMITER)
    public void testVerifyCredentialTooShort() throws Exception {
        final int userId = PRIMARY_USER_ID;
        setCredential(userId, newPassword("password"));
        VerifyCredentialResponse response =
                mService.verifyCredential(newPassword("a"), userId, /* flags= */ 0);
        assertEquals(VerifyCredentialResponse.RESPONSE_CRED_TOO_SHORT, response.getResponseCode());
    }

    @Test
    @DisableFlags(android.security.Flags.FLAG_SOFTWARE_RATELIMITER)
    public void testVerifyCredentialTooShort_softwareRateLimiterFlagDisabled() throws Exception {
        final int userId = PRIMARY_USER_ID;
        setCredential(userId, newPassword("password"));
        VerifyCredentialResponse response =
                mService.verifyCredential(newPassword("a"), userId, /* flags= */ 0);
        assertEquals(VerifyCredentialResponse.RESPONSE_OTHER_ERROR, response.getResponseCode());
    }

    private void checkRecordedFrpNotificationIntent() {
        if (android.security.Flags.frpEnforcement()) {
            Intent savedNotificationIntent = mService.getSavedFrpNotificationIntent();
+4 −0
Original line number Diff line number Diff line
@@ -145,6 +145,10 @@ public class MockSyntheticPasswordManager extends SyntheticPasswordManager {
        return mWeaverHidl;
    }

    public boolean isWeaverEnabled() {
        return mWeaverService != null;
    }

    public int getSumOfWeaverFailureCounters() {
        return mWeaverService.getSumOfFailureCounters();
    }
+30 −0
Original line number Diff line number Diff line
@@ -48,7 +48,10 @@ import static org.mockito.Mockito.when;
import android.app.admin.PasswordMetrics;
import android.content.pm.UserInfo;
import android.os.RemoteException;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -61,6 +64,7 @@ import com.android.server.locksettings.SyntheticPasswordManager.SyntheticPasswor

import libcore.util.HexEncoding;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -76,6 +80,8 @@ import java.util.Arrays;
@RunWith(AndroidJUnit4.class)
public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {

    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    public static final byte[] PAYLOAD = new byte[] {1, 2, -1, -2, 55};
    public static final byte[] PAYLOAD2 = new byte[] {2, 3, -2, -3, 44, 1};

@@ -179,6 +185,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
    }

    @Test
    @EnableFlags(android.security.Flags.FLAG_SOFTWARE_RATELIMITER)
    public void testVerifyCredential() throws RemoteException {
        LockscreenCredential password = newPassword("password");
        LockscreenCredential badPassword = newPassword("badpassword");
@@ -188,6 +195,29 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
                password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
        verify(mActivityManager).unlockUser2(eq(PRIMARY_USER_ID), any());

        int expectedResponseCode =
                mSpManager.isWeaverEnabled()
                        ? VerifyCredentialResponse.RESPONSE_CRED_INCORRECT
                        : VerifyCredentialResponse.RESPONSE_OTHER_ERROR;
        assertEquals(
                expectedResponseCode,
                mService.verifyCredential(badPassword, PRIMARY_USER_ID, 0 /* flags */)
                        .getResponseCode());
    }

    @Test
    @DisableFlags(android.security.Flags.FLAG_SOFTWARE_RATELIMITER)
    public void testVerifyCredential_softwareRateLimiterFlagDisabled() throws RemoteException {
        LockscreenCredential password = newPassword("password");
        LockscreenCredential badPassword = newPassword("badpassword");

        initSpAndSetCredential(PRIMARY_USER_ID, password);
        assertEquals(
                VerifyCredentialResponse.RESPONSE_OK,
                mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */)
                        .getResponseCode());
        verify(mActivityManager).unlockUser2(eq(PRIMARY_USER_ID), any());

        assertEquals(
                VerifyCredentialResponse.RESPONSE_OTHER_ERROR,
                mService.verifyCredential(badPassword, PRIMARY_USER_ID, 0 /* flags */)