1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.locksettings.recoverablekeystore.storage;
18 
19 import android.annotation.Nullable;
20 import android.os.SystemClock;
21 import android.text.format.DateUtils;
22 import android.util.Log;
23 import android.util.SparseArray;
24 
25 import com.android.internal.annotations.VisibleForTesting;
26 import com.android.security.SecureBox;
27 
28 import java.security.KeyPair;
29 import java.security.NoSuchAlgorithmException;
30 import java.util.ArrayList;
31 
32 /**
33  * Memory based storage for keyPair used to send encrypted credentials from a remote device.
34  *
35  * @hide
36  */
37 public class RemoteLockscreenValidationSessionStorage {
38 
39     private static final long SESSION_TIMEOUT_MILLIS = 10L * DateUtils.MINUTE_IN_MILLIS;
40     private static final String TAG = "RemoteLockscreenValidation";
41 
42     @VisibleForTesting
43     final SparseArray<LockscreenVerificationSession> mSessionsByUserId =
44             new SparseArray<>(0);
45 
46     /**
47      * Returns session for given user or null.
48      *
49      * @param userId The user id
50      * @return The session info.
51      *
52      * @hide
53      */
54     @Nullable
get(int userId)55     public LockscreenVerificationSession get(int userId) {
56         synchronized (mSessionsByUserId) {
57             return mSessionsByUserId.get(userId);
58         }
59     }
60 
61     /**
62      * Creates a new session to verify credentials guess.
63      *
64      * Session will be automatically removed after 10 minutes of inactivity.
65      * @param userId The user id
66      *
67      * @hide
68      */
startSession(int userId)69     public LockscreenVerificationSession startSession(int userId) {
70         synchronized (mSessionsByUserId) {
71             if (mSessionsByUserId.get(userId) != null) {
72                 mSessionsByUserId.delete(userId);
73             }
74 
75             KeyPair newKeyPair;
76             try {
77                 newKeyPair = SecureBox.genKeyPair();
78             } catch (NoSuchAlgorithmException e) {
79                 // impossible
80                 throw new RuntimeException(e);
81             }
82             LockscreenVerificationSession newSession =
83                     new LockscreenVerificationSession(newKeyPair, SystemClock.elapsedRealtime());
84             mSessionsByUserId.put(userId, newSession);
85             return newSession;
86         }
87     }
88 
89     /**
90      * Deletes session for a user.
91      */
finishSession(int userId)92     public void finishSession(int userId) {
93         synchronized (mSessionsByUserId) {
94             mSessionsByUserId.delete(userId);
95         }
96     }
97 
98     /**
99      * Creates a task which deletes expired sessions.
100      */
getLockscreenValidationCleanupTask()101     public Runnable getLockscreenValidationCleanupTask() {
102         return new LockscreenValidationCleanupTask();
103     }
104 
105     /**
106      * Holder for KeyPair used by remote lock screen validation.
107      *
108      * @hide
109      */
110     public class LockscreenVerificationSession {
111         private final KeyPair mKeyPair;
112         private final long mElapsedStartTime;
113 
114         /**
115          * @hide
116          */
LockscreenVerificationSession(KeyPair keyPair, long elapsedStartTime)117         LockscreenVerificationSession(KeyPair keyPair, long elapsedStartTime) {
118             mKeyPair = keyPair;
119             mElapsedStartTime = elapsedStartTime;
120         }
121 
122         /**
123          * Returns SecureBox key pair.
124          */
getKeyPair()125         public KeyPair getKeyPair() {
126             return mKeyPair;
127         }
128 
129         /**
130          * Time when the session started.
131          */
getElapsedStartTimeMillis()132         private long getElapsedStartTimeMillis() {
133             return mElapsedStartTime;
134         }
135     }
136 
137     private class LockscreenValidationCleanupTask implements Runnable {
138         @Override
run()139         public void run() {
140             try {
141                 synchronized (mSessionsByUserId) {
142                     ArrayList<Integer> keysToRemove = new ArrayList<>();
143                     for (int i = 0; i < mSessionsByUserId.size(); i++) {
144                         long now = SystemClock.elapsedRealtime();
145                         long startTime = mSessionsByUserId.valueAt(i).getElapsedStartTimeMillis();
146                         if (now - startTime > SESSION_TIMEOUT_MILLIS) {
147                             int userId = mSessionsByUserId.keyAt(i);
148                             keysToRemove.add(userId);
149                         }
150                     }
151                     for (Integer userId : keysToRemove) {
152                         mSessionsByUserId.delete(userId);
153                     }
154                 }
155             } catch (Exception e) {
156                 Log.e(TAG, "Unexpected exception thrown during LockscreenValidationCleanupTask", e);
157             }
158         }
159 
160     }
161 
162 }
163