Home | History | Annotate | Download | only in server
      1 /*
      2  * Copyright (C) 2014 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 
     17 package com.android.server;
     18 
     19 import com.android.internal.annotations.VisibleForTesting;
     20 
     21 import android.content.ContentValues;
     22 import android.content.Context;
     23 import android.content.pm.UserInfo;
     24 import android.database.Cursor;
     25 import android.database.sqlite.SQLiteDatabase;
     26 import android.database.sqlite.SQLiteOpenHelper;
     27 import android.os.Environment;
     28 import android.os.UserManager;
     29 import android.util.ArrayMap;
     30 import android.util.Log;
     31 import android.util.Slog;
     32 import android.util.SparseArray;
     33 
     34 import java.io.File;
     35 import java.io.IOException;
     36 import java.io.RandomAccessFile;
     37 
     38 import static android.content.Context.USER_SERVICE;
     39 
     40 /**
     41  * Storage for the lock settings service.
     42  */
     43 class LockSettingsStorage {
     44 
     45     private static final String TAG = "LockSettingsStorage";
     46     private static final String TABLE = "locksettings";
     47     private static final boolean DEBUG = false;
     48 
     49     private static final String COLUMN_KEY = "name";
     50     private static final String COLUMN_USERID = "user";
     51     private static final String COLUMN_VALUE = "value";
     52 
     53     private static final String[] COLUMNS_FOR_QUERY = {
     54             COLUMN_VALUE
     55     };
     56     private static final String[] COLUMNS_FOR_PREFETCH = {
     57             COLUMN_KEY, COLUMN_VALUE
     58     };
     59 
     60     private static final String SYSTEM_DIRECTORY = "/system/";
     61     private static final String LOCK_PATTERN_FILE = "gatekeeper.pattern.key";
     62     private static final String BASE_ZERO_LOCK_PATTERN_FILE = "gatekeeper.gesture.key";
     63     private static final String LEGACY_LOCK_PATTERN_FILE = "gesture.key";
     64     private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key";
     65     private static final String LEGACY_LOCK_PASSWORD_FILE = "password.key";
     66     private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key";
     67 
     68     private static final Object DEFAULT = new Object();
     69 
     70     private final DatabaseHelper mOpenHelper;
     71     private final Context mContext;
     72     private final Cache mCache = new Cache();
     73     private final Object mFileWriteLock = new Object();
     74 
     75     private SparseArray<Integer> mStoredCredentialType;
     76 
     77     static class CredentialHash {
     78         static final int TYPE_NONE = -1;
     79         static final int TYPE_PATTERN = 1;
     80         static final int TYPE_PASSWORD = 2;
     81 
     82         static final int VERSION_LEGACY = 0;
     83         static final int VERSION_GATEKEEPER = 1;
     84 
     85         CredentialHash(byte[] hash, int version) {
     86             this.hash = hash;
     87             this.version = version;
     88             this.isBaseZeroPattern = false;
     89         }
     90 
     91         CredentialHash(byte[] hash, boolean isBaseZeroPattern) {
     92             this.hash = hash;
     93             this.version = VERSION_GATEKEEPER;
     94             this.isBaseZeroPattern = isBaseZeroPattern;
     95         }
     96 
     97         byte[] hash;
     98         int version;
     99         boolean isBaseZeroPattern;
    100     }
    101 
    102     public LockSettingsStorage(Context context, Callback callback) {
    103         mContext = context;
    104         mOpenHelper = new DatabaseHelper(context, callback);
    105         mStoredCredentialType = new SparseArray<Integer>();
    106     }
    107 
    108     public void writeKeyValue(String key, String value, int userId) {
    109         writeKeyValue(mOpenHelper.getWritableDatabase(), key, value, userId);
    110     }
    111 
    112     public void writeKeyValue(SQLiteDatabase db, String key, String value, int userId) {
    113         ContentValues cv = new ContentValues();
    114         cv.put(COLUMN_KEY, key);
    115         cv.put(COLUMN_USERID, userId);
    116         cv.put(COLUMN_VALUE, value);
    117 
    118         db.beginTransaction();
    119         try {
    120             db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
    121                     new String[] {key, Integer.toString(userId)});
    122             db.insert(TABLE, null, cv);
    123             db.setTransactionSuccessful();
    124             mCache.putKeyValue(key, value, userId);
    125         } finally {
    126             db.endTransaction();
    127         }
    128 
    129     }
    130 
    131     public String readKeyValue(String key, String defaultValue, int userId) {
    132         int version;
    133         synchronized (mCache) {
    134             if (mCache.hasKeyValue(key, userId)) {
    135                 return mCache.peekKeyValue(key, defaultValue, userId);
    136             }
    137             version = mCache.getVersion();
    138         }
    139 
    140         Cursor cursor;
    141         Object result = DEFAULT;
    142         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    143         if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
    144                 COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
    145                 new String[] { Integer.toString(userId), key },
    146                 null, null, null)) != null) {
    147             if (cursor.moveToFirst()) {
    148                 result = cursor.getString(0);
    149             }
    150             cursor.close();
    151         }
    152         mCache.putKeyValueIfUnchanged(key, result, userId, version);
    153         return result == DEFAULT ? defaultValue : (String) result;
    154     }
    155 
    156     public void prefetchUser(int userId) {
    157         int version;
    158         synchronized (mCache) {
    159             if (mCache.isFetched(userId)) {
    160                 return;
    161             }
    162             mCache.setFetched(userId);
    163             version = mCache.getVersion();
    164         }
    165 
    166         Cursor cursor;
    167         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    168         if ((cursor = db.query(TABLE, COLUMNS_FOR_PREFETCH,
    169                 COLUMN_USERID + "=?",
    170                 new String[] { Integer.toString(userId) },
    171                 null, null, null)) != null) {
    172             while (cursor.moveToNext()) {
    173                 String key = cursor.getString(0);
    174                 String value = cursor.getString(1);
    175                 mCache.putKeyValueIfUnchanged(key, value, userId, version);
    176             }
    177             cursor.close();
    178         }
    179 
    180         // Populate cache by reading the password and pattern files.
    181         readPasswordHash(userId);
    182         readPatternHash(userId);
    183     }
    184 
    185     public int getStoredCredentialType(int userId) {
    186         final Integer cachedStoredCredentialType = mStoredCredentialType.get(userId);
    187         if (cachedStoredCredentialType != null) {
    188             return cachedStoredCredentialType.intValue();
    189         }
    190 
    191         int storedCredentialType;
    192         CredentialHash pattern = readPatternHash(userId);
    193         if (pattern == null) {
    194             if (readPasswordHash(userId) != null) {
    195                 storedCredentialType = CredentialHash.TYPE_PASSWORD;
    196             } else {
    197                 storedCredentialType = CredentialHash.TYPE_NONE;
    198             }
    199         } else {
    200             CredentialHash password = readPasswordHash(userId);
    201             if (password != null) {
    202                 // Both will never be GateKeeper
    203                 if (password.version == CredentialHash.VERSION_GATEKEEPER) {
    204                     storedCredentialType = CredentialHash.TYPE_PASSWORD;
    205                 } else {
    206                     storedCredentialType = CredentialHash.TYPE_PATTERN;
    207                 }
    208             } else {
    209                 storedCredentialType = CredentialHash.TYPE_PATTERN;
    210             }
    211         }
    212         mStoredCredentialType.put(userId, storedCredentialType);
    213         return storedCredentialType;
    214     }
    215 
    216 
    217     public CredentialHash readPasswordHash(int userId) {
    218         byte[] stored = readFile(getLockPasswordFilename(userId));
    219         if (stored != null && stored.length > 0) {
    220             return new CredentialHash(stored, CredentialHash.VERSION_GATEKEEPER);
    221         }
    222 
    223         stored = readFile(getLegacyLockPasswordFilename(userId));
    224         if (stored != null && stored.length > 0) {
    225             return new CredentialHash(stored, CredentialHash.VERSION_LEGACY);
    226         }
    227 
    228         return null;
    229     }
    230 
    231     public CredentialHash readPatternHash(int userId) {
    232         byte[] stored = readFile(getLockPatternFilename(userId));
    233         if (stored != null && stored.length > 0) {
    234             return new CredentialHash(stored, CredentialHash.VERSION_GATEKEEPER);
    235         }
    236 
    237         stored = readFile(getBaseZeroLockPatternFilename(userId));
    238         if (stored != null && stored.length > 0) {
    239             return new CredentialHash(stored, true);
    240         }
    241 
    242         stored = readFile(getLegacyLockPatternFilename(userId));
    243         if (stored != null && stored.length > 0) {
    244             return new CredentialHash(stored, CredentialHash.VERSION_LEGACY);
    245         }
    246 
    247         return null;
    248     }
    249 
    250     public void removeChildProfileLock(int userId) {
    251         if (DEBUG)
    252             Slog.e(TAG, "Remove child profile lock for user: " + userId);
    253         try {
    254             deleteFile(getChildProfileLockFile(userId));
    255         } catch (Exception e) {
    256             e.printStackTrace();
    257         }
    258     }
    259 
    260     public void writeChildProfileLock(int userId, byte[] lock) {
    261         writeFile(getChildProfileLockFile(userId), lock);
    262     }
    263 
    264     public byte[] readChildProfileLock(int userId) {
    265         return readFile(getChildProfileLockFile(userId));
    266     }
    267 
    268     public boolean hasChildProfileLock(int userId) {
    269         return hasFile(getChildProfileLockFile(userId));
    270     }
    271 
    272     public boolean hasPassword(int userId) {
    273         return hasFile(getLockPasswordFilename(userId)) ||
    274             hasFile(getLegacyLockPasswordFilename(userId));
    275     }
    276 
    277     public boolean hasPattern(int userId) {
    278         return hasFile(getLockPatternFilename(userId)) ||
    279             hasFile(getBaseZeroLockPatternFilename(userId)) ||
    280             hasFile(getLegacyLockPatternFilename(userId));
    281     }
    282 
    283     private boolean hasFile(String name) {
    284         byte[] contents = readFile(name);
    285         return contents != null && contents.length > 0;
    286     }
    287 
    288     private byte[] readFile(String name) {
    289         int version;
    290         synchronized (mCache) {
    291             if (mCache.hasFile(name)) {
    292                 return mCache.peekFile(name);
    293             }
    294             version = mCache.getVersion();
    295         }
    296 
    297         RandomAccessFile raf = null;
    298         byte[] stored = null;
    299         try {
    300             raf = new RandomAccessFile(name, "r");
    301             stored = new byte[(int) raf.length()];
    302             raf.readFully(stored, 0, stored.length);
    303             raf.close();
    304         } catch (IOException e) {
    305             Slog.e(TAG, "Cannot read file " + e);
    306         } finally {
    307             if (raf != null) {
    308                 try {
    309                     raf.close();
    310                 } catch (IOException e) {
    311                     Slog.e(TAG, "Error closing file " + e);
    312                 }
    313             }
    314         }
    315         mCache.putFileIfUnchanged(name, stored, version);
    316         return stored;
    317     }
    318 
    319     private void writeFile(String name, byte[] hash) {
    320         synchronized (mFileWriteLock) {
    321             RandomAccessFile raf = null;
    322             try {
    323                 // Write the hash to file
    324                 raf = new RandomAccessFile(name, "rw");
    325                 // Truncate the file if pattern is null, to clear the lock
    326                 if (hash == null || hash.length == 0) {
    327                     raf.setLength(0);
    328                 } else {
    329                     raf.write(hash, 0, hash.length);
    330                 }
    331                 raf.close();
    332             } catch (IOException e) {
    333                 Slog.e(TAG, "Error writing to file " + e);
    334             } finally {
    335                 if (raf != null) {
    336                     try {
    337                         raf.close();
    338                     } catch (IOException e) {
    339                         Slog.e(TAG, "Error closing file " + e);
    340                     }
    341                 }
    342             }
    343             mCache.putFile(name, hash);
    344         }
    345     }
    346 
    347     private void deleteFile(String name) {
    348         if (DEBUG) Slog.e(TAG, "Delete file " + name);
    349         synchronized (mFileWriteLock) {
    350             File file = new File(name);
    351             if (file.exists()) {
    352                 file.delete();
    353                 mCache.putFile(name, null);
    354             }
    355         }
    356     }
    357 
    358     public void writePatternHash(byte[] hash, int userId) {
    359         mStoredCredentialType.put(userId, hash == null ? CredentialHash.TYPE_NONE
    360                 : CredentialHash.TYPE_PATTERN);
    361         writeFile(getLockPatternFilename(userId), hash);
    362         clearPasswordHash(userId);
    363     }
    364 
    365     private void clearPatternHash(int userId) {
    366         writeFile(getLockPatternFilename(userId), null);
    367     }
    368 
    369     public void writePasswordHash(byte[] hash, int userId) {
    370         mStoredCredentialType.put(userId, hash == null ? CredentialHash.TYPE_NONE
    371                 : CredentialHash.TYPE_PASSWORD);
    372         writeFile(getLockPasswordFilename(userId), hash);
    373         clearPatternHash(userId);
    374     }
    375 
    376     private void clearPasswordHash(int userId) {
    377         writeFile(getLockPasswordFilename(userId), null);
    378     }
    379 
    380     @VisibleForTesting
    381     String getLockPatternFilename(int userId) {
    382         return getLockCredentialFilePathForUser(userId, LOCK_PATTERN_FILE);
    383     }
    384 
    385     @VisibleForTesting
    386     String getLockPasswordFilename(int userId) {
    387         return getLockCredentialFilePathForUser(userId, LOCK_PASSWORD_FILE);
    388     }
    389 
    390     @VisibleForTesting
    391     String getLegacyLockPatternFilename(int userId) {
    392         return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PATTERN_FILE);
    393     }
    394 
    395     @VisibleForTesting
    396     String getLegacyLockPasswordFilename(int userId) {
    397         return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PASSWORD_FILE);
    398     }
    399 
    400     private String getBaseZeroLockPatternFilename(int userId) {
    401         return getLockCredentialFilePathForUser(userId, BASE_ZERO_LOCK_PATTERN_FILE);
    402     }
    403 
    404     @VisibleForTesting
    405     String getChildProfileLockFile(int userId) {
    406         return getLockCredentialFilePathForUser(userId, CHILD_PROFILE_LOCK_FILE);
    407     }
    408 
    409     private String getLockCredentialFilePathForUser(int userId, String basename) {
    410         String dataSystemDirectory =
    411                 android.os.Environment.getDataDirectory().getAbsolutePath() +
    412                         SYSTEM_DIRECTORY;
    413         if (userId == 0) {
    414             // Leave it in the same place for user 0
    415             return dataSystemDirectory + basename;
    416         } else {
    417             return new File(Environment.getUserSystemDirectory(userId), basename).getAbsolutePath();
    418         }
    419     }
    420 
    421     public void removeUser(int userId) {
    422         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    423 
    424         final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
    425         final UserInfo parentInfo = um.getProfileParent(userId);
    426 
    427         if (parentInfo == null) {
    428             // This user owns its lock settings files - safe to delete them
    429             synchronized (mFileWriteLock) {
    430                 String name = getLockPasswordFilename(userId);
    431                 File file = new File(name);
    432                 if (file.exists()) {
    433                     file.delete();
    434                     mCache.putFile(name, null);
    435                 }
    436                 name = getLockPatternFilename(userId);
    437                 file = new File(name);
    438                 if (file.exists()) {
    439                     file.delete();
    440                     mCache.putFile(name, null);
    441                 }
    442             }
    443         } else {
    444             // Manged profile
    445             removeChildProfileLock(userId);
    446         }
    447 
    448         try {
    449             db.beginTransaction();
    450             db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
    451             db.setTransactionSuccessful();
    452             mCache.removeUser(userId);
    453         } finally {
    454             db.endTransaction();
    455         }
    456     }
    457 
    458     @VisibleForTesting
    459     void closeDatabase() {
    460         mOpenHelper.close();
    461     }
    462 
    463     @VisibleForTesting
    464     void clearCache() {
    465         mCache.clear();
    466     }
    467 
    468     public interface Callback {
    469         void initialize(SQLiteDatabase db);
    470     }
    471 
    472     class DatabaseHelper extends SQLiteOpenHelper {
    473         private static final String TAG = "LockSettingsDB";
    474         private static final String DATABASE_NAME = "locksettings.db";
    475 
    476         private static final int DATABASE_VERSION = 2;
    477 
    478         private final Callback mCallback;
    479 
    480         public DatabaseHelper(Context context, Callback callback) {
    481             super(context, DATABASE_NAME, null, DATABASE_VERSION);
    482             setWriteAheadLoggingEnabled(true);
    483             mCallback = callback;
    484         }
    485 
    486         private void createTable(SQLiteDatabase db) {
    487             db.execSQL("CREATE TABLE " + TABLE + " (" +
    488                     "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
    489                     COLUMN_KEY + " TEXT," +
    490                     COLUMN_USERID + " INTEGER," +
    491                     COLUMN_VALUE + " TEXT" +
    492                     ");");
    493         }
    494 
    495         @Override
    496         public void onCreate(SQLiteDatabase db) {
    497             createTable(db);
    498             mCallback.initialize(db);
    499         }
    500 
    501         @Override
    502         public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
    503             int upgradeVersion = oldVersion;
    504             if (upgradeVersion == 1) {
    505                 // Previously migrated lock screen widget settings. Now defunct.
    506                 upgradeVersion = 2;
    507             }
    508 
    509             if (upgradeVersion != DATABASE_VERSION) {
    510                 Log.w(TAG, "Failed to upgrade database!");
    511             }
    512         }
    513     }
    514 
    515     /**
    516      * Cache consistency model:
    517      * - Writes to storage write directly to the cache, but this MUST happen within the atomic
    518      *   section either provided by the database transaction or mWriteLock, such that writes to the
    519      *   cache and writes to the backing storage are guaranteed to occur in the same order
    520      *
    521      * - Reads can populate the cache, but because they are no strong ordering guarantees with
    522      *   respect to writes this precaution is taken:
    523      *   - The cache is assigned a version number that increases every time the cache is modified.
    524      *     Reads from backing storage can only populate the cache if the backing storage
    525      *     has not changed since the load operation has begun.
    526      *     This guarantees that no read operation can shadow a write to the cache that happens
    527      *     after it had begun.
    528      */
    529     private static class Cache {
    530         private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>();
    531         private final CacheKey mCacheKey = new CacheKey();
    532         private int mVersion = 0;
    533 
    534         String peekKeyValue(String key, String defaultValue, int userId) {
    535             Object cached = peek(CacheKey.TYPE_KEY_VALUE, key, userId);
    536             return cached == DEFAULT ? defaultValue : (String) cached;
    537         }
    538 
    539         boolean hasKeyValue(String key, int userId) {
    540             return contains(CacheKey.TYPE_KEY_VALUE, key, userId);
    541         }
    542 
    543         void putKeyValue(String key, String value, int userId) {
    544             put(CacheKey.TYPE_KEY_VALUE, key, value, userId);
    545         }
    546 
    547         void putKeyValueIfUnchanged(String key, Object value, int userId, int version) {
    548             putIfUnchanged(CacheKey.TYPE_KEY_VALUE, key, value, userId, version);
    549         }
    550 
    551         byte[] peekFile(String fileName) {
    552             return (byte[]) peek(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
    553         }
    554 
    555         boolean hasFile(String fileName) {
    556             return contains(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
    557         }
    558 
    559         void putFile(String key, byte[] value) {
    560             put(CacheKey.TYPE_FILE, key, value, -1 /* userId */);
    561         }
    562 
    563         void putFileIfUnchanged(String key, byte[] value, int version) {
    564             putIfUnchanged(CacheKey.TYPE_FILE, key, value, -1 /* userId */, version);
    565         }
    566 
    567         void setFetched(int userId) {
    568             put(CacheKey.TYPE_FETCHED, "isFetched", "true", userId);
    569         }
    570 
    571         boolean isFetched(int userId) {
    572             return contains(CacheKey.TYPE_FETCHED, "", userId);
    573         }
    574 
    575 
    576         private synchronized void put(int type, String key, Object value, int userId) {
    577             // Create a new CachKey here because it may be saved in the map if the key is absent.
    578             mCache.put(new CacheKey().set(type, key, userId), value);
    579             mVersion++;
    580         }
    581 
    582         private synchronized void putIfUnchanged(int type, String key, Object value, int userId,
    583                 int version) {
    584             if (!contains(type, key, userId) && mVersion == version) {
    585                 put(type, key, value, userId);
    586             }
    587         }
    588 
    589         private synchronized boolean contains(int type, String key, int userId) {
    590             return mCache.containsKey(mCacheKey.set(type, key, userId));
    591         }
    592 
    593         private synchronized Object peek(int type, String key, int userId) {
    594             return mCache.get(mCacheKey.set(type, key, userId));
    595         }
    596 
    597         private synchronized int getVersion() {
    598             return mVersion;
    599         }
    600 
    601         synchronized void removeUser(int userId) {
    602             for (int i = mCache.size() - 1; i >= 0; i--) {
    603                 if (mCache.keyAt(i).userId == userId) {
    604                     mCache.removeAt(i);
    605                 }
    606             }
    607 
    608             // Make sure in-flight loads can't write to cache.
    609             mVersion++;
    610         }
    611 
    612         synchronized void clear() {
    613             mCache.clear();
    614             mVersion++;
    615         }
    616 
    617         private static final class CacheKey {
    618             static final int TYPE_KEY_VALUE = 0;
    619             static final int TYPE_FILE = 1;
    620             static final int TYPE_FETCHED = 2;
    621 
    622             String key;
    623             int userId;
    624             int type;
    625 
    626             public CacheKey set(int type, String key, int userId) {
    627                 this.type = type;
    628                 this.key = key;
    629                 this.userId = userId;
    630                 return this;
    631             }
    632 
    633             @Override
    634             public boolean equals(Object obj) {
    635                 if (!(obj instanceof CacheKey))
    636                     return false;
    637                 CacheKey o = (CacheKey) obj;
    638                 return userId == o.userId && type == o.type && key.equals(o.key);
    639             }
    640 
    641             @Override
    642             public int hashCode() {
    643                 return key.hashCode() ^ userId ^ type;
    644             }
    645         }
    646     }
    647 }
    648