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

Commit d20b2504 authored by Eric Biggers's avatar Eric Biggers
Browse files

Make the CE key always be encrypted by the synthetic password

Currently, whenever an LSKF isn't set, the CE key isn't encrypted by the
SP but rather by a non-auth-bound Keystore key.  This is inconsistent,
and it's preventing us from taking advantage of the secure deletion
support of Weaver (on devices that support Weaver), with the current
scheme relying entirely on rollback resistance support in Keystore.

Therefore, make the CE key always be encrypted by the SP-derived key.

To do this, encrypt the CE key by the SP-derived key when the SP is
initially created.  After the corresponding vold change, this will start
being the only time that the CE key is written to disk at all.

Also re-encrypt the CE key of existing users who don't have an LSKF, so
that we don't have to continue to support the legacy scheme.

Both operations are done via the new vold method setUserKeyProtection()
(accessed via StorageManagerService), which (re-)encrypts the CE key
using the given secret.  The CE key must be either not-yet-persisted or
default-encrypted.  This method replaces addUserKeyAuth(),
clearUserKeyAuth(), and fixateUserKeyAuth().

On the unlock side, make unlockUserKeyIfUnsecured() unwrap the SP and
unlock the CE key using it.  As this makes
tryDeriveVendorAuthSecretForUnsecuredPrimaryUser() no longer needed,
remove that too.

Test: atest com.android.server.locksettings && \
      atest com.android.server.am.UserControllerTest \
            com.android.server.pm.UserManagerServiceTest \
            com.android.server.pm.UserManagerTest
Test: Booted and rebooted Cuttlefish, both phone and auto builds
Test: Without the CLs in this topic, set up a Pixel 6 to have a user
      with an LSKF, a user with no LSKF or SP, and a user with an SP but
      no LSKF.  Upgraded to the CLs in this topic.  Checked log for
      the expected messages from LockSettingsService.  Verified that all
      the users can still be unlocked.  Verified that
      /data/system_de/$userId/spblob exists for all users, and that
      /data/misc/vold/user_keys/ce/$userId/encrypted_key exists but not
      a keymaster_key_blob file alongside it.  Verified that
      /data/system/locksettings.db contains
      migrated_all_users_to_sp_and_bound_ce=true.
Bug: 232452368
Change-Id: Ia753ea21bbaca8ef7a90c03fe73b66c896b1536e
parent 78e245a2
Loading
Loading
Loading
Loading
+1 −5
Original line number Diff line number Diff line
@@ -147,9 +147,7 @@ interface IStorageManager {
    @EnforcePermission("STORAGE_INTERNAL")
    void destroyUserStorage(in String volumeUuid, int userId, int flags) = 67;
    @EnforcePermission("STORAGE_INTERNAL")
    void addUserKeyAuth(int userId, int serialNumber, in byte[] secret) = 70;
    @EnforcePermission("STORAGE_INTERNAL")
    void fixateNewestUserKeyAuth(int userId) = 71;
    void setUserKeyProtection(int userId, in byte[] secret) = 70;
    @EnforcePermission("MOUNT_FORMAT_FILESYSTEMS")
    void fstrim(int flags, IVoldTaskListener listener) = 72;
    AppFuseMount mountProxyFileDescriptorBridge() = 73;
@@ -166,8 +164,6 @@ interface IStorageManager {
    @EnforcePermission("MOUNT_FORMAT_FILESYSTEMS")
    boolean needsCheckpoint() = 86;
    void abortChanges(in String message, boolean retry) = 87;
    @EnforcePermission("STORAGE_INTERNAL")
    void clearUserKeyAuth(int userId, int serialNumber, in byte[] secret) = 88;
    void fixupAppDir(in String path) = 89;
    void disableAppDataIsolation(in String pkgName, int pid, int userId) = 90;
    PendingIntent getManageSpaceActivityIntent(in String packageName, int requestCode) = 91;
+4 −57
Original line number Diff line number Diff line
@@ -3066,64 +3066,11 @@ class StorageManagerService extends IStorageManager.Stub
        }
    }

    private String encodeBytes(byte[] bytes) {
        if (ArrayUtils.isEmpty(bytes)) {
            return "!";
        } else {
            return HexDump.toHexString(bytes);
        }
    }

    @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
    /*
     * Add this secret to the set of ways we can recover a user's disk
     * encryption key.  Changing the secret for a disk encryption key is done in
     * two phases.  First, this method is called to add the new secret binding.
     * Second, fixateNewestUserKeyAuth is called to delete all other bindings.
     * This allows other places where a credential is used, such as Gatekeeper,
     * to be updated between the two calls.
     */
    @Override
    public void addUserKeyAuth(int userId, int serialNumber, byte[] secret) {

        try {
            mVold.addUserKeyAuth(userId, serialNumber, encodeBytes(secret));
        } catch (Exception e) {
            Slog.wtf(TAG, e);
        }
    }

    @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
    /*
     * Store a user's disk encryption key without secret binding.  Removing the
     * secret for a disk encryption key is done in two phases.  First, this
     * method is called to retrieve the key using the provided secret and store
     * it encrypted with a keystore key not bound to the user.  Second,
     * fixateNewestUserKeyAuth is called to delete the key's other bindings.
     */
    @Override
    public void clearUserKeyAuth(int userId, int serialNumber, byte[] secret) {

        try {
            mVold.clearUserKeyAuth(userId, serialNumber, encodeBytes(secret));
        } catch (Exception e) {
            Slog.wtf(TAG, e);
        }
    }

    /* Only for use by LockSettingsService */
    @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
    /*
     * Delete all bindings of a user's disk encryption key except the most
     * recently added one.
     */
    @Override
    public void fixateNewestUserKeyAuth(int userId) {

        try {
            mVold.fixateNewestUserKeyAuth(userId);
        } catch (Exception e) {
            Slog.wtf(TAG, e);
        }
    public void setUserKeyProtection(@UserIdInt int userId, byte[] secret) throws RemoteException {
        mVold.setUserKeyProtection(userId, HexDump.toHexString(secret));
    }

    /* Only for use by LockSettingsService */
@@ -3132,7 +3079,7 @@ class StorageManagerService extends IStorageManager.Stub
    public void unlockUserKey(@UserIdInt int userId, int serialNumber, byte[] secret)
        throws RemoteException {
        if (StorageManager.isFileEncrypted()) {
            mVold.unlockUserKey(userId, serialNumber, encodeBytes(secret));
            mVold.unlockUserKey(userId, serialNumber, HexDump.toHexString(secret));
        }
        synchronized (mLock) {
            mLocalUnlockedUsers.append(userId);
+62 −94
Original line number Diff line number Diff line
@@ -801,38 +801,10 @@ public class LockSettingsService extends ILockSettings.Stub {
                if (isCredentialSharableWithParent(userId)) {
                    tieProfileLockIfNecessary(userId, LockscreenCredential.createNone());
                }

                // If the user doesn't have a credential, try and derive their secret for the
                // AuthSecret HAL. The secret will have been enrolled if the user previously set a
                // credential and still needs to be passed to the HAL once that credential is
                // removed.
                if (mUserManager.getUserInfo(userId).isPrimary() && !isUserSecure(userId)) {
                    tryDeriveVendorAuthSecretForUnsecuredPrimaryUser(userId);
                }
            }
        });
    }

    private void tryDeriveVendorAuthSecretForUnsecuredPrimaryUser(@UserIdInt int userId) {
        synchronized (mSpManager) {
            // If there is no SP, then there is no vendor auth secret.
            if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
                return;
            }

            final long protectorId = getCurrentLskfBasedProtectorId(userId);
            AuthenticationResult result =
                    mSpManager.unlockLskfBasedProtector(getGateKeeperService(), protectorId,
                            LockscreenCredential.createNone(), userId, null);
            if (result.syntheticPassword != null) {
                Slog.i(TAG, "Unwrapped SP for unsecured primary user " + userId);
                onSyntheticPasswordKnown(userId, result.syntheticPassword);
            } else {
                Slog.e(TAG, "Failed to unwrap SP for unsecured primary user " + userId);
            }
        }
    }

    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
@@ -979,28 +951,54 @@ public class LockSettingsService extends ILockSettings.Stub {
            }
            mEarlyCreatedUsers = null; // no longer needed

            // Also do a one-time migration of all users to SP-based credentials.  This is needed
            // for the system user on the first boot of a device, as the system user is special and
            // never goes through the user creation flow that other users do.  It is also needed for
            // existing users on a device upgraded from Android 13 or earlier, where users with no
            // LSKF didn't necessarily have an SP.
            if (getString("migrated_all_users_to_sp", null, 0) == null) {
            // Also do a one-time migration of all users to SP-based credentials with the CE key
            // encrypted by the SP.  This is needed for the system user on the first boot of a
            // device, as the system user is special and never goes through the user creation flow
            // that other users do.  It is also needed for existing users on a device upgraded from
            // Android 13 or earlier, where users with no LSKF didn't necessarily have an SP, and if
            // they did have an SP then their CE key wasn't encrypted by it.
            //
            // If this gets interrupted (e.g. by the device powering off), there shouldn't be a
            // problem since this will run again on the next boot, and setUserKeyProtection() is
            // okay with the key being already protected by the given secret.
            if (getString("migrated_all_users_to_sp_and_bound_ce", null, 0) == null) {
                for (UserInfo user : mUserManager.getAliveUsers()) {
                    removeStateForReusedUserIdIfNecessary(user.id, user.serialNumber);
                    synchronized (mSpManager) {
                        if (!isSyntheticPasswordBasedCredentialLocked(user.id)) {
                            Slogf.i(TAG, "Migrating user %d to SP-based credential", user.id);
                            initializeSyntheticPasswordLocked(user.id);
                        }
                        migrateUserToSpWithBoundCeKeyLocked(user.id);
                    }
                }
                setString("migrated_all_users_to_sp", "true", 0);
                setString("migrated_all_users_to_sp_and_bound_ce", "true", 0);
            }

            mBootComplete = true;
        }
    }

    @GuardedBy("mSpManager")
    private void migrateUserToSpWithBoundCeKeyLocked(@UserIdInt int userId) {
        if (isUserSecure(userId)) {
            Slogf.d(TAG, "User %d is secured; no migration needed", userId);
            return;
        }
        long protectorId = getCurrentLskfBasedProtectorId(userId);
        if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
            Slogf.i(TAG, "Migrating unsecured user %d to SP-based credential", userId);
            initializeSyntheticPasswordLocked(userId);
        } else {
            Slogf.i(TAG, "Existing unsecured user %d has a synthetic password; re-encrypting CE " +
                    "key with it", userId);
            AuthenticationResult result = mSpManager.unlockLskfBasedProtector(
                    getGateKeeperService(), protectorId, LockscreenCredential.createNone(), userId,
                    null);
            if (result.syntheticPassword == null) {
                Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
                return;
            }
            setUserKeyProtection(userId, result.syntheticPassword.deriveFileBasedEncryptionKey());
        }
    }

    /**
     * Returns the lowest password quality that still presents the same UI for entering it.
     *
@@ -1961,19 +1959,12 @@ public class LockSettingsService extends ILockSettings.Stub {
        mStorage.writeChildProfileLock(userId, ArrayUtils.concat(iv, ciphertext));
    }

    private void setUserKeyProtection(int userId, byte[] key) {
        if (DEBUG) Slog.d(TAG, "setUserKeyProtection: user=" + userId);
        addUserKeyAuth(userId, key);
    }

    private void clearUserKeyProtection(int userId, byte[] secret) {
        if (DEBUG) Slog.d(TAG, "clearUserKeyProtection user=" + userId);
        final UserInfo userInfo = mUserManager.getUserInfo(userId);
    private void setUserKeyProtection(@UserIdInt int userId, byte[] secret) {
        final long callingId = Binder.clearCallingIdentity();
        try {
            mStorageManager.clearUserKeyAuth(userId, userInfo.serialNumber, secret);
            mStorageManager.setUserKeyProtection(userId, secret);
        } catch (RemoteException e) {
            throw new IllegalStateException("clearUserKeyAuth failed user=" + userId);
            throw new IllegalStateException("Failed to protect CE key for user " + userId, e);
        } finally {
            Binder.restoreCallingIdentity(callingId);
        }
@@ -1991,9 +1982,6 @@ public class LockSettingsService extends ILockSettings.Stub {
    /**
     * Unlocks the user's CE (credential-encrypted) storage if it's not already unlocked.
     * <p>
     * If the given {@code SyntheticPassword} is {@code null}, then an empty secret will be used.
     * Otherwise, a secret derived from the given {@code SyntheticPassword} will be used.
     * <p>
     * This method doesn't throw exceptions because it is called opportunistically whenever a user
     * is started.  Whether it worked or not can be detected by whether the key got unlocked or not.
     */
@@ -2004,56 +1992,38 @@ public class LockSettingsService extends ILockSettings.Stub {
        }
        final UserInfo userInfo = mUserManager.getUserInfo(userId);
        final String userType = isUserSecure(userId) ? "secured" : "unsecured";
        byte[] secret = null;
        final byte[] secret = sp.deriveFileBasedEncryptionKey();
        try {
            if (sp != null) {
                secret = sp.deriveFileBasedEncryptionKey();
            }
            mStorageManager.unlockUserKey(userId, userInfo.serialNumber, secret);
            Slogf.i(TAG, "Unlocked CE storage for %s user %d", userType, userId);
        } catch (RemoteException e) {
            Slogf.wtf(TAG, e, "Failed to unlock CE storage for %s user %d", userType, userId);
        } finally {
            if (secret != null) {
            Arrays.fill(secret, (byte) 0);
        }
    }
    }

    private void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
        synchronized (mSpManager) {
            if (isUserKeyUnlocked(userId)) {
                Slogf.d(TAG, "CE storage for user %d is already unlocked", userId);
                return;
            }
            if (isUserSecure(userId)) {
                Slogf.d(TAG, "Not unlocking CE storage for user %d yet because user is secured",
                        userId);
                return;
            }
            unlockUserKey(userId, null);
        }
    }

    private void addUserKeyAuth(int userId, byte[] secret) {
        final UserInfo userInfo = mUserManager.getUserInfo(userId);
        final long callingId = Binder.clearCallingIdentity();
        try {
            mStorageManager.addUserKeyAuth(userId, userInfo.serialNumber, secret);
        } catch (RemoteException e) {
            throw new IllegalStateException("Failed to add new key to vold " + userId, e);
        } finally {
            Binder.restoreCallingIdentity(callingId);
        }
            Slogf.i(TAG, "Unwrapping synthetic password for unsecured user %d", userId);
            AuthenticationResult result = mSpManager.unlockLskfBasedProtector(
                    getGateKeeperService(), getCurrentLskfBasedProtectorId(userId),
                    LockscreenCredential.createNone(), userId, null);
            if (result.syntheticPassword == null) {
                Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
                return;
            }

    private void fixateNewestUserKeyAuth(int userId) {
        if (DEBUG) Slog.d(TAG, "fixateNewestUserKeyAuth: user=" + userId);
        final long callingId = Binder.clearCallingIdentity();
        try {
            mStorageManager.fixateNewestUserKeyAuth(userId);
        } catch (RemoteException e) {
            // OK to ignore the exception as vold would just accept both old and new
            // keys if this call fails, and will fix itself during the next boot
            Slog.w(TAG, "fixateNewestUserKeyAuth failed", e);
        } finally {
            Binder.restoreCallingIdentity(callingId);
            onSyntheticPasswordKnown(userId, result.syntheticPassword);
            unlockUserKey(userId, result.syntheticPassword);
        }
    }

@@ -2652,7 +2622,8 @@ public class LockSettingsService extends ILockSettings.Stub {
    }

    /**
     * Creates the synthetic password (SP) for the given user and protects it with an empty LSKF.
     * 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 {@code PHASE_BOOT_COMPLETED} to ensure that the Weaver HAL is available if the device
@@ -2671,6 +2642,7 @@ public class LockSettingsService extends ILockSettings.Stub {
        final long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(),
                LockscreenCredential.createNone(), sp, userId);
        setCurrentLskfBasedProtectorId(protectorId, userId);
        setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
        onSyntheticPasswordKnown(userId, sp);
        return sp;
    }
@@ -2767,9 +2739,9 @@ public class LockSettingsService extends ILockSettings.Stub {
     * be empty) and replacing the old LSKF-based protector with it.  The SP itself is not changed.
     *
     * Also maintains the invariants described in {@link SyntheticPasswordManager} by
     * setting/clearing the protection (by the SP) on the user's file-based encryption key and
     * auth-bound Keystore keys when the LSKF is added/removed, respectively.  If the new LSKF is
     * nonempty, then the Gatekeeper auth token is also refreshed.
     * setting/clearing the protection (by the SP) on the user's auth-bound Keystore keys when the
     * LSKF is added/removed, respectively.  If the new LSKF is nonempty, then the Gatekeeper auth
     * token is also refreshed.
     */
    @GuardedBy("mSpManager")
    private long setLockCredentialWithSpLocked(LockscreenCredential credential,
@@ -2789,8 +2761,6 @@ public class LockSettingsService extends ILockSettings.Stub {
            } else {
                mSpManager.newSidForUser(getGateKeeperService(), sp, userId);
                mSpManager.verifyChallenge(getGateKeeperService(), sp, 0L, userId);
                setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
                fixateNewestUserKeyAuth(userId);
                setKeystorePassword(sp.deriveKeyStorePassword(), userId);
            }
        } else {
@@ -2801,8 +2771,6 @@ public class LockSettingsService extends ILockSettings.Stub {
            mSpManager.clearSidForUser(userId);
            gateKeeperClearSecureUserId(userId);
            unlockUserKey(userId, sp);
            clearUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
            fixateNewestUserKeyAuth(userId);
            unlockKeystore(sp.deriveKeyStorePassword(), userId);
            setKeystorePassword(null, userId);
            removeBiometricsForUser(userId);
+6 −5
Original line number Diff line number Diff line
@@ -80,11 +80,12 @@ import java.util.Set;
 *    LockscreenCredential.  The LSKF may be empty (none).  There may be escrow token-based
 *    protectors as well, only for specific use cases such as enterprise-managed users.
 *
 *  - While the user's LSKF is nonempty, the SP protects the user's CE (credential encrypted)
 *    storage and auth-bound Keystore keys: the user's CE key is encrypted by an SP-derived secret,
 *    and the user's Keystore and Gatekeeper passwords are other SP-derived secrets.  However, while
 *    the user's LSKF is empty, these protections are cleared; this is needed to invalidate the
 *    auth-bound keys and make StorageManagerService.unlockUserKey() work with an empty secret.
 *  - The user's credential-encrypted storage is always protected by the SP.
 *
 *  - The user's auth-bound Keystore keys are protected by the SP, but only while an LSKF is set.
 *    This works by setting the user's Keystore and Gatekeeper passwords to SP-derived secrets, but
 *    only while an LSKF is set.  When the LSKF is removed, these passwords are cleared,
 *    invalidating the user's auth-bound keys.
 *
 * Files stored on disk for each user:
 *   For the SP itself, stored under NULL_PROTECTOR_ID:
+3 −15
Original line number Diff line number Diff line
@@ -223,23 +223,11 @@ public abstract class BaseLockSettingsServiceTests {

        doAnswer(invocation -> {
            Object[] args = invocation.getArguments();
            mStorageManager.addUserKeyAuth(/* userId= */ (int) args[0],
                    /* serialNumber= */ (int) args[1], /* secret= */ (byte[]) args[2]);
            mStorageManager.setUserKeyProtection(/* userId= */ (int) args[0],
                    /* secret= */ (byte[]) args[1]);
            return null;
        }).when(sm).addUserKeyAuth(anyInt(), anyInt(), any());
        }).when(sm).setUserKeyProtection(anyInt(), any());

        doAnswer(invocation -> {
            Object[] args = invocation.getArguments();
            mStorageManager.clearUserKeyAuth(/* userId= */ (int) args[0],
                    /* serialNumber= */ (int) args[1], /* secret= */ (byte[]) args[2]);
            return null;
        }).when(sm).clearUserKeyAuth(anyInt(), anyInt(), any());

        doAnswer(invocation -> {
            Object[] args = invocation.getArguments();
            mStorageManager.fixateNewestUserKeyAuth(/* userId= */ (int) args[0]);
            return null;
        }).when(sm).fixateNewestUserKeyAuth(anyInt());
        return sm;
    }

Loading