LockSettingsService.java revision 987672d2621fa5278dbf48cbef46c50d3fafe4c1
1/* 2 * Copyright (C) 2012 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 17package com.android.server; 18 19import android.content.ContentResolver; 20import android.content.ContentValues; 21import android.content.Context; 22import android.content.pm.PackageManager; 23import android.content.pm.UserInfo; 24 25import static android.content.Context.USER_SERVICE; 26import static android.Manifest.permission.READ_PROFILE; 27import android.database.Cursor; 28import android.database.sqlite.SQLiteDatabase; 29import android.database.sqlite.SQLiteOpenHelper; 30import android.database.sqlite.SQLiteStatement; 31import android.os.Binder; 32import android.os.Environment; 33import android.os.IBinder; 34import android.os.RemoteException; 35import android.os.storage.IMountService; 36import android.os.ServiceManager; 37import android.os.storage.StorageManager; 38import android.os.SystemProperties; 39import android.os.UserHandle; 40import android.os.UserManager; 41import android.provider.Settings; 42import android.provider.Settings.Secure; 43import android.provider.Settings.SettingNotFoundException; 44import android.security.KeyStore; 45import android.text.TextUtils; 46import android.util.Log; 47import android.util.Slog; 48 49import com.android.internal.widget.ILockSettings; 50import com.android.internal.widget.ILockSettingsObserver; 51import com.android.internal.widget.LockPatternUtils; 52 53import java.io.File; 54import java.io.FileNotFoundException; 55import java.io.IOException; 56import java.io.RandomAccessFile; 57import java.util.ArrayList; 58import java.util.Arrays; 59import java.util.List; 60 61/** 62 * Keeps the lock pattern/password data and related settings for each user. 63 * Used by LockPatternUtils. Needs to be a service because Settings app also needs 64 * to be able to save lockscreen information for secondary users. 65 * @hide 66 */ 67public class LockSettingsService extends ILockSettings.Stub { 68 69 private static final String PERMISSION = "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"; 70 71 private static final String SYSTEM_DEBUGGABLE = "ro.debuggable"; 72 73 private final DatabaseHelper mOpenHelper; 74 private static final String TAG = "LockSettingsService"; 75 76 private static final String TABLE = "locksettings"; 77 private static final String COLUMN_KEY = "name"; 78 private static final String COLUMN_USERID = "user"; 79 private static final String COLUMN_VALUE = "value"; 80 81 private static final String[] COLUMNS_FOR_QUERY = { 82 COLUMN_VALUE 83 }; 84 85 private static final String SYSTEM_DIRECTORY = "/system/"; 86 private static final String LOCK_PATTERN_FILE = "gesture.key"; 87 private static final String LOCK_PASSWORD_FILE = "password.key"; 88 89 private final Context mContext; 90 private LockPatternUtils mLockPatternUtils; 91 private boolean mFirstCallToVold; 92 93 private final ArrayList<LockSettingsObserver> mObservers = new ArrayList<>(); 94 95 public LockSettingsService(Context context) { 96 mContext = context; 97 // Open the database 98 mOpenHelper = new DatabaseHelper(mContext); 99 100 mLockPatternUtils = new LockPatternUtils(context); 101 mFirstCallToVold = true; 102 } 103 104 public void systemReady() { 105 migrateOldData(); 106 } 107 108 private void migrateOldData() { 109 try { 110 // These Settings moved before multi-user was enabled, so we only have to do it for the 111 // root user. 112 if (getString("migrated", null, 0) == null) { 113 final ContentResolver cr = mContext.getContentResolver(); 114 for (String validSetting : VALID_SETTINGS) { 115 String value = Settings.Secure.getString(cr, validSetting); 116 if (value != null) { 117 setString(validSetting, value, 0); 118 } 119 } 120 // No need to move the password / pattern files. They're already in the right place. 121 setString("migrated", "true", 0); 122 Slog.i(TAG, "Migrated lock settings to new location"); 123 } 124 125 // These Settings changed after multi-user was enabled, hence need to be moved per user. 126 if (getString("migrated_user_specific", null, 0) == null) { 127 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE); 128 final ContentResolver cr = mContext.getContentResolver(); 129 List<UserInfo> users = um.getUsers(); 130 for (int user = 0; user < users.size(); user++) { 131 // Migrate owner info 132 final int userId = users.get(user).id; 133 final String OWNER_INFO = Secure.LOCK_SCREEN_OWNER_INFO; 134 String ownerInfo = Settings.Secure.getStringForUser(cr, OWNER_INFO, userId); 135 if (ownerInfo != null) { 136 setString(OWNER_INFO, ownerInfo, userId); 137 Settings.Secure.putStringForUser(cr, ownerInfo, "", userId); 138 } 139 140 // Migrate owner info enabled. Note there was a bug where older platforms only 141 // stored this value if the checkbox was toggled at least once. The code detects 142 // this case by handling the exception. 143 final String OWNER_INFO_ENABLED = Secure.LOCK_SCREEN_OWNER_INFO_ENABLED; 144 boolean enabled; 145 try { 146 int ivalue = Settings.Secure.getIntForUser(cr, OWNER_INFO_ENABLED, userId); 147 enabled = ivalue != 0; 148 setLong(OWNER_INFO_ENABLED, enabled ? 1 : 0, userId); 149 } catch (SettingNotFoundException e) { 150 // Setting was never stored. Store it if the string is not empty. 151 if (!TextUtils.isEmpty(ownerInfo)) { 152 setLong(OWNER_INFO_ENABLED, 1, userId); 153 } 154 } 155 Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId); 156 } 157 // No need to move the password / pattern files. They're already in the right place. 158 setString("migrated_user_specific", "true", 0); 159 Slog.i(TAG, "Migrated per-user lock settings to new location"); 160 } 161 } catch (RemoteException re) { 162 Slog.e(TAG, "Unable to migrate old data", re); 163 } 164 } 165 166 private final void checkWritePermission(int userId) { 167 mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsWrite"); 168 } 169 170 private final void checkPasswordReadPermission(int userId) { 171 mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsRead"); 172 } 173 174 private final void checkReadPermission(String requestedKey, int userId) { 175 final int callingUid = Binder.getCallingUid(); 176 for (int i = 0; i < READ_PROFILE_PROTECTED_SETTINGS.length; i++) { 177 String key = READ_PROFILE_PROTECTED_SETTINGS[i]; 178 if (key.equals(requestedKey) && mContext.checkCallingOrSelfPermission(READ_PROFILE) 179 != PackageManager.PERMISSION_GRANTED) { 180 throw new SecurityException("uid=" + callingUid 181 + " needs permission " + READ_PROFILE + " to read " 182 + requestedKey + " for user " + userId); 183 } 184 } 185 } 186 187 @Override 188 public void setBoolean(String key, boolean value, int userId) throws RemoteException { 189 checkWritePermission(userId); 190 191 writeToDb(key, value ? "1" : "0", userId); 192 } 193 194 @Override 195 public void setLong(String key, long value, int userId) throws RemoteException { 196 checkWritePermission(userId); 197 198 writeToDb(key, Long.toString(value), userId); 199 } 200 201 @Override 202 public void setString(String key, String value, int userId) throws RemoteException { 203 checkWritePermission(userId); 204 205 writeToDb(key, value, userId); 206 } 207 208 @Override 209 public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException { 210 checkReadPermission(key, userId); 211 212 String value = readFromDb(key, null, userId); 213 return TextUtils.isEmpty(value) ? 214 defaultValue : (value.equals("1") || value.equals("true")); 215 } 216 217 @Override 218 public long getLong(String key, long defaultValue, int userId) throws RemoteException { 219 checkReadPermission(key, userId); 220 221 String value = readFromDb(key, null, userId); 222 return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value); 223 } 224 225 @Override 226 public String getString(String key, String defaultValue, int userId) throws RemoteException { 227 checkReadPermission(key, userId); 228 229 return readFromDb(key, defaultValue, userId); 230 } 231 232 @Override 233 public void registerObserver(ILockSettingsObserver remote) throws RemoteException { 234 synchronized (mObservers) { 235 for (int i = 0; i < mObservers.size(); i++) { 236 if (mObservers.get(i).remote.asBinder() == remote.asBinder()) { 237 boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0")); 238 if (isDebuggable) { 239 throw new IllegalStateException("Observer was already registered."); 240 } else { 241 Log.e(TAG, "Observer was already registered."); 242 return; 243 } 244 } 245 } 246 LockSettingsObserver o = new LockSettingsObserver(); 247 o.remote = remote; 248 o.remote.asBinder().linkToDeath(o, 0); 249 mObservers.add(o); 250 } 251 } 252 253 @Override 254 public void unregisterObserver(ILockSettingsObserver remote) throws RemoteException { 255 synchronized (mObservers) { 256 for (int i = 0; i < mObservers.size(); i++) { 257 if (mObservers.get(i).remote.asBinder() == remote.asBinder()) { 258 mObservers.remove(i); 259 return; 260 } 261 } 262 } 263 } 264 265 public void notifyObservers(String key, int userId) { 266 synchronized (mObservers) { 267 for (int i = 0; i < mObservers.size(); i++) { 268 try { 269 mObservers.get(i).remote.onLockSettingChanged(key, userId); 270 } catch (RemoteException e) { 271 // The stack trace is not really helpful here. 272 Log.e(TAG, "Failed to notify ILockSettingsObserver: " + e); 273 } 274 } 275 } 276 } 277 278 private String getLockPatternFilename(int userId) { 279 String dataSystemDirectory = 280 android.os.Environment.getDataDirectory().getAbsolutePath() + 281 SYSTEM_DIRECTORY; 282 if (userId == 0) { 283 // Leave it in the same place for user 0 284 return dataSystemDirectory + LOCK_PATTERN_FILE; 285 } else { 286 return new File(Environment.getUserSystemDirectory(userId), LOCK_PATTERN_FILE) 287 .getAbsolutePath(); 288 } 289 } 290 291 private String getLockPasswordFilename(int userId) { 292 String dataSystemDirectory = 293 android.os.Environment.getDataDirectory().getAbsolutePath() + 294 SYSTEM_DIRECTORY; 295 if (userId == 0) { 296 // Leave it in the same place for user 0 297 return dataSystemDirectory + LOCK_PASSWORD_FILE; 298 } else { 299 return new File(Environment.getUserSystemDirectory(userId), LOCK_PASSWORD_FILE) 300 .getAbsolutePath(); 301 } 302 } 303 304 @Override 305 public boolean havePassword(int userId) throws RemoteException { 306 // Do we need a permissions check here? 307 308 return new File(getLockPasswordFilename(userId)).length() > 0; 309 } 310 311 @Override 312 public boolean havePattern(int userId) throws RemoteException { 313 // Do we need a permissions check here? 314 315 return new File(getLockPatternFilename(userId)).length() > 0; 316 } 317 318 private void maybeUpdateKeystore(String password, int userId) { 319 if (userId == UserHandle.USER_OWNER) { 320 final KeyStore keyStore = KeyStore.getInstance(); 321 // Conditionally reset the keystore if empty. If non-empty, we are just 322 // switching key guard type 323 if (TextUtils.isEmpty(password) && keyStore.isEmpty()) { 324 keyStore.reset(); 325 } else { 326 // Update the keystore password 327 keyStore.password(password); 328 } 329 } 330 } 331 332 @Override 333 public void setLockPattern(String pattern, int userId) throws RemoteException { 334 checkWritePermission(userId); 335 336 maybeUpdateKeystore(pattern, userId); 337 338 final byte[] hash = LockPatternUtils.patternToHash( 339 LockPatternUtils.stringToPattern(pattern)); 340 writeFile(getLockPatternFilename(userId), hash); 341 } 342 343 @Override 344 public void setLockPassword(String password, int userId) throws RemoteException { 345 checkWritePermission(userId); 346 347 maybeUpdateKeystore(password, userId); 348 349 writeFile(getLockPasswordFilename(userId), 350 mLockPatternUtils.passwordToHash(password, userId)); 351 } 352 353 @Override 354 public boolean checkPattern(String pattern, int userId) throws RemoteException { 355 checkPasswordReadPermission(userId); 356 try { 357 // Read all the bytes from the file 358 RandomAccessFile raf = new RandomAccessFile(getLockPatternFilename(userId), "r"); 359 final byte[] stored = new byte[(int) raf.length()]; 360 int got = raf.read(stored, 0, stored.length); 361 raf.close(); 362 if (got <= 0) { 363 return true; 364 } 365 // Compare the hash from the file with the entered pattern's hash 366 final byte[] hash = LockPatternUtils.patternToHash( 367 LockPatternUtils.stringToPattern(pattern)); 368 final boolean matched = Arrays.equals(stored, hash); 369 if (matched && !TextUtils.isEmpty(pattern)) { 370 maybeUpdateKeystore(pattern, userId); 371 } 372 return matched; 373 } catch (FileNotFoundException fnfe) { 374 Slog.e(TAG, "Cannot read file " + fnfe); 375 } catch (IOException ioe) { 376 Slog.e(TAG, "Cannot read file " + ioe); 377 } 378 return true; 379 } 380 381 @Override 382 public boolean checkPassword(String password, int userId) throws RemoteException { 383 checkPasswordReadPermission(userId); 384 385 try { 386 // Read all the bytes from the file 387 RandomAccessFile raf = new RandomAccessFile(getLockPasswordFilename(userId), "r"); 388 final byte[] stored = new byte[(int) raf.length()]; 389 int got = raf.read(stored, 0, stored.length); 390 raf.close(); 391 if (got <= 0) { 392 return true; 393 } 394 // Compare the hash from the file with the entered password's hash 395 final byte[] hash = mLockPatternUtils.passwordToHash(password, userId); 396 final boolean matched = Arrays.equals(stored, hash); 397 if (matched && !TextUtils.isEmpty(password)) { 398 maybeUpdateKeystore(password, userId); 399 } 400 return matched; 401 } catch (FileNotFoundException fnfe) { 402 Slog.e(TAG, "Cannot read file " + fnfe); 403 } catch (IOException ioe) { 404 Slog.e(TAG, "Cannot read file " + ioe); 405 } 406 return true; 407 } 408 409 @Override 410 public boolean checkVoldPassword(int userId) throws RemoteException { 411 if (!mFirstCallToVold) { 412 return false; 413 } 414 mFirstCallToVold = false; 415 416 checkPasswordReadPermission(userId); 417 418 // There's no guarantee that this will safely connect, but if it fails 419 // we will simply show the lock screen when we shouldn't, so relatively 420 // benign. There is an outside chance something nasty would happen if 421 // this service restarted before vold stales out the password in this 422 // case. The nastiness is limited to not showing the lock screen when 423 // we should, within the first minute of decrypting the phone if this 424 // service can't connect to vold, it restarts, and then the new instance 425 // does successfully connect. 426 final IMountService service = getMountService(); 427 String password = service.getPassword(); 428 service.clearPassword(); 429 if (password == null) { 430 return false; 431 } 432 433 try { 434 if (mLockPatternUtils.isLockPatternEnabled()) { 435 if (checkPattern(password, userId)) { 436 return true; 437 } 438 } 439 } catch (Exception e) { 440 } 441 442 try { 443 if (mLockPatternUtils.isLockPasswordEnabled()) { 444 if (checkPassword(password, userId)) { 445 return true; 446 } 447 } 448 } catch (Exception e) { 449 } 450 451 return false; 452 } 453 454 @Override 455 public void removeUser(int userId) { 456 checkWritePermission(userId); 457 458 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 459 try { 460 File file = new File(getLockPasswordFilename(userId)); 461 if (file.exists()) { 462 file.delete(); 463 } 464 file = new File(getLockPatternFilename(userId)); 465 if (file.exists()) { 466 file.delete(); 467 } 468 469 db.beginTransaction(); 470 db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null); 471 db.setTransactionSuccessful(); 472 } finally { 473 db.endTransaction(); 474 } 475 } 476 477 private void writeFile(String name, byte[] hash) { 478 try { 479 // Write the hash to file 480 RandomAccessFile raf = new RandomAccessFile(name, "rw"); 481 // Truncate the file if pattern is null, to clear the lock 482 if (hash == null || hash.length == 0) { 483 raf.setLength(0); 484 } else { 485 raf.write(hash, 0, hash.length); 486 } 487 raf.close(); 488 } catch (IOException ioe) { 489 Slog.e(TAG, "Error writing to file " + ioe); 490 } 491 } 492 493 private void writeToDb(String key, String value, int userId) { 494 writeToDb(mOpenHelper.getWritableDatabase(), key, value, userId); 495 notifyObservers(key, userId); 496 } 497 498 private void writeToDb(SQLiteDatabase db, String key, String value, int userId) { 499 ContentValues cv = new ContentValues(); 500 cv.put(COLUMN_KEY, key); 501 cv.put(COLUMN_USERID, userId); 502 cv.put(COLUMN_VALUE, value); 503 504 db.beginTransaction(); 505 try { 506 db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?", 507 new String[] {key, Integer.toString(userId)}); 508 db.insert(TABLE, null, cv); 509 db.setTransactionSuccessful(); 510 } finally { 511 db.endTransaction(); 512 } 513 } 514 515 private String readFromDb(String key, String defaultValue, int userId) { 516 Cursor cursor; 517 String result = defaultValue; 518 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 519 if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY, 520 COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?", 521 new String[] { Integer.toString(userId), key }, 522 null, null, null)) != null) { 523 if (cursor.moveToFirst()) { 524 result = cursor.getString(0); 525 } 526 cursor.close(); 527 } 528 return result; 529 } 530 531 class DatabaseHelper extends SQLiteOpenHelper { 532 private static final String TAG = "LockSettingsDB"; 533 private static final String DATABASE_NAME = "locksettings.db"; 534 535 private static final int DATABASE_VERSION = 2; 536 537 public DatabaseHelper(Context context) { 538 super(context, DATABASE_NAME, null, DATABASE_VERSION); 539 setWriteAheadLoggingEnabled(true); 540 } 541 542 private void createTable(SQLiteDatabase db) { 543 db.execSQL("CREATE TABLE " + TABLE + " (" + 544 "_id INTEGER PRIMARY KEY AUTOINCREMENT," + 545 COLUMN_KEY + " TEXT," + 546 COLUMN_USERID + " INTEGER," + 547 COLUMN_VALUE + " TEXT" + 548 ");"); 549 } 550 551 @Override 552 public void onCreate(SQLiteDatabase db) { 553 createTable(db); 554 initializeDefaults(db); 555 } 556 557 private void initializeDefaults(SQLiteDatabase db) { 558 // Get the lockscreen default from a system property, if available 559 boolean lockScreenDisable = SystemProperties.getBoolean("ro.lockscreen.disable.default", 560 false); 561 if (lockScreenDisable) { 562 writeToDb(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0); 563 } 564 } 565 566 @Override 567 public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { 568 int upgradeVersion = oldVersion; 569 if (upgradeVersion == 1) { 570 // Set the initial value for {@link LockPatternUtils#LOCKSCREEN_WIDGETS_ENABLED} 571 // during upgrade based on whether each user previously had widgets in keyguard. 572 maybeEnableWidgetSettingForUsers(db); 573 upgradeVersion = 2; 574 } 575 576 if (upgradeVersion != DATABASE_VERSION) { 577 Log.w(TAG, "Failed to upgrade database!"); 578 } 579 } 580 581 private void maybeEnableWidgetSettingForUsers(SQLiteDatabase db) { 582 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE); 583 final ContentResolver cr = mContext.getContentResolver(); 584 final List<UserInfo> users = um.getUsers(); 585 for (int i = 0; i < users.size(); i++) { 586 final int userId = users.get(i).id; 587 final boolean enabled = mLockPatternUtils.hasWidgetsEnabledInKeyguard(userId); 588 Log.v(TAG, "Widget upgrade uid=" + userId + ", enabled=" 589 + enabled + ", w[]=" + mLockPatternUtils.getAppWidgets()); 590 loadSetting(db, LockPatternUtils.LOCKSCREEN_WIDGETS_ENABLED, userId, enabled); 591 } 592 } 593 594 private void loadSetting(SQLiteDatabase db, String key, int userId, boolean value) { 595 SQLiteStatement stmt = null; 596 try { 597 stmt = db.compileStatement( 598 "INSERT OR REPLACE INTO locksettings(name,user,value) VALUES(?,?,?);"); 599 stmt.bindString(1, key); 600 stmt.bindLong(2, userId); 601 stmt.bindLong(3, value ? 1 : 0); 602 stmt.execute(); 603 } finally { 604 if (stmt != null) stmt.close(); 605 } 606 } 607 } 608 609 private static final String[] VALID_SETTINGS = new String[] { 610 LockPatternUtils.LOCKOUT_PERMANENT_KEY, 611 LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE, 612 LockPatternUtils.PATTERN_EVER_CHOSEN_KEY, 613 LockPatternUtils.PASSWORD_TYPE_KEY, 614 LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY, 615 LockPatternUtils.LOCK_PASSWORD_SALT_KEY, 616 LockPatternUtils.DISABLE_LOCKSCREEN_KEY, 617 LockPatternUtils.LOCKSCREEN_OPTIONS, 618 LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, 619 LockPatternUtils.BIOMETRIC_WEAK_EVER_CHOSEN_KEY, 620 LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS, 621 LockPatternUtils.PASSWORD_HISTORY_KEY, 622 Secure.LOCK_PATTERN_ENABLED, 623 Secure.LOCK_BIOMETRIC_WEAK_FLAGS, 624 Secure.LOCK_PATTERN_VISIBLE, 625 Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED 626 }; 627 628 // These are protected with a read permission 629 private static final String[] READ_PROFILE_PROTECTED_SETTINGS = new String[] { 630 Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, 631 Secure.LOCK_SCREEN_OWNER_INFO 632 }; 633 634 private IMountService getMountService() { 635 final IBinder service = ServiceManager.getService("mount"); 636 if (service != null) { 637 return IMountService.Stub.asInterface(service); 638 } 639 return null; 640 } 641 642 private class LockSettingsObserver implements DeathRecipient { 643 ILockSettingsObserver remote; 644 645 @Override 646 public void binderDied() { 647 mObservers.remove(this); 648 } 649 } 650} 651