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