blob: f7d01d2e2e109913fd8a4929e9a5d0118d41ad93 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.keychain.internal;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class GrantsDatabase {
private static final String TAG = "KeyChain";
private static final String DATABASE_NAME = "grants.db";
private static final int DATABASE_VERSION = 2;
private static final String TABLE_GRANTS = "grants";
private static final String GRANTS_ALIAS = "alias";
private static final String GRANTS_GRANTEE_UID = "uid";
private static final String SELECTION_COUNT_OF_MATCHING_GRANTS =
"SELECT COUNT(*) FROM "
+ TABLE_GRANTS
+ " WHERE "
+ GRANTS_GRANTEE_UID
+ "=? AND "
+ GRANTS_ALIAS
+ "=?";
private static final String SELECT_GRANTS_BY_UID_AND_ALIAS =
GRANTS_GRANTEE_UID + "=? AND " + GRANTS_ALIAS + "=?";
private static final String SELECTION_GRANTS_BY_UID = GRANTS_GRANTEE_UID + "=?";
private static final String SELECTION_GRANTS_BY_ALIAS = GRANTS_ALIAS + "=?";
private static final String TABLE_SELECTABLE = "userselectable";
private static final String SELECTABLE_IS_SELECTABLE = "is_selectable";
private static final String COUNT_SELECTABILITY_FOR_ALIAS =
"SELECT COUNT(*) FROM " + TABLE_SELECTABLE + " WHERE " + GRANTS_ALIAS + "=?";
public DatabaseHelper mDatabaseHelper;
private class DatabaseHelper extends SQLiteOpenHelper {
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION);
}
void createSelectableTable(final SQLiteDatabase db) {
// There are some broken V1 databases that actually have the 'userselectable'
// already created. Only create it if it does not exist.
db.execSQL(
"CREATE TABLE IF NOT EXISTS "
+ TABLE_SELECTABLE
+ " ( "
+ GRANTS_ALIAS
+ " STRING NOT NULL, "
+ SELECTABLE_IS_SELECTABLE
+ " STRING NOT NULL, "
+ "UNIQUE ("
+ GRANTS_ALIAS
+ "))");
}
@Override
public void onCreate(final SQLiteDatabase db) {
db.execSQL(
"CREATE TABLE "
+ TABLE_GRANTS
+ " ( "
+ GRANTS_ALIAS
+ " STRING NOT NULL, "
+ GRANTS_GRANTEE_UID
+ " INTEGER NOT NULL, "
+ "UNIQUE ("
+ GRANTS_ALIAS
+ ","
+ GRANTS_GRANTEE_UID
+ "))");
createSelectableTable(db);
}
private boolean hasEntryInUserSelectableTable(final SQLiteDatabase db, final String alias) {
final long numMatches =
DatabaseUtils.longForQuery(
db,
COUNT_SELECTABILITY_FOR_ALIAS,
new String[] {alias});
return numMatches > 0;
}
@Override
public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) {
Log.w(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
if (oldVersion == 1) {
// Version 1 of the database does not have the 'userselectable' table, meaning
// upgraded keys could not be selected by users.
// The upgrade from version 1 to 2 consists of creating the 'userselectable'
// table and adding all existing keys as user-selectable ones into that table.
oldVersion++;
createSelectableTable(db);
try (Cursor cursor =
db.query(
TABLE_GRANTS,
new String[] {GRANTS_ALIAS},
null,
null,
GRANTS_ALIAS,
null,
null)) {
while ((cursor != null) && (cursor.moveToNext())) {
final String alias = cursor.getString(0);
if (!hasEntryInUserSelectableTable(db, alias)) {
final ContentValues values = new ContentValues();
values.put(GRANTS_ALIAS, alias);
values.put(SELECTABLE_IS_SELECTABLE, Boolean.toString(true));
db.replace(TABLE_SELECTABLE, null, values);
}
}
}
}
}
}
public GrantsDatabase(Context context) {
mDatabaseHelper = new DatabaseHelper(context);
}
public void destroy() {
mDatabaseHelper.close();
mDatabaseHelper = null;
}
boolean hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias) {
final long numMatches =
DatabaseUtils.longForQuery(
db,
SELECTION_COUNT_OF_MATCHING_GRANTS,
new String[] {String.valueOf(uid), alias});
return numMatches > 0;
}
public boolean hasGrant(final int uid, final String alias) {
final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
return hasGrantInternal(db, uid, alias);
}
public void setGrant(final int uid, final String alias, final boolean value) {
final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
if (value) {
if (!hasGrantInternal(db, uid, alias)) {
final ContentValues values = new ContentValues();
values.put(GRANTS_ALIAS, alias);
values.put(GRANTS_GRANTEE_UID, uid);
db.insert(TABLE_GRANTS, GRANTS_ALIAS, values);
}
} else {
db.delete(
TABLE_GRANTS,
SELECT_GRANTS_BY_UID_AND_ALIAS,
new String[] {String.valueOf(uid), alias});
}
}
public void removeAliasInformation(String alias) {
final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_ALIAS, new String[] {alias});
db.delete(TABLE_SELECTABLE, SELECTION_GRANTS_BY_ALIAS, new String[] {alias});
}
public void removeAllAliasesInformation() {
final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
db.delete(TABLE_GRANTS, null /* whereClause */, null /* whereArgs */);
db.delete(TABLE_SELECTABLE, null /* whereClause */, null /* whereArgs */);
}
public void purgeOldGrants(PackageManager pm) {
final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
db.beginTransaction();
try (Cursor cursor = db.query(
TABLE_GRANTS,
new String[] {GRANTS_GRANTEE_UID}, null, null, GRANTS_GRANTEE_UID, null, null)) {
while ((cursor != null) && (cursor.moveToNext())) {
final int uid = cursor.getInt(0);
final boolean packageExists = pm.getPackagesForUid(uid) != null;
if (packageExists) {
continue;
}
Log.d(TAG, String.format(
"deleting grants for UID %d because its package is no longer installed",
uid));
db.delete(
TABLE_GRANTS,
SELECTION_GRANTS_BY_UID,
new String[] {Integer.toString(uid)});
}
db.setTransactionSuccessful();
}
db.endTransaction();
}
public void setIsUserSelectable(final String alias, final boolean userSelectable) {
final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
final ContentValues values = new ContentValues();
values.put(GRANTS_ALIAS, alias);
values.put(SELECTABLE_IS_SELECTABLE, Boolean.toString(userSelectable));
db.replace(TABLE_SELECTABLE, null, values);
}
public boolean isUserSelectable(final String alias) {
final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
try (Cursor res =
db.query(
TABLE_SELECTABLE,
new String[] {SELECTABLE_IS_SELECTABLE},
SELECTION_GRANTS_BY_ALIAS,
new String[] {alias},
null /* group by */,
null /* having */,
null /* order by */)) {
if (res == null || !res.moveToNext()) {
return false;
}
boolean isSelectable = Boolean.parseBoolean(res.getString(0));
if (res.getCount() > 1) {
// BUG! Should not have more than one result for any given alias.
Log.w(TAG, String.format("Have more than one result for alias %s", alias));
}
return isSelectable;
}
}
}