blob: fec0189377c82abf723d577d6abdbc8d2e09d6eb [file] [log] [blame]
Adrian Roos261d5ab2014-10-29 14:42:38 +01001/*
2 * Copyright (C) 2014 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
Andrew Scull507d11c2017-05-03 17:19:01 +010017package com.android.server.locksettings;
Adrian Roos261d5ab2014-10-29 14:42:38 +010018
Rubin Xu1de89b32016-11-30 20:03:13 +000019import static android.content.Context.USER_SERVICE;
Adrian Roose5424992014-11-07 21:47:17 +010020
Adrian Roos7374d3a2017-03-31 14:14:53 -070021import android.annotation.Nullable;
22import android.app.admin.DevicePolicyManager;
Adrian Roos261d5ab2014-10-29 14:42:38 +010023import android.content.ContentValues;
24import android.content.Context;
25import android.content.pm.UserInfo;
26import android.database.Cursor;
27import android.database.sqlite.SQLiteDatabase;
28import android.database.sqlite.SQLiteOpenHelper;
29import android.os.Environment;
Adrian Roos7374d3a2017-03-31 14:14:53 -070030import android.os.UserHandle;
Adrian Roos261d5ab2014-10-29 14:42:38 +010031import android.os.UserManager;
32import android.util.ArrayMap;
Adrian Roos261d5ab2014-10-29 14:42:38 +010033import android.util.Slog;
Rubin Xu1de89b32016-11-30 20:03:13 +000034
35import com.android.internal.annotations.VisibleForTesting;
36import com.android.internal.util.ArrayUtils;
Rubin Xue2f925f2019-08-14 16:53:55 +010037import com.android.internal.util.IndentingPrintWriter;
Adrian Roos7374d3a2017-03-31 14:14:53 -070038import com.android.internal.util.Preconditions;
Rubin Xu1de89b32016-11-30 20:03:13 +000039import com.android.internal.widget.LockPatternUtils;
Pavel Grafov19a4fb32019-03-15 12:55:12 +000040import com.android.internal.widget.LockPatternUtils.CredentialType;
Adrian Roos7374d3a2017-03-31 14:14:53 -070041import com.android.server.LocalServices;
42import com.android.server.PersistentDataBlockManagerInternal;
Adrian Roos261d5ab2014-10-29 14:42:38 +010043
Adrian Roos7374d3a2017-03-31 14:14:53 -070044import java.io.ByteArrayInputStream;
45import java.io.ByteArrayOutputStream;
46import java.io.DataInputStream;
47import java.io.DataOutputStream;
Adrian Roos261d5ab2014-10-29 14:42:38 +010048import java.io.File;
Rubin Xu40f14ac2019-09-04 15:58:07 +010049import java.io.FileNotFoundException;
Adrian Roos261d5ab2014-10-29 14:42:38 +010050import java.io.IOException;
51import java.io.RandomAccessFile;
Paul Crowley0555f982019-06-21 15:15:32 -070052import java.nio.channels.FileChannel;
53import java.nio.file.StandardOpenOption;
Rubin Xu7b7424b2017-03-31 18:03:20 +010054import java.util.ArrayList;
Rubin Xu4728d212019-03-15 11:38:36 +000055import java.util.Arrays;
Rubin Xu7b7424b2017-03-31 18:03:20 +010056import java.util.List;
57import java.util.Map;
Adrian Roos261d5ab2014-10-29 14:42:38 +010058
Adrian Roos261d5ab2014-10-29 14:42:38 +010059/**
60 * Storage for the lock settings service.
61 */
62class LockSettingsStorage {
63
64 private static final String TAG = "LockSettingsStorage";
65 private static final String TABLE = "locksettings";
Ricky Waidc283a82016-03-24 19:55:08 +000066 private static final boolean DEBUG = false;
Adrian Roos261d5ab2014-10-29 14:42:38 +010067
68 private static final String COLUMN_KEY = "name";
69 private static final String COLUMN_USERID = "user";
70 private static final String COLUMN_VALUE = "value";
71
72 private static final String[] COLUMNS_FOR_QUERY = {
73 COLUMN_VALUE
74 };
Adrian Roos3dcae682014-10-29 14:43:56 +010075 private static final String[] COLUMNS_FOR_PREFETCH = {
76 COLUMN_KEY, COLUMN_VALUE
77 };
Adrian Roos261d5ab2014-10-29 14:42:38 +010078
79 private static final String SYSTEM_DIRECTORY = "/system/";
Andres Moralese40bad82015-05-28 14:21:36 -070080 private static final String LOCK_PATTERN_FILE = "gatekeeper.pattern.key";
Andres Morales8fa56652015-03-31 09:19:50 -070081 private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key";
Ricky Waidc283a82016-03-24 19:55:08 +000082 private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key";
Adrian Roos261d5ab2014-10-29 14:42:38 +010083
Kenny Rootf76cfc32019-11-08 14:36:03 -080084 private static final String REBOOT_ESCROW_FILE = "reboot.escrow.key";
85
Rubin Xu3bf722a2016-12-15 16:07:38 +000086 private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/";
87
Adrian Roos3dcae682014-10-29 14:43:56 +010088 private static final Object DEFAULT = new Object();
89
Adrian Roos261d5ab2014-10-29 14:42:38 +010090 private final DatabaseHelper mOpenHelper;
91 private final Context mContext;
Adrian Roos3dcae682014-10-29 14:43:56 +010092 private final Cache mCache = new Cache();
Adrian Roos261d5ab2014-10-29 14:42:38 +010093 private final Object mFileWriteLock = new Object();
94
Adrian Roos7374d3a2017-03-31 14:14:53 -070095 private PersistentDataBlockManagerInternal mPersistentDataBlockManagerInternal;
96
Rubin Xu1de89b32016-11-30 20:03:13 +000097 @VisibleForTesting
98 public static class CredentialHash {
Andres Morales8fa56652015-03-31 09:19:50 -070099
Rubin Xua7587272019-07-30 17:14:28 +0100100 private CredentialHash(byte[] hash, @CredentialType int type) {
Rubin Xu1de89b32016-11-30 20:03:13 +0000101 if (type != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
102 if (hash == null) {
Rubin Xu7959e2a2019-07-31 11:26:53 +0100103 throw new IllegalArgumentException("Empty hash for CredentialHash");
Rubin Xu1de89b32016-11-30 20:03:13 +0000104 }
105 } else /* type == LockPatternUtils.CREDENTIAL_TYPE_NONE */ {
106 if (hash != null) {
Rubin Xu7959e2a2019-07-31 11:26:53 +0100107 throw new IllegalArgumentException(
108 "None type CredentialHash should not have hash");
Rubin Xu1de89b32016-11-30 20:03:13 +0000109 }
110 }
Andres Morales8fa56652015-03-31 09:19:50 -0700111 this.hash = hash;
Rubin Xu1de89b32016-11-30 20:03:13 +0000112 this.type = type;
Andres Morales8fa56652015-03-31 09:19:50 -0700113 }
114
Rubin Xu1de89b32016-11-30 20:03:13 +0000115 static CredentialHash create(byte[] hash, int type) {
116 if (type == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
Rubin Xu7959e2a2019-07-31 11:26:53 +0100117 throw new IllegalArgumentException("Bad type for CredentialHash");
Rubin Xu1de89b32016-11-30 20:03:13 +0000118 }
Rubin Xua7587272019-07-30 17:14:28 +0100119 return new CredentialHash(hash, type);
Rubin Xu1de89b32016-11-30 20:03:13 +0000120 }
121
122 static CredentialHash createEmptyHash() {
Rubin Xua7587272019-07-30 17:14:28 +0100123 return new CredentialHash(null, LockPatternUtils.CREDENTIAL_TYPE_NONE);
Rubin Xu1de89b32016-11-30 20:03:13 +0000124 }
125
Andres Morales8fa56652015-03-31 09:19:50 -0700126 byte[] hash;
Pavel Grafov19a4fb32019-03-15 12:55:12 +0000127 @CredentialType int type;
Andres Morales8fa56652015-03-31 09:19:50 -0700128 }
129
Rubin Xu0cbc19e2016-12-09 14:00:21 +0000130 public LockSettingsStorage(Context context) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100131 mContext = context;
Rubin Xu0cbc19e2016-12-09 14:00:21 +0000132 mOpenHelper = new DatabaseHelper(context);
133 }
134
135 public void setDatabaseOnCreateCallback(Callback callback) {
136 mOpenHelper.setCallback(callback);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100137 }
138
Adrian Roos3dcae682014-10-29 14:43:56 +0100139 public void writeKeyValue(String key, String value, int userId) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100140 writeKeyValue(mOpenHelper.getWritableDatabase(), key, value, userId);
141 }
142
Adrian Roos3dcae682014-10-29 14:43:56 +0100143 public void writeKeyValue(SQLiteDatabase db, String key, String value, int userId) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100144 ContentValues cv = new ContentValues();
145 cv.put(COLUMN_KEY, key);
146 cv.put(COLUMN_USERID, userId);
147 cv.put(COLUMN_VALUE, value);
148
149 db.beginTransaction();
150 try {
151 db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
152 new String[] {key, Integer.toString(userId)});
153 db.insert(TABLE, null, cv);
154 db.setTransactionSuccessful();
Adrian Roos3dcae682014-10-29 14:43:56 +0100155 mCache.putKeyValue(key, value, userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100156 } finally {
157 db.endTransaction();
158 }
159
160 }
161
Adrian Roos3dcae682014-10-29 14:43:56 +0100162 public String readKeyValue(String key, String defaultValue, int userId) {
163 int version;
164 synchronized (mCache) {
165 if (mCache.hasKeyValue(key, userId)) {
166 return mCache.peekKeyValue(key, defaultValue, userId);
167 }
168 version = mCache.getVersion();
169 }
170
Adrian Roos261d5ab2014-10-29 14:42:38 +0100171 Cursor cursor;
Adrian Roos3dcae682014-10-29 14:43:56 +0100172 Object result = DEFAULT;
Adrian Roos261d5ab2014-10-29 14:42:38 +0100173 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
174 if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
175 COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
176 new String[] { Integer.toString(userId), key },
177 null, null, null)) != null) {
178 if (cursor.moveToFirst()) {
179 result = cursor.getString(0);
180 }
181 cursor.close();
182 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100183 mCache.putKeyValueIfUnchanged(key, result, userId, version);
184 return result == DEFAULT ? defaultValue : (String) result;
Adrian Roos261d5ab2014-10-29 14:42:38 +0100185 }
186
Adrian Roos3dcae682014-10-29 14:43:56 +0100187 public void prefetchUser(int userId) {
188 int version;
189 synchronized (mCache) {
190 if (mCache.isFetched(userId)) {
191 return;
192 }
193 mCache.setFetched(userId);
194 version = mCache.getVersion();
195 }
196
197 Cursor cursor;
198 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
199 if ((cursor = db.query(TABLE, COLUMNS_FOR_PREFETCH,
200 COLUMN_USERID + "=?",
201 new String[] { Integer.toString(userId) },
202 null, null, null)) != null) {
203 while (cursor.moveToNext()) {
204 String key = cursor.getString(0);
205 String value = cursor.getString(1);
206 mCache.putKeyValueIfUnchanged(key, value, userId, version);
207 }
208 cursor.close();
209 }
210
211 // Populate cache by reading the password and pattern files.
Rubin Xu1de89b32016-11-30 20:03:13 +0000212 readCredentialHash(userId);
Adrian Roos3dcae682014-10-29 14:43:56 +0100213 }
214
Rubin Xu1de89b32016-11-30 20:03:13 +0000215 private CredentialHash readPasswordHashIfExists(int userId) {
Andres Morales8fa56652015-03-31 09:19:50 -0700216 byte[] stored = readFile(getLockPasswordFilename(userId));
Rubin Xu1de89b32016-11-30 20:03:13 +0000217 if (!ArrayUtils.isEmpty(stored)) {
Rubin Xu5e891bc2019-10-14 10:22:23 +0100218 return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN);
Andres Morales8fa56652015-03-31 09:19:50 -0700219 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100220 return null;
221 }
222
Rubin Xu1de89b32016-11-30 20:03:13 +0000223 private CredentialHash readPatternHashIfExists(int userId) {
Andres Morales8fa56652015-03-31 09:19:50 -0700224 byte[] stored = readFile(getLockPatternFilename(userId));
Rubin Xu1de89b32016-11-30 20:03:13 +0000225 if (!ArrayUtils.isEmpty(stored)) {
Rubin Xua7587272019-07-30 17:14:28 +0100226 return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PATTERN);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100227 }
228 return null;
229 }
230
Rubin Xu1de89b32016-11-30 20:03:13 +0000231 public CredentialHash readCredentialHash(int userId) {
232 CredentialHash passwordHash = readPasswordHashIfExists(userId);
Rubin Xua7587272019-07-30 17:14:28 +0100233 if (passwordHash != null) {
Rubin Xu1de89b32016-11-30 20:03:13 +0000234 return passwordHash;
Rubin Xu1de89b32016-11-30 20:03:13 +0000235 }
Rubin Xua7587272019-07-30 17:14:28 +0100236
237 CredentialHash patternHash = readPatternHashIfExists(userId);
238 if (patternHash != null) {
239 return patternHash;
240 }
241 return CredentialHash.createEmptyHash();
Rubin Xu1de89b32016-11-30 20:03:13 +0000242 }
243
Ricky Waidc283a82016-03-24 19:55:08 +0000244 public void removeChildProfileLock(int userId) {
245 if (DEBUG)
246 Slog.e(TAG, "Remove child profile lock for user: " + userId);
247 try {
248 deleteFile(getChildProfileLockFile(userId));
249 } catch (Exception e) {
250 e.printStackTrace();
251 }
252 }
253
254 public void writeChildProfileLock(int userId, byte[] lock) {
255 writeFile(getChildProfileLockFile(userId), lock);
256 }
257
258 public byte[] readChildProfileLock(int userId) {
259 return readFile(getChildProfileLockFile(userId));
260 }
261
262 public boolean hasChildProfileLock(int userId) {
263 return hasFile(getChildProfileLockFile(userId));
264 }
Andres Moralese40bad82015-05-28 14:21:36 -0700265
Kenny Rootf76cfc32019-11-08 14:36:03 -0800266 public void writeRebootEscrow(int userId, byte[] rebootEscrow) {
267 writeFile(getRebootEscrowFile(userId), rebootEscrow);
268 }
269
270 public byte[] readRebootEscrow(int userId) {
271 return readFile(getRebootEscrowFile(userId));
272 }
273
274 public boolean hasRebootEscrow(int userId) {
275 return hasFile(getRebootEscrowFile(userId));
276 }
277
278 public void removeRebootEscrow(int userId) {
279 deleteFile(getRebootEscrowFile(userId));
280 }
281
Adrian Roos3dcae682014-10-29 14:43:56 +0100282 public boolean hasPassword(int userId) {
Rubin Xua7587272019-07-30 17:14:28 +0100283 return hasFile(getLockPasswordFilename(userId));
Adrian Roos261d5ab2014-10-29 14:42:38 +0100284 }
285
Adrian Roos3dcae682014-10-29 14:43:56 +0100286 public boolean hasPattern(int userId) {
Rubin Xu7959e2a2019-07-31 11:26:53 +0100287 return hasFile(getLockPatternFilename(userId));
Adrian Roos261d5ab2014-10-29 14:42:38 +0100288 }
289
Adrian Roos3dcae682014-10-29 14:43:56 +0100290 private boolean hasFile(String name) {
291 byte[] contents = readFile(name);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100292 return contents != null && contents.length > 0;
293 }
294
Adrian Roos3dcae682014-10-29 14:43:56 +0100295 private byte[] readFile(String name) {
296 int version;
297 synchronized (mCache) {
298 if (mCache.hasFile(name)) {
299 return mCache.peekFile(name);
300 }
301 version = mCache.getVersion();
302 }
303
Adrian Roos261d5ab2014-10-29 14:42:38 +0100304 byte[] stored = null;
Rubin Xu40f14ac2019-09-04 15:58:07 +0100305 try (RandomAccessFile raf = new RandomAccessFile(name, "r")) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100306 stored = new byte[(int) raf.length()];
307 raf.readFully(stored, 0, stored.length);
308 raf.close();
Rubin Xu40f14ac2019-09-04 15:58:07 +0100309 } catch (FileNotFoundException suppressed) {
310 // readFile() is also called by hasFile() to check the existence of files, in this
311 // case FileNotFoundException is expected.
Adrian Roos261d5ab2014-10-29 14:42:38 +0100312 } catch (IOException e) {
313 Slog.e(TAG, "Cannot read file " + e);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100314 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100315 mCache.putFileIfUnchanged(name, stored, version);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100316 return stored;
317 }
318
Paul Crowley0555f982019-06-21 15:15:32 -0700319 private void fsyncDirectory(File directory) {
320 try {
321 try (FileChannel file = FileChannel.open(directory.toPath(),
322 StandardOpenOption.READ)) {
323 file.force(true);
324 }
325 } catch (IOException e) {
326 Slog.e(TAG, "Error syncing directory: " + directory, e);
327 }
328 }
329
Adrian Roos3dcae682014-10-29 14:43:56 +0100330 private void writeFile(String name, byte[] hash) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100331 synchronized (mFileWriteLock) {
332 RandomAccessFile raf = null;
333 try {
Rubin Xuaa32d152017-04-27 17:01:05 +0100334 // Write the hash to file, requiring each write to be synchronized to the
335 // underlying storage device immediately to avoid data loss in case of power loss.
336 // This also ensures future secdiscard operation on the file succeeds since the
337 // file would have been allocated on flash.
338 raf = new RandomAccessFile(name, "rws");
Adrian Roos261d5ab2014-10-29 14:42:38 +0100339 // Truncate the file if pattern is null, to clear the lock
340 if (hash == null || hash.length == 0) {
341 raf.setLength(0);
342 } else {
343 raf.write(hash, 0, hash.length);
344 }
345 raf.close();
Paul Crowley0555f982019-06-21 15:15:32 -0700346 fsyncDirectory((new File(name)).getAbsoluteFile().getParentFile());
Adrian Roos261d5ab2014-10-29 14:42:38 +0100347 } catch (IOException e) {
348 Slog.e(TAG, "Error writing to file " + e);
349 } finally {
350 if (raf != null) {
351 try {
352 raf.close();
353 } catch (IOException e) {
354 Slog.e(TAG, "Error closing file " + e);
355 }
356 }
357 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100358 mCache.putFile(name, hash);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100359 }
360 }
361
Andres Moralese40bad82015-05-28 14:21:36 -0700362 private void deleteFile(String name) {
Ricky Waidc283a82016-03-24 19:55:08 +0000363 if (DEBUG) Slog.e(TAG, "Delete file " + name);
364 synchronized (mFileWriteLock) {
365 File file = new File(name);
366 if (file.exists()) {
367 file.delete();
368 mCache.putFile(name, null);
369 }
Andres Moralese40bad82015-05-28 14:21:36 -0700370 }
371 }
372
Rubin Xu1de89b32016-11-30 20:03:13 +0000373 public void writeCredentialHash(CredentialHash hash, int userId) {
374 byte[] patternHash = null;
375 byte[] passwordHash = null;
Rubin Xu5e891bc2019-10-14 10:22:23 +0100376 if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN
377 || hash.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
378 || hash.type == LockPatternUtils.CREDENTIAL_TYPE_PIN) {
Rubin Xu1de89b32016-11-30 20:03:13 +0000379 passwordHash = hash.hash;
380 } else if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
381 patternHash = hash.hash;
Rubin Xu5e891bc2019-10-14 10:22:23 +0100382 } else {
383 Preconditions.checkArgument(hash.type == LockPatternUtils.CREDENTIAL_TYPE_NONE,
384 "Unknown credential type");
Rubin Xu1de89b32016-11-30 20:03:13 +0000385 }
386 writeFile(getLockPasswordFilename(userId), passwordHash);
387 writeFile(getLockPatternFilename(userId), patternHash);
Robin Lee68e4ba42015-03-10 12:34:28 +0000388 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100389
Adrian Roose5424992014-11-07 21:47:17 +0100390 @VisibleForTesting
391 String getLockPatternFilename(int userId) {
Adrian Roos3dcae682014-10-29 14:43:56 +0100392 return getLockCredentialFilePathForUser(userId, LOCK_PATTERN_FILE);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100393 }
394
Adrian Roose5424992014-11-07 21:47:17 +0100395 @VisibleForTesting
396 String getLockPasswordFilename(int userId) {
Adrian Roos3dcae682014-10-29 14:43:56 +0100397 return getLockCredentialFilePathForUser(userId, LOCK_PASSWORD_FILE);
398 }
399
Ricky Waia46b40f2016-03-31 16:48:29 +0100400 @VisibleForTesting
401 String getChildProfileLockFile(int userId) {
Ricky Waidc283a82016-03-24 19:55:08 +0000402 return getLockCredentialFilePathForUser(userId, CHILD_PROFILE_LOCK_FILE);
403 }
404
Kenny Rootf76cfc32019-11-08 14:36:03 -0800405 @VisibleForTesting
406 String getRebootEscrowFile(int userId) {
407 return getLockCredentialFilePathForUser(userId, REBOOT_ESCROW_FILE);
408 }
409
Adrian Roos3dcae682014-10-29 14:43:56 +0100410 private String getLockCredentialFilePathForUser(int userId, String basename) {
Rubin Xu3bf722a2016-12-15 16:07:38 +0000411 String dataSystemDirectory = Environment.getDataDirectory().getAbsolutePath() +
Adrian Roos261d5ab2014-10-29 14:42:38 +0100412 SYSTEM_DIRECTORY;
413 if (userId == 0) {
414 // Leave it in the same place for user 0
Adrian Roos3dcae682014-10-29 14:43:56 +0100415 return dataSystemDirectory + basename;
Adrian Roos261d5ab2014-10-29 14:42:38 +0100416 } else {
Adrian Roos3dcae682014-10-29 14:43:56 +0100417 return new File(Environment.getUserSystemDirectory(userId), basename).getAbsolutePath();
Adrian Roos261d5ab2014-10-29 14:42:38 +0100418 }
419 }
420
Rubin Xu3bf722a2016-12-15 16:07:38 +0000421 public void writeSyntheticPasswordState(int userId, long handle, String name, byte[] data) {
Rubin Xuaa7b5262018-04-16 14:45:21 +0100422 ensureSyntheticPasswordDirectoryForUser(userId);
Rubin Xu3bf722a2016-12-15 16:07:38 +0000423 writeFile(getSynthenticPasswordStateFilePathForUser(userId, handle, name), data);
424 }
425
426 public byte[] readSyntheticPasswordState(int userId, long handle, String name) {
427 return readFile(getSynthenticPasswordStateFilePathForUser(userId, handle, name));
428 }
429
Rubin Xuaa32d152017-04-27 17:01:05 +0100430 public void deleteSyntheticPasswordState(int userId, long handle, String name) {
Rubin Xu3bf722a2016-12-15 16:07:38 +0000431 String path = getSynthenticPasswordStateFilePathForUser(userId, handle, name);
432 File file = new File(path);
433 if (file.exists()) {
Rubin Xu841fd432018-03-01 16:40:04 +0000434 try (RandomAccessFile raf = new RandomAccessFile(path, "rws")) {
435 final int fileSize = (int) raf.length();
436 raf.write(new byte[fileSize]);
Rubin Xuaa32d152017-04-27 17:01:05 +0100437 } catch (Exception e) {
Rubin Xu841fd432018-03-01 16:40:04 +0000438 Slog.w(TAG, "Failed to zeroize " + path, e);
Rubin Xuaa32d152017-04-27 17:01:05 +0100439 } finally {
440 file.delete();
441 }
Rubin Xu3bf722a2016-12-15 16:07:38 +0000442 mCache.putFile(path, null);
443 }
444 }
445
Rubin Xu7b7424b2017-03-31 18:03:20 +0100446 public Map<Integer, List<Long>> listSyntheticPasswordHandlesForAllUsers(String stateName) {
447 Map<Integer, List<Long>> result = new ArrayMap<>();
448 final UserManager um = UserManager.get(mContext);
449 for (UserInfo user : um.getUsers(false)) {
450 result.put(user.id, listSyntheticPasswordHandlesForUser(stateName, user.id));
451 }
452 return result;
453 }
454
455 public List<Long> listSyntheticPasswordHandlesForUser(String stateName, int userId) {
456 File baseDir = getSyntheticPasswordDirectoryForUser(userId);
457 List<Long> result = new ArrayList<>();
458 File[] files = baseDir.listFiles();
459 if (files == null) {
460 return result;
461 }
462 for (File file : files) {
463 String[] parts = file.getName().split("\\.");
464 if (parts.length == 2 && parts[1].equals(stateName)) {
465 try {
466 result.add(Long.parseUnsignedLong(parts[0], 16));
467 } catch (NumberFormatException e) {
468 Slog.e(TAG, "Failed to parse handle " + parts[0]);
469 }
470 }
471 }
472 return result;
473 }
474
Rubin Xu3bf722a2016-12-15 16:07:38 +0000475 @VisibleForTesting
476 protected File getSyntheticPasswordDirectoryForUser(int userId) {
477 return new File(Environment.getDataSystemDeDirectory(userId) ,SYNTHETIC_PASSWORD_DIRECTORY);
478 }
479
Rubin Xuaa7b5262018-04-16 14:45:21 +0100480 /** Ensure per-user directory for synthetic password state exists */
481 private void ensureSyntheticPasswordDirectoryForUser(int userId) {
Rubin Xu3bf722a2016-12-15 16:07:38 +0000482 File baseDir = getSyntheticPasswordDirectoryForUser(userId);
Rubin Xu3bf722a2016-12-15 16:07:38 +0000483 if (!baseDir.exists()) {
484 baseDir.mkdir();
485 }
Rubin Xuaa7b5262018-04-16 14:45:21 +0100486 }
487
488 @VisibleForTesting
489 protected String getSynthenticPasswordStateFilePathForUser(int userId, long handle,
490 String name) {
491 final File baseDir = getSyntheticPasswordDirectoryForUser(userId);
492 final String baseName = String.format("%016x.%s", handle, name);
Rubin Xu3bf722a2016-12-15 16:07:38 +0000493 return new File(baseDir, baseName).getAbsolutePath();
494 }
495
Adrian Roos261d5ab2014-10-29 14:42:38 +0100496 public void removeUser(int userId) {
497 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
498
499 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
500 final UserInfo parentInfo = um.getProfileParent(userId);
501
Robin Lee68e4ba42015-03-10 12:34:28 +0000502 if (parentInfo == null) {
503 // This user owns its lock settings files - safe to delete them
504 synchronized (mFileWriteLock) {
Kenny Rootf76cfc32019-11-08 14:36:03 -0800505 deleteFilesAndRemoveCache(
506 getLockPasswordFilename(userId),
507 getLockPatternFilename(userId),
508 getRebootEscrowFile(userId));
Adrian Roos261d5ab2014-10-29 14:42:38 +0100509 }
Ricky Waidc283a82016-03-24 19:55:08 +0000510 } else {
Rubin Xu3bf722a2016-12-15 16:07:38 +0000511 // Managed profile
Ricky Waidc283a82016-03-24 19:55:08 +0000512 removeChildProfileLock(userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100513 }
514
Rubin Xu3bf722a2016-12-15 16:07:38 +0000515 File spStateDir = getSyntheticPasswordDirectoryForUser(userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100516 try {
517 db.beginTransaction();
518 db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
519 db.setTransactionSuccessful();
Adrian Roos3dcae682014-10-29 14:43:56 +0100520 mCache.removeUser(userId);
Rubin Xu3bf722a2016-12-15 16:07:38 +0000521 // The directory itself will be deleted as part of user deletion operation by the
522 // framework, so only need to purge cache here.
523 //TODO: (b/34600579) invoke secdiscardable
524 mCache.purgePath(spStateDir.getAbsolutePath());
Adrian Roos261d5ab2014-10-29 14:42:38 +0100525 } finally {
526 db.endTransaction();
527 }
528 }
529
Kenny Rootf76cfc32019-11-08 14:36:03 -0800530 private void deleteFilesAndRemoveCache(String... names) {
531 for (String name : names) {
532 File file = new File(name);
533 if (file.exists()) {
534 file.delete();
535 mCache.putFile(name, null);
536 }
537 }
538 }
539
Adrian Roose5424992014-11-07 21:47:17 +0100540 @VisibleForTesting
541 void closeDatabase() {
542 mOpenHelper.close();
543 }
544
545 @VisibleForTesting
546 void clearCache() {
547 mCache.clear();
548 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100549
Rubin Xu5e891bc2019-10-14 10:22:23 +0100550 @Nullable @VisibleForTesting
551 PersistentDataBlockManagerInternal getPersistentDataBlockManager() {
Adrian Roos7374d3a2017-03-31 14:14:53 -0700552 if (mPersistentDataBlockManagerInternal == null) {
553 mPersistentDataBlockManagerInternal =
554 LocalServices.getService(PersistentDataBlockManagerInternal.class);
555 }
556 return mPersistentDataBlockManagerInternal;
557 }
558
559 public void writePersistentDataBlock(int persistentType, int userId, int qualityForUi,
560 byte[] payload) {
Rubin Xu5e891bc2019-10-14 10:22:23 +0100561 PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlockManager();
Adrian Roos7374d3a2017-03-31 14:14:53 -0700562 if (persistentDataBlock == null) {
563 return;
564 }
565 persistentDataBlock.setFrpCredentialHandle(PersistentData.toBytes(
566 persistentType, userId, qualityForUi, payload));
567 }
568
569 public PersistentData readPersistentDataBlock() {
Rubin Xu5e891bc2019-10-14 10:22:23 +0100570 PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlockManager();
Adrian Roos7374d3a2017-03-31 14:14:53 -0700571 if (persistentDataBlock == null) {
572 return PersistentData.NONE;
573 }
Adrian Roosb2375942018-01-19 22:31:28 +0100574 try {
575 return PersistentData.fromBytes(persistentDataBlock.getFrpCredentialHandle());
576 } catch (IllegalStateException e) {
577 Slog.e(TAG, "Error reading persistent data block", e);
578 return PersistentData.NONE;
579 }
Adrian Roos7374d3a2017-03-31 14:14:53 -0700580 }
581
582 public static class PersistentData {
583 static final byte VERSION_1 = 1;
584 static final int VERSION_1_HEADER_SIZE = 1 + 1 + 4 + 4;
585
586 public static final int TYPE_NONE = 0;
Andrew Scull971f2942017-07-12 15:09:45 +0100587 public static final int TYPE_SP = 1;
588 public static final int TYPE_SP_WEAVER = 2;
Adrian Roos7374d3a2017-03-31 14:14:53 -0700589
590 public static final PersistentData NONE = new PersistentData(TYPE_NONE,
591 UserHandle.USER_NULL, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, null);
592
593 final int type;
594 final int userId;
595 final int qualityForUi;
596 final byte[] payload;
597
598 private PersistentData(int type, int userId, int qualityForUi, byte[] payload) {
599 this.type = type;
600 this.userId = userId;
601 this.qualityForUi = qualityForUi;
602 this.payload = payload;
603 }
604
605 public static PersistentData fromBytes(byte[] frpData) {
606 if (frpData == null || frpData.length == 0) {
607 return NONE;
608 }
609
610 DataInputStream is = new DataInputStream(new ByteArrayInputStream(frpData));
611 try {
612 byte version = is.readByte();
613 if (version == PersistentData.VERSION_1) {
614 int type = is.readByte() & 0xFF;
615 int userId = is.readInt();
616 int qualityForUi = is.readInt();
617 byte[] payload = new byte[frpData.length - VERSION_1_HEADER_SIZE];
618 System.arraycopy(frpData, VERSION_1_HEADER_SIZE, payload, 0, payload.length);
619 return new PersistentData(type, userId, qualityForUi, payload);
620 } else {
621 Slog.wtf(TAG, "Unknown PersistentData version code: " + version);
Adrian Roosb2375942018-01-19 22:31:28 +0100622 return NONE;
Adrian Roos7374d3a2017-03-31 14:14:53 -0700623 }
624 } catch (IOException e) {
625 Slog.wtf(TAG, "Could not parse PersistentData", e);
Adrian Roosb2375942018-01-19 22:31:28 +0100626 return NONE;
Adrian Roos7374d3a2017-03-31 14:14:53 -0700627 }
628 }
629
630 public static byte[] toBytes(int persistentType, int userId, int qualityForUi,
631 byte[] payload) {
632 if (persistentType == PersistentData.TYPE_NONE) {
633 Preconditions.checkArgument(payload == null,
634 "TYPE_NONE must have empty payload");
635 return null;
636 }
637 Preconditions.checkArgument(payload != null && payload.length > 0,
638 "empty payload must only be used with TYPE_NONE");
639
640 ByteArrayOutputStream os = new ByteArrayOutputStream(
641 VERSION_1_HEADER_SIZE + payload.length);
642 DataOutputStream dos = new DataOutputStream(os);
643 try {
644 dos.writeByte(PersistentData.VERSION_1);
645 dos.writeByte(persistentType);
646 dos.writeInt(userId);
647 dos.writeInt(qualityForUi);
648 dos.write(payload);
649 } catch (IOException e) {
Rubin Xu0f9c2ff2019-08-14 16:25:57 +0100650 throw new IllegalStateException("ByteArrayOutputStream cannot throw IOException");
Adrian Roos7374d3a2017-03-31 14:14:53 -0700651 }
652 return os.toByteArray();
653 }
654 }
655
Adrian Roos3dcae682014-10-29 14:43:56 +0100656 public interface Callback {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100657 void initialize(SQLiteDatabase db);
658 }
659
Rubin Xue2f925f2019-08-14 16:53:55 +0100660 public void dump(IndentingPrintWriter pw) {
661 final UserManager um = UserManager.get(mContext);
662 for (UserInfo user : um.getUsers(false)) {
663 File userPath = getSyntheticPasswordDirectoryForUser(user.id);
664 pw.println(String.format("User %d [%s]:", user.id, userPath.getAbsolutePath()));
665 pw.increaseIndent();
666 File[] files = userPath.listFiles();
667 if (files != null) {
668 for (File file : files) {
669 pw.println(String.format("%4d %s %s", file.length(),
670 LockSettingsService.timestampToString(file.lastModified()),
671 file.getName()));
672 }
673 } else {
674 pw.println("[Not found]");
675 }
676 pw.decreaseIndent();
677 }
678 }
679
Andrew Scull8fc2ec82017-05-19 10:50:36 +0100680 static class DatabaseHelper extends SQLiteOpenHelper {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100681 private static final String TAG = "LockSettingsDB";
682 private static final String DATABASE_NAME = "locksettings.db";
683
684 private static final int DATABASE_VERSION = 2;
Fyodor Kupolovfe2d5ed2017-09-01 15:13:33 -0700685 private static final int IDLE_CONNECTION_TIMEOUT_MS = 30000;
Adrian Roos261d5ab2014-10-29 14:42:38 +0100686
Rubin Xu0cbc19e2016-12-09 14:00:21 +0000687 private Callback mCallback;
Adrian Roos261d5ab2014-10-29 14:42:38 +0100688
Rubin Xu0cbc19e2016-12-09 14:00:21 +0000689 public DatabaseHelper(Context context) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100690 super(context, DATABASE_NAME, null, DATABASE_VERSION);
691 setWriteAheadLoggingEnabled(true);
Fyodor Kupolovfe2d5ed2017-09-01 15:13:33 -0700692 // Memory optimization - close idle connections after 30s of inactivity
693 setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
Rubin Xu0cbc19e2016-12-09 14:00:21 +0000694 }
695
696 public void setCallback(Callback callback) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100697 mCallback = callback;
698 }
699
700 private void createTable(SQLiteDatabase db) {
701 db.execSQL("CREATE TABLE " + TABLE + " (" +
702 "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
703 COLUMN_KEY + " TEXT," +
704 COLUMN_USERID + " INTEGER," +
705 COLUMN_VALUE + " TEXT" +
706 ");");
707 }
708
709 @Override
710 public void onCreate(SQLiteDatabase db) {
711 createTable(db);
Rubin Xu0cbc19e2016-12-09 14:00:21 +0000712 if (mCallback != null) {
713 mCallback.initialize(db);
714 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100715 }
716
717 @Override
718 public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
719 int upgradeVersion = oldVersion;
720 if (upgradeVersion == 1) {
721 // Previously migrated lock screen widget settings. Now defunct.
722 upgradeVersion = 2;
723 }
724
725 if (upgradeVersion != DATABASE_VERSION) {
Rubin Xue1beaf02019-10-22 11:36:51 +0100726 Slog.w(TAG, "Failed to upgrade database!");
Adrian Roos261d5ab2014-10-29 14:42:38 +0100727 }
728 }
729 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100730
731 /**
732 * Cache consistency model:
733 * - Writes to storage write directly to the cache, but this MUST happen within the atomic
734 * section either provided by the database transaction or mWriteLock, such that writes to the
735 * cache and writes to the backing storage are guaranteed to occur in the same order
736 *
737 * - Reads can populate the cache, but because they are no strong ordering guarantees with
738 * respect to writes this precaution is taken:
739 * - The cache is assigned a version number that increases every time the cache is modified.
740 * Reads from backing storage can only populate the cache if the backing storage
741 * has not changed since the load operation has begun.
742 * This guarantees that no read operation can shadow a write to the cache that happens
743 * after it had begun.
744 */
745 private static class Cache {
746 private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>();
747 private final CacheKey mCacheKey = new CacheKey();
748 private int mVersion = 0;
749
750 String peekKeyValue(String key, String defaultValue, int userId) {
751 Object cached = peek(CacheKey.TYPE_KEY_VALUE, key, userId);
752 return cached == DEFAULT ? defaultValue : (String) cached;
753 }
754
755 boolean hasKeyValue(String key, int userId) {
756 return contains(CacheKey.TYPE_KEY_VALUE, key, userId);
757 }
758
759 void putKeyValue(String key, String value, int userId) {
760 put(CacheKey.TYPE_KEY_VALUE, key, value, userId);
761 }
762
763 void putKeyValueIfUnchanged(String key, Object value, int userId, int version) {
764 putIfUnchanged(CacheKey.TYPE_KEY_VALUE, key, value, userId, version);
765 }
766
767 byte[] peekFile(String fileName) {
Rubin Xu4728d212019-03-15 11:38:36 +0000768 return copyOf((byte[]) peek(CacheKey.TYPE_FILE, fileName, -1 /* userId */));
Adrian Roos3dcae682014-10-29 14:43:56 +0100769 }
770
771 boolean hasFile(String fileName) {
772 return contains(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
773 }
774
775 void putFile(String key, byte[] value) {
Rubin Xu4728d212019-03-15 11:38:36 +0000776 put(CacheKey.TYPE_FILE, key, copyOf(value), -1 /* userId */);
Adrian Roos3dcae682014-10-29 14:43:56 +0100777 }
778
779 void putFileIfUnchanged(String key, byte[] value, int version) {
Rubin Xu4728d212019-03-15 11:38:36 +0000780 putIfUnchanged(CacheKey.TYPE_FILE, key, copyOf(value), -1 /* userId */, version);
Adrian Roos3dcae682014-10-29 14:43:56 +0100781 }
782
783 void setFetched(int userId) {
784 put(CacheKey.TYPE_FETCHED, "isFetched", "true", userId);
785 }
786
787 boolean isFetched(int userId) {
788 return contains(CacheKey.TYPE_FETCHED, "", userId);
789 }
790
791
792 private synchronized void put(int type, String key, Object value, int userId) {
793 // Create a new CachKey here because it may be saved in the map if the key is absent.
794 mCache.put(new CacheKey().set(type, key, userId), value);
795 mVersion++;
796 }
797
798 private synchronized void putIfUnchanged(int type, String key, Object value, int userId,
799 int version) {
800 if (!contains(type, key, userId) && mVersion == version) {
801 put(type, key, value, userId);
802 }
803 }
804
805 private synchronized boolean contains(int type, String key, int userId) {
806 return mCache.containsKey(mCacheKey.set(type, key, userId));
807 }
808
809 private synchronized Object peek(int type, String key, int userId) {
810 return mCache.get(mCacheKey.set(type, key, userId));
811 }
812
813 private synchronized int getVersion() {
814 return mVersion;
815 }
816
817 synchronized void removeUser(int userId) {
818 for (int i = mCache.size() - 1; i >= 0; i--) {
819 if (mCache.keyAt(i).userId == userId) {
820 mCache.removeAt(i);
821 }
822 }
823
824 // Make sure in-flight loads can't write to cache.
825 mVersion++;
826 }
827
Rubin Xu4728d212019-03-15 11:38:36 +0000828 private byte[] copyOf(byte[] data) {
829 return data != null ? Arrays.copyOf(data, data.length) : null;
830 }
831
Rubin Xu3bf722a2016-12-15 16:07:38 +0000832 synchronized void purgePath(String path) {
833 for (int i = mCache.size() - 1; i >= 0; i--) {
834 CacheKey entry = mCache.keyAt(i);
835 if (entry.type == CacheKey.TYPE_FILE && entry.key.startsWith(path)) {
836 mCache.removeAt(i);
837 }
838 }
839 mVersion++;
840 }
841
Adrian Roose5424992014-11-07 21:47:17 +0100842 synchronized void clear() {
843 mCache.clear();
844 mVersion++;
845 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100846
847 private static final class CacheKey {
848 static final int TYPE_KEY_VALUE = 0;
849 static final int TYPE_FILE = 1;
850 static final int TYPE_FETCHED = 2;
851
852 String key;
853 int userId;
854 int type;
855
856 public CacheKey set(int type, String key, int userId) {
857 this.type = type;
858 this.key = key;
859 this.userId = userId;
860 return this;
861 }
862
863 @Override
864 public boolean equals(Object obj) {
865 if (!(obj instanceof CacheKey))
866 return false;
867 CacheKey o = (CacheKey) obj;
868 return userId == o.userId && type == o.type && key.equals(o.key);
869 }
870
871 @Override
872 public int hashCode() {
873 return key.hashCode() ^ userId ^ type;
874 }
875 }
876 }
Rubin Xua55b1682017-01-31 10:06:56 +0000877
Adrian Roos261d5ab2014-10-29 14:42:38 +0100878}