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