LSS: pass secret to AuthSecret HAL when no credential

If there was once a credential, a secret will have been enrolled. When
the credential is removed, that secret is still enrolled but still needs
to be derived. This adds that derivation in the case that the secret is
enrolled by the user doesn't have a credential.

Bug: 77942316
Test: runtest frameworks-services -c com.android.server.locksettings.SyntheticPasswordTests
Change-Id: I099a9537ab0739830a234b5f4f3721f4e8476571
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 4b58d53..fd4bd1d 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -584,10 +584,43 @@
                 if (mUserManager.getUserInfo(userId).isManagedProfile()) {
                     tieManagedProfileLockIfNecessary(userId, null);
                 }
+
+                // If the user doesn't have a credential, try and derive their secret for the
+                // AuthSecret HAL. The secret will have been enrolled if the user previously set a
+                // credential and still needs to be passed to the HAL once that credential is
+                // removed.
+                if (mUserManager.getUserInfo(userId).isPrimary() && !isUserSecure(userId)) {
+                    tryDeriveAuthTokenForUnsecuredPrimaryUser(userId);
+                }
             }
         });
     }
 
+    private void tryDeriveAuthTokenForUnsecuredPrimaryUser(@UserIdInt int userId) {
+        synchronized (mSpManager) {
+            // Make sure the user has a synthetic password to derive
+            if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
+                return;
+            }
+
+            try {
+                final long handle = getSyntheticPasswordHandleLocked(userId);
+                final String noCredential = null;
+                AuthenticationResult result =
+                        mSpManager.unwrapPasswordBasedSyntheticPassword(
+                                getGateKeeperService(), handle, noCredential, userId, null);
+                if (result.authToken != null) {
+                    Slog.i(TAG, "Retrieved auth token for user " + userId);
+                    onAuthTokenKnownForUser(userId, result.authToken);
+                } else {
+                    Slog.e(TAG, "Auth token not available for user " + userId);
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failure retrieving auth token", e);
+            }
+        }
+    }
+
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 96f8160..2dc3510 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.IActivityManager;
+import android.app.KeyguardManager;
 import android.app.NotificationManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
@@ -102,7 +103,8 @@
         LocalServices.addService(DevicePolicyManagerInternal.class, mDevicePolicyManagerInternal);
 
         mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager,
-                mDevicePolicyManager, mock(StorageManager.class), mock(TrustManager.class));
+                mDevicePolicyManager, mock(StorageManager.class), mock(TrustManager.class),
+                mock(KeyguardManager.class));
         mStorage = new LockSettingsStorageTestable(mContext,
                 new File(getContext().getFilesDir(), "locksettings"));
         File storageDir = mStorage.mStorageDir;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 237091d..6e1f357 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -20,6 +20,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.app.KeyguardManager;
 import android.app.NotificationManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
@@ -79,7 +80,7 @@
 
         MockLockSettingsContext context = new MockLockSettingsContext(getContext(), mockUserManager,
                 mock(NotificationManager.class), mock(DevicePolicyManager.class),
-                mock(StorageManager.class), mock(TrustManager.class));
+                mock(StorageManager.class), mock(TrustManager.class), mock(KeyguardManager.class));
         mStorage = new LockSettingsStorageTestable(context,
                 new File(getContext().getFilesDir(), "locksettings"));
         mStorage.setDatabaseOnCreateCallback(new LockSettingsStorage.Callback() {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java b/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java
index 3ad30f3..b332532 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java
@@ -16,6 +16,7 @@
 
 package com.android.server.locksettings;
 
+import android.app.KeyguardManager;
 import android.app.NotificationManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
@@ -32,16 +33,19 @@
     private DevicePolicyManager mDevicePolicyManager;
     private StorageManager mStorageManager;
     private TrustManager mTrustManager;
+    private KeyguardManager mKeyguardManager;
 
     public MockLockSettingsContext(Context base, UserManager userManager,
             NotificationManager notificationManager, DevicePolicyManager devicePolicyManager,
-            StorageManager storageManager, TrustManager trustManager) {
+            StorageManager storageManager, TrustManager trustManager,
+            KeyguardManager keyguardManager) {
         super(base);
         mUserManager = userManager;
         mNotificationManager = notificationManager;
         mDevicePolicyManager = devicePolicyManager;
         mStorageManager = storageManager;
         mTrustManager = trustManager;
+        mKeyguardManager = keyguardManager;
     }
 
     @Override
@@ -56,6 +60,8 @@
             return mStorageManager;
         } else if (TRUST_SERVICE.equals(name)) {
             return mTrustManager;
+        } else if (KEYGUARD_SERVICE.equals(name)) {
+            return mKeyguardManager;
         } else {
             throw new RuntimeException("System service not mocked: " + name);
         }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index e9f9800..142b950 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -217,6 +217,38 @@
         verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
     }
 
+    public void testNoSyntheticPasswordOrCredentialDoesNotPassAuthSecret() throws RemoteException {
+        // Setting null doesn't create a synthetic password
+        initializeCredentialUnderSP(null, PRIMARY_USER_ID);
+
+        reset(mAuthSecretService);
+        mService.onUnlockUser(PRIMARY_USER_ID);
+        mService.mHandler.runWithScissors(() -> {}, 0 /*now*/); // Flush runnables on handler
+        verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
+    }
+
+    public void testSyntheticPasswordAndCredentialDoesNotPassAuthSecret() throws RemoteException {
+        final String PASSWORD = "passwordForASyntheticPassword";
+        initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+
+        reset(mAuthSecretService);
+        mService.onUnlockUser(PRIMARY_USER_ID);
+        mService.mHandler.runWithScissors(() -> {}, 0 /*now*/); // Flush runnables on handler
+        verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
+    }
+
+    public void testSyntheticPasswordButNoCredentialPassesAuthSecret() throws RemoteException {
+        final String PASSWORD = "getASyntheticPassword";
+        initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+        mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, PASSWORD,
+                PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
+
+        reset(mAuthSecretService);
+        mService.onUnlockUser(PRIMARY_USER_ID);
+        mService.mHandler.runWithScissors(() -> {}, 0 /*now*/); // Flush runnables on handler
+        verify(mAuthSecretService).primaryUserCredential(any(ArrayList.class));
+    }
+
     public void testManagedProfileUnifiedChallengeMigration() throws RemoteException {
         final String UnifiedPassword = "testManagedProfileUnifiedChallengeMigration-pwd";
         disableSyntheticPassword();