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

Commit f8adf54f authored by Paul Crowley's avatar Paul Crowley Committed by Android (Google) Code Review
Browse files

Merge "Fix insider attack resistance on headless devices"

parents 3d269617 2b317707
Loading
Loading
Loading
Loading
+114 −25
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRY
import static android.content.Context.KEYGUARD_SERVICE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_SYSTEM;
import static android.provider.DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION;

import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
@@ -70,6 +71,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.sqlite.SQLiteDatabase;
import android.hardware.authsecret.IAuthSecret;
@@ -219,6 +221,8 @@ public class LockSettingsService extends ILockSettings.Stub {
    private static final String PROFILE_KEY_NAME_ENCRYPT = "profile_key_name_encrypt_";
    private static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_";

    private static final int HEADLESS_VENDOR_AUTH_SECRET_LENGTH = 32;

    // Order of holding lock: mSeparateChallengeLock -> mSpManager -> this
    // Do not call into ActivityManager while holding mSpManager lock.
    private final Object mSeparateChallengeLock = new Object();
@@ -267,6 +271,13 @@ public class LockSettingsService extends ILockSettings.Stub {
    @VisibleForTesting
    protected boolean mHasSecureLockScreen;

    @VisibleForTesting
    protected final Object mHeadlessAuthSecretLock = new Object();

    @VisibleForTesting
    @GuardedBy("mHeadlessAuthSecretLock")
    protected byte[] mAuthSecret;

    protected IGateKeeperService mGateKeeperService;
    protected IAuthSecret mAuthSecretService;

@@ -563,6 +574,15 @@ public class LockSettingsService extends ILockSettings.Stub {
                java.security.KeyStore ks) {
            return new ManagedProfilePasswordCache(ks, getUserManager());
        }

        public boolean isHeadlessSystemUserMode() {
            return UserManager.isHeadlessSystemUserMode();
        }

        public boolean isMainUserPermanentAdmin() {
            return Resources.getSystem()
                    .getBoolean(com.android.internal.R.bool.config_isMainUserPermanentAdmin);
        }
    }

    public LockSettingsService(Context context) {
@@ -1697,7 +1717,7 @@ public class LockSettingsService extends ILockSettings.Stub {
                throw new IllegalStateException("password change failed");
            }

            onSyntheticPasswordKnown(userId, sp);
            onSyntheticPasswordUnlocked(userId, sp);
            setLockCredentialWithSpLocked(credential, sp, userId);
            sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
            return true;
@@ -2009,7 +2029,7 @@ public class LockSettingsService extends ILockSettings.Stub {
                Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
                return;
            }
            onSyntheticPasswordKnown(userId, result.syntheticPassword);
            onSyntheticPasswordUnlocked(userId, result.syntheticPassword);
            unlockUserKey(userId, result.syntheticPassword);
        }
    }
@@ -2602,43 +2622,112 @@ public class LockSettingsService extends ILockSettings.Stub {
        }
    }

    private void onSyntheticPasswordKnown(@UserIdInt int userId, SyntheticPassword sp) {
    private void onSyntheticPasswordCreated(@UserIdInt int userId, SyntheticPassword sp) {
        onSyntheticPasswordKnown(userId, sp, true);
    }

    private void onSyntheticPasswordUnlocked(@UserIdInt int userId, SyntheticPassword sp) {
        onSyntheticPasswordKnown(userId, sp, false);
    }

    private void onSyntheticPasswordKnown(
            @UserIdInt int userId, SyntheticPassword sp, boolean justCreated) {
        if (mInjector.isGsiRunning()) {
            Slog.w(TAG, "Running in GSI; skipping calls to AuthSecret and RebootEscrow");
            return;
        }

        mRebootEscrowManager.callToRebootEscrowIfNeeded(userId, sp.getVersion(),
                sp.getSyntheticPassword());

        callToAuthSecretIfNeeded(userId, sp);
        mRebootEscrowManager.callToRebootEscrowIfNeeded(
                userId, sp.getVersion(), sp.getSyntheticPassword());
        callToAuthSecretIfNeeded(userId, sp, justCreated);
    }

    private void callToAuthSecretIfNeeded(@UserIdInt int userId, SyntheticPassword sp) {
        // If the given user is the primary user, pass the auth secret to the HAL.  Only the system
        // user can be primary.  Check for the system user ID before calling getUserInfo(), as other
        // users may still be under construction.
    /**
     * Handles generation, storage, and sending of the vendor auth secret. Here we try to retrieve
     * the auth secret to send it to the auth secret HAL, generate a fresh secret if need be, store
     * it encrypted on disk so that the given user can unlock it in future, and stash it in memory
     * so that when future users are created they can also unlock it.
     *
     * <p>Called whenever the SP of a user is available, except in GSI.
     */
    private void callToAuthSecretIfNeeded(
            @UserIdInt int userId, SyntheticPassword sp, boolean justCreated) {
        if (mAuthSecretService == null) {
            // If there's no IAuthSecret service, we don't need to maintain a auth secret
            return;
        }
        // User may be partially created, so use the internal user manager interface
        final UserManagerInternal userManagerInternal = mInjector.getUserManagerInternal();
        final UserInfo userInfo = userManagerInternal.getUserInfo(userId);
        if (userInfo == null) {
            // User may be partially deleted, skip this.
            return;
        }
        final byte[] authSecret;
        if (!mInjector.isHeadlessSystemUserMode()) {
            // On non-headless systems, the auth secret is derived from user 0's
            // SP, and only user 0 passes it to the HAL.
            if (userId != USER_SYSTEM) {
                return;
            }
            authSecret = sp.deriveVendorAuthSecret();
        } else if (!mInjector.isMainUserPermanentAdmin() || !userInfo.isFull()) {
            // Only full users can receive or pass on the auth secret.
            // If there is no main permanent admin user, we don't try to create or send
            // an auth secret, since there may sometimes be no full users.
            return;
        } else if (justCreated) {
            if (userInfo.isMain()) {
                // The first user is just being created, so we create a new auth secret
                // at the same time.
                Slog.i(TAG, "Generating new vendor auth secret and storing for user: " + userId);
                authSecret = SecureRandomUtils.randomBytes(HEADLESS_VENDOR_AUTH_SECRET_LENGTH);
                // Store it in memory, for when new users are created.
                synchronized (mHeadlessAuthSecretLock) {
                    mAuthSecret = authSecret;
                }
            } else {
                // A new user is being created. Another user should already have logged in at
                // this point, and therefore the auth secret should be stored in memory.
                synchronized (mHeadlessAuthSecretLock) {
                    authSecret = mAuthSecret;
                }
                if (authSecret == null) {
                    Slog.e(TAG, "Creating non-main user " + userId
                            + " but vendor auth secret is not in memory");
                    return;
                }
        if (userId == UserHandle.USER_SYSTEM &&
                mUserManager.getUserInfo(userId).isPrimary()) {
            final byte[] secret = sp.deriveVendorAuthSecret();
            }
            // Store the auth secret encrypted using the user's SP (which was just created).
            mSpManager.writeVendorAuthSecret(authSecret, sp, userId);
        } else {
            // The user already exists, so the auth secret should be stored encrypted
            // with that user's SP.
            authSecret = mSpManager.readVendorAuthSecret(sp, userId);
            if (authSecret == null) {
                Slog.e(TAG, "Unable to read vendor auth secret for user: " + userId);
                return;
            }
            // Store it in memory, for when new users are created.
            synchronized (mHeadlessAuthSecretLock) {
                mAuthSecret = authSecret;
            }
        }
        Slog.i(TAG, "Sending vendor auth secret to IAuthSecret HAL as user: " + userId);
        try {
                mAuthSecretService.setPrimaryUserCredential(secret);
            mAuthSecretService.setPrimaryUserCredential(authSecret);
        } catch (RemoteException e) {
                Slog.w(TAG, "Failed to pass primary user secret to AuthSecret HAL", e);
            }
            Slog.w(TAG, "Failed to send vendor auth secret to IAuthSecret HAL", e);
        }
    }

    /**
     * Creates the synthetic password (SP) for the given user, protects it with an empty LSKF, and
     * protects the user's CE key with a key derived from the SP.
     * <p>
     * This is called just once in the lifetime of the user: at user creation time (possibly delayed
     * until the time when Weaver is guaranteed to be available), or when upgrading from Android 13
     * or earlier where users with no LSKF didn't necessarily have an SP.
     *
     * <p>This is called just once in the lifetime of the user: at user creation time (possibly
     * delayed until the time when Weaver is guaranteed to be available), or when upgrading from
     * Android 13 or earlier where users with no LSKF didn't necessarily have an SP.
     */
    @VisibleForTesting
    SyntheticPassword initializeSyntheticPassword(int userId) {
@@ -2653,7 +2742,7 @@ public class LockSettingsService extends ILockSettings.Stub {
                    LockscreenCredential.createNone(), sp, userId);
            setCurrentLskfBasedProtectorId(protectorId, userId);
            setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
            onSyntheticPasswordKnown(userId, sp);
            onSyntheticPasswordCreated(userId, sp);
            return sp;
        }
    }
@@ -2720,7 +2809,7 @@ public class LockSettingsService extends ILockSettings.Stub {
        }
        mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);

        onSyntheticPasswordKnown(userId, sp);
        onSyntheticPasswordUnlocked(userId, sp);
    }

    private void setDeviceUnlockedForUser(int userId) {
@@ -3008,7 +3097,7 @@ public class LockSettingsService extends ILockSettings.Stub {
                    + "verification.");
            return false;
        }
        onSyntheticPasswordKnown(userId, result.syntheticPassword);
        onSyntheticPasswordUnlocked(userId, result.syntheticPassword);
        setLockCredentialWithSpLocked(credential, result.syntheticPassword, userId);
        return true;
    }
+32 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChang
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.admin.PasswordMetrics;
import android.content.Context;
import android.content.pm.UserInfo;
@@ -93,6 +94,9 @@ import java.util.Set;
 *                     while the LSKF is nonempty.
 *     SP_E0_NAME, SP_P1_NAME: Information needed to create and use escrow token-based protectors.
 *                             Deleted when escrow token support is disabled for the user.
 *     VENDOR_AUTH_SECRET_NAME: A copy of the secret passed using the IAuthSecret interface,
 *                              encrypted using a secret derived from the SP using
 *                              PERSONALIZATION_AUTHSECRET_ENCRYPTION_KEY.
 *
 *     For each protector, stored under the corresponding protector ID:
 *       SP_BLOB_NAME: The encrypted SP secret (the SP itself or the P0 value).  Always exists.
@@ -120,6 +124,7 @@ class SyntheticPasswordManager {
    private static final String PASSWORD_DATA_NAME = "pwd";
    private static final String WEAVER_SLOT_NAME = "weaver";
    private static final String PASSWORD_METRICS_NAME = "metrics";
    private static final String VENDOR_AUTH_SECRET_NAME = "vendor_auth_secret";

    // used for files associated with the SP itself, not with a particular protector
    public static final long NULL_PROTECTOR_ID = 0L;
@@ -158,6 +163,8 @@ class SyntheticPasswordManager {
    private static final byte[] PERSONALIZATION_SP_GK_AUTH = "sp-gk-authentication".getBytes();
    private static final byte[] PERSONALIZATION_FBE_KEY = "fbe-key".getBytes();
    private static final byte[] PERSONALIZATION_AUTHSECRET_KEY = "authsecret-hal".getBytes();
    private static final byte[] PERSONALIZATION_AUTHSECRET_ENCRYPTION_KEY =
            "vendor-authsecret-encryption-key".getBytes();
    private static final byte[] PERSONALIZATION_SP_SPLIT = "sp-split".getBytes();
    private static final byte[] PERSONALIZATION_PASSWORD_HASH = "pw-hash".getBytes();
    private static final byte[] PERSONALIZATION_E0 = "e0-encryption".getBytes();
@@ -249,6 +256,10 @@ class SyntheticPasswordManager {
            return deriveSubkey(PERSONALIZATION_PASSWORD_METRICS);
        }

        public byte[] deriveVendorAuthSecretEncryptionKey() {
            return deriveSubkey(PERSONALIZATION_AUTHSECRET_ENCRYPTION_KEY);
        }

        /**
         * Assigns escrow data to this synthetic password. This is a prerequisite to call
         * {@link SyntheticPassword#recreateFromEscrow}.
@@ -1737,4 +1748,25 @@ class SyntheticPasswordManager {
            mListeners.finishBroadcast();
        }
    }

    public void writeVendorAuthSecret(
            @NonNull final byte[] vendorAuthSecret,
            @NonNull final SyntheticPassword sp,
            @UserIdInt final int userId) {
        final byte[] encrypted =
                SyntheticPasswordCrypto.encrypt(
                        sp.deriveVendorAuthSecretEncryptionKey(), new byte[0], vendorAuthSecret);
        saveState(VENDOR_AUTH_SECRET_NAME, encrypted, NULL_PROTECTOR_ID, userId);
        syncState(userId);
    }

    public @Nullable byte[] readVendorAuthSecret(
            @NonNull final SyntheticPassword sp, @UserIdInt final int userId) {
        final byte[] encrypted = loadState(VENDOR_AUTH_SECRET_NAME, NULL_PROTECTOR_ID, userId);
        if (encrypted == null) {
            return null;
        }
        return SyntheticPasswordCrypto.decrypt(
                sp.deriveVendorAuthSecretEncryptionKey(), new byte[0], encrypted);
    }
}
+56 −16
Original line number Diff line number Diff line
@@ -49,8 +49,8 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
import android.provider.Settings;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.security.KeyStore;

import androidx.test.InstrumentationRegistry;
@@ -83,16 +83,15 @@ public abstract class BaseLockSettingsServiceTests {
    protected static final int MANAGED_PROFILE_USER_ID = 12;
    protected static final int TURNED_OFF_PROFILE_USER_ID = 17;
    protected static final int SECONDARY_USER_ID = 20;
    protected static final int TERTIARY_USER_ID = 21;

    private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER_ID, null, null,
            UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY
                    | UserInfo.FLAG_MAIN);
    private static final UserInfo SECONDARY_USER_INFO = new UserInfo(SECONDARY_USER_ID, null, null,
            UserInfo.FLAG_INITIALIZED);
    protected UserInfo mPrimaryUserInfo;
    protected UserInfo mSecondaryUserInfo;
    protected UserInfo mTertiaryUserInfo;

    private ArrayList<UserInfo> mPrimaryUserProfiles = new ArrayList<>();

    LockSettingsService mService;
    LockSettingsServiceTestable mService;
    LockSettingsInternal mLocalService;

    MockLockSettingsContext mContext;
@@ -117,6 +116,7 @@ public abstract class BaseLockSettingsServiceTests {
    FingerprintManager mFingerprintManager;
    FaceManager mFaceManager;
    PackageManager mPackageManager;
    LockSettingsServiceTestable.MockInjector mInjector;
    @Rule
    public FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();

@@ -162,22 +162,61 @@ public abstract class BaseLockSettingsServiceTests {
        mSpManager = new MockSyntheticPasswordManager(mContext, mStorage, mGateKeeperService,
                mUserManager, mPasswordSlotManager);
        mAuthSecretService = mock(IAuthSecret.class);
        mService = new LockSettingsServiceTestable(mContext, mStorage,
                mGateKeeperService, mKeyStore, setUpStorageManagerMock(), mActivityManager,
                mSpManager, mAuthSecretService, mGsiService, mRecoverableKeyStoreManager,
                mUserManagerInternal, mDeviceStateCache);
        mInjector =
                new LockSettingsServiceTestable.MockInjector(
                        mContext,
                        mStorage,
                        mKeyStore,
                        mActivityManager,
                        setUpStorageManagerMock(),
                        mSpManager,
                        mGsiService,
                        mRecoverableKeyStoreManager,
                        mUserManagerInternal,
                        mDeviceStateCache);
        mService =
                new LockSettingsServiceTestable(mInjector, mGateKeeperService, mAuthSecretService);
        mService.mHasSecureLockScreen = true;
        when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO);
        mPrimaryUserProfiles.add(PRIMARY_USER_INFO);
        mPrimaryUserInfo =
                new UserInfo(
                        PRIMARY_USER_ID,
                        null,
                        null,
                        UserInfo.FLAG_INITIALIZED
                                | UserInfo.FLAG_ADMIN
                                | UserInfo.FLAG_PRIMARY
                                | UserInfo.FLAG_MAIN
                                | UserInfo.FLAG_FULL);
        mSecondaryUserInfo =
                new UserInfo(
                        SECONDARY_USER_ID,
                        null,
                        null,
                        UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_FULL);
        mTertiaryUserInfo =
                new UserInfo(
                        TERTIARY_USER_ID,
                        null,
                        null,
                        UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_FULL);

        when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(mPrimaryUserInfo);
        when(mUserManagerInternal.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(mPrimaryUserInfo);
        mPrimaryUserProfiles.add(mPrimaryUserInfo);
        installChildProfile(MANAGED_PROFILE_USER_ID);
        installAndTurnOffChildProfile(TURNED_OFF_PROFILE_USER_ID);
        for (UserInfo profile : mPrimaryUserProfiles) {
            when(mUserManager.getProfiles(eq(profile.id))).thenReturn(mPrimaryUserProfiles);
        }
        when(mUserManager.getUserInfo(eq(SECONDARY_USER_ID))).thenReturn(SECONDARY_USER_INFO);
        when(mUserManager.getUserInfo(eq(SECONDARY_USER_ID))).thenReturn(mSecondaryUserInfo);
        when(mUserManagerInternal.getUserInfo(eq(SECONDARY_USER_ID)))
                .thenReturn(mSecondaryUserInfo);
        when(mUserManager.getUserInfo(eq(TERTIARY_USER_ID))).thenReturn(mTertiaryUserInfo);
        when(mUserManagerInternal.getUserInfo(eq(TERTIARY_USER_ID))).thenReturn(mTertiaryUserInfo);

        final ArrayList<UserInfo> allUsers = new ArrayList<>(mPrimaryUserProfiles);
        allUsers.add(SECONDARY_USER_INFO);
        allUsers.add(mSecondaryUserInfo);
        allUsers.add(mTertiaryUserInfo);
        when(mUserManager.getUsers()).thenReturn(allUsers);

        when(mActivityManager.unlockUser2(anyInt(), any())).thenAnswer(
@@ -227,9 +266,10 @@ public abstract class BaseLockSettingsServiceTests {
        userInfo.profileGroupId = PRIMARY_USER_ID;
        mPrimaryUserProfiles.add(userInfo);
        when(mUserManager.getUserInfo(eq(profileId))).thenReturn(userInfo);
        when(mUserManager.getProfileParent(eq(profileId))).thenReturn(PRIMARY_USER_INFO);
        when(mUserManager.getProfileParent(eq(profileId))).thenReturn(mPrimaryUserInfo);
        when(mUserManager.isUserRunning(eq(profileId))).thenReturn(true);
        when(mUserManager.isUserUnlocked(eq(profileId))).thenReturn(true);
        when(mUserManagerInternal.getUserInfo(eq(profileId))).thenReturn(userInfo);
        // TODO(b/258213147): Remove
        when(mUserManagerInternal.isUserManaged(eq(profileId))).thenReturn(true);
        when(mDeviceStateCache.isUserOrganizationManaged(eq(profileId)))
+25 −12
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import android.os.RemoteException;
import android.os.storage.IStorageManager;
import android.security.KeyStore;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.service.gatekeeper.IGateKeeperService;

import com.android.internal.widget.LockscreenCredential;
import com.android.server.ServiceThread;
@@ -40,7 +41,7 @@ import java.io.FileNotFoundException;

public class LockSettingsServiceTestable extends LockSettingsService {

    private static class MockInjector extends LockSettingsService.Injector {
    public static class MockInjector extends LockSettingsService.Injector {

        private LockSettingsStorage mLockSettingsStorage;
        private KeyStore mKeyStore;
@@ -52,6 +53,9 @@ public class LockSettingsServiceTestable extends LockSettingsService {
        private UserManagerInternal mUserManagerInternal;
        private DeviceStateCache mDeviceStateCache;

        public boolean mIsHeadlessSystemUserMode = false;
        public boolean mIsMainUserPermanentAdmin = false;

        public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
                IActivityManager activityManager,
                IStorageManager storageManager, SyntheticPasswordManager spManager,
@@ -140,19 +144,22 @@ public class LockSettingsServiceTestable extends LockSettingsService {
            return mock(ManagedProfilePasswordCache.class);
        }

        @Override
        public boolean isHeadlessSystemUserMode() {
            return mIsHeadlessSystemUserMode;
        }

    public MockInjector mInjector;
        @Override
        public boolean isMainUserPermanentAdmin() {
            return mIsMainUserPermanentAdmin;
        }
    }

    protected LockSettingsServiceTestable(Context context,
            LockSettingsStorage storage, FakeGateKeeperService gatekeeper, KeyStore keystore,
            IStorageManager storageManager, IActivityManager mActivityManager,
            SyntheticPasswordManager spManager, IAuthSecret authSecretService,
            FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager,
            UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
        super(new MockInjector(context, storage, keystore, mActivityManager,
                storageManager, spManager, gsiService, recoverableKeyStoreManager,
                userManagerInternal, deviceStateCache));
    protected LockSettingsServiceTestable(
            LockSettingsService.Injector injector,
            IGateKeeperService gatekeeper,
            IAuthSecret authSecretService) {
        super(injector);
        mGateKeeperService = gatekeeper;
        mAuthSecretService = authSecretService;
    }
@@ -199,4 +206,10 @@ public class LockSettingsServiceTestable extends LockSettingsService {
        UserInfo userInfo = mUserManager.getUserInfo(userId);
        return userInfo.isCloneProfile() || userInfo.isManagedProfile();
    }

    void clearAuthSecret() {
        synchronized (mHeadlessAuthSecretLock) {
            mAuthSecret = null;
        }
    }
}
+64 −1

File changed.

Preview size limit exceeded, changes collapsed.

Loading