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