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; 18 19 import com.android.internal.annotations.VisibleForTesting; 20 21 import android.content.ContentValues; 22 import android.content.Context; 23 import android.content.pm.UserInfo; 24 import android.database.Cursor; 25 import android.database.sqlite.SQLiteDatabase; 26 import android.database.sqlite.SQLiteOpenHelper; 27 import android.os.Environment; 28 import android.os.UserManager; 29 import android.util.ArrayMap; 30 import android.util.Log; 31 import android.util.Slog; 32 33 import java.io.File; 34 import java.io.IOException; 35 import java.io.RandomAccessFile; 36 37 import static android.content.Context.USER_SERVICE; 38 39 /** 40 * Storage for the lock settings service. 41 */ 42 class LockSettingsStorage { 43 44 private static final String TAG = "LockSettingsStorage"; 45 private static final String TABLE = "locksettings"; 46 47 private static final String COLUMN_KEY = "name"; 48 private static final String COLUMN_USERID = "user"; 49 private static final String COLUMN_VALUE = "value"; 50 51 private static final String[] COLUMNS_FOR_QUERY = { 52 COLUMN_VALUE 53 }; 54 private static final String[] COLUMNS_FOR_PREFETCH = { 55 COLUMN_KEY, COLUMN_VALUE 56 }; 57 58 private static final String SYSTEM_DIRECTORY = "/system/"; 59 private static final String LOCK_PATTERN_FILE = "gatekeeper.pattern.key"; 60 private static final String BASE_ZERO_LOCK_PATTERN_FILE = "gatekeeper.gesture.key"; 61 private static final String LEGACY_LOCK_PATTERN_FILE = "gesture.key"; 62 private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key"; 63 private static final String LEGACY_LOCK_PASSWORD_FILE = "password.key"; 64 65 private static final Object DEFAULT = new Object(); 66 67 private final DatabaseHelper mOpenHelper; 68 private final Context mContext; 69 private final Cache mCache = new Cache(); 70 private final Object mFileWriteLock = new Object(); 71 72 private int mStoredCredentialType; 73 74 class CredentialHash { 75 static final int TYPE_NONE = -1; 76 static final int TYPE_PATTERN = 1; 77 static final int TYPE_PASSWORD = 2; 78 79 static final int VERSION_LEGACY = 0; 80 static final int VERSION_GATEKEEPER = 1; 81 82 CredentialHash(byte[] hash, int version) { 83 this.hash = hash; 84 this.version = version; 85 this.isBaseZeroPattern = false; 86 } 87 88 CredentialHash(byte[] hash, boolean isBaseZeroPattern) { 89 this.hash = hash; 90 this.version = VERSION_GATEKEEPER; 91 this.isBaseZeroPattern = isBaseZeroPattern; 92 } 93 94 byte[] hash; 95 int version; 96 boolean isBaseZeroPattern; 97 } 98 99 public LockSettingsStorage(Context context, Callback callback) { 100 mContext = context; 101 mOpenHelper = new DatabaseHelper(context, callback); 102 } 103 104 public void writeKeyValue(String key, String value, int userId) { 105 writeKeyValue(mOpenHelper.getWritableDatabase(), key, value, userId); 106 } 107 108 public void writeKeyValue(SQLiteDatabase db, String key, String value, int userId) { 109 ContentValues cv = new ContentValues(); 110 cv.put(COLUMN_KEY, key); 111 cv.put(COLUMN_USERID, userId); 112 cv.put(COLUMN_VALUE, value); 113 114 db.beginTransaction(); 115 try { 116 db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?", 117 new String[] {key, Integer.toString(userId)}); 118 db.insert(TABLE, null, cv); 119 db.setTransactionSuccessful(); 120 mCache.putKeyValue(key, value, userId); 121 } finally { 122 db.endTransaction(); 123 } 124 125 } 126 127 public String readKeyValue(String key, String defaultValue, int userId) { 128 int version; 129 synchronized (mCache) { 130 if (mCache.hasKeyValue(key, userId)) { 131 return mCache.peekKeyValue(key, defaultValue, userId); 132 } 133 version = mCache.getVersion(); 134 } 135 136 Cursor cursor; 137 Object result = DEFAULT; 138 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 139 if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY, 140 COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?", 141 new String[] { Integer.toString(userId), key }, 142 null, null, null)) != null) { 143 if (cursor.moveToFirst()) { 144 result = cursor.getString(0); 145 } 146 cursor.close(); 147 } 148 mCache.putKeyValueIfUnchanged(key, result, userId, version); 149 return result == DEFAULT ? defaultValue : (String) result; 150 } 151 152 public void prefetchUser(int userId) { 153 int version; 154 synchronized (mCache) { 155 if (mCache.isFetched(userId)) { 156 return; 157 } 158 mCache.setFetched(userId); 159 version = mCache.getVersion(); 160 } 161 162 Cursor cursor; 163 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 164 if ((cursor = db.query(TABLE, COLUMNS_FOR_PREFETCH, 165 COLUMN_USERID + "=?", 166 new String[] { Integer.toString(userId) }, 167 null, null, null)) != null) { 168 while (cursor.moveToNext()) { 169 String key = cursor.getString(0); 170 String value = cursor.getString(1); 171 mCache.putKeyValueIfUnchanged(key, value, userId, version); 172 } 173 cursor.close(); 174 } 175 176 // Populate cache by reading the password and pattern files. 177 readPasswordHash(userId); 178 readPatternHash(userId); 179 } 180 181 public int getStoredCredentialType(int userId) { 182 if (mStoredCredentialType != 0) { 183 return mStoredCredentialType; 184 } 185 186 CredentialHash pattern = readPatternHash(userId); 187 if (pattern == null) { 188 if (readPasswordHash(userId) != null) { 189 mStoredCredentialType = CredentialHash.TYPE_PASSWORD; 190 } else { 191 mStoredCredentialType = CredentialHash.TYPE_NONE; 192 } 193 } else { 194 CredentialHash password = readPasswordHash(userId); 195 if (password != null) { 196 // Both will never be GateKeeper 197 if (password.version == CredentialHash.VERSION_GATEKEEPER) { 198 mStoredCredentialType = CredentialHash.TYPE_PASSWORD; 199 } else { 200 mStoredCredentialType = CredentialHash.TYPE_PATTERN; 201 } 202 } else { 203 mStoredCredentialType = CredentialHash.TYPE_PATTERN; 204 } 205 } 206 207 return mStoredCredentialType; 208 } 209 210 211 public CredentialHash readPasswordHash(int userId) { 212 byte[] stored = readFile(getLockPasswordFilename(userId)); 213 if (stored != null && stored.length > 0) { 214 return new CredentialHash(stored, CredentialHash.VERSION_GATEKEEPER); 215 } 216 217 stored = readFile(getLegacyLockPasswordFilename(userId)); 218 if (stored != null && stored.length > 0) { 219 return new CredentialHash(stored, CredentialHash.VERSION_LEGACY); 220 } 221 222 return null; 223 } 224 225 public CredentialHash readPatternHash(int userId) { 226 byte[] stored = readFile(getLockPatternFilename(userId)); 227 if (stored != null && stored.length > 0) { 228 return new CredentialHash(stored, CredentialHash.VERSION_GATEKEEPER); 229 } 230 231 stored = readFile(getBaseZeroLockPatternFilename(userId)); 232 if (stored != null && stored.length > 0) { 233 return new CredentialHash(stored, true); 234 } 235 236 stored = readFile(getLegacyLockPatternFilename(userId)); 237 if (stored != null && stored.length > 0) { 238 return new CredentialHash(stored, CredentialHash.VERSION_LEGACY); 239 } 240 241 return null; 242 } 243 244 245 public boolean hasPassword(int userId) { 246 return hasFile(getLockPasswordFilename(userId)) || 247 hasFile(getLegacyLockPasswordFilename(userId)); 248 } 249 250 public boolean hasPattern(int userId) { 251 return hasFile(getLockPatternFilename(userId)) || 252 hasFile(getBaseZeroLockPatternFilename(userId)) || 253 hasFile(getLegacyLockPatternFilename(userId)); 254 } 255 256 private boolean hasFile(String name) { 257 byte[] contents = readFile(name); 258 return contents != null && contents.length > 0; 259 } 260 261 private byte[] readFile(String name) { 262 int version; 263 synchronized (mCache) { 264 if (mCache.hasFile(name)) { 265 return mCache.peekFile(name); 266 } 267 version = mCache.getVersion(); 268 } 269 270 RandomAccessFile raf = null; 271 byte[] stored = null; 272 try { 273 raf = new RandomAccessFile(name, "r"); 274 stored = new byte[(int) raf.length()]; 275 raf.readFully(stored, 0, stored.length); 276 raf.close(); 277 } catch (IOException e) { 278 Slog.e(TAG, "Cannot read file " + e); 279 } finally { 280 if (raf != null) { 281 try { 282 raf.close(); 283 } catch (IOException e) { 284 Slog.e(TAG, "Error closing file " + e); 285 } 286 } 287 } 288 mCache.putFileIfUnchanged(name, stored, version); 289 return stored; 290 } 291 292 private void writeFile(String name, byte[] hash) { 293 synchronized (mFileWriteLock) { 294 RandomAccessFile raf = null; 295 try { 296 // Write the hash to file 297 raf = new RandomAccessFile(name, "rw"); 298 // Truncate the file if pattern is null, to clear the lock 299 if (hash == null || hash.length == 0) { 300 raf.setLength(0); 301 } else { 302 raf.write(hash, 0, hash.length); 303 } 304 raf.close(); 305 } catch (IOException e) { 306 Slog.e(TAG, "Error writing to file " + e); 307 } finally { 308 if (raf != null) { 309 try { 310 raf.close(); 311 } catch (IOException e) { 312 Slog.e(TAG, "Error closing file " + e); 313 } 314 } 315 } 316 mCache.putFile(name, hash); 317 } 318 } 319 320 private void deleteFile(String name) { 321 File f = new File(name); 322 if (f != null) { 323 f.delete(); 324 } 325 } 326 327 public void writePatternHash(byte[] hash, int userId) { 328 mStoredCredentialType = hash == null 329 ? CredentialHash.TYPE_NONE 330 : CredentialHash.TYPE_PATTERN; 331 writeFile(getLockPatternFilename(userId), hash); 332 clearPasswordHash(userId); 333 } 334 335 private void clearPatternHash(int userId) { 336 writeFile(getLockPatternFilename(userId), null); 337 } 338 339 public void writePasswordHash(byte[] hash, int userId) { 340 mStoredCredentialType = hash == null 341 ? CredentialHash.TYPE_NONE 342 : CredentialHash.TYPE_PASSWORD; 343 writeFile(getLockPasswordFilename(userId), hash); 344 clearPatternHash(userId); 345 } 346 347 private void clearPasswordHash(int userId) { 348 writeFile(getLockPasswordFilename(userId), null); 349 } 350 351 @VisibleForTesting 352 String getLockPatternFilename(int userId) { 353 return getLockCredentialFilePathForUser(userId, LOCK_PATTERN_FILE); 354 } 355 356 @VisibleForTesting 357 String getLockPasswordFilename(int userId) { 358 return getLockCredentialFilePathForUser(userId, LOCK_PASSWORD_FILE); 359 } 360 361 @VisibleForTesting 362 String getLegacyLockPatternFilename(int userId) { 363 return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PATTERN_FILE); 364 } 365 366 @VisibleForTesting 367 String getLegacyLockPasswordFilename(int userId) { 368 return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PASSWORD_FILE); 369 } 370 371 private String getBaseZeroLockPatternFilename(int userId) { 372 return getLockCredentialFilePathForUser(userId, BASE_ZERO_LOCK_PATTERN_FILE); 373 } 374 375 private String getLockCredentialFilePathForUser(int userId, String basename) { 376 userId = getUserParentOrSelfId(userId); 377 String dataSystemDirectory = 378 android.os.Environment.getDataDirectory().getAbsolutePath() + 379 SYSTEM_DIRECTORY; 380 if (userId == 0) { 381 // Leave it in the same place for user 0 382 return dataSystemDirectory + basename; 383 } else { 384 return new File(Environment.getUserSystemDirectory(userId), basename).getAbsolutePath(); 385 } 386 } 387 388 private int getUserParentOrSelfId(int userId) { 389 if (userId != 0) { 390 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE); 391 final UserInfo pi = um.getProfileParent(userId); 392 if (pi != null) { 393 return pi.id; 394 } 395 } 396 return userId; 397 } 398 399 public void removeUser(int userId) { 400 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 401 402 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE); 403 final UserInfo parentInfo = um.getProfileParent(userId); 404 405 if (parentInfo == null) { 406 // This user owns its lock settings files - safe to delete them 407 synchronized (mFileWriteLock) { 408 String name = getLockPasswordFilename(userId); 409 File file = new File(name); 410 if (file.exists()) { 411 file.delete(); 412 mCache.putFile(name, null); 413 } 414 name = getLockPatternFilename(userId); 415 file = new File(name); 416 if (file.exists()) { 417 file.delete(); 418 mCache.putFile(name, null); 419 } 420 } 421 } 422 423 try { 424 db.beginTransaction(); 425 db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null); 426 db.setTransactionSuccessful(); 427 mCache.removeUser(userId); 428 } finally { 429 db.endTransaction(); 430 } 431 } 432 433 @VisibleForTesting 434 void closeDatabase() { 435 mOpenHelper.close(); 436 } 437 438 @VisibleForTesting 439 void clearCache() { 440 mCache.clear(); 441 } 442 443 public interface Callback { 444 void initialize(SQLiteDatabase db); 445 } 446 447 class DatabaseHelper extends SQLiteOpenHelper { 448 private static final String TAG = "LockSettingsDB"; 449 private static final String DATABASE_NAME = "locksettings.db"; 450 451 private static final int DATABASE_VERSION = 2; 452 453 private final Callback mCallback; 454 455 public DatabaseHelper(Context context, Callback callback) { 456 super(context, DATABASE_NAME, null, DATABASE_VERSION); 457 setWriteAheadLoggingEnabled(true); 458 mCallback = callback; 459 } 460 461 private void createTable(SQLiteDatabase db) { 462 db.execSQL("CREATE TABLE " + TABLE + " (" + 463 "_id INTEGER PRIMARY KEY AUTOINCREMENT," + 464 COLUMN_KEY + " TEXT," + 465 COLUMN_USERID + " INTEGER," + 466 COLUMN_VALUE + " TEXT" + 467 ");"); 468 } 469 470 @Override 471 public void onCreate(SQLiteDatabase db) { 472 createTable(db); 473 mCallback.initialize(db); 474 } 475 476 @Override 477 public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { 478 int upgradeVersion = oldVersion; 479 if (upgradeVersion == 1) { 480 // Previously migrated lock screen widget settings. Now defunct. 481 upgradeVersion = 2; 482 } 483 484 if (upgradeVersion != DATABASE_VERSION) { 485 Log.w(TAG, "Failed to upgrade database!"); 486 } 487 } 488 } 489 490 /** 491 * Cache consistency model: 492 * - Writes to storage write directly to the cache, but this MUST happen within the atomic 493 * section either provided by the database transaction or mWriteLock, such that writes to the 494 * cache and writes to the backing storage are guaranteed to occur in the same order 495 * 496 * - Reads can populate the cache, but because they are no strong ordering guarantees with 497 * respect to writes this precaution is taken: 498 * - The cache is assigned a version number that increases every time the cache is modified. 499 * Reads from backing storage can only populate the cache if the backing storage 500 * has not changed since the load operation has begun. 501 * This guarantees that no read operation can shadow a write to the cache that happens 502 * after it had begun. 503 */ 504 private static class Cache { 505 private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>(); 506 private final CacheKey mCacheKey = new CacheKey(); 507 private int mVersion = 0; 508 509 String peekKeyValue(String key, String defaultValue, int userId) { 510 Object cached = peek(CacheKey.TYPE_KEY_VALUE, key, userId); 511 return cached == DEFAULT ? defaultValue : (String) cached; 512 } 513 514 boolean hasKeyValue(String key, int userId) { 515 return contains(CacheKey.TYPE_KEY_VALUE, key, userId); 516 } 517 518 void putKeyValue(String key, String value, int userId) { 519 put(CacheKey.TYPE_KEY_VALUE, key, value, userId); 520 } 521 522 void putKeyValueIfUnchanged(String key, Object value, int userId, int version) { 523 putIfUnchanged(CacheKey.TYPE_KEY_VALUE, key, value, userId, version); 524 } 525 526 byte[] peekFile(String fileName) { 527 return (byte[]) peek(CacheKey.TYPE_FILE, fileName, -1 /* userId */); 528 } 529 530 boolean hasFile(String fileName) { 531 return contains(CacheKey.TYPE_FILE, fileName, -1 /* userId */); 532 } 533 534 void putFile(String key, byte[] value) { 535 put(CacheKey.TYPE_FILE, key, value, -1 /* userId */); 536 } 537 538 void putFileIfUnchanged(String key, byte[] value, int version) { 539 putIfUnchanged(CacheKey.TYPE_FILE, key, value, -1 /* userId */, version); 540 } 541 542 void setFetched(int userId) { 543 put(CacheKey.TYPE_FETCHED, "isFetched", "true", userId); 544 } 545 546 boolean isFetched(int userId) { 547 return contains(CacheKey.TYPE_FETCHED, "", userId); 548 } 549 550 551 private synchronized void put(int type, String key, Object value, int userId) { 552 // Create a new CachKey here because it may be saved in the map if the key is absent. 553 mCache.put(new CacheKey().set(type, key, userId), value); 554 mVersion++; 555 } 556 557 private synchronized void putIfUnchanged(int type, String key, Object value, int userId, 558 int version) { 559 if (!contains(type, key, userId) && mVersion == version) { 560 put(type, key, value, userId); 561 } 562 } 563 564 private synchronized boolean contains(int type, String key, int userId) { 565 return mCache.containsKey(mCacheKey.set(type, key, userId)); 566 } 567 568 private synchronized Object peek(int type, String key, int userId) { 569 return mCache.get(mCacheKey.set(type, key, userId)); 570 } 571 572 private synchronized int getVersion() { 573 return mVersion; 574 } 575 576 synchronized void removeUser(int userId) { 577 for (int i = mCache.size() - 1; i >= 0; i--) { 578 if (mCache.keyAt(i).userId == userId) { 579 mCache.removeAt(i); 580 } 581 } 582 583 // Make sure in-flight loads can't write to cache. 584 mVersion++; 585 } 586 587 synchronized void clear() { 588 mCache.clear(); 589 mVersion++; 590 } 591 592 private static final class CacheKey { 593 static final int TYPE_KEY_VALUE = 0; 594 static final int TYPE_FILE = 1; 595 static final int TYPE_FETCHED = 2; 596 597 String key; 598 int userId; 599 int type; 600 601 public CacheKey set(int type, String key, int userId) { 602 this.type = type; 603 this.key = key; 604 this.userId = userId; 605 return this; 606 } 607 608 @Override 609 public boolean equals(Object obj) { 610 if (!(obj instanceof CacheKey)) 611 return false; 612 CacheKey o = (CacheKey) obj; 613 return userId == o.userId && type == o.type && key.equals(o.key); 614 } 615 616 @Override 617 public int hashCode() { 618 return key.hashCode() ^ userId ^ type; 619 } 620 } 621 } 622 } 623