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 17package com.android.keychain.internal; 18 19import android.content.ContentValues; 20import android.content.Context; 21import android.content.pm.PackageManager; 22import android.database.Cursor; 23import android.database.DatabaseUtils; 24import android.database.sqlite.SQLiteDatabase; 25import android.database.sqlite.SQLiteOpenHelper; 26import android.util.Log; 27 28public 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