From 5a4e185a612d87e667ee62a329f10bfd01c23179 Mon Sep 17 00:00:00 2001 From: Haining Chen <hainingc@google.com> Date: Wed, 17 Jan 2024 11:26:04 -0800 Subject: [PATCH] Report primary auth attempts from LockSettingsService Report authentication attempts with PIN/pattern/password from LockSettingsService as the source of truth. Also add a new listener to allow other system components to response to primary auth results Bug: 285053096 Test: m -j Test: atest LockSettingsServiceTests Change-Id: I9115b70742cb724b373c028d8e00d178aa1a8096 --- .../widget/ILockSettingsStateListener.aidl | 36 ++++++++++ .../internal/widget/LockSettingsInternal.java | 12 ++++ .../locksettings/LockSettingsService.java | 46 ++++++++++++ .../LockSettingsServiceTests.java | 70 +++++++++++++++++++ 4 files changed, 164 insertions(+) create mode 100644 core/java/com/android/internal/widget/ILockSettingsStateListener.aidl diff --git a/core/java/com/android/internal/widget/ILockSettingsStateListener.aidl b/core/java/com/android/internal/widget/ILockSettingsStateListener.aidl new file mode 100644 index 000000000000..25e30034fe8f --- /dev/null +++ b/core/java/com/android/internal/widget/ILockSettingsStateListener.aidl @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 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; + +/** + * Callback interface between LockSettingService and other system services to be notified about the + * state of primary authentication (i.e. PIN/pattern/password). + * @hide + */ +oneway interface ILockSettingsStateListener { + /** + * Defines behavior in response to a successful authentication + * @param userId The user Id for the requested authentication + */ + void onAuthenticationSucceeded(int userId); + + /** + * Defines behavior in response to a failed authentication + * @param userId The user Id for the requested authentication + */ + void onAuthenticationFailed(int userId); +} \ No newline at end of file diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java index 8114e1fd3bb0..627e8779f9d0 100644 --- a/core/java/com/android/internal/widget/LockSettingsInternal.java +++ b/core/java/com/android/internal/widget/LockSettingsInternal.java @@ -166,4 +166,16 @@ public abstract class LockSettingsInternal { * Refreshes pending strong auth timeout with the latest admin requirement set by device policy. */ public abstract void refreshStrongAuthTimeout(int userId); + + /** + * Register a LockSettingsStateListener + * @param listener The listener to be registered + */ + public abstract void registerLockSettingsStateListener(ILockSettingsStateListener listener); + + /** + * Unregister a LockSettingsStateListener + * @param listener The listener to be unregistered + */ + public abstract void unregisterLockSettingsStateListener(ILockSettingsStateListener listener); } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index ad090829a2f6..c81ce26b6403 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -16,6 +16,7 @@ package com.android.server.locksettings; +import static android.security.Flags.reportPrimaryAuthAttempts; import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE; import static android.Manifest.permission.MANAGE_BIOMETRIC; import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS; @@ -90,6 +91,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.IProgressListener; import android.os.Process; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; @@ -135,6 +137,7 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.widget.ICheckCredentialProgressCallback; import com.android.internal.widget.ILockSettings; +import com.android.internal.widget.ILockSettingsStateListener; import com.android.internal.widget.IWeakEscrowTokenActivatedListener; import com.android.internal.widget.IWeakEscrowTokenRemovedListener; import com.android.internal.widget.LockPatternUtils; @@ -327,6 +330,9 @@ public class LockSettingsService extends ILockSettings.Stub { private HashMap<UserHandle, UserManager> mUserManagerCache = new HashMap<>(); + private final RemoteCallbackList<ILockSettingsStateListener> mLockSettingsStateListeners = + new RemoteCallbackList<>(); + // This class manages life cycle events for encrypted users on File Based Encryption (FBE) // devices. The most basic of these is to show/hide notifications about missing features until // the user unlocks the account and credential-encrypted storage is available. @@ -2342,9 +2348,37 @@ public class LockSettingsService extends ILockSettings.Stub { requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId); } } + if (reportPrimaryAuthAttempts()) { + final boolean success = + response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK; + notifyLockSettingsStateListeners(success, userId); + } return response; } + private void notifyLockSettingsStateListeners(boolean success, int userId) { + int i = mLockSettingsStateListeners.beginBroadcast(); + try { + while (i > 0) { + i--; + try { + if (success) { + mLockSettingsStateListeners.getBroadcastItem(i) + .onAuthenticationSucceeded(userId); + } else { + mLockSettingsStateListeners.getBroadcastItem(i) + .onAuthenticationFailed(userId); + } + } catch (RemoteException e) { + Slog.e(TAG, "Exception while notifying LockSettingsStateListener:" + + " success = " + success + ", userId = " + userId, e); + } + } + } finally { + mLockSettingsStateListeners.finishBroadcast(); + } + } + @Override public VerifyCredentialResponse verifyTiedProfileChallenge(LockscreenCredential credential, int userId, @LockPatternUtils.VerifyFlag int flags) { @@ -3662,6 +3696,18 @@ public class LockSettingsService extends ILockSettings.Stub { public void refreshStrongAuthTimeout(int userId) { mStrongAuth.refreshStrongAuthTimeout(userId); } + + @Override + public void registerLockSettingsStateListener( + @NonNull ILockSettingsStateListener listener) { + mLockSettingsStateListeners.register(listener); + } + + @Override + public void unregisterLockSettingsStateListener( + @NonNull ILockSettingsStateListener listener) { + mLockSettingsStateListeners.unregister(listener); + } } private class RebootEscrowCallbacks implements RebootEscrowManager.Callbacks { diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java index 5a62d92e8e12..5081198f0058 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java @@ -16,6 +16,8 @@ package com.android.server.locksettings; +import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS; + import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN; @@ -30,25 +32,30 @@ import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.PropertyInvalidatedCache; +import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.service.gatekeeper.GateKeeperResponse; import android.text.TextUtils; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.widget.ILockSettingsStateListener; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockscreenCredential; import com.android.internal.widget.VerifyCredentialResponse; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -59,6 +66,7 @@ import org.junit.runner.RunWith; @Presubmit @RunWith(AndroidJUnit4.class) public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Before public void setUp() { @@ -398,6 +406,60 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { eq(CREDENTIAL_TYPE_PASSWORD), any(), eq(MANAGED_PROFILE_USER_ID)); } + @Test + public void testVerifyCredential_notifyLockSettingsStateListeners_whenGoodPassword() + throws Exception { + mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS); + final LockscreenCredential password = newPassword("password"); + setCredential(PRIMARY_USER_ID, password); + final ILockSettingsStateListener listener = mockLockSettingsStateListener(); + mLocalService.registerLockSettingsStateListener(listener); + + assertEquals(VerifyCredentialResponse.RESPONSE_OK, + mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */) + .getResponseCode()); + + verify(listener).onAuthenticationSucceeded(PRIMARY_USER_ID); + } + + @Test + public void testVerifyCredential_notifyLockSettingsStateListeners_whenBadPassword() + throws Exception { + mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS); + final LockscreenCredential password = newPassword("password"); + setCredential(PRIMARY_USER_ID, password); + final LockscreenCredential badPassword = newPassword("badPassword"); + final ILockSettingsStateListener listener = mockLockSettingsStateListener(); + mLocalService.registerLockSettingsStateListener(listener); + + assertEquals(VerifyCredentialResponse.RESPONSE_ERROR, + mService.verifyCredential(badPassword, PRIMARY_USER_ID, 0 /* flags */) + .getResponseCode()); + + verify(listener).onAuthenticationFailed(PRIMARY_USER_ID); + } + + @Test + public void testLockSettingsStateListener_registeredThenUnregistered() throws Exception { + mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS); + final LockscreenCredential password = newPassword("password"); + setCredential(PRIMARY_USER_ID, password); + final LockscreenCredential badPassword = newPassword("badPassword"); + final ILockSettingsStateListener listener = mockLockSettingsStateListener(); + + mLocalService.registerLockSettingsStateListener(listener); + assertEquals(VerifyCredentialResponse.RESPONSE_OK, + mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */) + .getResponseCode()); + verify(listener).onAuthenticationSucceeded(PRIMARY_USER_ID); + + mLocalService.unregisterLockSettingsStateListener(listener); + assertEquals(VerifyCredentialResponse.RESPONSE_ERROR, + mService.verifyCredential(badPassword, PRIMARY_USER_ID, 0 /* flags */) + .getResponseCode()); + verify(listener, never()).onAuthenticationFailed(PRIMARY_USER_ID); + } + @Test public void testSetCredentialNotPossibleInSecureFrpModeDuringSuw() { setUserSetupComplete(false); @@ -537,4 +599,12 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { assertNotEquals(0, mGateKeeperService.getSecureUserId(userId)); } } + + private ILockSettingsStateListener mockLockSettingsStateListener() { + ILockSettingsStateListener listener = mock(ILockSettingsStateListener.Stub.class); + IBinder binder = mock(IBinder.class); + when(binder.isBinderAlive()).thenReturn(true); + when(listener.asBinder()).thenReturn(binder); + return listener; + } } -- GitLab