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

Commit f4c7c30b authored by Eric Biggers's avatar Eric Biggers Committed by Gerrit Code Review
Browse files

Merge changes Ica472482,I32381136 into main

* changes:
  locksettings: clean up references to managed profiles
  Sync tieProfileLockIfNecessary() with internal main
parents 29a22951 6a2770b9
Loading
Loading
Loading
Loading
+5 −5
Original line number Diff line number Diff line
@@ -134,12 +134,12 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
    }

    /**
     * Creates a LockscreenCredential object representing a managed password for profile with
     * unified challenge. This credentiall will have type {@code CREDENTIAL_TYPE_PASSWORD} for now.
     * TODO: consider add a new credential type for this. This can then supersede the
     * isLockTiedToParent argument in various places in LSS.
     * Creates a LockscreenCredential object representing the system-generated, system-managed
     * password for a profile with unified challenge. This credential has type {@code
     * CREDENTIAL_TYPE_PASSWORD} for now. TODO: consider add a new credential type for this. This
     * can then supersede the isLockTiedToParent argument in various places in LSS.
     */
    public static LockscreenCredential createManagedPassword(@NonNull byte[] password) {
    public static LockscreenCredential createUnifiedProfilePassword(@NonNull byte[] password) {
        return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
                Arrays.copyOf(password, password.length), /* hasInvalidChars= */ false);
    }
+39 −35
Original line number Diff line number Diff line
@@ -209,7 +209,7 @@ import javax.crypto.spec.GCMParameterSpec;
 *   <li>Protect each user's data using their SP.  For example, use the SP to encrypt/decrypt the
 *   user's credential-encrypted (CE) key for file-based encryption (FBE).</li>
 *
 *   <li>Generate, protect, and use profile passwords for managed profiles.</li>
 *   <li>Generate, protect, and use unified profile passwords.</li>
 *
 *   <li>Support unlocking the SP by alternative means: resume-on-reboot (reboot escrow) for easier
 *   OTA updates, and escrow tokens when set up by the Device Policy Controller (DPC).</li>
@@ -287,7 +287,7 @@ public class LockSettingsService extends ILockSettings.Stub {

    private final java.security.KeyStore mJavaKeyStore;
    private final RecoverableKeyStoreManager mRecoverableKeyStoreManager;
    private ManagedProfilePasswordCache mManagedProfilePasswordCache;
    private final UnifiedProfilePasswordCache mUnifiedProfilePasswordCache;

    private final RebootEscrowManager mRebootEscrowManager;

@@ -404,7 +404,8 @@ public class LockSettingsService extends ILockSettings.Stub {
        for (int i = 0; i < newPasswordChars.length; i++) {
            newPassword[i] = (byte) newPasswordChars[i];
        }
        LockscreenCredential credential = LockscreenCredential.createManagedPassword(newPassword);
        LockscreenCredential credential =
                LockscreenCredential.createUnifiedProfilePassword(newPassword);
        Arrays.fill(newPasswordChars, '\u0000');
        Arrays.fill(newPassword, (byte) 0);
        Arrays.fill(randomLockSeed, (byte) 0);
@@ -424,7 +425,7 @@ public class LockSettingsService extends ILockSettings.Stub {
        if (!isCredentialSharableWithParent(profileUserId)) {
            return;
        }
        // Do not tie profile when work challenge is enabled
        // Do not tie profile when separate challenge is enabled
        if (getSeparateProfileChallengeEnabledInternal(profileUserId)) {
            return;
        }
@@ -432,11 +433,14 @@ public class LockSettingsService extends ILockSettings.Stub {
        if (mStorage.hasChildProfileLock(profileUserId)) {
            return;
        }
        final UserInfo parent = mUserManager.getProfileParent(profileUserId);
        if (parent == null) {
            return;
        }
        // If parent does not have a screen lock, simply clear credential from the profile,
        // to maintain the invariant that unified profile should always have the same secure state
        // as its parent.
        final int parentId = mUserManager.getProfileParent(profileUserId).id;
        if (!isUserSecure(parentId) && !profileUserPassword.isNone()) {
        if (!isUserSecure(parent.id) && !profileUserPassword.isNone()) {
            Slogf.i(TAG, "Clearing password for profile user %d to match parent", profileUserId);
            setLockCredentialInternal(LockscreenCredential.createNone(), profileUserPassword,
                    profileUserId, /* isLockTiedToParent= */ true);
@@ -447,7 +451,7 @@ public class LockSettingsService extends ILockSettings.Stub {
        // This can only happen during an upgrade path where SID is yet to be
        // generated when the user unlocks for the first time.
        try {
            parentSid = getGateKeeperService().getSecureUserId(parentId);
            parentSid = getGateKeeperService().getSecureUserId(parent.id);
            if (parentSid == 0) {
                return;
            }
@@ -458,8 +462,8 @@ public class LockSettingsService extends ILockSettings.Stub {
        try (LockscreenCredential unifiedProfilePassword = generateRandomProfilePassword()) {
            setLockCredentialInternal(unifiedProfilePassword, profileUserPassword, profileUserId,
                    /* isLockTiedToParent= */ true);
            tieProfileLockToParent(profileUserId, parentId, unifiedProfilePassword);
            mManagedProfilePasswordCache.storePassword(profileUserId, unifiedProfilePassword,
            tieProfileLockToParent(profileUserId, parent.id, unifiedProfilePassword);
            mUnifiedProfilePasswordCache.storePassword(profileUserId, unifiedProfilePassword,
                    parentSid);
        }
    }
@@ -617,9 +621,9 @@ public class LockSettingsService extends ILockSettings.Stub {
            }
        }

        public @NonNull ManagedProfilePasswordCache getManagedProfilePasswordCache(
        public @NonNull UnifiedProfilePasswordCache getUnifiedProfilePasswordCache(
                java.security.KeyStore ks) {
            return new ManagedProfilePasswordCache(ks);
            return new UnifiedProfilePasswordCache(ks);
        }

        public boolean isHeadlessSystemUserMode() {
@@ -662,7 +666,7 @@ public class LockSettingsService extends ILockSettings.Stub {
        mGatekeeperPasswords = new LongSparseArray<>();

        mSpManager = injector.getSyntheticPasswordManager(mStorage);
        mManagedProfilePasswordCache = injector.getManagedProfilePasswordCache(mJavaKeyStore);
        mUnifiedProfilePasswordCache = injector.getUnifiedProfilePasswordCache(mJavaKeyStore);
        mBiometricDeferredQueue = new BiometricDeferredQueue(mSpManager, mHandler);

        mRebootEscrowManager = injector.getRebootEscrowManager(new RebootEscrowCallbacks(),
@@ -686,8 +690,8 @@ public class LockSettingsService extends ILockSettings.Stub {
    }

    /**
     * If the account is credential-encrypted, show notification requesting the user to unlock the
     * device.
     * If the user is a managed profile whose credential-encrypted storage is locked, show a
     * notification requesting the user to unlock the device.
     */
    private void maybeShowEncryptionNotificationForUser(@UserIdInt int userId, String reason) {
        final UserInfo user = mUserManager.getUserInfo(userId);
@@ -843,7 +847,7 @@ public class LockSettingsService extends ILockSettings.Stub {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                // Hide notification first, as tie managed profile lock takes time
                // Hide notification first, as tie profile lock takes time
                hideEncryptionNotification(new UserHandle(userId));

                if (isCredentialSharableWithParent(userId)) {
@@ -1455,13 +1459,13 @@ public class LockSettingsService extends ILockSettings.Stub {

        cipher.init(Cipher.DECRYPT_MODE, decryptionKey, new GCMParameterSpec(128, iv));
        decryptionResult = cipher.doFinal(encryptedPassword);
        LockscreenCredential credential = LockscreenCredential.createManagedPassword(
        LockscreenCredential credential = LockscreenCredential.createUnifiedProfilePassword(
                decryptionResult);
        Arrays.fill(decryptionResult, (byte) 0);
        try {
            long parentSid = getGateKeeperService().getSecureUserId(
                    mUserManager.getProfileParent(userId).id);
            mManagedProfilePasswordCache.storePassword(userId, credential, parentSid);
            mUnifiedProfilePasswordCache.storePassword(userId, credential, parentSid);
        } catch (RemoteException e) {
            Slogf.w(TAG, "Failed to talk to GateKeeper service", e);
        }
@@ -1547,7 +1551,7 @@ public class LockSettingsService extends ILockSettings.Stub {
                        // so it goes into the cache
                        getDecryptedPasswordForTiedProfile(profile.id);
                    } catch (GeneralSecurityException | IOException e) {
                        Slog.d(TAG, "Cache work profile password failed", e);
                        Slog.d(TAG, "Cache unified profile password failed", e);
                    }
                }
            }
@@ -1601,19 +1605,19 @@ public class LockSettingsService extends ILockSettings.Stub {
    }

    /**
     * Synchronize all profile's work challenge of the given user if it's unified: tie or clear them
     * Synchronize all profile's 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.
     * When clearing tied 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 profile that shares lock credentials with parent.
     * (e.g. managed and clone profile).
     */
    private void synchronizeUnifiedWorkChallengeForProfiles(int userId,
    private void synchronizeUnifiedChallengeForProfiles(int userId,
            Map<Integer, LockscreenCredential> profilePasswordMap) {
        if (isCredentialSharableWithParent(userId)) {
            return;
@@ -1632,7 +1636,7 @@ public class LockSettingsService extends ILockSettings.Stub {
                    tieProfileLockIfNecessary(profileUserId,
                            LockscreenCredential.createNone());
                } else {
                    // We use cached work profile password computed before clearing the parent's
                    // We use cached profile password computed before clearing the parent's
                    // credential, otherwise they get lost
                    if (profilePasswordMap != null
                            && profilePasswordMap.containsKey(profileUserId)) {
@@ -1774,7 +1778,7 @@ public class LockSettingsService extends ILockSettings.Stub {
                notifyPasswordChanged(credential, userId);
            }
            if (isCredentialSharableWithParent(userId)) {
                // Make sure the profile doesn't get locked straight after setting work challenge.
                // Make sure the profile doesn't get locked straight after setting challenge.
                setDeviceUnlockedForUser(userId);
            }
            notifySeparateProfileChallengeChanged(userId);
@@ -2365,7 +2369,7 @@ public class LockSettingsService extends ILockSettings.Stub {
        }

        try {
            // Unlock work profile, and work profile with unified lock must use password only
            // Unlock profile with unified lock
            return doVerifyCredential(getDecryptedPasswordForTiedProfile(userId),
                    userId, null /* progressCallback */, flags);
        } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
@@ -2489,7 +2493,7 @@ public class LockSettingsService extends ILockSettings.Stub {
        mStrongAuth.removeUser(userId);

        AndroidKeyStoreMaintenance.onUserRemoved(userId);
        mManagedProfilePasswordCache.removePassword(userId);
        mUnifiedProfilePasswordCache.removePassword(userId);

        gateKeeperClearSecureUserId(userId);
        removeKeystoreProfileKey(userId);
@@ -2979,7 +2983,7 @@ public class LockSettingsService extends ILockSettings.Stub {
                credential, sp, userId);
        final Map<Integer, LockscreenCredential> profilePasswords;
        if (!credential.isNone()) {
            // not needed by synchronizeUnifiedWorkChallengeForProfiles()
            // not needed by synchronizeUnifiedChallengeForProfiles()
            profilePasswords = null;

            if (!mSpManager.hasSidForUser(userId)) {
@@ -2990,8 +2994,8 @@ public class LockSettingsService extends ILockSettings.Stub {
                }
            }
        } else {
            // Cache all profile password if they use unified work challenge. This will later be
            // used to clear the profile's password in synchronizeUnifiedWorkChallengeForProfiles()
            // Cache all profile password if they use unified challenge. This will later be used to
            // clear the profile's password in synchronizeUnifiedChallengeForProfiles().
            profilePasswords = getDecryptedPasswordsForAllTiedProfiles(userId);

            mSpManager.clearSidForUser(userId);
@@ -3007,10 +3011,10 @@ public class LockSettingsService extends ILockSettings.Stub {
        }
        setCurrentLskfBasedProtectorId(newProtectorId, userId);
        LockPatternUtils.invalidateCredentialTypeCache();
        synchronizeUnifiedWorkChallengeForProfiles(userId, profilePasswords);
        synchronizeUnifiedChallengeForProfiles(userId, profilePasswords);

        setUserPasswordMetrics(credential, userId);
        mManagedProfilePasswordCache.removePassword(userId);
        mUnifiedProfilePasswordCache.removePassword(userId);
        if (savedCredentialType != CREDENTIAL_TYPE_NONE) {
            mSpManager.destroyAllWeakTokenBasedProtectors(userId);
        }
@@ -3111,7 +3115,7 @@ public class LockSettingsService extends ILockSettings.Stub {
                try {
                    currentCredential = getDecryptedPasswordForTiedProfile(userId);
                } catch (Exception e) {
                    Slog.e(TAG, "Failed to get work profile credential", e);
                    Slog.e(TAG, "Failed to get unified profile password", e);
                    return null;
                }
            }
@@ -3281,7 +3285,7 @@ public class LockSettingsService extends ILockSettings.Stub {
    @Override
    public boolean tryUnlockWithCachedUnifiedChallenge(int userId) {
        checkPasswordReadPermission();
        try (LockscreenCredential cred = mManagedProfilePasswordCache.retrievePassword(userId)) {
        try (LockscreenCredential cred = mUnifiedProfilePasswordCache.retrievePassword(userId)) {
            if (cred == null) {
                return false;
            }
@@ -3293,7 +3297,7 @@ public class LockSettingsService extends ILockSettings.Stub {
    @Override
    public void removeCachedUnifiedChallenge(int userId) {
        checkWritePermission();
        mManagedProfilePasswordCache.removePassword(userId);
        mUnifiedProfilePasswordCache.removePassword(userId);
    }

    static String timestampToString(long timestamp) {
+2 −2
Original line number Diff line number Diff line
@@ -501,10 +501,10 @@ class LockSettingsStorage {
        final UserInfo parentInfo = um.getProfileParent(userId);

        if (parentInfo == null) {
            // This user owns its lock settings files - safe to delete them
            // Delete files specific to non-profile users.
            deleteFile(getRebootEscrowFile(userId));
        } else {
            // Managed profile
            // Delete files specific to profile users.
            removeChildProfileLock(userId);
        }

+18 −16
Original line number Diff line number Diff line
@@ -43,30 +43,31 @@ import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;

/**
 * Caches *unified* work challenge for managed profiles. The cached credential is encrypted using
 * a keystore key auth-bound to the parent user's lockscreen credential, similar to how unified
 * work challenge is normally secured.
 *
 * <p> The cache is filled whenever the managed profile's unified challenge is created or derived
 * (as part of the parent user's credential verification flow). It's removed when the profile is
 * deleted or a (separate) lockscreen credential is explicitly set on the profile. There is also
 * an ADB command to evict the cache "cmd lock_settings remove-cache --user X", to assist
 * development and testing.

 * <p> The encrypted credential is stored in-memory only so the cache does not persist across
 * reboots.
 * An in-memory cache for unified profile passwords.  A "unified profile password" is the random
 * password that the system automatically generates and manages for each profile that uses a unified
 * challenge and where the parent user has a secure lock screen.
 * <p>
 * Each password in this cache is encrypted by a Keystore key that is auth-bound to the parent user.
 * This is very similar to how the password is protected on-disk, but the in-memory cache uses a
 * much longer timeout on the keys: 7 days instead of 30 seconds.  This enables use cases like
 * unpausing work apps without requiring authentication as frequently.
 * <p>
 * Unified profile passwords are cached when they are created, or when they are decrypted as part of
 * the parent user's LSKF verification flow.  They are removed when the profile is deleted or when a
 * separate challenge is explicitly set on the profile.  There is also an ADB command to evict a
 * cached password, "locksettings remove-cache --user X", to assist development and testing.
 */
@VisibleForTesting // public visibility is needed for Mockito
public class ManagedProfilePasswordCache {
public class UnifiedProfilePasswordCache {

    private static final String TAG = "ManagedProfilePasswordCache";
    private static final String TAG = "UnifiedProfilePasswordCache";
    private static final int KEY_LENGTH = 256;
    private static final int CACHE_TIMEOUT_SECONDS = (int) TimeUnit.DAYS.toSeconds(7);

    private final SparseArray<byte[]> mEncryptedPasswords = new SparseArray<>();
    private final KeyStore mKeyStore;

    public ManagedProfilePasswordCache(KeyStore keyStore) {
    public UnifiedProfilePasswordCache(KeyStore keyStore) {
        mKeyStore = keyStore;
    }

@@ -151,7 +152,8 @@ public class ManagedProfilePasswordCache {
                Slog.d(TAG, "Cannot decrypt", e);
                return null;
            }
            LockscreenCredential result = LockscreenCredential.createManagedPassword(credential);
            LockscreenCredential result =
                    LockscreenCredential.createUnifiedProfilePassword(credential);
            Arrays.fill(credential, (byte) 0);
            return result;
        }
+2 −2
Original line number Diff line number Diff line
@@ -140,9 +140,9 @@ public class LockSettingsServiceTestable extends LockSettingsService {
        }

        @Override
        public ManagedProfilePasswordCache getManagedProfilePasswordCache(
        public UnifiedProfilePasswordCache getUnifiedProfilePasswordCache(
                java.security.KeyStore ks) {
            return mock(ManagedProfilePasswordCache.class);
            return mock(UnifiedProfilePasswordCache.class);
        }

        @Override