1 /* 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 17 package com.android.server.locksettings; 18 19 import static android.content.Context.USER_SERVICE; 20 21 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; 22 import static com.android.internal.widget.LockPatternUtils.USER_FRP; 23 24 import android.annotation.Nullable; 25 import android.app.admin.DevicePolicyManager; 26 import android.app.backup.BackupManager; 27 import android.content.ContentValues; 28 import android.content.Context; 29 import android.content.pm.UserInfo; 30 import android.database.Cursor; 31 import android.database.sqlite.SQLiteDatabase; 32 import android.database.sqlite.SQLiteOpenHelper; 33 import android.os.Environment; 34 import android.os.UserHandle; 35 import android.os.UserManager; 36 import android.provider.Settings; 37 import android.text.TextUtils; 38 import android.util.ArrayMap; 39 import android.util.Slog; 40 41 import com.android.internal.annotations.VisibleForTesting; 42 import com.android.internal.util.ArrayUtils; 43 import com.android.internal.util.IndentingPrintWriter; 44 import com.android.internal.util.Preconditions; 45 import com.android.internal.widget.LockPatternUtils; 46 import com.android.internal.widget.LockPatternUtils.CredentialType; 47 import com.android.server.LocalServices; 48 import com.android.server.PersistentDataBlockManagerInternal; 49 50 import java.io.ByteArrayInputStream; 51 import java.io.ByteArrayOutputStream; 52 import java.io.DataInputStream; 53 import java.io.DataOutputStream; 54 import java.io.File; 55 import java.io.FileNotFoundException; 56 import java.io.IOException; 57 import java.io.RandomAccessFile; 58 import java.nio.channels.FileChannel; 59 import java.nio.file.StandardOpenOption; 60 import java.util.ArrayList; 61 import java.util.Arrays; 62 import java.util.List; 63 import java.util.Map; 64 65 /** 66 * Storage for the lock settings service. 67 */ 68 class LockSettingsStorage { 69 70 private static final String TAG = "LockSettingsStorage"; 71 private static final String TABLE = "locksettings"; 72 private static final boolean DEBUG = false; 73 74 private static final String COLUMN_KEY = "name"; 75 private static final String COLUMN_USERID = "user"; 76 private static final String COLUMN_VALUE = "value"; 77 78 private static final String[] COLUMNS_FOR_QUERY = { 79 COLUMN_VALUE 80 }; 81 private static final String[] COLUMNS_FOR_PREFETCH = { 82 COLUMN_KEY, COLUMN_VALUE 83 }; 84 85 private static final String SYSTEM_DIRECTORY = "/system/"; 86 private static final String LOCK_PATTERN_FILE = "gatekeeper.pattern.key"; 87 private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key"; 88 private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key"; 89 90 private static final String REBOOT_ESCROW_FILE = "reboot.escrow.key"; 91 92 private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/"; 93 94 private static final Object DEFAULT = new Object(); 95 96 private static final String[] SETTINGS_TO_BACKUP = new String[] { 97 Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, 98 Settings.Secure.LOCK_SCREEN_OWNER_INFO, 99 Settings.Secure.LOCK_PATTERN_VISIBLE, 100 LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS 101 }; 102 103 private final DatabaseHelper mOpenHelper; 104 private final Context mContext; 105 private final Cache mCache = new Cache(); 106 private final Object mFileWriteLock = new Object(); 107 108 private PersistentDataBlockManagerInternal mPersistentDataBlockManagerInternal; 109 110 @VisibleForTesting 111 public static class CredentialHash { 112 CredentialHash(byte[] hash, @CredentialType int type)113 private CredentialHash(byte[] hash, @CredentialType int type) { 114 if (type != LockPatternUtils.CREDENTIAL_TYPE_NONE) { 115 if (hash == null) { 116 throw new IllegalArgumentException("Empty hash for CredentialHash"); 117 } 118 } else /* type == LockPatternUtils.CREDENTIAL_TYPE_NONE */ { 119 if (hash != null) { 120 throw new IllegalArgumentException( 121 "None type CredentialHash should not have hash"); 122 } 123 } 124 this.hash = hash; 125 this.type = type; 126 } 127 create(byte[] hash, int type)128 static CredentialHash create(byte[] hash, int type) { 129 if (type == LockPatternUtils.CREDENTIAL_TYPE_NONE) { 130 throw new IllegalArgumentException("Bad type for CredentialHash"); 131 } 132 return new CredentialHash(hash, type); 133 } 134 createEmptyHash()135 static CredentialHash createEmptyHash() { 136 return new CredentialHash(null, LockPatternUtils.CREDENTIAL_TYPE_NONE); 137 } 138 139 byte[] hash; 140 @CredentialType int type; 141 } 142 LockSettingsStorage(Context context)143 public LockSettingsStorage(Context context) { 144 mContext = context; 145 mOpenHelper = new DatabaseHelper(context); 146 } 147 setDatabaseOnCreateCallback(Callback callback)148 public void setDatabaseOnCreateCallback(Callback callback) { 149 mOpenHelper.setCallback(callback); 150 } 151 152 @VisibleForTesting(visibility = PACKAGE) writeKeyValue(String key, String value, int userId)153 public void writeKeyValue(String key, String value, int userId) { 154 writeKeyValue(mOpenHelper.getWritableDatabase(), key, value, userId); 155 } 156 157 @VisibleForTesting writeKeyValue(SQLiteDatabase db, String key, String value, int userId)158 public void writeKeyValue(SQLiteDatabase db, String key, String value, int userId) { 159 ContentValues cv = new ContentValues(); 160 cv.put(COLUMN_KEY, key); 161 cv.put(COLUMN_USERID, userId); 162 cv.put(COLUMN_VALUE, value); 163 164 db.beginTransaction(); 165 try { 166 db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?", 167 new String[] {key, Integer.toString(userId)}); 168 db.insert(TABLE, null, cv); 169 db.setTransactionSuccessful(); 170 mCache.putKeyValue(key, value, userId); 171 } finally { 172 db.endTransaction(); 173 } 174 175 } 176 177 @VisibleForTesting readKeyValue(String key, String defaultValue, int userId)178 public String readKeyValue(String key, String defaultValue, int userId) { 179 int version; 180 synchronized (mCache) { 181 if (mCache.hasKeyValue(key, userId)) { 182 return mCache.peekKeyValue(key, defaultValue, userId); 183 } 184 version = mCache.getVersion(); 185 } 186 187 Cursor cursor; 188 Object result = DEFAULT; 189 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 190 if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY, 191 COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?", 192 new String[] { Integer.toString(userId), key }, 193 null, null, null)) != null) { 194 if (cursor.moveToFirst()) { 195 result = cursor.getString(0); 196 } 197 cursor.close(); 198 } 199 mCache.putKeyValueIfUnchanged(key, result, userId, version); 200 return result == DEFAULT ? defaultValue : (String) result; 201 } 202 203 @VisibleForTesting removeKey(String key, int userId)204 public void removeKey(String key, int userId) { 205 removeKey(mOpenHelper.getWritableDatabase(), key, userId); 206 } 207 removeKey(SQLiteDatabase db, String key, int userId)208 private void removeKey(SQLiteDatabase db, String key, int userId) { 209 ContentValues cv = new ContentValues(); 210 cv.put(COLUMN_KEY, key); 211 cv.put(COLUMN_USERID, userId); 212 213 db.beginTransaction(); 214 try { 215 db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?", 216 new String[] {key, Integer.toString(userId)}); 217 db.setTransactionSuccessful(); 218 mCache.removeKey(key, userId); 219 } finally { 220 db.endTransaction(); 221 } 222 223 } 224 prefetchUser(int userId)225 public void prefetchUser(int userId) { 226 int version; 227 synchronized (mCache) { 228 if (mCache.isFetched(userId)) { 229 return; 230 } 231 mCache.setFetched(userId); 232 version = mCache.getVersion(); 233 } 234 235 Cursor cursor; 236 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 237 if ((cursor = db.query(TABLE, COLUMNS_FOR_PREFETCH, 238 COLUMN_USERID + "=?", 239 new String[] { Integer.toString(userId) }, 240 null, null, null)) != null) { 241 while (cursor.moveToNext()) { 242 String key = cursor.getString(0); 243 String value = cursor.getString(1); 244 mCache.putKeyValueIfUnchanged(key, value, userId, version); 245 } 246 cursor.close(); 247 } 248 249 // Populate cache by reading the password and pattern files. 250 readCredentialHash(userId); 251 } 252 readPasswordHashIfExists(int userId)253 private CredentialHash readPasswordHashIfExists(int userId) { 254 byte[] stored = readFile(getLockPasswordFilename(userId)); 255 if (!ArrayUtils.isEmpty(stored)) { 256 return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN); 257 } 258 return null; 259 } 260 readPatternHashIfExists(int userId)261 private CredentialHash readPatternHashIfExists(int userId) { 262 byte[] stored = readFile(getLockPatternFilename(userId)); 263 if (!ArrayUtils.isEmpty(stored)) { 264 return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PATTERN); 265 } 266 return null; 267 } 268 readCredentialHash(int userId)269 public CredentialHash readCredentialHash(int userId) { 270 CredentialHash passwordHash = readPasswordHashIfExists(userId); 271 if (passwordHash != null) { 272 return passwordHash; 273 } 274 275 CredentialHash patternHash = readPatternHashIfExists(userId); 276 if (patternHash != null) { 277 return patternHash; 278 } 279 return CredentialHash.createEmptyHash(); 280 } 281 removeChildProfileLock(int userId)282 public void removeChildProfileLock(int userId) { 283 if (DEBUG) 284 Slog.e(TAG, "Remove child profile lock for user: " + userId); 285 try { 286 deleteFile(getChildProfileLockFile(userId)); 287 } catch (Exception e) { 288 e.printStackTrace(); 289 } 290 } 291 writeChildProfileLock(int userId, byte[] lock)292 public void writeChildProfileLock(int userId, byte[] lock) { 293 writeFile(getChildProfileLockFile(userId), lock); 294 } 295 readChildProfileLock(int userId)296 public byte[] readChildProfileLock(int userId) { 297 return readFile(getChildProfileLockFile(userId)); 298 } 299 hasChildProfileLock(int userId)300 public boolean hasChildProfileLock(int userId) { 301 return hasFile(getChildProfileLockFile(userId)); 302 } 303 writeRebootEscrow(int userId, byte[] rebootEscrow)304 public void writeRebootEscrow(int userId, byte[] rebootEscrow) { 305 writeFile(getRebootEscrowFile(userId), rebootEscrow); 306 } 307 readRebootEscrow(int userId)308 public byte[] readRebootEscrow(int userId) { 309 return readFile(getRebootEscrowFile(userId)); 310 } 311 hasRebootEscrow(int userId)312 public boolean hasRebootEscrow(int userId) { 313 return hasFile(getRebootEscrowFile(userId)); 314 } 315 removeRebootEscrow(int userId)316 public void removeRebootEscrow(int userId) { 317 deleteFile(getRebootEscrowFile(userId)); 318 } 319 hasPassword(int userId)320 public boolean hasPassword(int userId) { 321 return hasFile(getLockPasswordFilename(userId)); 322 } 323 hasPattern(int userId)324 public boolean hasPattern(int userId) { 325 return hasFile(getLockPatternFilename(userId)); 326 } 327 hasFile(String name)328 private boolean hasFile(String name) { 329 byte[] contents = readFile(name); 330 return contents != null && contents.length > 0; 331 } 332 readFile(String name)333 private byte[] readFile(String name) { 334 int version; 335 synchronized (mCache) { 336 if (mCache.hasFile(name)) { 337 return mCache.peekFile(name); 338 } 339 version = mCache.getVersion(); 340 } 341 342 byte[] stored = null; 343 try (RandomAccessFile raf = new RandomAccessFile(name, "r")) { 344 stored = new byte[(int) raf.length()]; 345 raf.readFully(stored, 0, stored.length); 346 raf.close(); 347 } catch (FileNotFoundException suppressed) { 348 // readFile() is also called by hasFile() to check the existence of files, in this 349 // case FileNotFoundException is expected. 350 } catch (IOException e) { 351 Slog.e(TAG, "Cannot read file " + e); 352 } 353 mCache.putFileIfUnchanged(name, stored, version); 354 return stored; 355 } 356 fsyncDirectory(File directory)357 private void fsyncDirectory(File directory) { 358 try { 359 try (FileChannel file = FileChannel.open(directory.toPath(), 360 StandardOpenOption.READ)) { 361 file.force(true); 362 } 363 } catch (IOException e) { 364 Slog.e(TAG, "Error syncing directory: " + directory, e); 365 } 366 } 367 writeFile(String name, byte[] hash)368 private void writeFile(String name, byte[] hash) { 369 synchronized (mFileWriteLock) { 370 RandomAccessFile raf = null; 371 try { 372 // Write the hash to file, requiring each write to be synchronized to the 373 // underlying storage device immediately to avoid data loss in case of power loss. 374 // This also ensures future secdiscard operation on the file succeeds since the 375 // file would have been allocated on flash. 376 raf = new RandomAccessFile(name, "rws"); 377 // Truncate the file if pattern is null, to clear the lock 378 if (hash == null || hash.length == 0) { 379 raf.setLength(0); 380 } else { 381 raf.write(hash, 0, hash.length); 382 } 383 raf.close(); 384 fsyncDirectory((new File(name)).getAbsoluteFile().getParentFile()); 385 } catch (IOException e) { 386 Slog.e(TAG, "Error writing to file " + e); 387 } finally { 388 if (raf != null) { 389 try { 390 raf.close(); 391 } catch (IOException e) { 392 Slog.e(TAG, "Error closing file " + e); 393 } 394 } 395 } 396 mCache.putFile(name, hash); 397 } 398 } 399 deleteFile(String name)400 private void deleteFile(String name) { 401 if (DEBUG) Slog.e(TAG, "Delete file " + name); 402 synchronized (mFileWriteLock) { 403 File file = new File(name); 404 if (file.exists()) { 405 file.delete(); 406 mCache.putFile(name, null); 407 } 408 } 409 } 410 writeCredentialHash(CredentialHash hash, int userId)411 public void writeCredentialHash(CredentialHash hash, int userId) { 412 byte[] patternHash = null; 413 byte[] passwordHash = null; 414 if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN 415 || hash.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD 416 || hash.type == LockPatternUtils.CREDENTIAL_TYPE_PIN) { 417 passwordHash = hash.hash; 418 } else if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) { 419 patternHash = hash.hash; 420 } else { 421 Preconditions.checkArgument(hash.type == LockPatternUtils.CREDENTIAL_TYPE_NONE, 422 "Unknown credential type"); 423 } 424 writeFile(getLockPasswordFilename(userId), passwordHash); 425 writeFile(getLockPatternFilename(userId), patternHash); 426 } 427 428 @VisibleForTesting getLockPatternFilename(int userId)429 String getLockPatternFilename(int userId) { 430 return getLockCredentialFilePathForUser(userId, LOCK_PATTERN_FILE); 431 } 432 433 @VisibleForTesting getLockPasswordFilename(int userId)434 String getLockPasswordFilename(int userId) { 435 return getLockCredentialFilePathForUser(userId, LOCK_PASSWORD_FILE); 436 } 437 438 @VisibleForTesting getChildProfileLockFile(int userId)439 String getChildProfileLockFile(int userId) { 440 return getLockCredentialFilePathForUser(userId, CHILD_PROFILE_LOCK_FILE); 441 } 442 443 @VisibleForTesting getRebootEscrowFile(int userId)444 String getRebootEscrowFile(int userId) { 445 return getLockCredentialFilePathForUser(userId, REBOOT_ESCROW_FILE); 446 } 447 getLockCredentialFilePathForUser(int userId, String basename)448 private String getLockCredentialFilePathForUser(int userId, String basename) { 449 String dataSystemDirectory = Environment.getDataDirectory().getAbsolutePath() + 450 SYSTEM_DIRECTORY; 451 if (userId == 0) { 452 // Leave it in the same place for user 0 453 return dataSystemDirectory + basename; 454 } else { 455 return new File(Environment.getUserSystemDirectory(userId), basename).getAbsolutePath(); 456 } 457 } 458 writeSyntheticPasswordState(int userId, long handle, String name, byte[] data)459 public void writeSyntheticPasswordState(int userId, long handle, String name, byte[] data) { 460 ensureSyntheticPasswordDirectoryForUser(userId); 461 writeFile(getSynthenticPasswordStateFilePathForUser(userId, handle, name), data); 462 } 463 readSyntheticPasswordState(int userId, long handle, String name)464 public byte[] readSyntheticPasswordState(int userId, long handle, String name) { 465 return readFile(getSynthenticPasswordStateFilePathForUser(userId, handle, name)); 466 } 467 deleteSyntheticPasswordState(int userId, long handle, String name)468 public void deleteSyntheticPasswordState(int userId, long handle, String name) { 469 String path = getSynthenticPasswordStateFilePathForUser(userId, handle, name); 470 File file = new File(path); 471 if (file.exists()) { 472 try (RandomAccessFile raf = new RandomAccessFile(path, "rws")) { 473 final int fileSize = (int) raf.length(); 474 raf.write(new byte[fileSize]); 475 } catch (Exception e) { 476 Slog.w(TAG, "Failed to zeroize " + path, e); 477 } finally { 478 file.delete(); 479 } 480 mCache.putFile(path, null); 481 } 482 } 483 listSyntheticPasswordHandlesForAllUsers(String stateName)484 public Map<Integer, List<Long>> listSyntheticPasswordHandlesForAllUsers(String stateName) { 485 Map<Integer, List<Long>> result = new ArrayMap<>(); 486 final UserManager um = UserManager.get(mContext); 487 for (UserInfo user : um.getUsers(false)) { 488 result.put(user.id, listSyntheticPasswordHandlesForUser(stateName, user.id)); 489 } 490 return result; 491 } 492 listSyntheticPasswordHandlesForUser(String stateName, int userId)493 public List<Long> listSyntheticPasswordHandlesForUser(String stateName, int userId) { 494 File baseDir = getSyntheticPasswordDirectoryForUser(userId); 495 List<Long> result = new ArrayList<>(); 496 File[] files = baseDir.listFiles(); 497 if (files == null) { 498 return result; 499 } 500 for (File file : files) { 501 String[] parts = file.getName().split("\\."); 502 if (parts.length == 2 && parts[1].equals(stateName)) { 503 try { 504 result.add(Long.parseUnsignedLong(parts[0], 16)); 505 } catch (NumberFormatException e) { 506 Slog.e(TAG, "Failed to parse handle " + parts[0]); 507 } 508 } 509 } 510 return result; 511 } 512 513 @VisibleForTesting getSyntheticPasswordDirectoryForUser(int userId)514 protected File getSyntheticPasswordDirectoryForUser(int userId) { 515 return new File(Environment.getDataSystemDeDirectory(userId) ,SYNTHETIC_PASSWORD_DIRECTORY); 516 } 517 518 /** Ensure per-user directory for synthetic password state exists */ ensureSyntheticPasswordDirectoryForUser(int userId)519 private void ensureSyntheticPasswordDirectoryForUser(int userId) { 520 File baseDir = getSyntheticPasswordDirectoryForUser(userId); 521 if (!baseDir.exists()) { 522 baseDir.mkdir(); 523 } 524 } 525 526 @VisibleForTesting getSynthenticPasswordStateFilePathForUser(int userId, long handle, String name)527 protected String getSynthenticPasswordStateFilePathForUser(int userId, long handle, 528 String name) { 529 final File baseDir = getSyntheticPasswordDirectoryForUser(userId); 530 final String baseName = String.format("%016x.%s", handle, name); 531 return new File(baseDir, baseName).getAbsolutePath(); 532 } 533 removeUser(int userId)534 public void removeUser(int userId) { 535 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 536 537 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE); 538 final UserInfo parentInfo = um.getProfileParent(userId); 539 540 if (parentInfo == null) { 541 // This user owns its lock settings files - safe to delete them 542 synchronized (mFileWriteLock) { 543 deleteFilesAndRemoveCache( 544 getLockPasswordFilename(userId), 545 getLockPatternFilename(userId), 546 getRebootEscrowFile(userId)); 547 } 548 } else { 549 // Managed profile 550 removeChildProfileLock(userId); 551 } 552 553 File spStateDir = getSyntheticPasswordDirectoryForUser(userId); 554 try { 555 db.beginTransaction(); 556 db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null); 557 db.setTransactionSuccessful(); 558 mCache.removeUser(userId); 559 // The directory itself will be deleted as part of user deletion operation by the 560 // framework, so only need to purge cache here. 561 //TODO: (b/34600579) invoke secdiscardable 562 mCache.purgePath(spStateDir.getAbsolutePath()); 563 } finally { 564 db.endTransaction(); 565 } 566 } 567 deleteFilesAndRemoveCache(String... names)568 private void deleteFilesAndRemoveCache(String... names) { 569 for (String name : names) { 570 File file = new File(name); 571 if (file.exists()) { 572 file.delete(); 573 mCache.putFile(name, null); 574 } 575 } 576 } 577 setBoolean(String key, boolean value, int userId)578 public void setBoolean(String key, boolean value, int userId) { 579 setString(key, value ? "1" : "0", userId); 580 } 581 setLong(String key, long value, int userId)582 public void setLong(String key, long value, int userId) { 583 setString(key, Long.toString(value), userId); 584 } 585 setInt(String key, int value, int userId)586 public void setInt(String key, int value, int userId) { 587 setString(key, Integer.toString(value), userId); 588 } 589 setString(String key, String value, int userId)590 public void setString(String key, String value, int userId) { 591 Preconditions.checkArgument(userId != USER_FRP, "cannot store lock settings for FRP user"); 592 593 writeKeyValue(key, value, userId); 594 if (ArrayUtils.contains(SETTINGS_TO_BACKUP, key)) { 595 BackupManager.dataChanged("com.android.providers.settings"); 596 } 597 } 598 getBoolean(String key, boolean defaultValue, int userId)599 public boolean getBoolean(String key, boolean defaultValue, int userId) { 600 String value = getString(key, null, userId); 601 return TextUtils.isEmpty(value) 602 ? defaultValue : (value.equals("1") || value.equals("true")); 603 } 604 getLong(String key, long defaultValue, int userId)605 public long getLong(String key, long defaultValue, int userId) { 606 String value = getString(key, null, userId); 607 return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value); 608 } 609 getInt(String key, int defaultValue, int userId)610 public int getInt(String key, int defaultValue, int userId) { 611 String value = getString(key, null, userId); 612 return TextUtils.isEmpty(value) ? defaultValue : Integer.parseInt(value); 613 } 614 getString(String key, String defaultValue, int userId)615 public String getString(String key, String defaultValue, int userId) { 616 if (userId == USER_FRP) { 617 return null; 618 } 619 620 if (LockPatternUtils.LEGACY_LOCK_PATTERN_ENABLED.equals(key)) { 621 key = Settings.Secure.LOCK_PATTERN_ENABLED; 622 } 623 624 return readKeyValue(key, defaultValue, userId); 625 } 626 627 @VisibleForTesting closeDatabase()628 void closeDatabase() { 629 mOpenHelper.close(); 630 } 631 632 @VisibleForTesting clearCache()633 void clearCache() { 634 mCache.clear(); 635 } 636 637 @Nullable @VisibleForTesting getPersistentDataBlockManager()638 PersistentDataBlockManagerInternal getPersistentDataBlockManager() { 639 if (mPersistentDataBlockManagerInternal == null) { 640 mPersistentDataBlockManagerInternal = 641 LocalServices.getService(PersistentDataBlockManagerInternal.class); 642 } 643 return mPersistentDataBlockManagerInternal; 644 } 645 writePersistentDataBlock(int persistentType, int userId, int qualityForUi, byte[] payload)646 public void writePersistentDataBlock(int persistentType, int userId, int qualityForUi, 647 byte[] payload) { 648 PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlockManager(); 649 if (persistentDataBlock == null) { 650 return; 651 } 652 persistentDataBlock.setFrpCredentialHandle(PersistentData.toBytes( 653 persistentType, userId, qualityForUi, payload)); 654 } 655 readPersistentDataBlock()656 public PersistentData readPersistentDataBlock() { 657 PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlockManager(); 658 if (persistentDataBlock == null) { 659 return PersistentData.NONE; 660 } 661 try { 662 return PersistentData.fromBytes(persistentDataBlock.getFrpCredentialHandle()); 663 } catch (IllegalStateException e) { 664 Slog.e(TAG, "Error reading persistent data block", e); 665 return PersistentData.NONE; 666 } 667 } 668 669 public static class PersistentData { 670 static final byte VERSION_1 = 1; 671 static final int VERSION_1_HEADER_SIZE = 1 + 1 + 4 + 4; 672 673 public static final int TYPE_NONE = 0; 674 public static final int TYPE_SP = 1; 675 public static final int TYPE_SP_WEAVER = 2; 676 677 public static final PersistentData NONE = new PersistentData(TYPE_NONE, 678 UserHandle.USER_NULL, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, null); 679 680 final int type; 681 final int userId; 682 final int qualityForUi; 683 final byte[] payload; 684 PersistentData(int type, int userId, int qualityForUi, byte[] payload)685 private PersistentData(int type, int userId, int qualityForUi, byte[] payload) { 686 this.type = type; 687 this.userId = userId; 688 this.qualityForUi = qualityForUi; 689 this.payload = payload; 690 } 691 fromBytes(byte[] frpData)692 public static PersistentData fromBytes(byte[] frpData) { 693 if (frpData == null || frpData.length == 0) { 694 return NONE; 695 } 696 697 DataInputStream is = new DataInputStream(new ByteArrayInputStream(frpData)); 698 try { 699 byte version = is.readByte(); 700 if (version == PersistentData.VERSION_1) { 701 int type = is.readByte() & 0xFF; 702 int userId = is.readInt(); 703 int qualityForUi = is.readInt(); 704 byte[] payload = new byte[frpData.length - VERSION_1_HEADER_SIZE]; 705 System.arraycopy(frpData, VERSION_1_HEADER_SIZE, payload, 0, payload.length); 706 return new PersistentData(type, userId, qualityForUi, payload); 707 } else { 708 Slog.wtf(TAG, "Unknown PersistentData version code: " + version); 709 return NONE; 710 } 711 } catch (IOException e) { 712 Slog.wtf(TAG, "Could not parse PersistentData", e); 713 return NONE; 714 } 715 } 716 toBytes(int persistentType, int userId, int qualityForUi, byte[] payload)717 public static byte[] toBytes(int persistentType, int userId, int qualityForUi, 718 byte[] payload) { 719 if (persistentType == PersistentData.TYPE_NONE) { 720 Preconditions.checkArgument(payload == null, 721 "TYPE_NONE must have empty payload"); 722 return null; 723 } 724 Preconditions.checkArgument(payload != null && payload.length > 0, 725 "empty payload must only be used with TYPE_NONE"); 726 727 ByteArrayOutputStream os = new ByteArrayOutputStream( 728 VERSION_1_HEADER_SIZE + payload.length); 729 DataOutputStream dos = new DataOutputStream(os); 730 try { 731 dos.writeByte(PersistentData.VERSION_1); 732 dos.writeByte(persistentType); 733 dos.writeInt(userId); 734 dos.writeInt(qualityForUi); 735 dos.write(payload); 736 } catch (IOException e) { 737 throw new IllegalStateException("ByteArrayOutputStream cannot throw IOException"); 738 } 739 return os.toByteArray(); 740 } 741 } 742 743 public interface Callback { initialize(SQLiteDatabase db)744 void initialize(SQLiteDatabase db); 745 } 746 dump(IndentingPrintWriter pw)747 public void dump(IndentingPrintWriter pw) { 748 final UserManager um = UserManager.get(mContext); 749 for (UserInfo user : um.getUsers(false)) { 750 File userPath = getSyntheticPasswordDirectoryForUser(user.id); 751 pw.println(String.format("User %d [%s]:", user.id, userPath.getAbsolutePath())); 752 pw.increaseIndent(); 753 File[] files = userPath.listFiles(); 754 if (files != null) { 755 for (File file : files) { 756 pw.println(String.format("%4d %s %s", file.length(), 757 LockSettingsService.timestampToString(file.lastModified()), 758 file.getName())); 759 } 760 } else { 761 pw.println("[Not found]"); 762 } 763 pw.decreaseIndent(); 764 } 765 } 766 767 static class DatabaseHelper extends SQLiteOpenHelper { 768 private static final String TAG = "LockSettingsDB"; 769 private static final String DATABASE_NAME = "locksettings.db"; 770 771 private static final int DATABASE_VERSION = 2; 772 private static final int IDLE_CONNECTION_TIMEOUT_MS = 30000; 773 774 private Callback mCallback; 775 DatabaseHelper(Context context)776 public DatabaseHelper(Context context) { 777 super(context, DATABASE_NAME, null, DATABASE_VERSION); 778 setWriteAheadLoggingEnabled(true); 779 // Memory optimization - close idle connections after 30s of inactivity 780 setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS); 781 } 782 setCallback(Callback callback)783 public void setCallback(Callback callback) { 784 mCallback = callback; 785 } 786 createTable(SQLiteDatabase db)787 private void createTable(SQLiteDatabase db) { 788 db.execSQL("CREATE TABLE " + TABLE + " (" + 789 "_id INTEGER PRIMARY KEY AUTOINCREMENT," + 790 COLUMN_KEY + " TEXT," + 791 COLUMN_USERID + " INTEGER," + 792 COLUMN_VALUE + " TEXT" + 793 ");"); 794 } 795 796 @Override onCreate(SQLiteDatabase db)797 public void onCreate(SQLiteDatabase db) { 798 createTable(db); 799 if (mCallback != null) { 800 mCallback.initialize(db); 801 } 802 } 803 804 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion)805 public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { 806 int upgradeVersion = oldVersion; 807 if (upgradeVersion == 1) { 808 // Previously migrated lock screen widget settings. Now defunct. 809 upgradeVersion = 2; 810 } 811 812 if (upgradeVersion != DATABASE_VERSION) { 813 Slog.w(TAG, "Failed to upgrade database!"); 814 } 815 } 816 } 817 818 /** 819 * Cache consistency model: 820 * - Writes to storage write directly to the cache, but this MUST happen within the atomic 821 * section either provided by the database transaction or mWriteLock, such that writes to the 822 * cache and writes to the backing storage are guaranteed to occur in the same order 823 * 824 * - Reads can populate the cache, but because they are no strong ordering guarantees with 825 * respect to writes this precaution is taken: 826 * - The cache is assigned a version number that increases every time the cache is modified. 827 * Reads from backing storage can only populate the cache if the backing storage 828 * has not changed since the load operation has begun. 829 * This guarantees that no read operation can shadow a write to the cache that happens 830 * after it had begun. 831 */ 832 private static class Cache { 833 private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>(); 834 private final CacheKey mCacheKey = new CacheKey(); 835 private int mVersion = 0; 836 peekKeyValue(String key, String defaultValue, int userId)837 String peekKeyValue(String key, String defaultValue, int userId) { 838 Object cached = peek(CacheKey.TYPE_KEY_VALUE, key, userId); 839 return cached == DEFAULT ? defaultValue : (String) cached; 840 } 841 hasKeyValue(String key, int userId)842 boolean hasKeyValue(String key, int userId) { 843 return contains(CacheKey.TYPE_KEY_VALUE, key, userId); 844 } 845 putKeyValue(String key, String value, int userId)846 void putKeyValue(String key, String value, int userId) { 847 put(CacheKey.TYPE_KEY_VALUE, key, value, userId); 848 } 849 putKeyValueIfUnchanged(String key, Object value, int userId, int version)850 void putKeyValueIfUnchanged(String key, Object value, int userId, int version) { 851 putIfUnchanged(CacheKey.TYPE_KEY_VALUE, key, value, userId, version); 852 } 853 removeKey(String key, int userId)854 void removeKey(String key, int userId) { 855 remove(CacheKey.TYPE_KEY_VALUE, key, userId); 856 } 857 peekFile(String fileName)858 byte[] peekFile(String fileName) { 859 return copyOf((byte[]) peek(CacheKey.TYPE_FILE, fileName, -1 /* userId */)); 860 } 861 hasFile(String fileName)862 boolean hasFile(String fileName) { 863 return contains(CacheKey.TYPE_FILE, fileName, -1 /* userId */); 864 } 865 putFile(String key, byte[] value)866 void putFile(String key, byte[] value) { 867 put(CacheKey.TYPE_FILE, key, copyOf(value), -1 /* userId */); 868 } 869 putFileIfUnchanged(String key, byte[] value, int version)870 void putFileIfUnchanged(String key, byte[] value, int version) { 871 putIfUnchanged(CacheKey.TYPE_FILE, key, copyOf(value), -1 /* userId */, version); 872 } 873 setFetched(int userId)874 void setFetched(int userId) { 875 put(CacheKey.TYPE_FETCHED, "isFetched", "true", userId); 876 } 877 isFetched(int userId)878 boolean isFetched(int userId) { 879 return contains(CacheKey.TYPE_FETCHED, "", userId); 880 } 881 remove(int type, String key, int userId)882 private synchronized void remove(int type, String key, int userId) { 883 mCache.remove(mCacheKey.set(type, key, userId)); 884 } 885 put(int type, String key, Object value, int userId)886 private synchronized void put(int type, String key, Object value, int userId) { 887 // Create a new CachKey here because it may be saved in the map if the key is absent. 888 mCache.put(new CacheKey().set(type, key, userId), value); 889 mVersion++; 890 } 891 putIfUnchanged(int type, String key, Object value, int userId, int version)892 private synchronized void putIfUnchanged(int type, String key, Object value, int userId, 893 int version) { 894 if (!contains(type, key, userId) && mVersion == version) { 895 put(type, key, value, userId); 896 } 897 } 898 contains(int type, String key, int userId)899 private synchronized boolean contains(int type, String key, int userId) { 900 return mCache.containsKey(mCacheKey.set(type, key, userId)); 901 } 902 peek(int type, String key, int userId)903 private synchronized Object peek(int type, String key, int userId) { 904 return mCache.get(mCacheKey.set(type, key, userId)); 905 } 906 getVersion()907 private synchronized int getVersion() { 908 return mVersion; 909 } 910 removeUser(int userId)911 synchronized void removeUser(int userId) { 912 for (int i = mCache.size() - 1; i >= 0; i--) { 913 if (mCache.keyAt(i).userId == userId) { 914 mCache.removeAt(i); 915 } 916 } 917 918 // Make sure in-flight loads can't write to cache. 919 mVersion++; 920 } 921 copyOf(byte[] data)922 private byte[] copyOf(byte[] data) { 923 return data != null ? Arrays.copyOf(data, data.length) : null; 924 } 925 purgePath(String path)926 synchronized void purgePath(String path) { 927 for (int i = mCache.size() - 1; i >= 0; i--) { 928 CacheKey entry = mCache.keyAt(i); 929 if (entry.type == CacheKey.TYPE_FILE && entry.key.startsWith(path)) { 930 mCache.removeAt(i); 931 } 932 } 933 mVersion++; 934 } 935 clear()936 synchronized void clear() { 937 mCache.clear(); 938 mVersion++; 939 } 940 941 private static final class CacheKey { 942 static final int TYPE_KEY_VALUE = 0; 943 static final int TYPE_FILE = 1; 944 static final int TYPE_FETCHED = 2; 945 946 String key; 947 int userId; 948 int type; 949 set(int type, String key, int userId)950 public CacheKey set(int type, String key, int userId) { 951 this.type = type; 952 this.key = key; 953 this.userId = userId; 954 return this; 955 } 956 957 @Override equals(Object obj)958 public boolean equals(Object obj) { 959 if (!(obj instanceof CacheKey)) 960 return false; 961 CacheKey o = (CacheKey) obj; 962 return userId == o.userId && type == o.type && key.equals(o.key); 963 } 964 965 @Override hashCode()966 public int hashCode() { 967 return key.hashCode() ^ userId ^ type; 968 } 969 } 970 } 971 972 } 973