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

Commit 264b27f8 authored by Eric Biggers's avatar Eric Biggers
Browse files

Eliminate unnecessary work for empty LSKFs

Improve the performance of creating and unlocking the LSKF-based SP
protector when the LSKF is empty by using the minimum scrypt parameters,
and by skipping the Gatekeeper step when Weaver is unavailable.

This also has the "benefit" that it avoids depending on auth-bound keys
working correctly for the device to boot when the LSKF is empty.  That
is relevant when an OEM has not implemented auth-bound keys correctly.

Bug: 232452368
Bug: 251131631
Bug: 251147505
Bug: 251398909
Change-Id: Ic58580455a789d177c077d12efe5ab6c0ec2085b
parent 026b01f0
Loading
Loading
Loading
Loading
+97 −77
Original line number Diff line number Diff line
@@ -333,16 +333,25 @@ public class SyntheticPasswordManager {
        byte scryptLogP;
        public int credentialType;
        byte[] salt;
        // If Weaver is available, then this field is empty.  Otherwise, it is the Gatekeeper
        // password handle that resulted from enrolling the hashed LSKF.
        // This is the Gatekeeper password handle that resulted from enrolling the stretched LSKF,
        // when applicable.  This field isn't used if Weaver is available, or in new protectors when
        // the LSKF is empty.
        public byte[] passwordHandle;

        public static PasswordData create(int passwordType) {
        public static PasswordData create(int credentialType) {
            PasswordData result = new PasswordData();
            if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
                // When the LSKF is empty, scrypt provides no security benefit, so just use the
                // minimum parameters (N=2, r=1, p=1).
                result.scryptLogN = 1;
                result.scryptLogR = 0;
                result.scryptLogP = 0;
            } else {
                result.scryptLogN = PASSWORD_SCRYPT_LOG_N;
                result.scryptLogR = PASSWORD_SCRYPT_LOG_R;
                result.scryptLogP = PASSWORD_SCRYPT_LOG_P;
            result.credentialType = passwordType;
            }
            result.credentialType = credentialType;
            result.salt = secureRandom(PASSWORD_SALT_LENGTH);
            return result;
        }
@@ -776,11 +785,12 @@ public class SyntheticPasswordManager {
        long protectorId = generateProtectorId();
        PasswordData pwd = PasswordData.create(credential.getType());
        byte[] stretchedLskf = stretchLskf(credential, pwd);
        final long sid;
        long sid = GateKeeper.INVALID_SECURE_USER_ID;
        final byte[] protectorSecret;

        if (isWeaverAvailable()) {
            // Protector uses Weaver to verify the LSKF
            // Weaver is available, so make the protector use it to verify the LSKF.  Do this even
            // if the LSKF is empty, as that gives us support for securely deleting the protector.
            int weaverSlot = getNextAvailableWeaverSlot();
            Slog.i(TAG, "Weaver enroll password to slot " + weaverSlot + " for user " + userId);
            byte[] weaverSecret = weaverEnroll(weaverSlot, stretchedLskfToWeaverKey(stretchedLskf),
@@ -794,14 +804,14 @@ public class SyntheticPasswordManager {
            // No need to pass in quality since the credential type already encodes sufficient info
            synchronizeWeaverFrpPassword(pwd, 0, userId, weaverSlot);

            pwd.passwordHandle = null;
            sid = GateKeeper.INVALID_SECURE_USER_ID;
            protectorSecret = transformUnderWeaverSecret(stretchedLskf, weaverSecret);
        } else {
            // Protector uses Gatekeeper to verify the LSKF

            // In case GK enrollment leaves persistent state around (in RPMB), this will nuke them
            // to prevent them from accumulating and causing problems.
            // 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.
            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.
                try {
                    gatekeeper.clearSecureUserId(fakeUserId(userId));
                } catch (RemoteException ignore) {
@@ -812,15 +822,16 @@ public class SyntheticPasswordManager {
                    response = gatekeeper.enroll(fakeUserId(userId), null, null,
                            stretchedLskfToGkPassword(stretchedLskf));
                } catch (RemoteException e) {
                throw new IllegalStateException("Failed to enroll LSKF for new SP protector for "
                        + "user " + userId, e);
                    throw new IllegalStateException("Failed to enroll LSKF for new SP protector"
                            + " for user " + userId, e);
                }
                if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
                throw new IllegalStateException("Failed to enroll LSKF for new SP protector for "
                        + "user " + userId);
                    throw new IllegalStateException("Failed to enroll LSKF for new SP protector"
                            + " for user " + userId);
                }
                pwd.passwordHandle = response.getPayload();
                sid = sidFromPasswordHandle(pwd.passwordHandle);
            }
            protectorSecret = transformUnderSecdiscardable(stretchedLskf,
                    createSecdiscardable(protectorId, userId));
            // No need to pass in quality since the credential type already encodes sufficient info
@@ -1049,7 +1060,7 @@ public class SyntheticPasswordManager {
        byte[] stretchedLskf = stretchLskf(credential, pwd);

        final byte[] protectorSecret;
        final long sid;
        long sid = GateKeeper.INVALID_SECURE_USER_ID;
        int weaverSlot = loadWeaverSlot(protectorId, userId);
        if (weaverSlot != INVALID_WEAVER_SLOT) {
            // Protector uses Weaver to verify the LSKF
@@ -1062,11 +1073,18 @@ public class SyntheticPasswordManager {
            if (result.gkResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
                return result;
            }
            sid = GateKeeper.INVALID_SECURE_USER_ID;
            protectorSecret = transformUnderWeaverSecret(stretchedLskf,
                    result.gkResponse.getGatekeeperHAT());
        } else {
            // Protector uses Gatekeeper to verify the LSKF
            // Weaver is unavailable, so the protector uses Gatekeeper to verify the LSKF, unless
            // the LSKF is empty in which case Gatekeeper might not have been used at all.
            if (pwd.passwordHandle == null) {
                if (!credential.isNone()) {
                    Slog.e(TAG, "Missing Gatekeeper password handle for nonempty LSKF");
                    result.gkResponse = VerifyCredentialResponse.ERROR;
                    return result;
                }
            } else {
                byte[] gkPassword = stretchedLskfToGkPassword(stretchedLskf);
                GateKeeperResponse response;
                try {
@@ -1110,6 +1128,7 @@ public class SyntheticPasswordManager {
                    return result;
                }
                sid = sidFromPasswordHandle(pwd.passwordHandle);
            }
            protectorSecret = transformUnderSecdiscardable(stretchedLskf,
                    loadSecdiscardable(protectorId, userId));
        }
@@ -1463,7 +1482,8 @@ public class SyntheticPasswordManager {
        return result;
    }

    private int fakeUserId(int userId) {
    @VisibleForTesting
    static int fakeUserId(int userId) {
        return 100000 + userId;
    }

+39 −1
Original line number Diff line number Diff line
@@ -75,15 +75,34 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
    }

    @Test
    public void testLskfBasedProtector() throws RemoteException {
    public void testNoneLskfBasedProtector() throws RemoteException {
        final int USER_ID = 10;
        MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mContext, mStorage,
                mGateKeeperService, mUserManager, mPasswordSlotManager);
        SyntheticPassword sp = manager.newSyntheticPassword(USER_ID);
        assertFalse(lskfGatekeeperHandleExists(USER_ID));
        long protectorId = manager.createLskfBasedProtector(mGateKeeperService,
                LockscreenCredential.createNone(), sp, USER_ID);
        assertFalse(lskfGatekeeperHandleExists(USER_ID));

        AuthenticationResult result = manager.unlockLskfBasedProtector(mGateKeeperService,
                protectorId, LockscreenCredential.createNone(), USER_ID, null);
        assertArrayEquals(result.syntheticPassword.deriveKeyStorePassword(),
                sp.deriveKeyStorePassword());
    }

    @Test
    public void testNonNoneLskfBasedProtector() throws RemoteException {
        final int USER_ID = 10;
        final LockscreenCredential password = newPassword("user-password");
        final LockscreenCredential badPassword = newPassword("bad-password");
        MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mContext, mStorage,
                mGateKeeperService, mUserManager, mPasswordSlotManager);
        SyntheticPassword sp = manager.newSyntheticPassword(USER_ID);
        assertFalse(lskfGatekeeperHandleExists(USER_ID));
        long protectorId = manager.createLskfBasedProtector(mGateKeeperService, password, sp,
                USER_ID);
        assertTrue(lskfGatekeeperHandleExists(USER_ID));

        AuthenticationResult result = manager.unlockLskfBasedProtector(mGateKeeperService,
                protectorId, password, USER_ID, null);
@@ -95,6 +114,10 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
        assertNull(result.syntheticPassword);
    }

    private boolean lskfGatekeeperHandleExists(int userId) throws RemoteException {
        return mGateKeeperService.getSecureUserId(SyntheticPasswordManager.fakeUserId(userId)) != 0;
    }

    private boolean hasSyntheticPassword(int userId) throws RemoteException {
        return mService.getLong(CURRENT_LSKF_BASED_PROTECTOR_ID_KEY, 0, userId) != 0;
    }
@@ -429,6 +452,21 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
                mService.getHashFactor(profilePassword, MANAGED_PROFILE_USER_ID));
    }

    @Test
    public void testPasswordData_scryptParams() {
        // CREDENTIAL_TYPE_NONE should result in the minimum scrypt params being used.
        PasswordData data = PasswordData.create(CREDENTIAL_TYPE_NONE);
        assertEquals(1, data.scryptLogN);
        assertEquals(0, data.scryptLogR);
        assertEquals(0, data.scryptLogP);

        // Any other credential type should result in the real scrypt params being used.
        data = PasswordData.create(CREDENTIAL_TYPE_PASSWORD);
        assertTrue(data.scryptLogN > 1);
        assertTrue(data.scryptLogR > 0);
        assertTrue(data.scryptLogP > 0);
    }

    @Test
    public void testPasswordData_serializeDeserialize() {
        PasswordData data = new PasswordData();