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

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

Merge changes Ifdc0d507,I93780a20

* changes:
  Skip creating password data file for unsecured users
  Skip creating password metrics file for unsecured users
parents ceea5bfb 3bfee24f
Loading
Loading
Loading
Loading
+9 −7
Original line number Diff line number Diff line
@@ -256,10 +256,10 @@ public class LockSettingsService extends ILockSettings.Stub {
    @GuardedBy("mUserCreationAndRemovalLock")
    private boolean mBootComplete;

    // Current password metric for all users on the device. Updated when user unlocks
    // the device or changes password. Removed when user is stopped.
    // Current password metrics for all secured users on the device. Updated when user unlocks the
    // device or changes password. Removed when user is stopped.
    @GuardedBy("this")
    final SparseArray<PasswordMetrics> mUserPasswordMetrics = new SparseArray<>();
    private final SparseArray<PasswordMetrics> mUserPasswordMetrics = new SparseArray<>();
    @VisibleForTesting
    protected boolean mHasSecureLockScreen;

@@ -2274,8 +2274,11 @@ public class LockSettingsService extends ILockSettings.Stub {
        }
    }

    private PasswordMetrics loadPasswordMetrics(SyntheticPassword sp, int userHandle) {
    private @Nullable PasswordMetrics loadPasswordMetrics(SyntheticPassword sp, int userHandle) {
        synchronized (mSpManager) {
            if (!isUserSecure(userHandle)) {
                return null;
            }
            return mSpManager.getPasswordMetrics(sp, getCurrentLskfBasedProtectorId(userHandle),
                    userHandle);
        }
@@ -2703,14 +2706,13 @@ public class LockSettingsService extends ILockSettings.Stub {
        return handle;
    }

    private void onCredentialVerified(SyntheticPassword sp, PasswordMetrics metrics, int userId) {
    private void onCredentialVerified(SyntheticPassword sp, @Nullable PasswordMetrics metrics,
            int userId) {

        if (metrics != null) {
            synchronized (this) {
                mUserPasswordMetrics.put(userId,  metrics);
            }
        } else {
            Slog.wtf(TAG, "Null metrics after credential verification");
        }

        unlockKeystore(sp.deriveKeyStorePassword(), userId);
+77 −45
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
import com.android.internal.widget.LockPatternUtils;
@@ -97,9 +98,11 @@ import java.util.Set;
 *     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.
 *       PASSWORD_DATA_NAME: Data used for LSKF verification, such as the scrypt salt and
 *                           parameters.  Only exists for LSKF-based protectors.
 *                           parameters.  Only exists for LSKF-based protectors.  Doesn't exist when
 *                           the LSKF is empty, except in old protectors.
 *       PASSWORD_METRICS_NAME: Metrics about the LSKF, encrypted by a key derived from the SP.
 *                              Only exists for LSKF-based protectors.
 *                              Only exists for LSKF-based protectors.  Doesn't exist when the LSKF
 *                              is empty, except in old protectors.
 *       SECDISCARDABLE_NAME: A large number of random bytes that all need to be known in order to
 *                            decrypt SP_BLOB_NAME.  When the protector is deleted, this file is
 *                            overwritten and deleted as a "best-effort" attempt to support secure
@@ -333,24 +336,15 @@ public class SyntheticPasswordManager {
        byte scryptLogP;
        public int credentialType;
        byte[] salt;
        // 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.
        // When Weaver is unavailable, this is the Gatekeeper password handle that resulted from
        // enrolling the stretched LSKF.
        public byte[] passwordHandle;

        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 = credentialType;
            result.salt = secureRandom(PASSWORD_SALT_LENGTH);
            return result;
@@ -611,7 +605,6 @@ public class SyntheticPasswordManager {
    int getCredentialType(long protectorId, int userId) {
        byte[] passwordData = loadState(PASSWORD_DATA_NAME, protectorId, userId);
        if (passwordData == null) {
            Slog.w(TAG, "getCredentialType: encountered empty password data for user " + userId);
            return LockPatternUtils.CREDENTIAL_TYPE_NONE;
        }
        return PasswordData.fromBytes(passwordData).credentialType;
@@ -783,7 +776,8 @@ public class SyntheticPasswordManager {
    public long createLskfBasedProtector(IGateKeeperService gatekeeper,
            LockscreenCredential credential, SyntheticPassword sp, int userId) {
        long protectorId = generateProtectorId();
        PasswordData pwd = PasswordData.create(credential.getType());
        // There's no need to store password data about an empty LSKF.
        PasswordData pwd = credential.isNone() ? null : PasswordData.create(credential.getType());
        byte[] stretchedLskf = stretchLskf(credential, pwd);
        long sid = GateKeeper.INVALID_SECURE_USER_ID;
        final byte[] protectorSecret;
@@ -837,8 +831,10 @@ public class SyntheticPasswordManager {
            // No need to pass in quality since the credential type already encodes sufficient info
            synchronizeFrpPassword(pwd, 0, userId);
        }
        if (!credential.isNone()) {
            saveState(PASSWORD_DATA_NAME, pwd.toBytes(), protectorId, userId);
            savePasswordMetrics(credential, sp, protectorId, userId);
        }
        createSyntheticPasswordBlob(protectorId, PROTECTOR_TYPE_LSKF_BASED, sp, protectorSecret,
                sid, userId);
        return protectorId;
@@ -883,10 +879,11 @@ public class SyntheticPasswordManager {
    public void migrateFrpPasswordLocked(long protectorId, UserInfo userInfo,
            int requestedQuality) {
        if (mStorage.getPersistentDataBlockManager() != null
                && LockPatternUtils.userOwnsFrpCredential(mContext, userInfo)) {
                && LockPatternUtils.userOwnsFrpCredential(mContext, userInfo)
                && getCredentialType(protectorId, userInfo.id) !=
                        LockPatternUtils.CREDENTIAL_TYPE_NONE) {
            PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, protectorId,
                    userInfo.id));
            if (pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
            int weaverSlot = loadWeaverSlot(protectorId, userInfo.id);
            if (weaverSlot != INVALID_WEAVER_SLOT) {
                synchronizeWeaverFrpPassword(pwd, requestedQuality, userInfo.id, weaverSlot);
@@ -895,14 +892,13 @@ public class SyntheticPasswordManager {
            }
        }
    }
    }

    private void synchronizeFrpPassword(PasswordData pwd,
            int requestedQuality, int userId) {
    private void synchronizeFrpPassword(@Nullable PasswordData pwd, int requestedQuality,
            int userId) {
        if (mStorage.getPersistentDataBlockManager() != null
                && LockPatternUtils.userOwnsFrpCredential(mContext,
                mUserManager.getUserInfo(userId))) {
            if (pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
            if (pwd != null && pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
                mStorage.writePersistentDataBlock(PersistentData.TYPE_SP, userId, requestedQuality,
                        pwd.toBytes());
            } else {
@@ -911,12 +907,12 @@ public class SyntheticPasswordManager {
        }
    }

    private void synchronizeWeaverFrpPassword(PasswordData pwd, int requestedQuality, int userId,
            int weaverSlot) {
    private void synchronizeWeaverFrpPassword(@Nullable PasswordData pwd, int requestedQuality,
            int userId, int weaverSlot) {
        if (mStorage.getPersistentDataBlockManager() != null
                && LockPatternUtils.userOwnsFrpCredential(mContext,
                mUserManager.getUserInfo(userId))) {
            if (pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
            if (pwd != null && pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
                mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, weaverSlot,
                        requestedQuality, pwd.toBytes());
            } else {
@@ -1047,12 +1043,20 @@ public class SyntheticPasswordManager {
            return result;
        }

        PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, protectorId,
                    userId));

        if (!credential.checkAgainstStoredType(pwd.credentialType)) {
        // Load the PasswordData file.  If it doesn't exist, then the LSKF is empty (i.e.,
        // CREDENTIAL_TYPE_NONE), and we'll skip the scrypt and Gatekeeper steps.  If it exists,
        // then either the LSKF is nonempty, or it's an old protector that uses scrypt and
        // Gatekeeper even though the LSKF is empty.
        byte[] pwdDataBytes = loadState(PASSWORD_DATA_NAME, protectorId, userId);
        PasswordData pwd = null;
        int storedType = LockPatternUtils.CREDENTIAL_TYPE_NONE;
        if (pwdDataBytes != null) {
            pwd = PasswordData.fromBytes(pwdDataBytes);
            storedType = pwd.credentialType;
        }
        if (!credential.checkAgainstStoredType(storedType)) {
            Slog.e(TAG, TextUtils.formatSimple("Credential type mismatch: expected %d actual %d",
                    pwd.credentialType, credential.getType()));
                    storedType, credential.getType()));
            result.gkResponse = VerifyCredentialResponse.ERROR;
            return result;
        }
@@ -1078,7 +1082,7 @@ public class SyntheticPasswordManager {
        } else {
            // 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 (pwd == null || pwd.passwordHandle == null) {
                if (!credential.isNone()) {
                    Slog.e(TAG, "Missing Gatekeeper password handle for nonempty LSKF");
                    result.gkResponse = VerifyCredentialResponse.ERROR;
@@ -1149,7 +1153,8 @@ public class SyntheticPasswordManager {

        // Upgrade case: store the metrics if the device did not have stored metrics before, should
        // only happen once on old protectors.
        if (result.syntheticPassword != null && !hasPasswordMetrics(protectorId, userId)) {
        if (result.syntheticPassword != null && !credential.isNone() &&
                !hasPasswordMetrics(protectorId, userId)) {
            savePasswordMetrics(credential, result.syntheticPassword, protectorId, userId);
        }
        return result;
@@ -1415,6 +1420,11 @@ public class SyntheticPasswordManager {
        }
    }

    @VisibleForTesting
    boolean hasPasswordData(long protectorId, int userId) {
        return hasState(PASSWORD_DATA_NAME, protectorId, userId);
    }

    /**
     * Retrieves a user's saved password metrics from their LSKF-based SP protector.  The
     * SyntheticPassword itself is needed to decrypt the file containing the password metrics.
@@ -1422,10 +1432,16 @@ public class SyntheticPasswordManager {
    public @Nullable PasswordMetrics getPasswordMetrics(SyntheticPassword sp, long protectorId,
            int userId) {
        final byte[] encrypted = loadState(PASSWORD_METRICS_NAME, protectorId, userId);
        if (encrypted == null) return null;
        if (encrypted == null) {
            Slogf.e(TAG, "Failed to read password metrics file for user %d", userId);
            return null;
        }
        final byte[] decrypted = SyntheticPasswordCrypto.decrypt(sp.deriveMetricsKey(),
                /* personalization= */ new byte[0], encrypted);
        if (decrypted == null) return null;
        if (decrypted == null) {
            Slogf.e(TAG, "Failed to decrypt password metrics file for user %d", userId);
            return null;
        }
        return VersionedPasswordMetrics.deserialize(decrypted).getMetrics();
    }

@@ -1437,7 +1453,8 @@ public class SyntheticPasswordManager {
        saveState(PASSWORD_METRICS_NAME, encrypted, protectorId, userId);
    }

    private boolean hasPasswordMetrics(long protectorId, int userId) {
    @VisibleForTesting
    boolean hasPasswordMetrics(long protectorId, int userId) {
        return hasState(PASSWORD_METRICS_NAME, protectorId, userId);
    }

@@ -1500,8 +1517,23 @@ public class SyntheticPasswordManager {
        return TextUtils.formatSimple("%s%x", PROTECTOR_KEY_ALIAS_PREFIX, protectorId);
    }

    private byte[] stretchLskf(LockscreenCredential credential, PasswordData data) {
    /**
     * Stretches <code>credential</code>, if needed, using the parameters from <code>data</code>.
     * <p>
     * When the credential is empty, stetching provides no security benefit.  Thus, new protectors
     * for an empty credential use <code>null</code> {@link PasswordData} and skip the stretching.
     * <p>
     * However, old protectors always stored {@link PasswordData} and did the stretching, regardless
     * of whether the credential was empty or not.  For this reason, this method also continues to
     * support stretching of empty credentials so that old protectors can still be unlocked.
     */
    @VisibleForTesting
    byte[] stretchLskf(LockscreenCredential credential, @Nullable PasswordData data) {
        final byte[] password = credential.isNone() ? DEFAULT_PASSWORD : credential.getCredential();
        if (data == null) {
            Preconditions.checkArgument(credential.isNone());
            return Arrays.copyOf(password, STRETCHED_LSKF_LENGTH);
        }
        return scrypt(password, data.salt, 1 << data.scryptLogN, 1 << data.scryptLogR,
                1 << data.scryptLogP, STRETCHED_LSKF_LENGTH);
    }
+45 −11
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.eq;
@@ -49,6 +50,8 @@ import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationRe
import com.android.server.locksettings.SyntheticPasswordManager.PasswordData;
import com.android.server.locksettings.SyntheticPasswordManager.SyntheticPassword;

import libcore.util.HexEncoding;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -84,6 +87,8 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
        long protectorId = manager.createLskfBasedProtector(mGateKeeperService,
                LockscreenCredential.createNone(), sp, USER_ID);
        assertFalse(lskfGatekeeperHandleExists(USER_ID));
        assertFalse(manager.hasPasswordData(protectorId, USER_ID));
        assertFalse(manager.hasPasswordMetrics(protectorId, USER_ID));

        AuthenticationResult result = manager.unlockLskfBasedProtector(mGateKeeperService,
                protectorId, LockscreenCredential.createNone(), USER_ID, null);
@@ -103,6 +108,8 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
        long protectorId = manager.createLskfBasedProtector(mGateKeeperService, password, sp,
                USER_ID);
        assertTrue(lskfGatekeeperHandleExists(USER_ID));
        assertTrue(manager.hasPasswordData(protectorId, USER_ID));
        assertTrue(manager.hasPasswordMetrics(protectorId, USER_ID));

        AuthenticationResult result = manager.unlockLskfBasedProtector(mGateKeeperService,
                protectorId, password, USER_ID, null);
@@ -452,19 +459,46 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
                mService.getHashFactor(profilePassword, MANAGED_PROFILE_USER_ID));
    }

    // Tests stretching of a nonempty LSKF.
    @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);
    public void testStretchLskf_enabled() {
        byte[] actual = mSpManager.stretchLskf(newPin("12345"), createTestPasswordData());
        String expected = "467986710DE8F0D4F4A3668DFF58C9B7E5DB96A79B7CCF415BBD4D7767F8CFFA";
        assertEquals(expected, HexEncoding.encodeToString(actual));
    }

        // 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);
    // Tests the case where stretching is disabled for an empty LSKF.
    @Test
    public void testStretchLskf_disabled() {
        byte[] actual = mSpManager.stretchLskf(nonePassword(), null);
        // "default-password", zero padded
        String expected = "64656661756C742D70617373776F726400000000000000000000000000000000";
        assertEquals(expected, HexEncoding.encodeToString(actual));
    }

    // Tests the legacy case where stretching is enabled for an empty LSKF.
    @Test
    public void testStretchLskf_emptyButEnabled() {
        byte[] actual = mSpManager.stretchLskf(nonePassword(), createTestPasswordData());
        String expected = "9E6DDCC1EC388BB1E1CD54097AF924CA80BCB90993196FA8F6122FF58EB333DE";
        assertEquals(expected, HexEncoding.encodeToString(actual));
    }

    // Tests the forbidden case where stretching is disabled for a nonempty LSKF.
    @Test
    public void testStretchLskf_nonEmptyButDisabled() {
        assertThrows(IllegalArgumentException.class,
                () -> mSpManager.stretchLskf(newPin("12345"), null));
    }

    private PasswordData createTestPasswordData() {
        PasswordData data = new PasswordData();
        // For the unit test, the scrypt parameters have to be constant; the salt can't be random.
        data.scryptLogN = 11;
        data.scryptLogR = 3;
        data.scryptLogP = 1;
        data.salt = "abcdefghijklmnop".getBytes();
        return data;
    }

    @Test