• 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 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 {
DatabaseHelper(Context context)61         public DatabaseHelper(Context context) {
62             super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION);
63         }
64 
createSelectableTable(final SQLiteDatabase db)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
onCreate(final SQLiteDatabase db)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 
hasEntryInUserSelectableTable(final SQLiteDatabase db, final String alias)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
onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion)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 
GrantsDatabase(Context context)145     public GrantsDatabase(Context context) {
146         mDatabaseHelper = new DatabaseHelper(context);
147     }
148 
destroy()149     public void destroy() {
150         mDatabaseHelper.close();
151         mDatabaseHelper = null;
152     }
153 
hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias)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 
hasGrant(final int uid, final String alias)163     public boolean hasGrant(final int uid, final String alias) {
164         final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
165         return hasGrantInternal(db, uid, alias);
166     }
167 
setGrant(final int uid, final String alias, final boolean value)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 
removeAliasInformation(String alias)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 
removeAllAliasesInformation()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 
purgeOldGrants(PackageManager pm)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 
setIsUserSelectable(final String alias, final boolean userSelectable)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 
isUserSelectable(final String alias)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