LockSettingsService.java revision 4f7884542ce8fba5bfed01ed834a32e6d3e2dea5
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), mLockPatternUtils.passwordToHash(password)); 350 } 351 352 @Override 353 public boolean checkPattern(String pattern, int userId) throws RemoteException { 354 checkPasswordReadPermission(userId); 355 try { 356 // Read all the bytes from the file 357 RandomAccessFile raf = new RandomAccessFile(getLockPatternFilename(userId), "r"); 358 final byte[] stored = new byte[(int) raf.length()]; 359 int got = raf.read(stored, 0, stored.length); 360 raf.close(); 361 if (got <= 0) { 362 return true; 363 } 364 // Compare the hash from the file with the entered pattern's hash 365 final byte[] hash = LockPatternUtils.patternToHash( 366 LockPatternUtils.stringToPattern(pattern)); 367 final boolean matched = Arrays.equals(stored, hash); 368 if (matched && !TextUtils.isEmpty(pattern)) { 369 maybeUpdateKeystore(pattern, userId); 370 } 371 return matched; 372 } catch (FileNotFoundException fnfe) { 373 Slog.e(TAG, "Cannot read file " + fnfe); 374 } catch (IOException ioe) { 375 Slog.e(TAG, "Cannot read file " + ioe); 376 } 377 return true; 378 } 379 380 @Override 381 public boolean checkPassword(String password, int userId) throws RemoteException { 382 checkPasswordReadPermission(userId); 383 384 try { 385 // Read all the bytes from the file 386 RandomAccessFile raf = new RandomAccessFile(getLockPasswordFilename(userId), "r"); 387 final byte[] stored = new byte[(int) raf.length()]; 388 int got = raf.read(stored, 0, stored.length); 389 raf.close(); 390 if (got <= 0) { 391 return true; 392 } 393 // Compare the hash from the file with the entered password's hash 394 final byte[] hash = mLockPatternUtils.passwordToHash(password); 395 final boolean matched = Arrays.equals(stored, hash); 396 if (matched && !TextUtils.isEmpty(password)) { 397 maybeUpdateKeystore(password, userId); 398 } 399 return matched; 400 } catch (FileNotFoundException fnfe) { 401 Slog.e(TAG, "Cannot read file " + fnfe); 402 } catch (IOException ioe) { 403 Slog.e(TAG, "Cannot read file " + ioe); 404 } 405 return true; 406 } 407 408 @Override 409 public boolean checkVoldPassword(int userId) throws RemoteException { 410 if (!mFirstCallToVold) { 411 return false; 412 } 413 mFirstCallToVold = false; 414 415 checkPasswordReadPermission(userId); 416 417 // There's no guarantee that this will safely connect, but if it fails 418 // we will simply show the lock screen when we shouldn't, so relatively 419 // benign. There is an outside chance something nasty would happen if 420 // this service restarted before vold stales out the password in this 421 // case. The nastiness is limited to not showing the lock screen when 422 // we should, within the first minute of decrypting the phone if this 423 // service can't connect to vold, it restarts, and then the new instance 424 // does successfully connect. 425 final IMountService service = getMountService(); 426 String password = service.getPassword(); 427 service.clearPassword(); 428 if (password == null) { 429 return false; 430 } 431 432 try { 433 if (mLockPatternUtils.isLockPatternEnabled()) { 434 if (checkPattern(password, userId)) { 435 return true; 436 } 437 } 438 } catch (Exception e) { 439 } 440 441 try { 442 if (mLockPatternUtils.isLockPasswordEnabled()) { 443 if (checkPassword(password, userId)) { 444 return true; 445 } 446 } 447 } catch (Exception e) { 448 } 449 450 return false; 451 } 452 453 @Override 454 public void removeUser(int userId) { 455 checkWritePermission(userId); 456 457 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 458 try { 459 File file = new File(getLockPasswordFilename(userId)); 460 if (file.exists()) { 461 file.delete(); 462 } 463 file = new File(getLockPatternFilename(userId)); 464 if (file.exists()) { 465 file.delete(); 466 } 467 468 db.beginTransaction(); 469 db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null); 470 db.setTransactionSuccessful(); 471 } finally { 472 db.endTransaction(); 473 } 474 } 475 476 private void writeFile(String name, byte[] hash) { 477 try { 478 // Write the hash to file 479 RandomAccessFile raf = new RandomAccessFile(name, "rw"); 480 // Truncate the file if pattern is null, to clear the lock 481 if (hash == null || hash.length == 0) { 482 raf.setLength(0); 483 } else { 484 raf.write(hash, 0, hash.length); 485 } 486 raf.close(); 487 } catch (IOException ioe) { 488 Slog.e(TAG, "Error writing to file " + ioe); 489 } 490 } 491 492 private void writeToDb(String key, String value, int userId) { 493 writeToDb(mOpenHelper.getWritableDatabase(), key, value, userId); 494 notifyObservers(key, userId); 495 } 496 497 private void writeToDb(SQLiteDatabase db, String key, String value, int userId) { 498 ContentValues cv = new ContentValues(); 499 cv.put(COLUMN_KEY, key); 500 cv.put(COLUMN_USERID, userId); 501 cv.put(COLUMN_VALUE, value); 502 503 db.beginTransaction(); 504 try { 505 db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?", 506 new String[] {key, Integer.toString(userId)}); 507 db.insert(TABLE, null, cv); 508 db.setTransactionSuccessful(); 509 } finally { 510 db.endTransaction(); 511 } 512 } 513 514 private String readFromDb(String key, String defaultValue, int userId) { 515 Cursor cursor; 516 String result = defaultValue; 517 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 518 if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY, 519 COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?", 520 new String[] { Integer.toString(userId), key }, 521 null, null, null)) != null) { 522 if (cursor.moveToFirst()) { 523 result = cursor.getString(0); 524 } 525 cursor.close(); 526 } 527 return result; 528 } 529 530 class DatabaseHelper extends SQLiteOpenHelper { 531 private static final String TAG = "LockSettingsDB"; 532 private static final String DATABASE_NAME = "locksettings.db"; 533 534 private static final int DATABASE_VERSION = 2; 535 536 public DatabaseHelper(Context context) { 537 super(context, DATABASE_NAME, null, DATABASE_VERSION); 538 setWriteAheadLoggingEnabled(true); 539 } 540 541 private void createTable(SQLiteDatabase db) { 542 db.execSQL("CREATE TABLE " + TABLE + " (" + 543 "_id INTEGER PRIMARY KEY AUTOINCREMENT," + 544 COLUMN_KEY + " TEXT," + 545 COLUMN_USERID + " INTEGER," + 546 COLUMN_VALUE + " TEXT" + 547 ");"); 548 } 549 550 @Override 551 public void onCreate(SQLiteDatabase db) { 552 createTable(db); 553 initializeDefaults(db); 554 } 555 556 private void initializeDefaults(SQLiteDatabase db) { 557 // Get the lockscreen default from a system property, if available 558 boolean lockScreenDisable = SystemProperties.getBoolean("ro.lockscreen.disable.default", 559 false); 560 if (lockScreenDisable) { 561 writeToDb(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0); 562 } 563 } 564 565 @Override 566 public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { 567 int upgradeVersion = oldVersion; 568 if (upgradeVersion == 1) { 569 // Set the initial value for {@link LockPatternUtils#LOCKSCREEN_WIDGETS_ENABLED} 570 // during upgrade based on whether each user previously had widgets in keyguard. 571 maybeEnableWidgetSettingForUsers(db); 572 upgradeVersion = 2; 573 } 574 575 if (upgradeVersion != DATABASE_VERSION) { 576 Log.w(TAG, "Failed to upgrade database!"); 577 } 578 } 579 580 private void maybeEnableWidgetSettingForUsers(SQLiteDatabase db) { 581 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE); 582 final ContentResolver cr = mContext.getContentResolver(); 583 final List<UserInfo> users = um.getUsers(); 584 for (int i = 0; i < users.size(); i++) { 585 final int userId = users.get(i).id; 586 final boolean enabled = mLockPatternUtils.hasWidgetsEnabledInKeyguard(userId); 587 Log.v(TAG, "Widget upgrade uid=" + userId + ", enabled=" 588 + enabled + ", w[]=" + mLockPatternUtils.getAppWidgets()); 589 loadSetting(db, LockPatternUtils.LOCKSCREEN_WIDGETS_ENABLED, userId, enabled); 590 } 591 } 592 593 private void loadSetting(SQLiteDatabase db, String key, int userId, boolean value) { 594 SQLiteStatement stmt = null; 595 try { 596 stmt = db.compileStatement( 597 "INSERT OR REPLACE INTO locksettings(name,user,value) VALUES(?,?,?);"); 598 stmt.bindString(1, key); 599 stmt.bindLong(2, userId); 600 stmt.bindLong(3, value ? 1 : 0); 601 stmt.execute(); 602 } finally { 603 if (stmt != null) stmt.close(); 604 } 605 } 606 } 607 608 private static final String[] VALID_SETTINGS = new String[] { 609 LockPatternUtils.LOCKOUT_PERMANENT_KEY, 610 LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE, 611 LockPatternUtils.PATTERN_EVER_CHOSEN_KEY, 612 LockPatternUtils.PASSWORD_TYPE_KEY, 613 LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY, 614 LockPatternUtils.LOCK_PASSWORD_SALT_KEY, 615 LockPatternUtils.DISABLE_LOCKSCREEN_KEY, 616 LockPatternUtils.LOCKSCREEN_OPTIONS, 617 LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, 618 LockPatternUtils.BIOMETRIC_WEAK_EVER_CHOSEN_KEY, 619 LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS, 620 LockPatternUtils.PASSWORD_HISTORY_KEY, 621 Secure.LOCK_PATTERN_ENABLED, 622 Secure.LOCK_BIOMETRIC_WEAK_FLAGS, 623 Secure.LOCK_PATTERN_VISIBLE, 624 Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED 625 }; 626 627 // These are protected with a read permission 628 private static final String[] READ_PROFILE_PROTECTED_SETTINGS = new String[] { 629 Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, 630 Secure.LOCK_SCREEN_OWNER_INFO 631 }; 632 633 private IMountService getMountService() { 634 final IBinder service = ServiceManager.getService("mount"); 635 if (service != null) { 636 return IMountService.Stub.asInterface(service); 637 } 638 return null; 639 } 640 641 private class LockSettingsObserver implements DeathRecipient { 642 ILockSettingsObserver remote; 643 644 @Override 645 public void binderDied() { 646 mObservers.remove(this); 647 } 648 } 649} 650