Merge "Small tweaks in LockSettingsService"
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 58fb145..ef6e6c2 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -569,11 +569,12 @@
     /**
      * Clear any lock pattern or password.
      */
-    public void clearLock(int userHandle) {
+    public void clearLock(String savedCredential, int userHandle) {
         setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userHandle);
 
-        try {
-            getLockSettings().setLockCredential(null, CREDENTIAL_TYPE_NONE, null, userHandle);
+        try{
+            getLockSettings().setLockCredential(null, CREDENTIAL_TYPE_NONE, savedCredential,
+                    userHandle);
         } catch (RemoteException e) {
             // well, we tried...
         }
@@ -758,7 +759,6 @@
     public void saveLockPassword(String password, String savedPassword, int quality,
             int userHandle) {
         try {
-            DevicePolicyManager dpm = getDevicePolicyManager();
             if (password == null || password.length() < MIN_LOCK_PASSWORD_SIZE) {
                 throw new IllegalArgumentException("password must not be null and at least "
                         + "of length " + MIN_LOCK_PASSWORD_SIZE);
@@ -769,48 +769,55 @@
             getLockSettings().setLockCredential(password, CREDENTIAL_TYPE_PASSWORD, savedPassword,
                     userHandle);
 
-            // Update the device encryption password.
-            if (userHandle == UserHandle.USER_SYSTEM
-                    && LockPatternUtils.isDeviceEncryptionEnabled()) {
-                if (!shouldEncryptWithCredentials(true)) {
-                    clearEncryptionPassword();
-                } else {
-                    boolean numeric = computedQuality
-                            == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
-                    boolean numericComplex = computedQuality
-                            == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
-                    int type = numeric || numericComplex ? StorageManager.CRYPT_TYPE_PIN
-                            : StorageManager.CRYPT_TYPE_PASSWORD;
-                    updateEncryptionPassword(type, password);
-                }
-            }
-
-            // Add the password to the password history. We assume all
-            // password hashes have the same length for simplicity of implementation.
-            String passwordHistory = getString(PASSWORD_HISTORY_KEY, userHandle);
-            if (passwordHistory == null) {
-                passwordHistory = "";
-            }
-            int passwordHistoryLength = getRequestedPasswordHistoryLength(userHandle);
-            if (passwordHistoryLength == 0) {
-                passwordHistory = "";
-            } else {
-                byte[] hash = passwordToHash(password, userHandle);
-                passwordHistory = new String(hash, StandardCharsets.UTF_8) + "," + passwordHistory;
-                // Cut it to contain passwordHistoryLength hashes
-                // and passwordHistoryLength -1 commas.
-                passwordHistory = passwordHistory.substring(0, Math.min(hash.length
-                        * passwordHistoryLength + passwordHistoryLength - 1, passwordHistory
-                        .length()));
-            }
-            setString(PASSWORD_HISTORY_KEY, passwordHistory, userHandle);
-            onAfterChangingPassword(userHandle);
+            addEncryptionPassword(password, computedQuality, userHandle);
+            updatePasswordHistory(password, userHandle);
         } catch (RemoteException re) {
             // Cant do much
             Log.e(TAG, "Unable to save lock password " + re);
         }
     }
 
+    private void addEncryptionPassword(String password, int quality, int userHandle) {
+        // Update the device encryption password.
+        if (userHandle == UserHandle.USER_SYSTEM
+                && LockPatternUtils.isDeviceEncryptionEnabled()) {
+            if (!shouldEncryptWithCredentials(true)) {
+                clearEncryptionPassword();
+            } else {
+                boolean numeric = quality == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+                boolean numericComplex = quality
+                        == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
+                int type = numeric || numericComplex ? StorageManager.CRYPT_TYPE_PIN
+                        : StorageManager.CRYPT_TYPE_PASSWORD;
+                updateEncryptionPassword(type, password);
+            }
+        }
+    }
+
+    private void updatePasswordHistory(String password, int userHandle) {
+
+        // Add the password to the password history. We assume all
+        // password hashes have the same length for simplicity of implementation.
+        String passwordHistory = getString(PASSWORD_HISTORY_KEY, userHandle);
+        if (passwordHistory == null) {
+            passwordHistory = "";
+        }
+        int passwordHistoryLength = getRequestedPasswordHistoryLength(userHandle);
+        if (passwordHistoryLength == 0) {
+            passwordHistory = "";
+        } else {
+            byte[] hash = passwordToHash(password, userHandle);
+            passwordHistory = new String(hash, StandardCharsets.UTF_8) + "," + passwordHistory;
+            // Cut it to contain passwordHistoryLength hashes
+            // and passwordHistoryLength -1 commas.
+            passwordHistory = passwordHistory.substring(0, Math.min(hash.length
+                    * passwordHistoryLength + passwordHistoryLength - 1, passwordHistory
+                    .length()));
+        }
+        setString(PASSWORD_HISTORY_KEY, passwordHistory, userHandle);
+        onAfterChangingPassword(userHandle);
+    }
+
     /**
      * Determine if the device supports encryption, even if it's set to default. This
      * differs from isDeviceEncrypted() in that it returns true even if the device is
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 8ef34dc..a073d8e 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -68,6 +68,7 @@
 import android.service.gatekeeper.GateKeeperResponse;
 import android.service.gatekeeper.IGateKeeperService;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
 
@@ -94,8 +95,10 @@
 import java.security.SecureRandom;
 import java.security.UnrecoverableKeyException;
 import java.security.cert.CertificateException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -231,7 +234,7 @@
         }
         // Do not tie it to parent when parent does not have a screen lock
         final int parentId = mUserManager.getProfileParent(managedUserId).id;
-        if (!mStorage.hasPassword(parentId) && !mStorage.hasPattern(parentId)) {
+        if (!isUserSecure(parentId)) {
             if (DEBUG) Slog.v(TAG, "Parent does not have a screen lock");
             return;
         }
@@ -378,7 +381,7 @@
         }
 
         final UserHandle userHandle = user.getUserHandle();
-        final boolean isSecure = mStorage.hasPassword(userId) || mStorage.hasPattern(userId);
+        final boolean isSecure = isUserSecure(userId);
         if (isSecure && !mUserManager.isUserUnlockingOrUnlocked(userHandle)) {
             UserInfo parent = mUserManager.getProfileParent(userId);
             if (parent != null &&
@@ -453,35 +456,34 @@
     }
 
     public void onUnlockUser(final int userId) {
-        // Hide notification first, as tie managed profile lock takes time
-        hideEncryptionNotification(new UserHandle(userId));
+        // Perform tasks which require locks in LSS on a handler, as we are callbacks from
+        // ActivityManager.unlockUser()
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                // Hide notification first, as tie managed profile lock takes time
+                hideEncryptionNotification(new UserHandle(userId));
 
-        if (mUserManager.getUserInfo(userId).isManagedProfile()) {
-            // As tieManagedProfileLockIfNecessary() may try to unlock user, we should not do it
-            // in onUnlockUser() synchronously, otherwise it may cause a deadlock
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
+                // Now we have unlocked the parent user we should show notifications
+                // about any profiles that exist.
+                List<UserInfo> profiles = mUserManager.getProfiles(userId);
+                for (int i = 0; i < profiles.size(); i++) {
+                    UserInfo profile = profiles.get(i);
+                    final boolean isSecure = isUserSecure(profile.id);
+                    if (isSecure && profile.isManagedProfile()) {
+                        UserHandle userHandle = profile.getUserHandle();
+                        if (!mUserManager.isUserUnlockingOrUnlocked(userHandle) &&
+                                !mUserManager.isQuietModeEnabled(userHandle)) {
+                            showEncryptionNotificationForProfile(userHandle);
+                        }
+                    }
+                }
+
+                if (mUserManager.getUserInfo(userId).isManagedProfile()) {
                     tieManagedProfileLockIfNecessary(userId, null);
                 }
-            });
-        }
-
-        // Now we have unlocked the parent user we should show notifications
-        // about any profiles that exist.
-        List<UserInfo> profiles = mUserManager.getProfiles(userId);
-        for (int i = 0; i < profiles.size(); i++) {
-            UserInfo profile = profiles.get(i);
-            final boolean isSecure =
-                    mStorage.hasPassword(profile.id) || mStorage.hasPattern(profile.id);
-            if (isSecure && profile.isManagedProfile()) {
-                UserHandle userHandle = profile.getUserHandle();
-                if (!mUserManager.isUserUnlockingOrUnlocked(userHandle) &&
-                        !mUserManager.isQuietModeEnabled(userHandle)) {
-                    showEncryptionNotificationForProfile(userHandle);
-                }
             }
-        }
+        });
     }
 
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -809,6 +811,10 @@
         return mStorage.hasPattern(userId);
     }
 
+    private boolean isUserSecure(int userId) {
+        return mStorage.hasCredential(userId);
+    }
+
     private void setKeystorePassword(String password, int userHandle) {
         final KeyStore ks = KeyStore.getInstance();
         ks.onUserPasswordChanged(userHandle, password);
@@ -914,21 +920,52 @@
         }
     }
 
-    private byte[] getCurrentHandle(int userId) {
-        CredentialHash credential = mStorage.readCredentialHash(userId);
-
-        // sanity check
-        if (credential.type != LockPatternUtils.CREDENTIAL_TYPE_NONE && credential.hash == null) {
-            Slog.e(TAG, "Stored handle type [" + credential.type + "] but no handle available");
+    private Map<Integer, String> getDecryptedPasswordsForAllTiedProfiles(int userId) {
+        if (mUserManager.getUserInfo(userId).isManagedProfile()) {
+            return null;
         }
-        return credential.hash;
+        Map<Integer, String> result = new ArrayMap<Integer, String>();
+        final List<UserInfo> profiles = mUserManager.getProfiles(userId);
+        final int size = profiles.size();
+        for (int i = 0; i < size; i++) {
+            final UserInfo profile = profiles.get(i);
+            if (!profile.isManagedProfile()) {
+                continue;
+            }
+            final int managedUserId = profile.id;
+            if (mLockPatternUtils.isSeparateProfileChallengeEnabled(managedUserId)) {
+                continue;
+            }
+            try {
+                result.put(userId, getDecryptedPasswordForTiedProfile(userId));
+            } catch (KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException
+                    | NoSuchPaddingException | InvalidKeyException
+                    | InvalidAlgorithmParameterException | IllegalBlockSizeException
+                    | BadPaddingException | CertificateException | IOException e) {
+                // ignore
+            }
+        }
+        return result;
     }
 
-    private void onUserLockChanged(int userId) throws RemoteException {
+    /**
+     * Synchronize all profile's work challenge of the given user if it's unified: tie or clear them
+     * depending on the parent user's secure state.
+     *
+     * When clearing tied work challenges, a pre-computed password table for profiles are required,
+     * since changing password for profiles requires existing password, and existing passwords can
+     * only be computed before the parent user's password is cleared.
+     *
+     * Strictly this is a recursive function, since setLockCredentialInternal ends up calling this
+     * method again on profiles. However the recursion is guaranteed to terminate as this method
+     * terminates when the user is a managed profile.
+     */
+    private void synchronizeUnifiedWorkChallengeForProfiles(int userId,
+            Map<Integer, String> profilePasswordMap) throws RemoteException {
         if (mUserManager.getUserInfo(userId).isManagedProfile()) {
             return;
         }
-        final boolean isSecure = mStorage.hasPassword(userId) || mStorage.hasPattern(userId);
+        final boolean isSecure = isUserSecure(userId);
         final List<UserInfo> profiles = mUserManager.getProfiles(userId);
         final int size = profiles.size();
         for (int i = 0; i < size; i++) {
@@ -941,11 +978,17 @@
                 if (isSecure) {
                     tieManagedProfileLockIfNecessary(managedUserId, null);
                 } else {
-                    clearUserKeyProtection(managedUserId);
-                    getGateKeeperService().clearSecureUserId(managedUserId);
-                    mStorage.writeCredentialHash(CredentialHash.createEmptyHash(), managedUserId);
-                    setKeystorePassword(null, managedUserId);
-                    fixateNewestUserKeyAuth(managedUserId);
+                    // We use cached work profile password computed before clearing the parent's
+                    // credential, otherwise they get lost
+                    if (profilePasswordMap != null && profilePasswordMap.containsKey(managedUserId)) {
+                        setLockCredentialInternal(null, LockPatternUtils.CREDENTIAL_TYPE_NONE,
+                                profilePasswordMap.get(managedUserId), managedUserId);
+                    } else {
+                        Slog.wtf(TAG, "clear tied profile challenges, but no password supplied.");
+                        // Supplying null here would lead to untrusted credential change
+                        setLockCredentialInternal(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, null,
+                                managedUserId);
+                    }
                     mStorage.removeChildProfileLock(managedUserId);
                     removeKeystoreProfileKey(managedUserId);
                 }
@@ -978,7 +1021,6 @@
 
     private void setLockCredentialInternal(String credential, int credentialType,
             String savedCredential, int userId) throws RemoteException {
-        byte[] currentHandle = getCurrentHandle(userId);
         if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
             if (credential != null) {
                 Slog.wtf(TAG, "CredentialType is none, but credential is non-null.");
@@ -988,27 +1030,31 @@
             mStorage.writeCredentialHash(CredentialHash.createEmptyHash(), userId);
             setKeystorePassword(null, userId);
             fixateNewestUserKeyAuth(userId);
-            onUserLockChanged(userId);
+            synchronizeUnifiedWorkChallengeForProfiles(userId, null);
             notifyActivePasswordMetricsAvailable(null, userId);
             return;
         }
         if (credential == null) {
             throw new RemoteException("Null credential with mismatched credential type");
         }
+
+        CredentialHash currentHandle = mStorage.readCredentialHash(userId);
         if (isManagedProfileWithUnifiedLock(userId)) {
             // get credential from keystore when managed profile has unified lock
-            try {
-                savedCredential = getDecryptedPasswordForTiedProfile(userId);
-            } catch (FileNotFoundException e) {
-                Slog.i(TAG, "Child profile key not found");
-            } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
-                    | NoSuchAlgorithmException | NoSuchPaddingException
-                    | InvalidAlgorithmParameterException | IllegalBlockSizeException
-                    | BadPaddingException | CertificateException | IOException e) {
-                Slog.e(TAG, "Failed to decrypt child profile key", e);
+            if (savedCredential == null) {
+                try {
+                    savedCredential = getDecryptedPasswordForTiedProfile(userId);
+                } catch (FileNotFoundException e) {
+                    Slog.i(TAG, "Child profile key not found");
+                } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
+                        | NoSuchAlgorithmException | NoSuchPaddingException
+                        | InvalidAlgorithmParameterException | IllegalBlockSizeException
+                        | BadPaddingException | CertificateException | IOException e) {
+                    Slog.e(TAG, "Failed to decrypt child profile key", e);
+                }
             }
         } else {
-            if (currentHandle == null) {
+            if (currentHandle.hash == null) {
                 if (savedCredential != null) {
                     Slog.w(TAG, "Saved credential provided, but none stored");
                 }
@@ -1016,17 +1062,19 @@
             }
         }
 
-        byte[] enrolledHandle = enrollCredential(currentHandle, savedCredential, credential,
+        byte[] enrolledHandle = enrollCredential(currentHandle.hash, savedCredential, credential,
                 userId);
         if (enrolledHandle != null) {
             CredentialHash willStore = CredentialHash.create(enrolledHandle, credentialType);
             mStorage.writeCredentialHash(willStore, userId);
-            // Refresh the auth token
-            setUserKeyProtection(userId, credential,
-                    doVerifyCredential(credential, credentialType, true, 0, userId,
-                            null /* progressCallback */));
+            // push new secret and auth token to vold
+            GateKeeperResponse gkResponse = getGateKeeperService()
+                    .verifyChallenge(userId, 0, willStore.hash, credential.getBytes());
+            setUserKeyProtection(userId, credential, convertResponse(gkResponse));
             fixateNewestUserKeyAuth(userId);
-            onUserLockChanged(userId);
+            // Refresh the auth token
+            doVerifyCredential(credential, credentialType, true, 0, userId, null /* progressCallback */);
+            synchronizeUnifiedWorkChallengeForProfiles(userId, null);
         } else {
             throw new RemoteException("Failed to enroll " +
                     (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD ? "password"
@@ -1034,6 +1082,26 @@
         }
     }
 
+    private VerifyCredentialResponse convertResponse(GateKeeperResponse gateKeeperResponse) {
+        VerifyCredentialResponse response;
+        int responseCode = gateKeeperResponse.getResponseCode();
+        if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
+            response = new VerifyCredentialResponse(gateKeeperResponse.getTimeout());
+        } else if (responseCode == GateKeeperResponse.RESPONSE_OK) {
+            byte[] token = gateKeeperResponse.getPayload();
+            if (token == null) {
+                // something's wrong if there's no payload with a challenge
+                Slog.e(TAG, "verifyChallenge response had no associated payload");
+                response = VerifyCredentialResponse.ERROR;
+            } else {
+                response = new VerifyCredentialResponse(token);
+            }
+        } else {
+            response = VerifyCredentialResponse.ERROR;
+        }
+        return response;
+    }
+
     @VisibleForTesting
     protected void tieProfileLockToParent(int userId, String password) {
         if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
@@ -1123,6 +1191,7 @@
 
     private void setUserKeyProtection(int userId, String credential, VerifyCredentialResponse vcr)
             throws RemoteException {
+        if (DEBUG) Slog.d(TAG, "setUserKeyProtection: user=" + userId);
         if (vcr == null) {
             throw new RemoteException("Null response verifying a credential we just set");
         }
@@ -1138,6 +1207,7 @@
     }
 
     private void clearUserKeyProtection(int userId) throws RemoteException {
+        if (DEBUG) Slog.d(TAG, "clearUserKeyProtection user=" + userId);
         addUserKeyAuth(userId, null, null);
     }
 
@@ -1171,6 +1241,7 @@
 
     private void fixateNewestUserKeyAuth(int userId)
             throws RemoteException {
+        if (DEBUG) Slog.d(TAG, "fixateNewestUserKeyAuth: user=" + userId);
         final IStorageManager storageManager = mInjector.getStorageManager();
         final long callingId = Binder.clearCallingIdentity();
         try {
@@ -1370,27 +1441,10 @@
                 return VerifyCredentialResponse.ERROR;
             }
         }
-
-        VerifyCredentialResponse response;
-        boolean shouldReEnroll = false;
         GateKeeperResponse gateKeeperResponse = getGateKeeperService()
                 .verifyChallenge(userId, challenge, storedHash.hash, credential.getBytes());
-        int responseCode = gateKeeperResponse.getResponseCode();
-        if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
-            response = new VerifyCredentialResponse(gateKeeperResponse.getTimeout());
-        } else if (responseCode == GateKeeperResponse.RESPONSE_OK) {
-            byte[] token = gateKeeperResponse.getPayload();
-            if (token == null) {
-                // something's wrong if there's no payload with a challenge
-                Slog.e(TAG, "verifyChallenge response had no associated payload");
-                response = VerifyCredentialResponse.ERROR;
-            } else {
-                shouldReEnroll = gateKeeperResponse.getShouldReEnroll();
-                response = new VerifyCredentialResponse(token);
-            }
-        } else {
-            response = VerifyCredentialResponse.ERROR;
-        }
+        VerifyCredentialResponse response = convertResponse(gateKeeperResponse);
+        boolean shouldReEnroll = gateKeeperResponse.getShouldReEnroll();
 
         if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
 
diff --git a/services/core/java/com/android/server/LockSettingsShellCommand.java b/services/core/java/com/android/server/LockSettingsShellCommand.java
index e131251..1ab5303 100644
--- a/services/core/java/com/android/server/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/LockSettingsShellCommand.java
@@ -116,7 +116,7 @@
     }
 
     private void runClear() throws RemoteException {
-        mLockPatternUtils.clearLock(mCurrentUserId);
+        mLockPatternUtils.clearLock(mOld, mCurrentUserId);
         getOutPrintWriter().println("Lock credential cleared");
     }
 
diff --git a/services/core/java/com/android/server/LockSettingsStorage.java b/services/core/java/com/android/server/LockSettingsStorage.java
index c858036..385b1cf 100644
--- a/services/core/java/com/android/server/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/LockSettingsStorage.java
@@ -290,6 +290,10 @@
             hasFile(getLegacyLockPatternFilename(userId));
     }
 
+    public boolean hasCredential(int userId) {
+        return hasPassword(userId) || hasPattern(userId);
+    }
+
     private boolean hasFile(String name) {
         byte[] contents = readFile(name);
         return contents != null && contents.length > 0;
@@ -650,4 +654,5 @@
             }
         }
     }
+
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 003b6d0..9d8372a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4297,7 +4297,7 @@
             if (!TextUtils.isEmpty(password)) {
                 mLockPatternUtils.saveLockPassword(password, null, quality, userHandle);
             } else {
-                mLockPatternUtils.clearLock(userHandle);
+                mLockPatternUtils.clearLock(null, userHandle);
             }
             boolean requireEntry = (flags & DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY) != 0;
             if (requireEntry) {
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/LockSettingsShellCommandTest.java
index d6ee367..84ebb19 100644
--- a/services/tests/servicestests/src/com/android/server/LockSettingsShellCommandTest.java
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsShellCommandTest.java
@@ -137,6 +137,6 @@
         assertEquals(0, mCommand.exec(new Binder(), in, out, err,
                 new String[] { "clear", "--old", "1234" },
                 mShellCallback, mResultReceiver));
-        verify(mLockPatternUtils).clearLock(mUserId);
+        verify(mLockPatternUtils).clearLock("1234", mUserId);
     }
 }