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

Commit 4702c441 authored by Eric Biggers's avatar Eric Biggers Committed by Android (Google) Code Review
Browse files

Merge changes from topic "frp-fix"

* changes:
  Fix FRP credential overwritten before it is verified
  Convert locksettings unit tests to use FakeSettingsProvider
parents 24853626 74555559
Loading
Loading
Loading
Loading
+4 −13
Original line number Diff line number Diff line
@@ -550,16 +550,6 @@ public class LockSettingsService extends ILockSettings.Stub {
            return (BiometricManager) mContext.getSystemService(Context.BIOMETRIC_SERVICE);
        }

        public int settingsGlobalGetInt(ContentResolver contentResolver, String keyName,
                int defaultValue) {
            return Settings.Global.getInt(contentResolver, keyName, defaultValue);
        }

        public int settingsSecureGetInt(ContentResolver contentResolver, String keyName,
                int defaultValue, int userId) {
            return Settings.Secure.getIntForUser(contentResolver, keyName, defaultValue, userId);
        }

        public java.security.KeyStore getJavaKeyStore() {
            try {
                java.security.KeyStore ks = java.security.KeyStore.getInstance(
@@ -1027,9 +1017,9 @@ public class LockSettingsService extends ILockSettings.Stub {

    private void enforceFrpResolved() {
        final ContentResolver cr = mContext.getContentResolver();
        final boolean inSetupWizard = mInjector.settingsSecureGetInt(cr,
        final boolean inSetupWizard = Settings.Secure.getIntForUser(cr,
                Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_SYSTEM) == 0;
        final boolean secureFrp = mInjector.settingsSecureGetInt(cr,
        final boolean secureFrp = Settings.Secure.getIntForUser(cr,
                Settings.Secure.SECURE_FRP_MODE, 0, UserHandle.USER_SYSTEM) == 1;
        if (inSetupWizard && secureFrp) {
            throw new SecurityException("Cannot change credential in SUW while factory reset"
@@ -2155,7 +2145,7 @@ public class LockSettingsService extends ILockSettings.Stub {
        if (credential == null || credential.isNone()) {
            throw new IllegalArgumentException("Credential can't be null or empty");
        }
        if (userId == USER_FRP && mInjector.settingsGlobalGetInt(mContext.getContentResolver(),
        if (userId == USER_FRP && Settings.Global.getInt(mContext.getContentResolver(),
                Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
            Slog.e(TAG, "FRP credential can only be verified prior to provisioning.");
            return VerifyCredentialResponse.ERROR;
@@ -3282,6 +3272,7 @@ public class LockSettingsService extends ILockSettings.Stub {
            for (UserInfo user : users) {
                if (userOwnsFrpCredential(mContext, user)) {
                    if (!isUserSecure(user.id)) {
                        Slogf.d(TAG, "Clearing FRP credential tied to user %d", user.id);
                        mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, user.id,
                                0, null);
                    }
+1 −1
Original line number Diff line number Diff line
@@ -550,7 +550,7 @@ class LockSettingsStorage {
        mCache.clear();
    }

    @Nullable @VisibleForTesting
    @Nullable
    PersistentDataBlockManagerInternal getPersistentDataBlockManager() {
        if (mPersistentDataBlockManagerInternal == null) {
            mPersistentDataBlockManagerInternal =
+61 −12
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.hardware.weaver.V1_0.WeaverStatus;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserManager;
import android.provider.Settings;
import android.security.GateKeeper;
import android.security.Scrypt;
import android.service.gatekeeper.GateKeeperResponse;
@@ -457,6 +458,11 @@ public class SyntheticPasswordManager {
        mPasswordSlotManager = passwordSlotManager;
    }

    private boolean isDeviceProvisioned() {
        return Settings.Global.getInt(mContext.getContentResolver(),
                Settings.Global.DEVICE_PROVISIONED, 0) != 0;
    }

    @VisibleForTesting
    protected IWeaver getWeaverService() throws RemoteException {
        try {
@@ -770,6 +776,17 @@ public class SyntheticPasswordManager {
    private int getNextAvailableWeaverSlot() {
        Set<Integer> usedSlots = getUsedWeaverSlots();
        usedSlots.addAll(mPasswordSlotManager.getUsedSlots());
        // If the device is not yet provisioned, then the Weaver slot used by the FRP credential may
        // be still needed and must not be reused yet.  (This *should* instead check "has FRP been
        // resolved yet?", which would allow reusing the slot a bit earlier.  However, the
        // SECURE_FRP_MODE setting gets set to 1 too late for it to be used here.)
        if (!isDeviceProvisioned()) {
            PersistentData persistentData = mStorage.readPersistentDataBlock();
            if (persistentData != null && persistentData.type == PersistentData.TYPE_SP_WEAVER) {
                int slot = persistentData.userId; // Note: field name is misleading
                usedSlots.add(slot);
            }
        }
        for (int i = 0; i < mWeaverConfig.slots; i++) {
            if (!usedSlots.contains(i)) {
                return i;
@@ -814,9 +831,14 @@ public class SyntheticPasswordManager {

            protectorSecret = transformUnderWeaverSecret(stretchedLskf, weaverSecret);
        } else {
            // Weaver is unavailable, so make the protector use Gatekeeper to verify the LSKF
            // instead.  However, skip Gatekeeper when the LSKF is empty, since it wouldn't give any
            // benefit in that case as Gatekeeper isn't expected to provide secure deletion.
            // Weaver is unavailable, so make the protector use Gatekeeper (GK) to verify the LSKF.
            //
            // However, skip GK when the LSKF is empty.  There are two reasons for this, one
            // performance and one correctness.  The performance reason is that GK wouldn't give any
            // benefit with an empty LSKF anyway, since GK isn't expected to provide secure
            // deletion.  The correctness reason is that it is unsafe to enroll a password in the
            // 'fakeUserId' GK range on an FRP-protected device that is in the setup wizard with FRP
            // not passed yet, as that may overwrite the enrollment used by the FRP credential.
            if (!credential.isNone()) {
                // In case GK enrollment leaves persistent state around (in RPMB), this will nuke
                // them to prevent them from accumulating and causing problems.
@@ -908,12 +930,40 @@ public class SyntheticPasswordManager {
        }
    }

    private static boolean isNoneCredential(PasswordData pwd) {
        return pwd == null || pwd.credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE;
    }

    private boolean shouldSynchronizeFrpCredential(@Nullable PasswordData pwd, int userId) {
        if (mStorage.getPersistentDataBlockManager() == null) {
            return false;
        }
        UserInfo userInfo = mUserManager.getUserInfo(userId);
        if (!LockPatternUtils.userOwnsFrpCredential(mContext, userInfo)) {
            return false;
        }
        // When initializing the synthetic password of the user that will own the FRP credential,
        // the FRP data block must not be cleared if the device isn't provisioned yet, since in this
        // case the old value of the block may still be needed for the FRP authentication step.  The
        // FRP data block will instead be cleared later, by
        // LockSettingsService.DeviceProvisionedObserver.clearFrpCredentialIfOwnerNotSecure().
        //
        // Don't check the SECURE_FRP_MODE setting here, as it gets set to 1 too late.
        //
        // Don't delay anything for a nonempty credential.  A nonempty credential can be set before
        // the device has been provisioned, but it's guaranteed to be after FRP was resolved.
        if (isNoneCredential(pwd) && !isDeviceProvisioned()) {
            Slog.d(TAG, "Not clearing FRP credential yet because device is not yet provisioned");
            return false;
        }
        return true;
    }

    private void synchronizeFrpPassword(@Nullable PasswordData pwd, int requestedQuality,
            int userId) {
        if (mStorage.getPersistentDataBlockManager() != null
                && LockPatternUtils.userOwnsFrpCredential(mContext,
                mUserManager.getUserInfo(userId))) {
            if (pwd != null && pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
        if (shouldSynchronizeFrpCredential(pwd, userId)) {
            Slogf.d(TAG, "Syncing Gatekeeper-based FRP credential tied to user %d", userId);
            if (!isNoneCredential(pwd)) {
                mStorage.writePersistentDataBlock(PersistentData.TYPE_SP, userId, requestedQuality,
                        pwd.toBytes());
            } else {
@@ -924,10 +974,9 @@ public class SyntheticPasswordManager {

    private void synchronizeWeaverFrpPassword(@Nullable PasswordData pwd, int requestedQuality,
            int userId, int weaverSlot) {
        if (mStorage.getPersistentDataBlockManager() != null
                && LockPatternUtils.userOwnsFrpCredential(mContext,
                mUserManager.getUserInfo(userId))) {
            if (pwd != null && pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
        if (shouldSynchronizeFrpCredential(pwd, userId)) {
            Slogf.d(TAG, "Syncing Weaver-based FRP credential tied to user %d", userId);
            if (!isNoneCredential(pwd)) {
                mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, weaverSlot,
                        requestedQuality, pwd.toBytes());
            } else {
@@ -1058,7 +1107,7 @@ public class SyntheticPasswordManager {
        AuthenticationResult result = new AuthenticationResult();

        if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
            // This should never happen, due to the migration done in LSS.bootCompleted().
            // This should never happen, due to the migration done in LSS.onThirdPartyAppsStarted().
            Slogf.wtf(TAG, "Synthetic password not found for user %d", userId);
            result.gkResponse = VerifyCredentialResponse.ERROR;
            return result;
+31 −9
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.DeviceStateCache;
import android.app.trust.TrustManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.hardware.authsecret.V1_0.IAuthSecret;
@@ -41,14 +42,18 @@ import android.hardware.fingerprint.FingerprintManager;
import android.os.FileUtils;
import android.os.IProgressListener;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
import android.provider.Settings;
import android.security.KeyStore;

import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;

import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
import com.android.internal.widget.LockscreenCredential;
@@ -59,6 +64,7 @@ import com.android.server.wm.WindowManagerInternal;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -106,7 +112,8 @@ public abstract class BaseLockSettingsServiceTests {
    FingerprintManager mFingerprintManager;
    FaceManager mFaceManager;
    PackageManager mPackageManager;
    FakeSettings mSettings;
    @Rule
    public FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();

    @Before
    public void setUp_baseServices() throws Exception {
@@ -126,7 +133,6 @@ public abstract class BaseLockSettingsServiceTests {
        mFingerprintManager = mock(FingerprintManager.class);
        mFaceManager = mock(FaceManager.class);
        mPackageManager = mock(PackageManager.class);
        mSettings = new FakeSettings();

        LocalServices.removeServiceForTest(LockSettingsInternal.class);
        LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
@@ -134,12 +140,13 @@ public abstract class BaseLockSettingsServiceTests {
        LocalServices.addService(DevicePolicyManagerInternal.class, mDevicePolicyManagerInternal);
        LocalServices.addService(WindowManagerInternal.class, mMockWindowManager);

        mContext = new MockLockSettingsContext(InstrumentationRegistry.getContext(), mUserManager,
                mNotificationManager, mDevicePolicyManager, mock(StorageManager.class),
                mock(TrustManager.class), mock(KeyguardManager.class), mFingerprintManager,
                mFaceManager, mPackageManager);
        final Context origContext = InstrumentationRegistry.getContext();
        mContext = new MockLockSettingsContext(origContext,
                mSettingsRule.mockContentResolver(origContext), mUserManager, mNotificationManager,
                mDevicePolicyManager, mock(StorageManager.class), mock(TrustManager.class),
                mock(KeyguardManager.class), mFingerprintManager, mFaceManager, mPackageManager);
        mStorage = new LockSettingsStorageTestable(mContext,
                new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings"));
                new File(origContext.getFilesDir(), "locksettings"));
        File storageDir = mStorage.mStorageDir;
        if (storageDir.exists()) {
            FileUtils.deleteContents(storageDir);
@@ -153,7 +160,7 @@ public abstract class BaseLockSettingsServiceTests {
        mService = new LockSettingsServiceTestable(mContext, mStorage,
                mGateKeeperService, mKeyStore, setUpStorageManagerMock(), mActivityManager,
                mSpManager, mAuthSecretService, mGsiService, mRecoverableKeyStoreManager,
                mUserManagerInternal, mDeviceStateCache, mSettings);
                mUserManagerInternal, mDeviceStateCache);
        mService.mHasSecureLockScreen = true;
        when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO);
        mPrimaryUserProfiles.add(PRIMARY_USER_INFO);
@@ -186,10 +193,25 @@ public abstract class BaseLockSettingsServiceTests {
        mockBiometricsHardwareFingerprintsAndTemplates(PRIMARY_USER_ID);
        mockBiometricsHardwareFingerprintsAndTemplates(MANAGED_PROFILE_USER_ID);

        mSettings.setDeviceProvisioned(true);
        setDeviceProvisioned(true);
        mLocalService = LocalServices.getService(LockSettingsInternal.class);
    }

    protected void setDeviceProvisioned(boolean provisioned) {
        Settings.Global.putInt(mContext.getContentResolver(),
                Settings.Global.DEVICE_PROVISIONED, provisioned ? 1 : 0);
    }

    protected void setUserSetupComplete(boolean complete) {
        Settings.Secure.putIntForUser(mContext.getContentResolver(),
                Settings.Secure.USER_SETUP_COMPLETE, complete ? 1 : 0, UserHandle.USER_SYSTEM);
    }

    protected void setSecureFrpMode(boolean secure) {
        Settings.Secure.putIntForUser(mContext.getContentResolver(),
                Settings.Secure.SECURE_FRP_MODE, secure ? 1 : 0, UserHandle.USER_SYSTEM);
    }

    private UserInfo installChildProfile(int profileId) {
        final UserInfo userInfo = new UserInfo(
            profileId, null, null, UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_MANAGED_PROFILE);
+0 −60
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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 android.content.ContentResolver;
import android.os.UserHandle;
import android.provider.Settings;

public class FakeSettings {

    private int mDeviceProvisioned;
    private int mSecureFrpMode;
    private int mUserSetupComplete;

    public void setDeviceProvisioned(boolean provisioned) {
        mDeviceProvisioned = provisioned ? 1 : 0;
    }

    public void setSecureFrpMode(boolean secure) {
        mSecureFrpMode = secure ? 1 : 0;
    }

    public void setUserSetupComplete(boolean complete) {
        mUserSetupComplete = complete ? 1 : 0;
    }

    public int globalGetInt(String keyName) {
        switch (keyName) {
            case Settings.Global.DEVICE_PROVISIONED:
                return mDeviceProvisioned;
            default:
                throw new IllegalArgumentException("Unhandled global settings: " + keyName);
        }
    }

    public int secureGetInt(ContentResolver contentResolver, String keyName, int defaultValue,
            int userId) {
        if (Settings.Secure.SECURE_FRP_MODE.equals(keyName) && userId == UserHandle.USER_SYSTEM) {
            return mSecureFrpMode;
        }
        if (Settings.Secure.USER_SETUP_COMPLETE.equals(keyName)
                && userId == UserHandle.USER_SYSTEM) {
            return mUserSetupComplete;
        }
        return defaultValue;
    }
}
Loading