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

Commit 4ba2c3f4 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Ensure a user's SP does't change."

parents 52c36856 1416bd02
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -123,4 +123,13 @@ public abstract class DevicePolicyManagerInternal {
     * @param userId User ID of the profile.
     */
    public abstract void reportSeparateProfileChallengeChanged(@UserIdInt int userId);

    /**
     * Check whether the user could have their password reset in an untrusted manor due to there
     * being an admin which can call {@link #resetPassword} to reset the password without knowledge
     * of the previous password.
     *
     * @param userId The user in question
     */
    public abstract boolean canUserHaveUntrustedCredentialReset(@UserIdInt int userId);
}
+95 −9
Original line number Diff line number Diff line
@@ -90,6 +90,7 @@ import android.util.ArrayMap;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -1874,6 +1875,7 @@ public class LockSettingsService extends ILockSettings.Stub {
        mSpManager.removeUser(userId);
        mStorage.removeUser(userId);
        mStrongAuth.removeUser(userId);
        cleanSpCache();

        final KeyStore ks = KeyStore.getInstance();
        ks.onUserRemoved(userId);
@@ -2111,6 +2113,63 @@ public class LockSettingsService extends ILockSettings.Stub {
        return null;
    }

    /**
     * A user's synthetic password does not change so it must be cached in certain circumstances to
     * enable untrusted credential reset.
     *
     * Untrusted credential reset will be removed in a future version (b/68036371) at which point
     * this cache is no longer needed as the SP will always be known when changing the user's
     * credential.
     */
    @GuardedBy("mSpManager")
    private SparseArray<AuthenticationToken> mSpCache = new SparseArray();

    private void onAuthTokenKnownForUser(@UserIdInt int userId, AuthenticationToken auth) {
        // Update the SP cache, removing the entry when allowed
        synchronized (mSpManager) {
            if (shouldCacheSpForUser(userId)) {
                Slog.i(TAG, "Caching SP for user " + userId);
                mSpCache.put(userId, auth);
            } else {
                Slog.i(TAG, "Not caching SP for user " + userId);
                mSpCache.delete(userId);
            }
        }
    }

    /** Clean up the SP cache by removing unneeded entries. */
    private void cleanSpCache() {
        synchronized (mSpManager) {
            // Preserve indicies after removal by iterating backwards
            for (int i = mSpCache.size() - 1; i >= 0; --i) {
                final int userId = mSpCache.keyAt(i);
                if (!shouldCacheSpForUser(userId)) {
                    Slog.i(TAG, "Uncaching SP for user " + userId);
                    mSpCache.removeAt(i);
                }
            }
        }
    }

    private boolean shouldCacheSpForUser(@UserIdInt int userId) {
        // Before the user setup has completed, an admin could be installed that requires the SP to
        // be cached (see below).
        if (Settings.Secure.getIntForUser(mContext.getContentResolver(),
                    Settings.Secure.USER_SETUP_COMPLETE, 0, userId) == 0) {
            return true;
        }

        // If the user has an admin which can perform an untrusted credential reset, the SP needs to
        // be cached. If there isn't a DevicePolicyManager then there can't be an admin in the first
        // place so caching is not necessary.
        final DevicePolicyManagerInternal dpmi = LocalServices.getService(
                DevicePolicyManagerInternal.class);
        if (dpmi == null) {
            return false;
        }
        return dpmi.canUserHaveUntrustedCredentialReset(userId);
    }

    /**
     * Precondition: vold and keystore unlocked.
     *
@@ -2126,9 +2185,7 @@ public class LockSettingsService extends ILockSettings.Stub {
     * 3. Once a user is migrated to have synthetic password, its value will never change, no matter
     *     whether the user changes his lockscreen PIN or clear/reset it. When the user clears its
     *     lockscreen PIN, we still maintain the existing synthetic password in a password blob
     *     protected by a default PIN. The only exception is when the DPC performs an untrusted
     *     credential change, in which case we have no way to derive the existing synthetic password
     *     and has to create a new one.
     *     protected by a default PIN.
     * 4. The user SID is linked with synthetic password, but its cleared/re-created when the user
     *     clears/re-creates his lockscreen PIN.
     *
@@ -2148,13 +2205,23 @@ public class LockSettingsService extends ILockSettings.Stub {
     *     This is the untrusted credential reset, OR the user sets a new lockscreen password
     *     FOR THE FIRST TIME on a SP-enabled device. New credential and new SID will be created
     */
    @GuardedBy("mSpManager")
    @VisibleForTesting
    protected AuthenticationToken initializeSyntheticPasswordLocked(byte[] credentialHash,
            String credential, int credentialType, int requestedQuality,
            int userId) throws RemoteException {
        Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId);
        AuthenticationToken auth = mSpManager.newSyntheticPasswordAndSid(getGateKeeperService(),
        // Load from the cache or a make a new one
        AuthenticationToken auth = mSpCache.get(userId);
        if (auth != null) {
            // If the synthetic password has been cached, we can only be in case 3., described
            // above, for an untrusted credential reset so a new SID is still needed.
            mSpManager.newSidForUser(getGateKeeperService(), auth, userId);
        } else {
            auth = mSpManager.newSyntheticPasswordAndSid(getGateKeeperService(),
                      credentialHash, credential, userId);
        }
        onAuthTokenKnownForUser(userId, auth);
        if (auth == null) {
            Slog.wtf(TAG, "initializeSyntheticPasswordLocked returns null auth token");
            return null;
@@ -2269,6 +2336,8 @@ public class LockSettingsService extends ILockSettings.Stub {
                trustManager.setDeviceLockedForUser(userId, false);
            }
            mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);

            onAuthTokenKnownForUser(userId, authResult.authToken);
        } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
            if (response.getTimeout() > 0) {
                requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
@@ -2287,6 +2356,7 @@ public class LockSettingsService extends ILockSettings.Stub {
     * SID is gone. We also clear password from (software-based) keystore and vold, which will be
     * added back when new password is set in future.
     */
    @GuardedBy("mSpManager")
    private long setLockCredentialWithAuthTokenLocked(String credential, int credentialType,
            AuthenticationToken auth, int requestedQuality, int userId) throws RemoteException {
        if (DEBUG) Slog.d(TAG, "setLockCredentialWithAuthTokenLocked: user=" + userId);
@@ -2334,6 +2404,7 @@ public class LockSettingsService extends ILockSettings.Stub {
        return newHandle;
    }

    @GuardedBy("mSpManager")
    private void spBasedSetLockCredentialInternalLocked(String credential, int credentialType,
            String savedCredential, int requestedQuality, int userId) throws RemoteException {
        if (DEBUG) Slog.d(TAG, "spBasedSetLockCredentialInternalLocked: user=" + userId);
@@ -2369,13 +2440,19 @@ public class LockSettingsService extends ILockSettings.Stub {
            setLockCredentialWithAuthTokenLocked(credential, credentialType, auth, requestedQuality,
                    userId);
            mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId);
            onAuthTokenKnownForUser(userId, auth);
        } else if (response != null
                && response.getResponseCode() == VerifyCredentialResponse.RESPONSE_ERROR) {
            // We are performing an untrusted credential change i.e. by DevicePolicyManager.
            // So provision a new SP and SID. This would invalidate existing escrow tokens.
            // Still support this for now but this flow will be removed in the next release.

            Slog.w(TAG, "Untrusted credential change invoked");

            if (mSpCache.get(userId) == null) {
                throw new IllegalStateException(
                        "Untrusted credential reset not possible without cached SP");
            }

            initializeSyntheticPasswordLocked(null, credential, credentialType, requestedQuality,
                    userId);
            synchronizeUnifiedWorkChallengeForProfiles(userId, null);
@@ -2486,8 +2563,9 @@ public class LockSettingsService extends ILockSettings.Stub {

    private boolean setLockCredentialWithTokenInternal(String credential, int type,
            long tokenHandle, byte[] token, int requestedQuality, int userId) throws RemoteException {
        final AuthenticationResult result;
        synchronized (mSpManager) {
            AuthenticationResult result = mSpManager.unwrapTokenBasedSyntheticPassword(
            result = mSpManager.unwrapTokenBasedSyntheticPassword(
                    getGateKeeperService(), tokenHandle, token, userId);
            if (result.authToken == null) {
                Slog.w(TAG, "Invalid escrow token supplied");
@@ -2508,8 +2586,9 @@ public class LockSettingsService extends ILockSettings.Stub {
            setLockCredentialWithAuthTokenLocked(credential, type, result.authToken,
                    requestedQuality, userId);
            mSpManager.destroyPasswordBasedSyntheticPassword(oldHandle, userId);
            return true;
        }
        onAuthTokenKnownForUser(userId, result.authToken);
        return true;
    }

    @Override
@@ -2529,6 +2608,7 @@ public class LockSettingsService extends ILockSettings.Stub {
            }
        }
        unlockUser(userId, null, authResult.authToken.deriveDiskEncryptionKey());
        onAuthTokenKnownForUser(userId, authResult.authToken);
    }

    @Override
@@ -2610,6 +2690,8 @@ public class LockSettingsService extends ILockSettings.Stub {
    private class DeviceProvisionedObserver extends ContentObserver {
        private final Uri mDeviceProvisionedUri = Settings.Global.getUriFor(
                Settings.Global.DEVICE_PROVISIONED);
        private final Uri mUserSetupCompleteUri = Settings.Secure.getUriFor(
                Settings.Secure.USER_SETUP_COMPLETE);

        private boolean mRegistered;

@@ -2627,6 +2709,8 @@ public class LockSettingsService extends ILockSettings.Stub {
                    reportDeviceSetupComplete();
                    clearFrpCredentialIfOwnerNotSecure();
                }
            } else if (mUserSetupCompleteUri.equals(uri)) {
                cleanSpCache();
            }
        }

@@ -2678,6 +2762,8 @@ public class LockSettingsService extends ILockSettings.Stub {
            if (register) {
                mContext.getContentResolver().registerContentObserver(mDeviceProvisionedUri,
                        false, this);
                mContext.getContentResolver().registerContentObserver(mUserSetupCompleteUri,
                        false, this, UserHandle.USER_ALL);
            } else {
                mContext.getContentResolver().unregisterContentObserver(this);
            }
+30 −3
Original line number Diff line number Diff line
@@ -4517,6 +4517,28 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        }
    }
    private boolean canPOorDOCallResetPassword(ActiveAdmin admin, @UserIdInt int userId) {
        // Only if the admins targets a pre-O SDK
        return getTargetSdk(admin.info.getPackageName(), userId) < Build.VERSION_CODES.O;
    }
    /* PO or DO could do an untrusted reset in certain conditions. */
    private boolean canUserHaveUntrustedCredentialReset(@UserIdInt int userId) {
        synchronized (this) {
            // An active DO or PO might be able to fo an untrusted credential reset
            for (final ActiveAdmin admin : getUserData(userId).mAdminList) {
                if (!isActiveAdminWithPolicyForUserLocked(admin,
                          DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, userId)) {
                    continue;
                }
                if (canPOorDOCallResetPassword(admin, userId)) {
                    return true;
                }
            }
            return false;
        }
    }
    @Override
    public boolean resetPassword(String passwordOrNull, int flags) throws RemoteException {
        final int callingUid = mInjector.binderGetCallingUid();
@@ -4535,12 +4557,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                    null, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, callingUid);
            final boolean preN;
            if (admin != null) {
                final int targetSdk = getTargetSdk(admin.info.getPackageName(), userHandle);
                if (targetSdk >= Build.VERSION_CODES.O) {
                if (!canPOorDOCallResetPassword(admin, userHandle)) {
                    throw new SecurityException("resetPassword() is deprecated for DPC targeting O"
                            + " or later");
                }
                preN = targetSdk <= android.os.Build.VERSION_CODES.M;
                preN = getTargetSdk(admin.info.getPackageName(),
                        userHandle) <= android.os.Build.VERSION_CODES.M;
            } else {
                // Otherwise, make sure the caller has any active admin with the right policy.
                admin = getActiveAdminForCallerLocked(null,
@@ -10111,6 +10133,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                updateMaximumTimeToLockLocked(userId);
            }
        }
        @Override
        public boolean canUserHaveUntrustedCredentialReset(@UserIdInt int userId) {
            return DevicePolicyManagerService.this.canUserHaveUntrustedCredentialReset(userId);
        }
    }
    private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
+7 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.when;
import android.app.IActivityManager;
import android.app.NotificationManager;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.trust.TrustManager;
import android.content.ComponentName;
import android.content.pm.UserInfo;
@@ -41,6 +42,7 @@ import android.test.AndroidTestCase;

import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.LocalServices;

import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -75,6 +77,7 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase {
    FakeStorageManager mStorageManager;
    IActivityManager mActivityManager;
    DevicePolicyManager mDevicePolicyManager;
    DevicePolicyManagerInternal mDevicePolicyManagerInternal;
    KeyStore mKeyStore;
    MockSyntheticPasswordManager mSpManager;

@@ -88,6 +91,10 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase {
        mStorageManager = new FakeStorageManager();
        mActivityManager = mock(IActivityManager.class);
        mDevicePolicyManager = mock(DevicePolicyManager.class);
        mDevicePolicyManagerInternal = mock(DevicePolicyManagerInternal.class);

        LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
        LocalServices.addService(DevicePolicyManagerInternal.class, mDevicePolicyManagerInternal);

        mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager,
                mDevicePolicyManager, mock(StorageManager.class), mock(TrustManager.class));
+116 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.server.locksettings;

import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;

import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.server.testutils.TestUtils.assertExpectException;

import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;

import android.os.RemoteException;

import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;

/**
 * Run the synthetic password tests with caching enabled.
 *
 * By default, those tests run without caching. Untrusted credential reset depends on caching so
 * this class included those tests.
 */
public class CachedSyntheticPasswordTests extends SyntheticPasswordTests {

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        enableSpCaching(true);
    }

    private void enableSpCaching(boolean enable) {
        when(mDevicePolicyManagerInternal
                .canUserHaveUntrustedCredentialReset(anyInt())).thenReturn(enable);
    }

    public void testSyntheticPasswordClearCredentialUntrusted() throws RemoteException {
        final String PASSWORD = "testSyntheticPasswordClearCredential-password";
        final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword";

        initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
        long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
        // clear password
        mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, null,
                PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
        assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));

        // set a new password
        mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
                    .getResponseCode());
        assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
    }

    public void testSyntheticPasswordChangeCredentialUntrusted() throws RemoteException {
        final String PASSWORD = "testSyntheticPasswordClearCredential-password";
        final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword";

        initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
        long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
        // Untrusted change password
        mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
        assertNotEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
        assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));

        // Verify the password
        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
                    .getResponseCode());
    }

    public void testUntrustedCredentialChangeBlockedIfSpNotCached() throws RemoteException {
        final String PASSWORD = "testUntrustedCredentialChangeBlockedIfSpNotCached-password";
        final String NEWPASSWORD = "testUntrustedCredentialChangeBlockedIfSpNotCached-newpassword";

        // Disable caching for this test
        enableSpCaching(false);

        initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
        long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
        // Untrusted change password
        assertExpectException(IllegalStateException.class, /* messageRegex= */ null,
                () -> mService.setLockCredential(
                        NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
                        null, PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID));
        assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));

        // Verify the new password doesn't work but the old one still does
        assertEquals(VerifyCredentialResponse.RESPONSE_ERROR, mService.verifyCredential(
                NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
                        .getResponseCode());
        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
                        .getResponseCode());
    }

}
Loading