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