Merge "KeyChain: Refactor DB handling + tests"
diff --git a/robotests/src/com/android/keychain/internal/GrantsDatabaseTest.java b/robotests/src/com/android/keychain/internal/GrantsDatabaseTest.java
new file mode 100644
index 0000000..074b376
--- /dev/null
+++ b/robotests/src/com/android/keychain/internal/GrantsDatabaseTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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 com.android.keychain.TestConfig;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Unit tests for {@link com.android.keychain.internal.GrantsDatabase}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public final class GrantsDatabaseTest {
+ private static final String DUMMY_ALIAS = "dummy_alias";
+ private static final String DUMMY_ALIAS2 = "another_dummy_alias";
+ private static final int DUMMY_UID = 1000;
+ private static final int DUMMY_UID2 = 1001;
+
+ private GrantsDatabase mGrantsDB;
+
+ @Before
+ public void setUp() {
+ mGrantsDB = new GrantsDatabase(RuntimeEnvironment.application);
+ }
+
+ @Test
+ public void testSetGrant_notMixingUIDs() {
+ mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS, true);
+ Assert.assertFalse(mGrantsDB.hasGrant(DUMMY_UID2, DUMMY_ALIAS));
+ }
+
+ @Test
+ public void testSetGrant_notMixingAliases() {
+ mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS, true);
+ Assert.assertFalse(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS2));
+ }
+
+ @Test
+ public void testSetGrantTrue() {
+ Assert.assertFalse(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS));
+ mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS, true);
+ Assert.assertTrue(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS));
+ }
+
+ @Test
+ public void testSetGrantFalse() {
+ mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS, false);
+ Assert.assertFalse(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS));
+ }
+
+ @Test
+ public void testSetGrantTrueThenFalse() {
+ mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS, true);
+ Assert.assertTrue(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS));
+ mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS, false);
+ Assert.assertFalse(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS));
+ }
+
+ @Test
+ public void testRemoveGrantsForAlias() {
+ mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS, true);
+ mGrantsDB.setGrant(DUMMY_UID2, DUMMY_ALIAS, true);
+ Assert.assertTrue(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS));
+ mGrantsDB.removeGrantsForAlias(DUMMY_ALIAS);
+ Assert.assertFalse(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS));
+ Assert.assertFalse(mGrantsDB.hasGrant(DUMMY_UID2, DUMMY_ALIAS));
+ }
+
+ @Test
+ public void testRemoveAllGrants() {
+ mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS, true);
+ mGrantsDB.setGrant(DUMMY_UID2, DUMMY_ALIAS, true);
+ mGrantsDB.setGrant(DUMMY_UID, DUMMY_ALIAS2, true);
+ mGrantsDB.removeAllGrants();
+ Assert.assertFalse(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS));
+ Assert.assertFalse(mGrantsDB.hasGrant(DUMMY_UID2, DUMMY_ALIAS));
+ Assert.assertFalse(mGrantsDB.hasGrant(DUMMY_UID, DUMMY_ALIAS2));
+ }
+}
diff --git a/src/com/android/keychain/KeyChainService.java b/src/com/android/keychain/KeyChainService.java
index f45a1e6..f4366ee 100644
--- a/src/com/android/keychain/KeyChainService.java
+++ b/src/com/android/keychain/KeyChainService.java
@@ -37,6 +37,7 @@
import android.security.KeyChain;
import android.security.KeyStore;
import android.util.Log;
+import com.android.keychain.internal.GrantsDatabase;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.cert.CertificateException;
@@ -54,25 +55,8 @@
private static final String TAG = "KeyChain";
- private static final String DATABASE_NAME = "grants.db";
- private static final int DATABASE_VERSION = 1;
- private static final String TABLE_GRANTS = "grants";
- private static final String GRANTS_ALIAS = "alias";
- private static final String GRANTS_GRANTEE_UID = "uid";
-
/** created in onCreate(), closed in onDestroy() */
- public DatabaseHelper mDatabaseHelper;
-
- 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 + "=?";
+ public GrantsDatabase mGrantsDb;
public KeyChainService() {
super(KeyChainService.class.getSimpleName());
@@ -80,14 +64,14 @@
@Override public void onCreate() {
super.onCreate();
- mDatabaseHelper = new DatabaseHelper(this);
+ mGrantsDb = new GrantsDatabase(this);
}
@Override
public void onDestroy() {
super.onDestroy();
- mDatabaseHelper.close();
- mDatabaseHelper = null;
+ mGrantsDb.destroy();
+ mGrantsDb = null;
}
private final IKeyChainService.Stub mIKeyChainService = new IKeyChainService.Stub() {
@@ -124,7 +108,7 @@
}
final int callingUid = getCallingUid();
- if (!hasGrantInternal(mDatabaseHelper.getReadableDatabase(), callingUid, alias)) {
+ if (!mGrantsDb.hasGrant(callingUid, alias)) {
throw new IllegalStateException("uid " + callingUid
+ " doesn't have permission to access the requested alias");
}
@@ -203,7 +187,7 @@
if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) {
return false;
}
- removeGrantsForAlias(alias);
+ mGrantsDb.removeGrantsForAlias(alias);
broadcastKeychainChange();
broadcastLegacyStorageChange();
return true;
@@ -217,7 +201,7 @@
@Override public boolean reset() {
// only Settings should be able to reset
checkSystemCaller();
- removeAllGrants(mDatabaseHelper.getWritableDatabase());
+ mGrantsDb.removeAllGrants();
boolean ok = true;
synchronized (mTrustedCertificateStore) {
// delete user-installed CA certs
@@ -283,12 +267,12 @@
@Override public boolean hasGrant(int uid, String alias) {
checkSystemCaller();
- return hasGrantInternal(mDatabaseHelper.getReadableDatabase(), uid, alias);
+ return mGrantsDb.hasGrant(uid, alias);
}
@Override public void setGrant(int uid, String alias, boolean value) {
checkSystemCaller();
- setGrantInternal(mDatabaseHelper.getWritableDatabase(), uid, alias, value);
+ mGrantsDb.setGrant(uid, alias, value);
broadcastPermissionChange(uid, alias, value);
broadcastLegacyStorageChange();
}
@@ -359,60 +343,6 @@
}
};
- private 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;
- }
-
- private void setGrantInternal(final SQLiteDatabase db,
- final int uid, final String alias, final boolean value) {
- 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});
- }
- }
-
- private void removeGrantsForAlias(String alias) {
- final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
- db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_ALIAS, new String[] {alias});
- }
-
- private void removeAllGrants(final SQLiteDatabase db) {
- db.delete(TABLE_GRANTS, null /* whereClause */, null /* whereArgs */);
- }
-
- private class DatabaseHelper extends SQLiteOpenHelper {
- public DatabaseHelper(Context context) {
- super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION);
- }
-
- @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 + "))");
- }
-
- @Override
- public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) {
- Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
-
- if (oldVersion == 1) {
- // the first upgrade step goes here
- oldVersion++;
- }
- }
- }
-
@Override public IBinder onBind(Intent intent) {
if (IKeyChainService.class.getName().equals(intent.getAction())) {
return mIKeyChainService;
@@ -423,35 +353,7 @@
@Override
protected void onHandleIntent(final Intent intent) {
if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
- purgeOldGrants();
- }
- }
-
- private void purgeOldGrants() {
- final PackageManager packageManager = getPackageManager();
- final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
- Cursor cursor = null;
- db.beginTransaction();
- try {
- cursor = db.query(TABLE_GRANTS,
- new String[]{GRANTS_GRANTEE_UID}, null, null, GRANTS_GRANTEE_UID, null, null);
- while (cursor.moveToNext()) {
- final int uid = cursor.getInt(0);
- final boolean packageExists = packageManager.getPackagesForUid(uid) != null;
- if (packageExists) {
- continue;
- }
- Log.d(TAG, "deleting grants for UID " + uid
- + " because its package is no longer installed");
- db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_UID,
- new String[]{Integer.toString(uid)});
- }
- db.setTransactionSuccessful();
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- db.endTransaction();
+ mGrantsDb.purgeOldGrants(getPackageManager());
}
}
diff --git a/src/com/android/keychain/internal/GrantsDatabase.java b/src/com/android/keychain/internal/GrantsDatabase.java
new file mode 100644
index 0000000..591d0a5
--- /dev/null
+++ b/src/com/android/keychain/internal/GrantsDatabase.java
@@ -0,0 +1,174 @@
+/*
+ * 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 = 1;
+ 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 + "=?";
+
+ public DatabaseHelper mDatabaseHelper;
+
+ private class DatabaseHelper extends SQLiteOpenHelper {
+ public DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION);
+ }
+
+ @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
+ + "))");
+ }
+
+ @Override
+ public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) {
+ Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
+
+ if (oldVersion == 1) {
+ // the first upgrade step goes here
+ oldVersion++;
+ }
+ }
+ }
+
+ 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 removeGrantsForAlias(String alias) {
+ final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
+ db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_ALIAS, new String[] {alias});
+ }
+
+ public void removeAllGrants() {
+ final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
+ db.delete(TABLE_GRANTS, null /* whereClause */, null /* whereArgs */);
+ }
+
+ public void purgeOldGrants(PackageManager pm) {
+ final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
+ Cursor cursor = null;
+ db.beginTransaction();
+ try {
+ cursor =
+ db.query(
+ TABLE_GRANTS,
+ new String[] {GRANTS_GRANTEE_UID},
+ null,
+ null,
+ GRANTS_GRANTEE_UID,
+ null,
+ null);
+ while (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();
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ db.endTransaction();
+ }
+ }
+}