blob: f7d01d2e2e109913fd8a4929e9a5d0118d41ad93 [file] [log] [blame]
Eran Messeri5844b632017-11-03 10:28:34 +00001/*
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";
Eran Messeri3d8661a2018-03-19 16:48:47 +000032 private static final int DATABASE_VERSION = 2;
Eran Messeri5844b632017-11-03 10:28:34 +000033 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
Eran Messeri133b2722017-11-07 17:51:50 +000053 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
Eran Messeri5844b632017-11-03 10:28:34 +000058 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
Eran Messeri3d8661a2018-03-19 16:48:47 +000065 void createSelectableTable(final SQLiteDatabase db) {
Eran Messeri9c4a8da2018-03-22 21:52:33 +000066 // There are some broken V1 databases that actually have the 'userselectable'
67 // already created. Only create it if it does not exist.
Eran Messeri3d8661a2018-03-19 16:48:47 +000068 db.execSQL(
Eran Messeri9c4a8da2018-03-22 21:52:33 +000069 "CREATE TABLE IF NOT EXISTS "
Eran Messeri3d8661a2018-03-19 16:48:47 +000070 + 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
Eran Messeri5844b632017-11-03 10:28:34 +000081 @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 + "))");
Eran Messeri133b2722017-11-07 17:51:50 +000096
Eran Messeri3d8661a2018-03-19 16:48:47 +000097 createSelectableTable(db);
Eran Messeri5844b632017-11-03 10:28:34 +000098 }
99
Eran Messeri9c4a8da2018-03-22 21:52:33 +0000100 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
Eran Messeri5844b632017-11-03 10:28:34 +0000109 @Override
110 public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) {
Eran Messeri3d8661a2018-03-19 16:48:47 +0000111 Log.w(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
Eran Messeri5844b632017-11-03 10:28:34 +0000112
113 if (oldVersion == 1) {
Eran Messeri3d8661a2018-03-19 16:48:47 +0000114 // 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.
Eran Messeri5844b632017-11-03 10:28:34 +0000118 oldVersion++;
Eran Messeri3d8661a2018-03-19 16:48:47 +0000119 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);
Eran Messeri9c4a8da2018-03-22 21:52:33 +0000133 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 }
Eran Messeri3d8661a2018-03-19 16:48:47 +0000139 }
140 }
Eran Messeri5844b632017-11-03 10:28:34 +0000141 }
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
Eran Messerid0bcac82017-11-20 11:55:31 +0000185 public void removeAliasInformation(String alias) {
Eran Messeri5844b632017-11-03 10:28:34 +0000186 final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
187 db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_ALIAS, new String[] {alias});
Eran Messerid0bcac82017-11-20 11:55:31 +0000188 db.delete(TABLE_SELECTABLE, SELECTION_GRANTS_BY_ALIAS, new String[] {alias});
Eran Messeri5844b632017-11-03 10:28:34 +0000189 }
190
Eran Messerid0bcac82017-11-20 11:55:31 +0000191 public void removeAllAliasesInformation() {
Eran Messeri5844b632017-11-03 10:28:34 +0000192 final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
193 db.delete(TABLE_GRANTS, null /* whereClause */, null /* whereArgs */);
Eran Messerid0bcac82017-11-20 11:55:31 +0000194 db.delete(TABLE_SELECTABLE, null /* whereClause */, null /* whereArgs */);
Eran Messeri5844b632017-11-03 10:28:34 +0000195 }
196
197 public void purgeOldGrants(PackageManager pm) {
198 final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
Eran Messeri5844b632017-11-03 10:28:34 +0000199 db.beginTransaction();
Eran Messeri5a81ab02017-11-09 22:25:20 +0000200 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())) {
Eran Messeri5844b632017-11-03 10:28:34 +0000204 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();
Eran Messeri5844b632017-11-03 10:28:34 +0000218 }
Eran Messeri5a81ab02017-11-09 22:25:20 +0000219
220 db.endTransaction();
Eran Messeri5844b632017-11-03 10:28:34 +0000221 }
Eran Messeri133b2722017-11-07 17:51:50 +0000222
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));
Eran Messerid0bcac82017-11-20 11:55:31 +0000248 if (res.getCount() > 1) {
Eran Messeri133b2722017-11-07 17:51:50 +0000249 // 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 }
Eran Messeri5844b632017-11-03 10:28:34 +0000255}