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