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