Home | History | Annotate | Download | only in internal
      1 /*
      2  * Copyright (C) 2017 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.keychain.internal;
     18 
     19 import android.content.ContentValues;
     20 import android.content.Context;
     21 import android.content.pm.PackageManager;
     22 import android.database.Cursor;
     23 import android.database.DatabaseUtils;
     24 import android.database.sqlite.SQLiteDatabase;
     25 import android.database.sqlite.SQLiteOpenHelper;
     26 import android.util.Log;
     27 
     28 public class GrantsDatabase {
     29     private static final String TAG = "KeyChain";
     30 
     31     private static final String DATABASE_NAME = "grants.db";
     32     private static final int DATABASE_VERSION = 2;
     33     private static final String TABLE_GRANTS = "grants";
     34     private static final String GRANTS_ALIAS = "alias";
     35     private static final String GRANTS_GRANTEE_UID = "uid";
     36 
     37     private static final String SELECTION_COUNT_OF_MATCHING_GRANTS =
     38             "SELECT COUNT(*) FROM "
     39                     + TABLE_GRANTS
     40                     + " WHERE "
     41                     + GRANTS_GRANTEE_UID
     42                     + "=? AND "
     43                     + GRANTS_ALIAS
     44                     + "=?";
     45 
     46     private static final String SELECT_GRANTS_BY_UID_AND_ALIAS =
     47             GRANTS_GRANTEE_UID + "=? AND " + GRANTS_ALIAS + "=?";
     48 
     49     private static final String SELECTION_GRANTS_BY_UID = GRANTS_GRANTEE_UID + "=?";
     50 
     51     private static final String SELECTION_GRANTS_BY_ALIAS = GRANTS_ALIAS + "=?";
     52 
     53     private static final String TABLE_SELECTABLE = "userselectable";
     54     private static final String SELECTABLE_IS_SELECTABLE = "is_selectable";
     55     private static final String COUNT_SELECTABILITY_FOR_ALIAS =
     56             "SELECT COUNT(*) FROM " + TABLE_SELECTABLE + " WHERE " + GRANTS_ALIAS + "=?";
     57 
     58     public DatabaseHelper mDatabaseHelper;
     59 
     60     private class DatabaseHelper extends SQLiteOpenHelper {
     61         public DatabaseHelper(Context context) {
     62             super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION);
     63         }
     64 
     65         void createSelectableTable(final SQLiteDatabase db) {
     66             // There are some broken V1 databases that actually have the 'userselectable'
     67             // already created. Only create it if it does not exist.
     68             db.execSQL(
     69                     "CREATE TABLE IF NOT EXISTS "
     70                             + TABLE_SELECTABLE
     71                             + " (  "
     72                             + GRANTS_ALIAS
     73                             + " STRING NOT NULL,  "
     74                             + SELECTABLE_IS_SELECTABLE
     75                             + " STRING NOT NULL,  "
     76                             + "UNIQUE ("
     77                             + GRANTS_ALIAS
     78                             + "))");
     79         }
     80 
     81         @Override
     82         public void onCreate(final SQLiteDatabase db) {
     83             db.execSQL(
     84                     "CREATE TABLE "
     85                             + TABLE_GRANTS
     86                             + " (  "
     87                             + GRANTS_ALIAS
     88                             + " STRING NOT NULL,  "
     89                             + GRANTS_GRANTEE_UID
     90                             + " INTEGER NOT NULL,  "
     91                             + "UNIQUE ("
     92                             + GRANTS_ALIAS
     93                             + ","
     94                             + GRANTS_GRANTEE_UID
     95                             + "))");
     96 
     97             createSelectableTable(db);
     98         }
     99 
    100         private boolean hasEntryInUserSelectableTable(final SQLiteDatabase db, final String alias) {
    101             final long numMatches =
    102                     DatabaseUtils.longForQuery(
    103                             db,
    104                             COUNT_SELECTABILITY_FOR_ALIAS,
    105                             new String[] {alias});
    106             return numMatches > 0;
    107         }
    108 
    109         @Override
    110         public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) {
    111             Log.w(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
    112 
    113             if (oldVersion == 1) {
    114                 // Version 1 of the database does not have the 'userselectable' table, meaning
    115                 // upgraded keys could not be selected by users.
    116                 // The upgrade from version 1 to 2 consists of creating the 'userselectable'
    117                 // table and adding all existing keys as user-selectable ones into that table.
    118                 oldVersion++;
    119                 createSelectableTable(db);
    120 
    121                 try (Cursor cursor =
    122                         db.query(
    123                                 TABLE_GRANTS,
    124                                 new String[] {GRANTS_ALIAS},
    125                                 null,
    126                                 null,
    127                                 GRANTS_ALIAS,
    128                                 null,
    129                                 null)) {
    130 
    131                     while ((cursor != null) && (cursor.moveToNext())) {
    132                         final String alias = cursor.getString(0);
    133                         if (!hasEntryInUserSelectableTable(db, alias)) {
    134                             final ContentValues values = new ContentValues();
    135                             values.put(GRANTS_ALIAS, alias);
    136                             values.put(SELECTABLE_IS_SELECTABLE, Boolean.toString(true));
    137                             db.replace(TABLE_SELECTABLE, null, values);
    138                         }
    139                     }
    140                 }
    141             }
    142         }
    143     }
    144 
    145     public GrantsDatabase(Context context) {
    146         mDatabaseHelper = new DatabaseHelper(context);
    147     }
    148 
    149     public void destroy() {
    150         mDatabaseHelper.close();
    151         mDatabaseHelper = null;
    152     }
    153 
    154     boolean hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias) {
    155         final long numMatches =
    156                 DatabaseUtils.longForQuery(
    157                         db,
    158                         SELECTION_COUNT_OF_MATCHING_GRANTS,
    159                         new String[] {String.valueOf(uid), alias});
    160         return numMatches > 0;
    161     }
    162 
    163     public boolean hasGrant(final int uid, final String alias) {
    164         final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
    165         return hasGrantInternal(db, uid, alias);
    166     }
    167 
    168     public void setGrant(final int uid, final String alias, final boolean value) {
    169         final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
    170         if (value) {
    171             if (!hasGrantInternal(db, uid, alias)) {
    172                 final ContentValues values = new ContentValues();
    173                 values.put(GRANTS_ALIAS, alias);
    174                 values.put(GRANTS_GRANTEE_UID, uid);
    175                 db.insert(TABLE_GRANTS, GRANTS_ALIAS, values);
    176             }
    177         } else {
    178             db.delete(
    179                     TABLE_GRANTS,
    180                     SELECT_GRANTS_BY_UID_AND_ALIAS,
    181                     new String[] {String.valueOf(uid), alias});
    182         }
    183     }
    184 
    185     public void removeAliasInformation(String alias) {
    186         final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
    187         db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_ALIAS, new String[] {alias});
    188         db.delete(TABLE_SELECTABLE, SELECTION_GRANTS_BY_ALIAS, new String[] {alias});
    189     }
    190 
    191     public void removeAllAliasesInformation() {
    192         final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
    193         db.delete(TABLE_GRANTS, null /* whereClause */, null /* whereArgs */);
    194         db.delete(TABLE_SELECTABLE, null /* whereClause */, null /* whereArgs */);
    195     }
    196 
    197     public void purgeOldGrants(PackageManager pm) {
    198         final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
    199         db.beginTransaction();
    200         try (Cursor cursor = db.query(
    201                 TABLE_GRANTS,
    202                 new String[] {GRANTS_GRANTEE_UID}, null, null, GRANTS_GRANTEE_UID, null, null)) {
    203             while ((cursor != null) && (cursor.moveToNext())) {
    204                 final int uid = cursor.getInt(0);
    205                 final boolean packageExists = pm.getPackagesForUid(uid) != null;
    206                 if (packageExists) {
    207                     continue;
    208                 }
    209                 Log.d(TAG, String.format(
    210                         "deleting grants for UID %d because its package is no longer installed",
    211                         uid));
    212                 db.delete(
    213                         TABLE_GRANTS,
    214                         SELECTION_GRANTS_BY_UID,
    215                         new String[] {Integer.toString(uid)});
    216             }
    217             db.setTransactionSuccessful();
    218         }
    219 
    220         db.endTransaction();
    221     }
    222 
    223     public void setIsUserSelectable(final String alias, final boolean userSelectable) {
    224         final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
    225         final ContentValues values = new ContentValues();
    226         values.put(GRANTS_ALIAS, alias);
    227         values.put(SELECTABLE_IS_SELECTABLE, Boolean.toString(userSelectable));
    228 
    229         db.replace(TABLE_SELECTABLE, null, values);
    230     }
    231 
    232     public boolean isUserSelectable(final String alias) {
    233         final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
    234         try (Cursor res =
    235                 db.query(
    236                         TABLE_SELECTABLE,
    237                         new String[] {SELECTABLE_IS_SELECTABLE},
    238                         SELECTION_GRANTS_BY_ALIAS,
    239                         new String[] {alias},
    240                         null /* group by */,
    241                         null /* having */,
    242                         null /* order by */)) {
    243             if (res == null || !res.moveToNext()) {
    244                 return false;
    245             }
    246 
    247             boolean isSelectable = Boolean.parseBoolean(res.getString(0));
    248             if (res.getCount() > 1) {
    249                 // BUG! Should not have more than one result for any given alias.
    250                 Log.w(TAG, String.format("Have more than one result for alias %s", alias));
    251             }
    252             return isSelectable;
    253         }
    254     }
    255 }
    256