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

Commit 342d4d06 authored by Dmitry Dementyev's avatar Dmitry Dementyev
Browse files

Add storage for remote LSKF verification session

Define task which removes session after 10 minutes of inactivity.
Bug: 254335492
Test: atest com.android.server.locksettings.recoverablekeystore
Change-Id: Idaebb8eb09b0df92fae04c3ad3edff67f37a0da7

Change-Id: I4bcf581cca29642899490b0a846ba116c3097bd5
parent 48c47b91
Loading
Loading
Loading
Loading
+92 −19
Original line number Diff line number Diff line
@@ -17,9 +17,17 @@
package com.android.server.locksettings.recoverablekeystore.storage;

import android.annotation.Nullable;
import android.os.SystemClock;
import android.text.format.DateUtils;
import android.util.Log;
import android.util.SparseArray;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.locksettings.recoverablekeystore.SecureBox;

import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;

/**
 * Memory based storage for keyPair used to send encrypted credentials from a remote device.
@@ -28,8 +36,12 @@ import java.security.KeyPair;
 */
public class RemoteLockscreenValidationSessionStorage {

    private final SparseArray<LockScreenVerificationSession> mSessionsByUserId =
            new SparseArray<>();
    private static final long SESSION_TIMEOUT_MILLIS = 10L * DateUtils.MINUTE_IN_MILLIS;
    private static final String TAG = "RemoteLockscreenValidation";

    @VisibleForTesting
    final SparseArray<LockscreenVerificationSession> mSessionsByUserId =
            new SparseArray<>(0);

    /**
     * Returns session for given user or null.
@@ -40,50 +52,111 @@ public class RemoteLockscreenValidationSessionStorage {
     * @hide
     */
    @Nullable
    public LockScreenVerificationSession get(int userId) {
    public LockscreenVerificationSession get(int userId) {
        synchronized (mSessionsByUserId) {
            return mSessionsByUserId.get(userId);
        }
    }

    /**
     * Creates a new session to verify lockscreen credentials guess.
     * Creates a new session to verify credentials guess.
     *
     * Session will be automatically removed after 10 minutes of inactivity.
     * @param userId The user id
     *
     * @hide
     */
    public LockScreenVerificationSession startSession(int userId) {
    public LockscreenVerificationSession startSession(int userId) {
        synchronized (mSessionsByUserId) {
            if (mSessionsByUserId.get(userId) != null) {
            mSessionsByUserId.remove(userId);
                mSessionsByUserId.delete(userId);
            }

            KeyPair newKeyPair;
            try {
                newKeyPair = SecureBox.genKeyPair();
            } catch (NoSuchAlgorithmException e) {
                // impossible
                throw new RuntimeException(e);
            }
        LockScreenVerificationSession newSession = null;
        // TODO(b/254335492): Schedule a task to remove session.
            LockscreenVerificationSession newSession =
                    new LockscreenVerificationSession(newKeyPair, SystemClock.elapsedRealtime());
            mSessionsByUserId.put(userId, newSession);
            return newSession;
        }
    }

    /**
     * Deletes session for a user.
     */
    public void remove(int userId) {
        mSessionsByUserId.remove(userId);
    public void finishSession(int userId) {
        synchronized (mSessionsByUserId) {
            mSessionsByUserId.delete(userId);
        }
    }

    /**
     * Holder for keypair used by remote lock screen validation.
     * Creates a task which deletes expired sessions.
     */
    public Runnable getLockscreenValidationCleanupTask() {
        return new LockscreenValidationCleanupTask();
    }

    /**
     * Holder for KeyPair used by remote lock screen validation.
     *
     * @hide
     */
    public static class LockScreenVerificationSession {
    public class LockscreenVerificationSession {
        private final KeyPair mKeyPair;
        private final long mSessionStartTimeMillis;
        private final long mElapsedStartTime;

        /**
         * @hide
         */
        public LockScreenVerificationSession(KeyPair keyPair, long sessionStartTimeMillis) {
        LockscreenVerificationSession(KeyPair keyPair, long elapsedStartTime) {
            mKeyPair = keyPair;
            mSessionStartTimeMillis = sessionStartTimeMillis;
            mElapsedStartTime = elapsedStartTime;
        }

        /**
         * Returns SecureBox key pair.
         */
        public KeyPair getKeyPair() {
            return mKeyPair;
        }

        /**
         * Time when the session started.
         */
        private long getElapsedStartTimeMillis() {
            return mElapsedStartTime;
        }
    }

    private class LockscreenValidationCleanupTask implements Runnable {
        @Override
        public void run() {
            try {
                synchronized (mSessionsByUserId) {
                    ArrayList<Integer> keysToRemove = new ArrayList<>();
                    for (int i = 0; i < mSessionsByUserId.size(); i++) {
                        long now = SystemClock.elapsedRealtime();
                        long startTime = mSessionsByUserId.valueAt(i).getElapsedStartTimeMillis();
                        if (now - startTime > SESSION_TIMEOUT_MILLIS) {
                            int userId = mSessionsByUserId.keyAt(i);
                            keysToRemove.add(userId);
                        }
                    }
                    for (Integer userId : keysToRemove) {
                        mSessionsByUserId.delete(userId);
                    }
                }
            } catch (Exception e) {
                Log.e(TAG, "Unexpected exception thrown during LockscreenValidationCleanupTask", e);
            }
        }

    }

}
+84 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.recoverablekeystore.storage;

import static com.google.common.truth.Truth.assertThat;

import android.os.SystemClock;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.server.locksettings.recoverablekeystore.SecureBox;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;


@SmallTest
@RunWith(AndroidJUnit4.class)
public class RemoteLockscreenValidationSessionStorageTest {
    private static final int USER_ID = 0;
    private static final int USER_ID_2 = 2;

    RemoteLockscreenValidationSessionStorage mStorage;

    @Before
    public void setUp() {
        mStorage = new RemoteLockscreenValidationSessionStorage();
    }

    @Test
    public void get_noStoredSessions_returnsNull() {
        assertThat(mStorage.get(USER_ID)).isNull();
    }

    @Test
    public void startSession() {
        mStorage.startSession(USER_ID);

        assertThat(mStorage.get(USER_ID)).isNotNull();
        assertThat(mStorage.get(USER_ID_2)).isNull();
    }

    @Test
    public void finishSession_removesSessionFromStorage() {
        mStorage.startSession(USER_ID);

        mStorage.finishSession(USER_ID);

        assertThat(mStorage.get(USER_ID)).isNull();
    }

    @Test
    public void getLockscreenValidationCleanupTask() throws Exception {
        long time11MinutesAgo = SystemClock.elapsedRealtime() - 11 * 60 * 1000;
        long time2MinutesAgo = SystemClock.elapsedRealtime() - 2 * 60 * 1000;
        mStorage.mSessionsByUserId.put(
                USER_ID, mStorage.new LockscreenVerificationSession(
                SecureBox.genKeyPair(), time11MinutesAgo));
        mStorage.mSessionsByUserId.put(
                USER_ID_2, mStorage.new LockscreenVerificationSession(
                SecureBox.genKeyPair(), time2MinutesAgo));

        mStorage.getLockscreenValidationCleanupTask().run();

        assertThat(mStorage.get(USER_ID)).isNull();
        assertThat(mStorage.get(USER_ID_2)).isNotNull();
    }
}