Handle managed profile with unified challenge in getHashFactor()

Settings passes null into getHashFactor() when a profile user has
unified challenge. In this case getHashFactor() needs to derive the real
profile password before it can calculate the hash factor.

Bug: 80077655
Test: runtest frameworks-services -c com.android.server.locksettings.SyntheticPasswordTests
Change-Id: Ifa1d88818b58f914fd3560bb6ef44012facde87b
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 1078f6e..36b04fc 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -2537,6 +2537,7 @@
      * Returns a fixed pseudorandom byte string derived from the user's synthetic password.
      * This is used to salt the password history hash to protect the hash against offline
      * bruteforcing, since rederiving this value requires a successful authentication.
+     * If user is a managed profile with unified challenge, currentCredential is ignored.
      */
     @Override
     public byte[] getHashFactor(String currentCredential, int userId) throws RemoteException {
@@ -2544,6 +2545,14 @@
         if (TextUtils.isEmpty(currentCredential)) {
             currentCredential = null;
         }
+        if (isManagedProfileWithUnifiedLock(userId)) {
+            try {
+                currentCredential = getDecryptedPasswordForTiedProfile(userId);
+            } catch (Exception e) {
+                Slog.e(TAG, "Failed to get work profile credential", e);
+                return null;
+            }
+        }
         synchronized (mSpManager) {
             if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
                 Slog.w(TAG, "Synthetic password not enabled");
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 142b950..94e02bc 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -448,6 +448,39 @@
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
     }
 
+    public void testgetHashFactorPrimaryUser() throws RemoteException {
+        final String password = "password";
+        mService.setLockCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+        final byte[] hashFactor = mService.getHashFactor(password, PRIMARY_USER_ID);
+        assertNotNull(hashFactor);
+
+        mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, password,
+                PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
+        final byte[] newHashFactor = mService.getHashFactor(null, PRIMARY_USER_ID);
+        assertNotNull(newHashFactor);
+        // Hash factor should never change after password change/removal
+        assertArrayEquals(hashFactor, newHashFactor);
+    }
+
+    public void testgetHashFactorManagedProfileUnifiedChallenge() throws RemoteException {
+        final String pattern = "1236";
+        mService.setLockCredential(pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, null,
+                PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID);
+        mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
+        assertNotNull(mService.getHashFactor(null, MANAGED_PROFILE_USER_ID));
+    }
+
+    public void testgetHashFactorManagedProfileSeparateChallenge() throws RemoteException {
+        final String primaryPassword = "primary";
+        final String profilePassword = "profile";
+        mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+        mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+                PASSWORD_QUALITY_ALPHABETIC, MANAGED_PROFILE_USER_ID);
+        assertNotNull(mService.getHashFactor(profilePassword, MANAGED_PROFILE_USER_ID));
+    }
+
     public void testPasswordData_serializeDeserialize() {
         PasswordData data = new PasswordData();
         data.scryptN = 11;