aboutsummaryrefslogtreecommitdiffstats
path: root/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider
diff options
context:
space:
mode:
Diffstat (limited to 'OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider')
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java268
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java126
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java971
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobContract.java41
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobDatabase.java48
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobProvider.java152
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java809
7 files changed, 2415 insertions, 0 deletions
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java
new file mode 100644
index 000000000..d2381f6f0
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.provider;
+
+import org.sufficientlysecure.keychain.Constants;
+
+import android.net.Uri;
+import android.provider.BaseColumns;
+
+public class KeychainContract {
+
+ interface KeyRingsColumns {
+ String MASTER_KEY_ID = "master_key_id"; // not a database id
+ String TYPE = "type"; // see KeyTypes
+ String KEY_RING_DATA = "key_ring_data"; // PGPPublicKeyRing / PGPSecretKeyRing blob
+ }
+
+ interface KeysColumns {
+ String KEY_ID = "key_id"; // not a database id
+ String TYPE = "type"; // see KeyTypes
+ String IS_MASTER_KEY = "is_master_key";
+ String ALGORITHM = "algorithm";
+ String KEY_SIZE = "key_size";
+ String CAN_CERTIFY = "can_certify";
+ String CAN_SIGN = "can_sign";
+ String CAN_ENCRYPT = "can_encrypt";
+ String IS_REVOKED = "is_revoked";
+ String CREATION = "creation";
+ String EXPIRY = "expiry";
+ String KEY_RING_ROW_ID = "key_ring_row_id"; // foreign key to key_rings._ID
+ String KEY_DATA = "key_data"; // PGPPublicKey/PGPSecretKey blob
+ String RANK = "rank";
+ }
+
+ interface UserIdsColumns {
+ String KEY_RING_ROW_ID = "key_ring_row_id"; // foreign key to key_rings._ID
+ String USER_ID = "user_id"; // not a database id
+ String RANK = "rank";
+ }
+
+ interface ApiAppsColumns {
+ String PACKAGE_NAME = "package_name";
+ String PACKAGE_SIGNATURE = "package_signature";
+ String KEY_ID = "key_id"; // not a database id
+ String ENCRYPTION_ALGORITHM = "encryption_algorithm";
+ String HASH_ALORITHM = "hash_algorithm";
+ String COMPRESSION = "compression";
+ }
+
+ public static final class KeyTypes {
+ public static final int PUBLIC = 0;
+ public static final int SECRET = 1;
+ }
+
+ public static final String CONTENT_AUTHORITY = Constants.PACKAGE_NAME + ".provider";
+
+ private static final Uri BASE_CONTENT_URI_INTERNAL = Uri
+ .parse("content://" + CONTENT_AUTHORITY);
+
+ public static final String BASE_KEY_RINGS = "key_rings";
+ public static final String BASE_DATA = "data";
+
+ public static final String PATH_PUBLIC = "public";
+ public static final String PATH_SECRET = "secret";
+
+ public static final String PATH_BY_MASTER_KEY_ID = "master_key_id";
+ public static final String PATH_BY_KEY_ID = "key_id";
+ public static final String PATH_BY_EMAILS = "emails";
+ public static final String PATH_BY_LIKE_EMAIL = "like_email";
+
+ public static final String PATH_USER_IDS = "user_ids";
+ public static final String PATH_KEYS = "keys";
+
+ public static final String BASE_API_APPS = "api_apps";
+ public static final String PATH_BY_PACKAGE_NAME = "package_name";
+
+ public static class KeyRings implements KeyRingsColumns, BaseColumns {
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
+ .appendPath(BASE_KEY_RINGS).build();
+
+ /** Use if multiple items get returned */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.key_ring";
+
+ /** Use if a single item is returned */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.key_ring";
+
+ public static Uri buildPublicKeyRingsUri() {
+ return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).build();
+ }
+
+ public static Uri buildPublicKeyRingsUri(String keyRingRowId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId).build();
+ }
+
+ public static Uri buildPublicKeyRingsByMasterKeyIdUri(String masterKeyId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC)
+ .appendPath(PATH_BY_MASTER_KEY_ID).appendPath(masterKeyId).build();
+ }
+
+ public static Uri buildPublicKeyRingsByKeyIdUri(String keyId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(PATH_BY_KEY_ID)
+ .appendPath(keyId).build();
+ }
+
+ public static Uri buildPublicKeyRingsByEmailsUri(String emails) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(PATH_BY_EMAILS)
+ .appendPath(emails).build();
+ }
+
+ public static Uri buildPublicKeyRingsByLikeEmailUri(String emails) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(PATH_BY_LIKE_EMAIL)
+ .appendPath(emails).build();
+ }
+
+ public static Uri buildSecretKeyRingsUri() {
+ return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).build();
+ }
+
+ public static Uri buildSecretKeyRingsUri(String keyRingRowId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId).build();
+ }
+
+ public static Uri buildSecretKeyRingsByMasterKeyIdUri(String masterKeyId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_SECRET)
+ .appendPath(PATH_BY_MASTER_KEY_ID).appendPath(masterKeyId).build();
+ }
+
+ public static Uri buildSecretKeyRingsByKeyIdUri(String keyId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(PATH_BY_KEY_ID)
+ .appendPath(keyId).build();
+ }
+
+ public static Uri buildSecretKeyRingsByEmailsUri(String emails) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(PATH_BY_EMAILS)
+ .appendPath(emails).build();
+ }
+
+ public static Uri buildSecretKeyRingsByLikeEmails(String emails) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(PATH_BY_LIKE_EMAIL)
+ .appendPath(emails).build();
+ }
+ }
+
+ public static class Keys implements KeysColumns, BaseColumns {
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
+ .appendPath(BASE_KEY_RINGS).build();
+
+ /** Use if multiple items get returned */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.key";
+
+ /** Use if a single item is returned */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.key";
+
+ public static Uri buildPublicKeysUri(String keyRingRowId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId)
+ .appendPath(PATH_KEYS).build();
+ }
+
+ public static Uri buildPublicKeysUri(String keyRingRowId, String keyRowId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId)
+ .appendPath(PATH_KEYS).appendPath(keyRowId).build();
+ }
+
+ public static Uri buildSecretKeysUri(String keyRingRowId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId)
+ .appendPath(PATH_KEYS).build();
+ }
+
+ public static Uri buildSecretKeysUri(String keyRingRowId, String keyRowId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId)
+ .appendPath(PATH_KEYS).appendPath(keyRowId).build();
+ }
+
+ public static Uri buildKeysUri(Uri keyRingUri) {
+ return keyRingUri.buildUpon().appendPath(PATH_KEYS).build();
+ }
+
+ public static Uri buildKeysUri(Uri keyRingUri, String keyRowId) {
+ return keyRingUri.buildUpon().appendPath(PATH_KEYS).appendPath(keyRowId).build();
+ }
+ }
+
+ public static class UserIds implements UserIdsColumns, BaseColumns {
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
+ .appendPath(BASE_KEY_RINGS).build();
+
+ /** Use if multiple items get returned */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.user_id";
+
+ /** Use if a single item is returned */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.user_id";
+
+ public static Uri buildPublicUserIdsUri(String keyRingRowId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId)
+ .appendPath(PATH_USER_IDS).build();
+ }
+
+ public static Uri buildPublicUserIdsUri(String keyRingRowId, String userIdRowId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId)
+ .appendPath(PATH_USER_IDS).appendPath(userIdRowId).build();
+ }
+
+ public static Uri buildSecretUserIdsUri(String keyRingRowId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId)
+ .appendPath(PATH_USER_IDS).build();
+ }
+
+ public static Uri buildSecretUserIdsUri(String keyRingRowId, String userIdRowId) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId)
+ .appendPath(PATH_USER_IDS).appendPath(userIdRowId).build();
+ }
+
+ public static Uri buildUserIdsUri(Uri keyRingUri) {
+ return keyRingUri.buildUpon().appendPath(PATH_USER_IDS).build();
+ }
+
+ public static Uri buildUserIdsUri(Uri keyRingUri, String userIdRowId) {
+ return keyRingUri.buildUpon().appendPath(PATH_USER_IDS).appendPath(userIdRowId).build();
+ }
+ }
+
+ public static class ApiApps implements ApiAppsColumns, BaseColumns {
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
+ .appendPath(BASE_API_APPS).build();
+
+ /** Use if multiple items get returned */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.api_apps";
+
+ /** Use if a single item is returned */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.api_apps";
+
+ public static Uri buildIdUri(String rowId) {
+ return CONTENT_URI.buildUpon().appendPath(rowId).build();
+ }
+
+ public static Uri buildByPackageNameUri(String packageName) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_BY_PACKAGE_NAME).appendPath(packageName)
+ .build();
+ }
+ }
+
+ public static class DataStream {
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
+ .appendPath(BASE_DATA).build();
+
+ public static Uri buildDataStreamUri(String streamFilename) {
+ return CONTENT_URI.buildUpon().appendPath(streamFilename).build();
+ }
+ }
+
+ private KeychainContract() {
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
new file mode 100644
index 000000000..60c5c91a8
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.provider;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns;
+import org.sufficientlysecure.keychain.util.Log;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.provider.BaseColumns;
+
+public class KeychainDatabase extends SQLiteOpenHelper {
+ private static final String DATABASE_NAME = "apg.db";
+ private static final int DATABASE_VERSION = 6;
+
+ public interface Tables {
+ String KEY_RINGS = "key_rings";
+ String KEYS = "keys";
+ String USER_IDS = "user_ids";
+ String API_APPS = "api_apps";
+ }
+
+ private static final String CREATE_KEY_RINGS = "CREATE TABLE IF NOT EXISTS " + Tables.KEY_RINGS
+ + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ + KeyRingsColumns.MASTER_KEY_ID + " INT64, " + KeyRingsColumns.TYPE + " INTEGER, "
+ + KeyRingsColumns.KEY_RING_DATA + " BLOB)";
+
+ private static final String CREATE_KEYS = "CREATE TABLE IF NOT EXISTS " + Tables.KEYS + " ("
+ + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + KeysColumns.KEY_ID
+ + " INT64, " + KeysColumns.TYPE + " INTEGER, " + KeysColumns.IS_MASTER_KEY
+ + " INTEGER, " + KeysColumns.ALGORITHM + " INTEGER, " + KeysColumns.KEY_SIZE
+ + " INTEGER, " + KeysColumns.CAN_CERTIFY + " INTEGER, " + KeysColumns.CAN_SIGN
+ + " INTEGER, " + KeysColumns.CAN_ENCRYPT + " INTEGER, " + KeysColumns.IS_REVOKED
+ + " INTEGER, " + KeysColumns.CREATION + " INTEGER, " + KeysColumns.EXPIRY
+ + " INTEGER, " + KeysColumns.KEY_DATA + " BLOB," + KeysColumns.RANK + " INTEGER, "
+ + KeysColumns.KEY_RING_ROW_ID + " INTEGER NOT NULL, FOREIGN KEY("
+ + KeysColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "("
+ + BaseColumns._ID + ") ON DELETE CASCADE)";
+
+ private static final String CREATE_USER_IDS = "CREATE TABLE IF NOT EXISTS " + Tables.USER_IDS
+ + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ + UserIdsColumns.USER_ID + " TEXT, " + UserIdsColumns.RANK + " INTEGER, "
+ + UserIdsColumns.KEY_RING_ROW_ID + " INTEGER NOT NULL, FOREIGN KEY("
+ + UserIdsColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "("
+ + BaseColumns._ID + ") ON DELETE CASCADE)";
+
+ private static final String CREATE_API_APPS = "CREATE TABLE IF NOT EXISTS " + Tables.API_APPS
+ + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ + ApiAppsColumns.PACKAGE_NAME + " TEXT UNIQUE, " + ApiAppsColumns.PACKAGE_SIGNATURE
+ + " BLOB, " + ApiAppsColumns.KEY_ID + " INT64, " + ApiAppsColumns.ENCRYPTION_ALGORITHM
+ + " INTEGER, " + ApiAppsColumns.HASH_ALORITHM + " INTEGER, "
+ + ApiAppsColumns.COMPRESSION + " INTEGER)";
+
+ KeychainDatabase(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ Log.w(Constants.TAG, "Creating database...");
+
+ db.execSQL(CREATE_KEY_RINGS);
+ db.execSQL(CREATE_KEYS);
+ db.execSQL(CREATE_USER_IDS);
+ db.execSQL(CREATE_API_APPS);
+ }
+
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ super.onOpen(db);
+ if (!db.isReadOnly()) {
+ // Enable foreign key constraints
+ db.execSQL("PRAGMA foreign_keys=ON;");
+ }
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.w(Constants.TAG, "Upgrading database from version " + oldVersion + " to " + newVersion);
+
+ // Upgrade from oldVersion through all methods to newest one
+ for (int version = oldVersion; version < newVersion; ++version) {
+ Log.w(Constants.TAG, "Upgrading database to version " + version);
+
+ switch (version) {
+ case 3:
+ db.execSQL("ALTER TABLE " + Tables.KEYS + " ADD COLUMN " + KeysColumns.CAN_CERTIFY
+ + " INTEGER DEFAULT 0;");
+ db.execSQL("UPDATE " + Tables.KEYS + " SET " + KeysColumns.CAN_CERTIFY
+ + " = 1 WHERE " + KeysColumns.IS_MASTER_KEY + "= 1;");
+ break;
+ case 4:
+ db.execSQL(CREATE_API_APPS);
+ case 5:
+ // new column: package_signature
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.API_APPS);
+ db.execSQL(CREATE_API_APPS);
+
+ default:
+ break;
+
+ }
+ }
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
new file mode 100644
index 000000000..70fb11e47
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
@@ -0,0 +1,971 @@
+/*
+ * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
+ *
+ * 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 org.sufficientlysecure.keychain.provider;
+
+import java.util.Arrays;
+import java.util.HashMap;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes;
+import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns;
+import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
+import org.sufficientlysecure.keychain.util.Log;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteConstraintException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.provider.BaseColumns;
+import android.text.TextUtils;
+
+public class KeychainProvider extends ContentProvider {
+ // public static final String ACTION_BROADCAST_DATABASE_CHANGE = Constants.PACKAGE_NAME
+ // + ".action.DATABASE_CHANGE";
+ //
+ // public static final String EXTRA_BROADCAST_KEY_TYPE = "key_type";
+ // public static final String EXTRA_BROADCAST_CONTENT_ITEM_TYPE = "contentItemType";
+
+ private static final int PUBLIC_KEY_RING = 101;
+ private static final int PUBLIC_KEY_RING_BY_ROW_ID = 102;
+ private static final int PUBLIC_KEY_RING_BY_MASTER_KEY_ID = 103;
+ private static final int PUBLIC_KEY_RING_BY_KEY_ID = 104;
+ private static final int PUBLIC_KEY_RING_BY_EMAILS = 105;
+ private static final int PUBLIC_KEY_RING_BY_LIKE_EMAIL = 106;
+
+ private static final int PUBLIC_KEY_RING_KEY = 111;
+ private static final int PUBLIC_KEY_RING_KEY_BY_ROW_ID = 112;
+
+ private static final int PUBLIC_KEY_RING_USER_ID = 121;
+ private static final int PUBLIC_KEY_RING_USER_ID_BY_ROW_ID = 122;
+
+ private static final int SECRET_KEY_RING = 201;
+ private static final int SECRET_KEY_RING_BY_ROW_ID = 202;
+ private static final int SECRET_KEY_RING_BY_MASTER_KEY_ID = 203;
+ private static final int SECRET_KEY_RING_BY_KEY_ID = 204;
+ private static final int SECRET_KEY_RING_BY_EMAILS = 205;
+ private static final int SECRET_KEY_RING_BY_LIKE_EMAIL = 206;
+
+ private static final int SECRET_KEY_RING_KEY = 211;
+ private static final int SECRET_KEY_RING_KEY_BY_ROW_ID = 212;
+
+ private static final int SECRET_KEY_RING_USER_ID = 221;
+ private static final int SECRET_KEY_RING_USER_ID_BY_ROW_ID = 222;
+
+ private static final int API_APPS = 301;
+ private static final int API_APPS_BY_ROW_ID = 302;
+ private static final int API_APPS_BY_PACKAGE_NAME = 303;
+
+ // private static final int DATA_STREAM = 401;
+
+ protected UriMatcher mUriMatcher;
+
+ /**
+ * Build and return a {@link UriMatcher} that catches all {@link Uri} variations supported by
+ * this {@link ContentProvider}.
+ */
+ protected UriMatcher buildUriMatcher() {
+ final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
+
+ String authority = KeychainContract.CONTENT_AUTHORITY;
+
+ /**
+ * public key rings
+ *
+ * <pre>
+ * key_rings/public
+ * key_rings/public/#
+ * key_rings/public/master_key_id/_
+ * key_rings/public/key_id/_
+ * key_rings/public/emails/_
+ * key_rings/public/like_email/_
+ * </pre>
+ */
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_PUBLIC, PUBLIC_KEY_RING);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_PUBLIC + "/#", PUBLIC_KEY_RING_BY_ROW_ID);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_MASTER_KEY_ID
+ + "/*", PUBLIC_KEY_RING_BY_MASTER_KEY_ID);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_KEY_ID + "/*",
+ PUBLIC_KEY_RING_BY_KEY_ID);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_EMAILS + "/*",
+ PUBLIC_KEY_RING_BY_EMAILS);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_EMAILS,
+ PUBLIC_KEY_RING_BY_EMAILS); // without emails specified
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_LIKE_EMAIL + "/*",
+ PUBLIC_KEY_RING_BY_LIKE_EMAIL);
+
+ /**
+ * public keys
+ *
+ * <pre>
+ * key_rings/public/#/keys
+ * key_rings/public/#/keys/#
+ * </pre>
+ */
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_PUBLIC + "/#/" + KeychainContract.PATH_KEYS,
+ PUBLIC_KEY_RING_KEY);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_PUBLIC + "/#/" + KeychainContract.PATH_KEYS + "/#",
+ PUBLIC_KEY_RING_KEY_BY_ROW_ID);
+
+ /**
+ * public user ids
+ *
+ * <pre>
+ * key_rings/public/#/user_ids
+ * key_rings/public/#/user_ids/#
+ * </pre>
+ */
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_PUBLIC + "/#/" + KeychainContract.PATH_USER_IDS,
+ PUBLIC_KEY_RING_USER_ID);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_PUBLIC + "/#/" + KeychainContract.PATH_USER_IDS + "/#",
+ PUBLIC_KEY_RING_USER_ID_BY_ROW_ID);
+
+ /**
+ * secret key rings
+ *
+ * <pre>
+ * key_rings/secret
+ * key_rings/secret/#
+ * key_rings/secret/master_key_id/_
+ * key_rings/secret/key_id/_
+ * key_rings/secret/emails/_
+ * key_rings/secret/like_email/_
+ * </pre>
+ */
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_SECRET, SECRET_KEY_RING);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_SECRET + "/#", SECRET_KEY_RING_BY_ROW_ID);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_MASTER_KEY_ID
+ + "/*", SECRET_KEY_RING_BY_MASTER_KEY_ID);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_KEY_ID + "/*",
+ SECRET_KEY_RING_BY_KEY_ID);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_EMAILS + "/*",
+ SECRET_KEY_RING_BY_EMAILS);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_EMAILS,
+ SECRET_KEY_RING_BY_EMAILS); // without emails specified
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_LIKE_EMAIL + "/*",
+ SECRET_KEY_RING_BY_LIKE_EMAIL);
+
+ /**
+ * secret keys
+ *
+ * <pre>
+ * key_rings/secret/#/keys
+ * key_rings/secret/#/keys/#
+ * </pre>
+ */
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_SECRET + "/#/" + KeychainContract.PATH_KEYS,
+ SECRET_KEY_RING_KEY);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_SECRET + "/#/" + KeychainContract.PATH_KEYS + "/#",
+ SECRET_KEY_RING_KEY_BY_ROW_ID);
+
+ /**
+ * secret user ids
+ *
+ * <pre>
+ * key_rings/secret/#/user_ids
+ * key_rings/secret/#/user_ids/#
+ * </pre>
+ */
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_SECRET + "/#/" + KeychainContract.PATH_USER_IDS,
+ SECRET_KEY_RING_USER_ID);
+ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ + KeychainContract.PATH_SECRET + "/#/" + KeychainContract.PATH_USER_IDS + "/#",
+ SECRET_KEY_RING_USER_ID_BY_ROW_ID);
+
+ /**
+ * API apps
+ */
+ matcher.addURI(authority, KeychainContract.BASE_API_APPS, API_APPS);
+ matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/#", API_APPS_BY_ROW_ID);
+ matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/"
+ + KeychainContract.PATH_BY_PACKAGE_NAME + "/*", API_APPS_BY_PACKAGE_NAME);
+
+ /**
+ * data stream
+ *
+ * <pre>
+ * data / _
+ * </pre>
+ */
+ // matcher.addURI(authority, KeychainContract.BASE_DATA + "/*", DATA_STREAM);
+
+ return matcher;
+ }
+
+ private KeychainDatabase mApgDatabase;
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean onCreate() {
+ mUriMatcher = buildUriMatcher();
+ mApgDatabase = new KeychainDatabase(getContext());
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getType(Uri uri) {
+ final int match = mUriMatcher.match(uri);
+ switch (match) {
+ case PUBLIC_KEY_RING:
+ case PUBLIC_KEY_RING_BY_EMAILS:
+ case PUBLIC_KEY_RING_BY_LIKE_EMAIL:
+ case SECRET_KEY_RING:
+ case SECRET_KEY_RING_BY_EMAILS:
+ case SECRET_KEY_RING_BY_LIKE_EMAIL:
+ return KeyRings.CONTENT_TYPE;
+
+ case PUBLIC_KEY_RING_BY_ROW_ID:
+ case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
+ case PUBLIC_KEY_RING_BY_KEY_ID:
+ case SECRET_KEY_RING_BY_ROW_ID:
+ case SECRET_KEY_RING_BY_MASTER_KEY_ID:
+ case SECRET_KEY_RING_BY_KEY_ID:
+ return KeyRings.CONTENT_ITEM_TYPE;
+
+ case PUBLIC_KEY_RING_KEY:
+ case SECRET_KEY_RING_KEY:
+ return Keys.CONTENT_TYPE;
+
+ case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
+ case SECRET_KEY_RING_KEY_BY_ROW_ID:
+ return Keys.CONTENT_ITEM_TYPE;
+
+ case PUBLIC_KEY_RING_USER_ID:
+ case SECRET_KEY_RING_USER_ID:
+ return UserIds.CONTENT_TYPE;
+
+ case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
+ case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
+ return UserIds.CONTENT_ITEM_TYPE;
+
+ case API_APPS:
+ return ApiApps.CONTENT_TYPE;
+
+ case API_APPS_BY_ROW_ID:
+ case API_APPS_BY_PACKAGE_NAME:
+ return ApiApps.CONTENT_ITEM_TYPE;
+
+ default:
+ throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
+ }
+
+ /**
+ * Returns type of the query (secret/public)
+ *
+ * @param uri
+ * @return
+ */
+ private int getKeyType(int match) {
+ int type;
+ switch (match) {
+ case PUBLIC_KEY_RING:
+ case PUBLIC_KEY_RING_BY_ROW_ID:
+ case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
+ case PUBLIC_KEY_RING_BY_KEY_ID:
+ case PUBLIC_KEY_RING_BY_EMAILS:
+ case PUBLIC_KEY_RING_BY_LIKE_EMAIL:
+ case PUBLIC_KEY_RING_KEY:
+ case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
+ case PUBLIC_KEY_RING_USER_ID:
+ case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
+ type = KeyTypes.PUBLIC;
+ break;
+
+ case SECRET_KEY_RING:
+ case SECRET_KEY_RING_BY_ROW_ID:
+ case SECRET_KEY_RING_BY_MASTER_KEY_ID:
+ case SECRET_KEY_RING_BY_KEY_ID:
+ case SECRET_KEY_RING_BY_EMAILS:
+ case SECRET_KEY_RING_BY_LIKE_EMAIL:
+ case SECRET_KEY_RING_KEY:
+ case SECRET_KEY_RING_KEY_BY_ROW_ID:
+ case SECRET_KEY_RING_USER_ID:
+ case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
+ type = KeyTypes.SECRET;
+ break;
+
+ default:
+ Log.e(Constants.TAG, "Unknown match " + match);
+ type = -1;
+ break;
+ }
+
+ return type;
+ }
+
+ /**
+ * Set result of query to specific columns, don't show blob column for external content provider
+ *
+ * @return
+ */
+ private HashMap<String, String> getProjectionMapForKeyRings() {
+ HashMap<String, String> projectionMap = new HashMap<String, String>();
+
+ projectionMap.put(BaseColumns._ID, Tables.KEY_RINGS + "." + BaseColumns._ID);
+ projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEY_RINGS + "."
+ + KeyRingsColumns.MASTER_KEY_ID);
+ projectionMap.put(KeyRingsColumns.KEY_RING_DATA, Tables.KEY_RINGS + "."
+ + KeyRingsColumns.KEY_RING_DATA);
+ projectionMap.put(UserIdsColumns.USER_ID, Tables.USER_IDS + "." + UserIdsColumns.USER_ID);
+
+ return projectionMap;
+ }
+
+ /**
+ * Set result of query to specific columns, don't show blob column for external content provider
+ *
+ * @return
+ */
+ private HashMap<String, String> getProjectionMapForKeys() {
+ HashMap<String, String> projectionMap = new HashMap<String, String>();
+
+ projectionMap.put(BaseColumns._ID, BaseColumns._ID);
+ projectionMap.put(KeysColumns.KEY_ID, KeysColumns.KEY_ID);
+ projectionMap.put(KeysColumns.IS_MASTER_KEY, KeysColumns.IS_MASTER_KEY);
+ projectionMap.put(KeysColumns.ALGORITHM, KeysColumns.ALGORITHM);
+ projectionMap.put(KeysColumns.KEY_SIZE, KeysColumns.KEY_SIZE);
+ projectionMap.put(KeysColumns.CAN_CERTIFY, KeysColumns.CAN_CERTIFY);
+ projectionMap.put(KeysColumns.CAN_SIGN, KeysColumns.CAN_SIGN);
+ projectionMap.put(KeysColumns.CAN_ENCRYPT, KeysColumns.CAN_ENCRYPT);
+ projectionMap.put(KeysColumns.IS_REVOKED, KeysColumns.IS_REVOKED);
+ projectionMap.put(KeysColumns.CREATION, KeysColumns.CREATION);
+ projectionMap.put(KeysColumns.EXPIRY, KeysColumns.EXPIRY);
+ projectionMap.put(KeysColumns.KEY_RING_ROW_ID, KeysColumns.KEY_RING_ROW_ID);
+ projectionMap.put(KeysColumns.KEY_DATA, KeysColumns.KEY_DATA);
+ projectionMap.put(KeysColumns.RANK, KeysColumns.RANK);
+
+ return projectionMap;
+ }
+
+ /**
+ * Builds default query for keyRings: KeyRings table is joined with UserIds
+ *
+ * @param qb
+ * @param match
+ * @param isMasterKey
+ * @param sortOrder
+ * @return
+ */
+ private SQLiteQueryBuilder buildKeyRingQuery(SQLiteQueryBuilder qb, int match, String sortOrder) {
+ // public or secret keyring
+ qb.appendWhere(Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + " = ");
+ qb.appendWhereEscapeString(Integer.toString(getKeyType(match)));
+
+ // join keyrings with userIds to every keyring
+ qb.setTables(Tables.KEY_RINGS + " INNER JOIN " + Tables.USER_IDS + " ON " + "("
+ + Tables.KEY_RINGS + "." + BaseColumns._ID + " = " + Tables.USER_IDS + "."
+ + UserIdsColumns.KEY_RING_ROW_ID + " AND " + Tables.USER_IDS + "."
+ + UserIdsColumns.RANK + " = '0')");
+
+ qb.setProjectionMap(getProjectionMapForKeyRings());
+
+ return qb;
+ }
+
+ /**
+ * Builds default query for keyRings: KeyRings table is joined with Keys and UserIds
+ *
+ * @param qb
+ * @param match
+ * @param isMasterKey
+ * @param sortOrder
+ * @return
+ */
+ private SQLiteQueryBuilder buildKeyRingQueryWithKeys(SQLiteQueryBuilder qb, int match,
+ String sortOrder) {
+ // public or secret keyring
+ qb.appendWhere(Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + " = ");
+ qb.appendWhereEscapeString(Integer.toString(getKeyType(match)));
+
+ // join keyrings with keys and userIds to every keyring
+ qb.setTables(Tables.KEY_RINGS + " INNER JOIN " + Tables.KEYS + " ON " + "("
+ + Tables.KEY_RINGS + "." + BaseColumns._ID + " = " + Tables.KEYS + "."
+ + KeysColumns.KEY_RING_ROW_ID + ") " + " INNER JOIN " + Tables.USER_IDS + " ON "
+ + "(" + Tables.KEY_RINGS + "." + BaseColumns._ID + " = " + Tables.USER_IDS + "."
+ + UserIdsColumns.KEY_RING_ROW_ID + " AND " + Tables.USER_IDS + "."
+ + UserIdsColumns.RANK + " = '0')");
+
+ qb.setProjectionMap(getProjectionMapForKeyRings());
+
+ return qb;
+ }
+
+ /** {@inheritDoc} */
+ @SuppressWarnings("deprecation")
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ Log.v(Constants.TAG, "query(uri=" + uri + ", proj=" + Arrays.toString(projection) + ")");
+
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ SQLiteDatabase db = mApgDatabase.getReadableDatabase();
+
+ int match = mUriMatcher.match(uri);
+
+ switch (match) {
+ case PUBLIC_KEY_RING:
+ case SECRET_KEY_RING:
+ qb = buildKeyRingQuery(qb, match, sortOrder);
+
+ if (TextUtils.isEmpty(sortOrder)) {
+ sortOrder = Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC";
+ }
+
+ break;
+
+ case PUBLIC_KEY_RING_BY_ROW_ID:
+ case SECRET_KEY_RING_BY_ROW_ID:
+ qb = buildKeyRingQuery(qb, match, sortOrder);
+
+ qb.appendWhere(" AND " + Tables.KEY_RINGS + "." + BaseColumns._ID + " = ");
+ qb.appendWhereEscapeString(uri.getLastPathSegment());
+
+ if (TextUtils.isEmpty(sortOrder)) {
+ sortOrder = Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC";
+ }
+
+ break;
+
+ case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
+ case SECRET_KEY_RING_BY_MASTER_KEY_ID:
+ qb = buildKeyRingQuery(qb, match, sortOrder);
+
+ qb.appendWhere(" AND " + Tables.KEY_RINGS + "." + KeyRingsColumns.MASTER_KEY_ID + " = ");
+ qb.appendWhereEscapeString(uri.getLastPathSegment());
+
+ if (TextUtils.isEmpty(sortOrder)) {
+ sortOrder = Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC";
+ }
+
+ break;
+
+ case SECRET_KEY_RING_BY_KEY_ID:
+ case PUBLIC_KEY_RING_BY_KEY_ID:
+ qb = buildKeyRingQueryWithKeys(qb, match, sortOrder);
+
+ qb.appendWhere(" AND " + Tables.KEYS + "." + KeysColumns.KEY_ID + " = ");
+ qb.appendWhereEscapeString(uri.getLastPathSegment());
+
+ if (TextUtils.isEmpty(sortOrder)) {
+ sortOrder = Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC";
+ }
+
+ break;
+
+ case SECRET_KEY_RING_BY_EMAILS:
+ case PUBLIC_KEY_RING_BY_EMAILS:
+ qb = buildKeyRingQuery(qb, match, sortOrder);
+
+ String emails = uri.getLastPathSegment();
+ String chunks[] = emails.split(" *, *");
+ boolean gotCondition = false;
+ String emailWhere = "";
+ for (int i = 0; i < chunks.length; ++i) {
+ if (chunks[i].length() == 0) {
+ continue;
+ }
+ if (i != 0) {
+ emailWhere += " OR ";
+ }
+ emailWhere += "tmp." + UserIdsColumns.USER_ID + " LIKE ";
+ // match '*<email>', so it has to be at the *end* of the user id
+ emailWhere += DatabaseUtils.sqlEscapeString("%<" + chunks[i] + ">");
+ gotCondition = true;
+ }
+
+ if (gotCondition) {
+ qb.appendWhere(" AND EXISTS (SELECT tmp." + BaseColumns._ID + " FROM "
+ + Tables.USER_IDS + " AS tmp WHERE tmp." + UserIdsColumns.KEY_RING_ROW_ID
+ + " = " + Tables.KEY_RINGS + "." + BaseColumns._ID + " AND (" + emailWhere
+ + "))");
+ }
+
+ break;
+
+ case SECRET_KEY_RING_BY_LIKE_EMAIL:
+ case PUBLIC_KEY_RING_BY_LIKE_EMAIL:
+ qb = buildKeyRingQuery(qb, match, sortOrder);
+
+ String likeEmail = uri.getLastPathSegment();
+
+ String likeEmailWhere = "tmp." + UserIdsColumns.USER_ID + " LIKE "
+ + DatabaseUtils.sqlEscapeString("%<%" + likeEmail + "%>");
+
+ qb.appendWhere(" AND EXISTS (SELECT tmp." + BaseColumns._ID + " FROM "
+ + Tables.USER_IDS + " AS tmp WHERE tmp." + UserIdsColumns.KEY_RING_ROW_ID
+ + " = " + Tables.KEY_RINGS + "." + BaseColumns._ID + " AND (" + likeEmailWhere
+ + "))");
+
+ break;
+
+ case PUBLIC_KEY_RING_KEY:
+ case SECRET_KEY_RING_KEY:
+ qb.setTables(Tables.KEYS);
+ qb.appendWhere(KeysColumns.TYPE + " = ");
+ qb.appendWhereEscapeString(Integer.toString(getKeyType(match)));
+
+ qb.appendWhere(" AND " + KeysColumns.KEY_RING_ROW_ID + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(2));
+
+ qb.setProjectionMap(getProjectionMapForKeys());
+
+ break;
+
+ case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
+ case SECRET_KEY_RING_KEY_BY_ROW_ID:
+ qb.setTables(Tables.KEYS);
+ qb.appendWhere(KeysColumns.TYPE + " = ");
+ qb.appendWhereEscapeString(Integer.toString(getKeyType(match)));
+
+ qb.appendWhere(" AND " + KeysColumns.KEY_RING_ROW_ID + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(2));
+
+ qb.appendWhere(" AND " + BaseColumns._ID + " = ");
+ qb.appendWhereEscapeString(uri.getLastPathSegment());
+
+ qb.setProjectionMap(getProjectionMapForKeys());
+
+ break;
+
+ case PUBLIC_KEY_RING_USER_ID:
+ case SECRET_KEY_RING_USER_ID:
+ qb.setTables(Tables.USER_IDS);
+ qb.appendWhere(UserIdsColumns.KEY_RING_ROW_ID + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(2));
+
+ break;
+
+ case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
+ case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
+ qb.setTables(Tables.USER_IDS);
+ qb.appendWhere(UserIdsColumns.KEY_RING_ROW_ID + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(2));
+
+ qb.appendWhere(" AND " + BaseColumns._ID + " = ");
+ qb.appendWhereEscapeString(uri.getLastPathSegment());
+
+ break;
+
+ case API_APPS:
+ qb.setTables(Tables.API_APPS);
+
+ break;
+ case API_APPS_BY_ROW_ID:
+ qb.setTables(Tables.API_APPS);
+
+ qb.appendWhere(BaseColumns._ID + " = ");
+ qb.appendWhereEscapeString(uri.getLastPathSegment());
+
+ break;
+ case API_APPS_BY_PACKAGE_NAME:
+ qb.setTables(Tables.API_APPS);
+ qb.appendWhere(ApiApps.PACKAGE_NAME + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(2));
+
+ break;
+
+ default:
+ throw new IllegalArgumentException("Unknown URI " + uri);
+
+ }
+
+ // If no sort order is specified use the default
+ String orderBy;
+ if (TextUtils.isEmpty(sortOrder)) {
+ orderBy = null;
+ } else {
+ orderBy = sortOrder;
+ }
+
+ Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
+
+ // Tell the cursor what uri to watch, so it knows when its source data changes
+ c.setNotificationUri(getContext().getContentResolver(), uri);
+
+ if (Constants.DEBUG) {
+ Log.d(Constants.TAG,
+ "Query: "
+ + qb.buildQuery(projection, selection, selectionArgs, null, null,
+ orderBy, null));
+ Log.d(Constants.TAG, "Cursor: " + DatabaseUtils.dumpCursorToString(c));
+ }
+
+ return c;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ Log.d(Constants.TAG, "insert(uri=" + uri + ", values=" + values.toString() + ")");
+
+ final SQLiteDatabase db = mApgDatabase.getWritableDatabase();
+
+ Uri rowUri = null;
+ long rowId = -1;
+ try {
+ final int match = mUriMatcher.match(uri);
+
+ switch (match) {
+ case PUBLIC_KEY_RING:
+ values.put(KeyRings.TYPE, KeyTypes.PUBLIC);
+
+ rowId = db.insertOrThrow(Tables.KEY_RINGS, null, values);
+ rowUri = KeyRings.buildPublicKeyRingsUri(Long.toString(rowId));
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+
+ break;
+ case PUBLIC_KEY_RING_KEY:
+ values.put(Keys.TYPE, KeyTypes.PUBLIC);
+
+ rowId = db.insertOrThrow(Tables.KEYS, null, values);
+ rowUri = Keys.buildPublicKeysUri(Long.toString(rowId));
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+
+ break;
+ case PUBLIC_KEY_RING_USER_ID:
+ rowId = db.insertOrThrow(Tables.USER_IDS, null, values);
+ rowUri = UserIds.buildPublicUserIdsUri(Long.toString(rowId));
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+
+ break;
+ case SECRET_KEY_RING:
+ values.put(KeyRings.TYPE, KeyTypes.SECRET);
+
+ rowId = db.insertOrThrow(Tables.KEY_RINGS, null, values);
+ rowUri = KeyRings.buildSecretKeyRingsUri(Long.toString(rowId));
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+
+ break;
+ case SECRET_KEY_RING_KEY:
+ values.put(Keys.TYPE, KeyTypes.SECRET);
+
+ rowId = db.insertOrThrow(Tables.KEYS, null, values);
+ rowUri = Keys.buildSecretKeysUri(Long.toString(rowId));
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+
+ break;
+ case SECRET_KEY_RING_USER_ID:
+ rowId = db.insertOrThrow(Tables.USER_IDS, null, values);
+ rowUri = UserIds.buildSecretUserIdsUri(Long.toString(rowId));
+
+ break;
+ case API_APPS:
+ rowId = db.insertOrThrow(Tables.API_APPS, null, values);
+ rowUri = ApiApps.buildIdUri(Long.toString(rowId));
+
+ break;
+ default:
+ throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
+
+ // notify of changes in db
+ getContext().getContentResolver().notifyChange(uri, null);
+
+ } catch (SQLiteConstraintException e) {
+ Log.e(Constants.TAG, "Constraint exception on insert! Entry already existing?");
+ }
+
+ return rowUri;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ Log.v(Constants.TAG, "delete(uri=" + uri + ")");
+
+ final SQLiteDatabase db = mApgDatabase.getWritableDatabase();
+
+ int count;
+ final int match = mUriMatcher.match(uri);
+
+ String defaultSelection = null;
+ switch (match) {
+ case PUBLIC_KEY_RING_BY_ROW_ID:
+ case SECRET_KEY_RING_BY_ROW_ID:
+ defaultSelection = BaseColumns._ID + "=" + uri.getLastPathSegment();
+ // corresponding keys and userIds are deleted by ON DELETE CASCADE
+ count = db.delete(Tables.KEY_RINGS,
+ buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection),
+ selectionArgs);
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+ break;
+ case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
+ case SECRET_KEY_RING_BY_MASTER_KEY_ID:
+ defaultSelection = KeyRings.MASTER_KEY_ID + "=" + uri.getLastPathSegment();
+ // corresponding keys and userIds are deleted by ON DELETE CASCADE
+ count = db.delete(Tables.KEY_RINGS,
+ buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection),
+ selectionArgs);
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+ break;
+ case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
+ case SECRET_KEY_RING_KEY_BY_ROW_ID:
+ count = db.delete(Tables.KEYS,
+ buildDefaultKeysSelection(uri, getKeyType(match), selection), selectionArgs);
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+ break;
+ case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
+ case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
+ count = db.delete(Tables.KEYS, buildDefaultUserIdsSelection(uri, selection),
+ selectionArgs);
+ break;
+ case API_APPS_BY_ROW_ID:
+ count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, false, selection),
+ selectionArgs);
+ break;
+ case API_APPS_BY_PACKAGE_NAME:
+ count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, true, selection),
+ selectionArgs);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
+
+ // notify of changes in db
+ getContext().getContentResolver().notifyChange(uri, null);
+
+ return count;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ Log.v(Constants.TAG, "update(uri=" + uri + ", values=" + values.toString() + ")");
+
+ final SQLiteDatabase db = mApgDatabase.getWritableDatabase();
+
+ String defaultSelection = null;
+ int count = 0;
+ try {
+ final int match = mUriMatcher.match(uri);
+ switch (match) {
+ case PUBLIC_KEY_RING_BY_ROW_ID:
+ case SECRET_KEY_RING_BY_ROW_ID:
+ defaultSelection = BaseColumns._ID + "=" + uri.getLastPathSegment();
+
+ count = db.update(
+ Tables.KEY_RINGS,
+ values,
+ buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match),
+ selection), selectionArgs);
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+
+ break;
+ case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
+ case SECRET_KEY_RING_BY_MASTER_KEY_ID:
+ defaultSelection = KeyRings.MASTER_KEY_ID + "=" + uri.getLastPathSegment();
+
+ count = db.update(
+ Tables.KEY_RINGS,
+ values,
+ buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match),
+ selection), selectionArgs);
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+
+ break;
+ case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
+ case SECRET_KEY_RING_KEY_BY_ROW_ID:
+ count = db
+ .update(Tables.KEYS, values,
+ buildDefaultKeysSelection(uri, getKeyType(match), selection),
+ selectionArgs);
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+
+ break;
+ case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
+ case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
+ count = db.update(Tables.USER_IDS, values,
+ buildDefaultUserIdsSelection(uri, selection), selectionArgs);
+ break;
+ case API_APPS_BY_ROW_ID:
+ count = db.update(Tables.API_APPS, values,
+ buildDefaultApiAppsSelection(uri, false, selection), selectionArgs);
+ break;
+ case API_APPS_BY_PACKAGE_NAME:
+ count = db.update(Tables.API_APPS, values,
+ buildDefaultApiAppsSelection(uri, true, selection), selectionArgs);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
+
+ // notify of changes in db
+ getContext().getContentResolver().notifyChange(uri, null);
+
+ } catch (SQLiteConstraintException e) {
+ Log.e(Constants.TAG, "Constraint exception on update! Entry already existing?");
+ }
+
+ return count;
+ }
+
+ /**
+ * Build default selection statement for KeyRings. If no extra selection is specified only build
+ * where clause with rowId
+ *
+ * @param uri
+ * @param selection
+ * @return
+ */
+ private String buildDefaultKeyRingsSelection(String defaultSelection, Integer keyType,
+ String selection) {
+ String andType = "";
+ if (keyType != null) {
+ andType = " AND " + KeyRingsColumns.TYPE + "=" + keyType;
+ }
+
+ String andSelection = "";
+ if (!TextUtils.isEmpty(selection)) {
+ andSelection = " AND (" + selection + ")";
+ }
+
+ return defaultSelection + andType + andSelection;
+ }
+
+ /**
+ * Build default selection statement for Keys. If no extra selection is specified only build
+ * where clause with rowId
+ *
+ * @param uri
+ * @param selection
+ * @return
+ */
+ private String buildDefaultKeysSelection(Uri uri, Integer keyType, String selection) {
+ String rowId = uri.getLastPathSegment();
+
+ String foreignKeyRingRowId = uri.getPathSegments().get(2);
+ String andForeignKeyRing = " AND " + KeysColumns.KEY_RING_ROW_ID + " = "
+ + foreignKeyRingRowId;
+
+ String andType = "";
+ if (keyType != null) {
+ andType = " AND " + KeysColumns.TYPE + "=" + keyType;
+ }
+
+ String andSelection = "";
+ if (!TextUtils.isEmpty(selection)) {
+ andSelection = " AND (" + selection + ")";
+ }
+
+ return BaseColumns._ID + "=" + rowId + andForeignKeyRing + andType + andSelection;
+ }
+
+ /**
+ * Build default selection statement for UserIds. If no extra selection is specified only build
+ * where clause with rowId
+ *
+ * @param uri
+ * @param selection
+ * @return
+ */
+ private String buildDefaultUserIdsSelection(Uri uri, String selection) {
+ String rowId = uri.getLastPathSegment();
+
+ String foreignKeyRingRowId = uri.getPathSegments().get(2);
+ String andForeignKeyRing = " AND " + KeysColumns.KEY_RING_ROW_ID + " = "
+ + foreignKeyRingRowId;
+
+ String andSelection = "";
+ if (!TextUtils.isEmpty(selection)) {
+ andSelection = " AND (" + selection + ")";
+ }
+
+ return BaseColumns._ID + "=" + rowId + andForeignKeyRing + andSelection;
+ }
+
+ /**
+ * Build default selection statement for API apps. If no extra selection is specified only build
+ * where clause with rowId
+ *
+ * @param uri
+ * @param selection
+ * @return
+ */
+ private String buildDefaultApiAppsSelection(Uri uri, boolean packageSelection, String selection) {
+ String lastPathSegment = uri.getLastPathSegment();
+
+ String andSelection = "";
+ if (!TextUtils.isEmpty(selection)) {
+ andSelection = " AND (" + selection + ")";
+ }
+
+ if (packageSelection) {
+ return ApiApps.PACKAGE_NAME + "=" + lastPathSegment + andSelection;
+ } else {
+ return BaseColumns._ID + "=" + lastPathSegment + andSelection;
+ }
+ }
+
+ // @Override
+ // public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ // int match = mUriMatcher.match(uri);
+ // if (match != DATA_STREAM) {
+ // throw new FileNotFoundException();
+ // }
+ // String fileName = uri.getLastPathSegment();
+ // File file = new File(getContext().getFilesDir().getAbsolutePath(), fileName);
+ // return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+ // }
+
+ /**
+ * This broadcast is send system wide to inform other application that a keyring was inserted,
+ * updated, or deleted
+ */
+ private void sendBroadcastDatabaseChange(int keyType, String contentItemType) {
+ // TODO: Disabled, old API
+ // Intent intent = new Intent();
+ // intent.setAction(ACTION_BROADCAST_DATABASE_CHANGE);
+ // intent.putExtra(EXTRA_BROADCAST_KEY_TYPE, keyType);
+ // intent.putExtra(EXTRA_BROADCAST_CONTENT_ITEM_TYPE, contentItemType);
+ //
+ // getContext().sendBroadcast(intent, Constants.PERMISSION_ACCESS_API);
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobContract.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobContract.java
new file mode 100644
index 000000000..a879d60a8
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobContract.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.provider;
+
+import org.sufficientlysecure.keychain.Constants;
+
+import android.net.Uri;
+import android.provider.BaseColumns;
+
+public class KeychainServiceBlobContract {
+
+ interface BlobsColumns {
+ String KEY = "key";
+ }
+
+ public static final String CONTENT_AUTHORITY = Constants.PACKAGE_NAME + ".blobs";
+
+ private static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
+
+ public static class Blobs implements BlobsColumns, BaseColumns {
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI;
+ }
+
+ private KeychainServiceBlobContract() {
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobDatabase.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobDatabase.java
new file mode 100644
index 000000000..fcee76fd7
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobDatabase.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2011 Markus Doits <markus.doits@googlemail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.provider;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.provider.BaseColumns;
+
+import org.sufficientlysecure.keychain.provider.KeychainServiceBlobContract.BlobsColumns;
+
+public class KeychainServiceBlobDatabase extends SQLiteOpenHelper {
+ private static final String DATABASE_NAME = "apg_blob.db";
+ private static final int DATABASE_VERSION = 2;
+
+ public static final String TABLE = "data";
+
+ public KeychainServiceBlobDatabase(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TABLE + " ( " + BaseColumns._ID
+ + " INTEGER PRIMARY KEY AUTOINCREMENT, " + BlobsColumns.KEY + " TEXT NOT NULL)");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // no upgrade necessary yet
+ }
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobProvider.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobProvider.java
new file mode 100644
index 000000000..5693e6de2
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobProvider.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2011 Markus Doits <markus.doits@googlemail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.provider;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.provider.KeychainServiceBlobContract.Blobs;
+import org.sufficientlysecure.keychain.provider.KeychainServiceBlobContract.BlobsColumns;
+import org.sufficientlysecure.keychain.util.Log;
+
+import android.content.ContentProvider;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.provider.BaseColumns;
+
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
+import java.util.UUID;
+
+public class KeychainServiceBlobProvider extends ContentProvider {
+ private static final String STORE_PATH = Constants.path.APP_DIR + "/ApgBlobs";
+
+ private KeychainServiceBlobDatabase mBlobDatabase = null;
+
+ public KeychainServiceBlobProvider() {
+ File dir = new File(STORE_PATH);
+ dir.mkdirs();
+ }
+
+ @Override
+ public boolean onCreate() {
+ mBlobDatabase = new KeychainServiceBlobDatabase(getContext());
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Uri insert(Uri uri, ContentValues ignored) {
+ // ContentValues are actually ignored, because we want to store a blob with no more
+ // information but have to create an record with the password generated here first
+ ContentValues vals = new ContentValues();
+
+ // Insert a random key in the database. This has to provided by the caller when updating or
+ // getting the blob
+ String password = UUID.randomUUID().toString();
+ vals.put(BlobsColumns.KEY, password);
+
+ SQLiteDatabase db = mBlobDatabase.getWritableDatabase();
+ long newRowId = db.insert(KeychainServiceBlobDatabase.TABLE, null, vals);
+ Uri insertedUri = ContentUris.withAppendedId(Blobs.CONTENT_URI, newRowId);
+
+ return Uri.withAppendedPath(insertedUri, password);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode) throws SecurityException,
+ FileNotFoundException {
+ Log.d(Constants.TAG, "openFile() called with uri: " + uri.toString() + " and mode: " + mode);
+
+ List<String> segments = uri.getPathSegments();
+ if (segments.size() < 2) {
+ throw new SecurityException("Password not found in URI");
+ }
+ String id = segments.get(0);
+ String key = segments.get(1);
+
+ Log.d(Constants.TAG, "Got id: " + id + " and key: " + key);
+
+ // get the data
+ SQLiteDatabase db = mBlobDatabase.getReadableDatabase();
+ Cursor result = db.query(KeychainServiceBlobDatabase.TABLE, new String[] { BaseColumns._ID },
+ BaseColumns._ID + " = ? and " + BlobsColumns.KEY + " = ?",
+ new String[] { id, key }, null, null, null);
+
+ if (result.getCount() == 0) {
+ // either the key is wrong or no id exists
+ throw new FileNotFoundException("No file found with that ID and/or password");
+ }
+
+ File targetFile = new File(STORE_PATH, id);
+ if (mode.equals("w")) {
+ Log.d(Constants.TAG, "Try to open file w");
+ if (!targetFile.exists()) {
+ try {
+ targetFile.createNewFile();
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "Got IEOException on creating new file", e);
+ throw new FileNotFoundException("Could not create file to write to");
+ }
+ }
+ return ParcelFileDescriptor.open(targetFile, ParcelFileDescriptor.MODE_WRITE_ONLY
+ | ParcelFileDescriptor.MODE_TRUNCATE);
+ } else if (mode.equals("r")) {
+ Log.d(Constants.TAG, "Try to open file r");
+ if (!targetFile.exists()) {
+ throw new FileNotFoundException("Error: Could not find the file requested");
+ }
+ return ParcelFileDescriptor.open(targetFile, ParcelFileDescriptor.MODE_READ_ONLY);
+ }
+
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getType(Uri uri) {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
new file mode 100644
index 000000000..1683c7c0e
--- /dev/null
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
@@ -0,0 +1,809 @@
+/*
+ * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.provider;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+
+import org.spongycastle.bcpg.ArmoredOutputStream;
+import org.spongycastle.openpgp.PGPKeyRing;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
+import org.sufficientlysecure.keychain.pgp.PgpHelper;
+import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
+import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
+import org.sufficientlysecure.keychain.service.remote.AppSettings;
+import org.sufficientlysecure.keychain.util.IterableIterator;
+import org.sufficientlysecure.keychain.util.Log;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.net.Uri;
+import android.os.RemoteException;
+
+public class ProviderHelper {
+
+ /**
+ * Private helper method to get PGPKeyRing from database
+ *
+ * @param context
+ * @param queryUri
+ * @return
+ */
+ public static PGPKeyRing getPGPKeyRing(Context context, Uri queryUri) {
+ Cursor cursor = context.getContentResolver().query(queryUri,
+ new String[] { KeyRings._ID, KeyRings.KEY_RING_DATA }, null, null, null);
+
+ PGPKeyRing keyRing = null;
+ if (cursor != null && cursor.moveToFirst()) {
+ int keyRingDataCol = cursor.getColumnIndex(KeyRings.KEY_RING_DATA);
+
+ byte[] data = cursor.getBlob(keyRingDataCol);
+ if (data != null) {
+ keyRing = PgpConversionHelper.BytesToPGPKeyRing(data);
+ }
+ }
+
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ return keyRing;
+ }
+
+ /**
+ * Retrieves the actual PGPPublicKeyRing object from the database blob based on the rowId
+ *
+ * @param context
+ * @param rowId
+ * @return
+ */
+ public static PGPPublicKeyRing getPGPPublicKeyRingByRowId(Context context, long rowId) {
+ Uri queryUri = KeyRings.buildPublicKeyRingsUri(Long.toString(rowId));
+ return (PGPPublicKeyRing) getPGPKeyRing(context, queryUri);
+ }
+
+ /**
+ * Retrieves the actual PGPPublicKeyRing object from the database blob based on the maserKeyId
+ *
+ * @param context
+ * @param masterKeyId
+ * @return
+ */
+ public static PGPPublicKeyRing getPGPPublicKeyRingByMasterKeyId(Context context,
+ long masterKeyId) {
+ Uri queryUri = KeyRings.buildPublicKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId));
+ return (PGPPublicKeyRing) getPGPKeyRing(context, queryUri);
+ }
+
+ /**
+ * Retrieves the actual PGPPublicKeyRing object from the database blob associated with a key
+ * with this keyId
+ *
+ * @param context
+ * @param keyId
+ * @return
+ */
+ public static PGPPublicKeyRing getPGPPublicKeyRingByKeyId(Context context, long keyId) {
+ Uri queryUri = KeyRings.buildPublicKeyRingsByKeyIdUri(Long.toString(keyId));
+ return (PGPPublicKeyRing) getPGPKeyRing(context, queryUri);
+ }
+
+ /**
+ * Retrieves the actual PGPPublicKey object from the database blob associated with a key with
+ * this keyId
+ *
+ * @param context
+ * @param keyId
+ * @return
+ */
+ public static PGPPublicKey getPGPPublicKeyByKeyId(Context context, long keyId) {
+ PGPPublicKeyRing keyRing = getPGPPublicKeyRingByKeyId(context, keyId);
+ if (keyRing == null) {
+ return null;
+ }
+
+ return keyRing.getPublicKey(keyId);
+ }
+
+ /**
+ * Retrieves the actual PGPSecretKeyRing object from the database blob based on the rowId
+ *
+ * @param context
+ * @param rowId
+ * @return
+ */
+ public static PGPSecretKeyRing getPGPSecretKeyRingByRowId(Context context, long rowId) {
+ Uri queryUri = KeyRings.buildSecretKeyRingsUri(Long.toString(rowId));
+ return (PGPSecretKeyRing) getPGPKeyRing(context, queryUri);
+ }
+
+ /**
+ * Retrieves the actual PGPSecretKeyRing object from the database blob based on the maserKeyId
+ *
+ * @param context
+ * @param masterKeyId
+ * @return
+ */
+ public static PGPSecretKeyRing getPGPSecretKeyRingByMasterKeyId(Context context,
+ long masterKeyId) {
+ Uri queryUri = KeyRings.buildSecretKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId));
+ return (PGPSecretKeyRing) getPGPKeyRing(context, queryUri);
+ }
+
+ /**
+ * Retrieves the actual PGPSecretKeyRing object from the database blob associated with a key
+ * with this keyId
+ *
+ * @param context
+ * @param keyId
+ * @return
+ */
+ public static PGPSecretKeyRing getPGPSecretKeyRingByKeyId(Context context, long keyId) {
+ Uri queryUri = KeyRings.buildSecretKeyRingsByKeyIdUri(Long.toString(keyId));
+ return (PGPSecretKeyRing) getPGPKeyRing(context, queryUri);
+ }
+
+ /**
+ * Retrieves the actual PGPSecretKey object from the database blob associated with a key with
+ * this keyId
+ *
+ * @param context
+ * @param keyId
+ * @return
+ */
+ public static PGPSecretKey getPGPSecretKeyByKeyId(Context context, long keyId) {
+ PGPSecretKeyRing keyRing = getPGPSecretKeyRingByKeyId(context, keyId);
+ if (keyRing == null) {
+ return null;
+ }
+
+ return keyRing.getSecretKey(keyId);
+ }
+
+ /**
+ * Saves PGPPublicKeyRing with its keys and userIds in DB
+ *
+ * @param context
+ * @param keyRing
+ * @return
+ * @throws IOException
+ * @throws GeneralException
+ */
+ @SuppressWarnings("unchecked")
+ public static void saveKeyRing(Context context, PGPPublicKeyRing keyRing) throws IOException {
+ PGPPublicKey masterKey = keyRing.getPublicKey();
+ long masterKeyId = masterKey.getKeyID();
+
+ // delete old version of this keyRing, which also deletes all keys and userIds on cascade
+ Uri deleteUri = KeyRings.buildPublicKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId));
+
+ try {
+ context.getContentResolver().delete(deleteUri, null, null);
+ } catch (UnsupportedOperationException e) {
+ Log.e(Constants.TAG, "Key could not be deleted! Maybe we are creating a new one!", e);
+ }
+
+ ContentValues values = new ContentValues();
+ values.put(KeyRings.MASTER_KEY_ID, masterKeyId);
+ values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded());
+
+ // insert new version of this keyRing
+ Uri uri = KeyRings.buildPublicKeyRingsUri();
+ Uri insertedUri = context.getContentResolver().insert(uri, values);
+ long keyRingRowId = Long.valueOf(insertedUri.getLastPathSegment());
+
+ // save all keys and userIds included in keyRing object in database
+ ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
+
+ int rank = 0;
+ for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) {
+ operations.add(buildPublicKeyOperations(context, keyRingRowId, key, rank));
+ ++rank;
+ }
+
+ int userIdRank = 0;
+ for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
+ operations.add(buildPublicUserIdOperations(context, keyRingRowId, userId, userIdRank));
+ ++userIdRank;
+ }
+
+ try {
+ context.getContentResolver().applyBatch(KeychainContract.CONTENT_AUTHORITY, operations);
+ } catch (RemoteException e) {
+ Log.e(Constants.TAG, "applyBatch failed!", e);
+ } catch (OperationApplicationException e) {
+ Log.e(Constants.TAG, "applyBatch failed!", e);
+ }
+ }
+
+ /**
+ * Saves PGPSecretKeyRing with its keys and userIds in DB
+ *
+ * @param context
+ * @param keyRing
+ * @return
+ * @throws IOException
+ * @throws GeneralException
+ */
+ @SuppressWarnings("unchecked")
+ public static void saveKeyRing(Context context, PGPSecretKeyRing keyRing) throws IOException {
+ PGPSecretKey masterKey = keyRing.getSecretKey();
+ long masterKeyId = masterKey.getKeyID();
+
+ // delete old version of this keyRing, which also deletes all keys and userIds on cascade
+ Uri deleteUri = KeyRings.buildSecretKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId));
+
+ try {
+ context.getContentResolver().delete(deleteUri, null, null);
+ } catch (UnsupportedOperationException e) {
+ Log.e(Constants.TAG, "Key could not be deleted! Maybe we are creating a new one!", e);
+ }
+
+ ContentValues values = new ContentValues();
+ values.put(KeyRings.MASTER_KEY_ID, masterKeyId);
+ values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded());
+
+ // insert new version of this keyRing
+ Uri uri = KeyRings.buildSecretKeyRingsUri();
+ Uri insertedUri = context.getContentResolver().insert(uri, values);
+ long keyRingRowId = Long.valueOf(insertedUri.getLastPathSegment());
+
+ // save all keys and userIds included in keyRing object in database
+ ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
+
+ int rank = 0;
+ for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) {
+ operations.add(buildSecretKeyOperations(context, keyRingRowId, key, rank));
+ ++rank;
+ }
+
+ int userIdRank = 0;
+ for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
+ operations.add(buildSecretUserIdOperations(context, keyRingRowId, userId, userIdRank));
+ ++userIdRank;
+ }
+
+ try {
+ context.getContentResolver().applyBatch(KeychainContract.CONTENT_AUTHORITY, operations);
+ } catch (RemoteException e) {
+ Log.e(Constants.TAG, "applyBatch failed!", e);
+ } catch (OperationApplicationException e) {
+ Log.e(Constants.TAG, "applyBatch failed!", e);
+ }
+ }
+
+ /**
+ * Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing
+ *
+ * @param context
+ * @param keyRingRowId
+ * @param key
+ * @param rank
+ * @return
+ * @throws IOException
+ */
+ private static ContentProviderOperation buildPublicKeyOperations(Context context,
+ long keyRingRowId, PGPPublicKey key, int rank) throws IOException {
+ ContentValues values = new ContentValues();
+ values.put(Keys.KEY_ID, key.getKeyID());
+ values.put(Keys.IS_MASTER_KEY, key.isMasterKey());
+ values.put(Keys.ALGORITHM, key.getAlgorithm());
+ values.put(Keys.KEY_SIZE, key.getBitStrength());
+ values.put(Keys.CAN_SIGN, PgpKeyHelper.isSigningKey(key));
+ values.put(Keys.CAN_ENCRYPT, PgpKeyHelper.isEncryptionKey(key));
+ values.put(Keys.IS_REVOKED, key.isRevoked());
+ values.put(Keys.CREATION, PgpKeyHelper.getCreationDate(key).getTime() / 1000);
+ Date expiryDate = PgpKeyHelper.getExpiryDate(key);
+ if (expiryDate != null) {
+ values.put(Keys.EXPIRY, expiryDate.getTime() / 1000);
+ }
+ values.put(Keys.KEY_RING_ROW_ID, keyRingRowId);
+ values.put(Keys.KEY_DATA, key.getEncoded());
+ values.put(Keys.RANK, rank);
+
+ Uri uri = Keys.buildPublicKeysUri(Long.toString(keyRingRowId));
+
+ return ContentProviderOperation.newInsert(uri).withValues(values).build();
+ }
+
+ /**
+ * Build ContentProviderOperation to add PublicUserIds to database corresponding to a keyRing
+ *
+ * @param context
+ * @param keyRingRowId
+ * @param key
+ * @param rank
+ * @return
+ * @throws IOException
+ */
+ private static ContentProviderOperation buildPublicUserIdOperations(Context context,
+ long keyRingRowId, String userId, int rank) {
+ ContentValues values = new ContentValues();
+ values.put(UserIds.KEY_RING_ROW_ID, keyRingRowId);
+ values.put(UserIds.USER_ID, userId);
+ values.put(UserIds.RANK, rank);
+
+ Uri uri = UserIds.buildPublicUserIdsUri(Long.toString(keyRingRowId));
+
+ return ContentProviderOperation.newInsert(uri).withValues(values).build();
+ }
+
+ /**
+ * Build ContentProviderOperation to add PGPSecretKey to database corresponding to a keyRing
+ *
+ * @param context
+ * @param keyRingRowId
+ * @param key
+ * @param rank
+ * @return
+ * @throws IOException
+ */
+ private static ContentProviderOperation buildSecretKeyOperations(Context context,
+ long keyRingRowId, PGPSecretKey key, int rank) throws IOException {
+ ContentValues values = new ContentValues();
+
+ boolean has_private = true;
+ if (key.isMasterKey()) {
+ if (PgpKeyHelper.isSecretKeyPrivateEmpty(key)) {
+ has_private = false;
+ }
+ }
+
+ values.put(Keys.KEY_ID, key.getKeyID());
+ values.put(Keys.IS_MASTER_KEY, key.isMasterKey());
+ values.put(Keys.ALGORITHM, key.getPublicKey().getAlgorithm());
+ values.put(Keys.KEY_SIZE, key.getPublicKey().getBitStrength());
+ values.put(Keys.CAN_CERTIFY, (PgpKeyHelper.isCertificationKey(key) && has_private));
+ values.put(Keys.CAN_SIGN, (PgpKeyHelper.isSigningKey(key) && has_private));
+ values.put(Keys.CAN_ENCRYPT, PgpKeyHelper.isEncryptionKey(key));
+ values.put(Keys.IS_REVOKED, key.getPublicKey().isRevoked());
+ values.put(Keys.CREATION, PgpKeyHelper.getCreationDate(key).getTime() / 1000);
+ Date expiryDate = PgpKeyHelper.getExpiryDate(key);
+ if (expiryDate != null) {
+ values.put(Keys.EXPIRY, expiryDate.getTime() / 1000);
+ }
+ values.put(Keys.KEY_RING_ROW_ID, keyRingRowId);
+ values.put(Keys.KEY_DATA, key.getEncoded());
+ values.put(Keys.RANK, rank);
+
+ Uri uri = Keys.buildSecretKeysUri(Long.toString(keyRingRowId));
+
+ return ContentProviderOperation.newInsert(uri).withValues(values).build();
+ }
+
+ /**
+ * Build ContentProviderOperation to add SecretUserIds to database corresponding to a keyRing
+ *
+ * @param context
+ * @param keyRingRowId
+ * @param key
+ * @param rank
+ * @return
+ * @throws IOException
+ */
+ private static ContentProviderOperation buildSecretUserIdOperations(Context context,
+ long keyRingRowId, String userId, int rank) {
+ ContentValues values = new ContentValues();
+ values.put(UserIds.KEY_RING_ROW_ID, keyRingRowId);
+ values.put(UserIds.USER_ID, userId);
+ values.put(UserIds.RANK, rank);
+
+ Uri uri = UserIds.buildSecretUserIdsUri(Long.toString(keyRingRowId));
+
+ return ContentProviderOperation.newInsert(uri).withValues(values).build();
+ }
+
+ /**
+ * Private helper method
+ *
+ * @param context
+ * @param queryUri
+ * @return
+ */
+ private static ArrayList<Long> getKeyRingsMasterKeyIds(Context context, Uri queryUri) {
+ Cursor cursor = context.getContentResolver().query(queryUri,
+ new String[] { KeyRings.MASTER_KEY_ID }, null, null, null);
+
+ ArrayList<Long> masterKeyIds = new ArrayList<Long>();
+ if (cursor != null) {
+ int masterKeyIdCol = cursor.getColumnIndex(KeyRings.MASTER_KEY_ID);
+ if (cursor.moveToFirst()) {
+ do {
+ masterKeyIds.add(cursor.getLong(masterKeyIdCol));
+ } while (cursor.moveToNext());
+ }
+ }
+
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ return masterKeyIds;
+ }
+
+ /**
+ * Retrieves ids of all SecretKeyRings
+ *
+ * @param context
+ * @return
+ */
+ public static ArrayList<Long> getSecretKeyRingsMasterKeyIds(Context context) {
+ Uri queryUri = KeyRings.buildSecretKeyRingsUri();
+ return getKeyRingsMasterKeyIds(context, queryUri);
+ }
+
+ /**
+ * Retrieves ids of all PublicKeyRings
+ *
+ * @param context
+ * @return
+ */
+ public static ArrayList<Long> getPublicKeyRingsMasterKeyIds(Context context) {
+ Uri queryUri = KeyRings.buildPublicKeyRingsUri();
+ return getKeyRingsMasterKeyIds(context, queryUri);
+ }
+
+ public static void deletePublicKeyRing(Context context, long rowId) {
+ ContentResolver cr = context.getContentResolver();
+ cr.delete(KeyRings.buildPublicKeyRingsUri(Long.toString(rowId)), null, null);
+ }
+
+ public static void deleteSecretKeyRing(Context context, long rowId) {
+ ContentResolver cr = context.getContentResolver();
+ cr.delete(KeyRings.buildSecretKeyRingsUri(Long.toString(rowId)), null, null);
+ }
+
+ /**
+ * Get master key id of keyring by its row id
+ *
+ * @param context
+ * @param keyRingRowId
+ * @return
+ */
+ public static long getPublicMasterKeyId(Context context, long keyRingRowId) {
+ Uri queryUri = KeyRings.buildPublicKeyRingsUri(String.valueOf(keyRingRowId));
+ return getMasterKeyId(context, queryUri);
+ }
+
+ /**
+ * Get empty status of master key of keyring by its row id
+ *
+ * @param context
+ * @param keyRingRowId
+ * @return
+ */
+ public static boolean getSecretMasterKeyCanSign(Context context, long keyRingRowId) {
+ Uri queryUri = KeyRings.buildSecretKeyRingsUri(String.valueOf(keyRingRowId));
+ return getMasterKeyCanSign(context, queryUri, keyRingRowId);
+ }
+
+ /**
+ * Private helper method to get master key private empty status of keyring by its row id
+ *
+ * @param context
+ * @param queryUri
+ * @param keyRingRowId
+ * @return
+ */
+ private static boolean getMasterKeyCanSign(Context context, Uri queryUri, long keyRingRowId) {
+ String[] projection = new String[] {
+ KeyRings.MASTER_KEY_ID,
+ "(SELECT COUNT(sign_keys." + Keys._ID + ") FROM " + Tables.KEYS
+ + " AS sign_keys WHERE sign_keys." + Keys.KEY_RING_ROW_ID + " = "
+ + KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
+ + " AND sign_keys." + Keys.CAN_SIGN + " = '1' AND " + Keys.IS_MASTER_KEY
+ + " = 1) AS sign", };
+
+ ContentResolver cr = context.getContentResolver();
+ Cursor cursor = cr.query(queryUri, projection, null, null, null);
+
+ long masterKeyId = -1;
+ if (cursor != null && cursor.moveToFirst()) {
+ int masterKeyIdCol = cursor.getColumnIndex("sign");
+
+ masterKeyId = cursor.getLong(masterKeyIdCol);
+ }
+
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ return (masterKeyId > 0);
+ }
+
+ /**
+ * Get master key id of keyring by its row id
+ *
+ * @param context
+ * @param keyRingRowId
+ * @return
+ */
+ public static long getSecretMasterKeyId(Context context, long keyRingRowId) {
+ Uri queryUri = KeyRings.buildSecretKeyRingsUri(String.valueOf(keyRingRowId));
+ return getMasterKeyId(context, queryUri);
+ }
+
+ /**
+ * Private helper method to get master key id of keyring by its row id
+ *
+ * @param context
+ * @param queryUri
+ * @param keyRingRowId
+ * @return
+ */
+ public static long getMasterKeyId(Context context, Uri queryUri) {
+ String[] projection = new String[] { KeyRings.MASTER_KEY_ID };
+
+ ContentResolver cr = context.getContentResolver();
+ Cursor cursor = cr.query(queryUri, projection, null, null, null);
+
+ long masterKeyId = -1;
+ if (cursor != null && cursor.moveToFirst()) {
+ int masterKeyIdCol = cursor.getColumnIndex(KeyRings.MASTER_KEY_ID);
+
+ masterKeyId = cursor.getLong(masterKeyIdCol);
+ }
+
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ return masterKeyId;
+ }
+
+ public static ArrayList<String> getPublicKeyRingsAsArmoredString(Context context,
+ long[] masterKeyIds) {
+ return getKeyRingsAsArmoredString(context, KeyRings.buildPublicKeyRingsUri(), masterKeyIds);
+ }
+
+ public static ArrayList<String> getSecretKeyRingsAsArmoredString(Context context,
+ long[] masterKeyIds) {
+ return getKeyRingsAsArmoredString(context, KeyRings.buildSecretKeyRingsUri(), masterKeyIds);
+ }
+
+ public static ArrayList<String> getKeyRingsAsArmoredString(Context context, Uri uri,
+ long[] masterKeyIds) {
+ ArrayList<String> output = new ArrayList<String>();
+
+ if (masterKeyIds != null && masterKeyIds.length > 0) {
+
+ Cursor cursor = getCursorWithSelectedKeyringMasterKeyIds(context, uri, masterKeyIds);
+
+ if (cursor != null) {
+ int masterIdCol = cursor.getColumnIndex(KeyRings.MASTER_KEY_ID);
+ int dataCol = cursor.getColumnIndex(KeyRings.KEY_RING_DATA);
+ if (cursor.moveToFirst()) {
+ do {
+ Log.d(Constants.TAG, "masterKeyId: " + cursor.getLong(masterIdCol));
+
+ // get actual keyring data blob and write it to ByteArrayOutputStream
+ try {
+ Object keyRing = null;
+ byte[] data = cursor.getBlob(dataCol);
+ if (data != null) {
+ keyRing = PgpConversionHelper.BytesToPGPKeyRing(data);
+ }
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ArmoredOutputStream aos = new ArmoredOutputStream(bos);
+ aos.setHeader("Version", PgpHelper.getFullVersion(context));
+
+ if (keyRing instanceof PGPSecretKeyRing) {
+ aos.write(((PGPSecretKeyRing) keyRing).getEncoded());
+ } else if (keyRing instanceof PGPPublicKeyRing) {
+ aos.write(((PGPPublicKeyRing) keyRing).getEncoded());
+ }
+ aos.close();
+
+ String armoredKey = bos.toString("UTF-8");
+
+ Log.d(Constants.TAG, "armouredKey:" + armoredKey);
+
+ output.add(armoredKey);
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "IOException", e);
+ }
+ } while (cursor.moveToNext());
+ }
+ }
+
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ } else {
+ Log.e(Constants.TAG, "No master keys given!");
+ }
+
+ if (output.size() > 0) {
+ return output;
+ } else {
+ return null;
+ }
+ }
+
+ public static byte[] getPublicKeyRingsAsByteArray(Context context, long[] masterKeyIds) {
+ return getKeyRingsAsByteArray(context, KeyRings.buildPublicKeyRingsUri(), masterKeyIds);
+ }
+
+ public static byte[] getSecretKeyRingsAsByteArray(Context context, long[] masterKeyIds) {
+ return getKeyRingsAsByteArray(context, KeyRings.buildSecretKeyRingsUri(), masterKeyIds);
+ }
+
+ public static byte[] getKeyRingsAsByteArray(Context context, Uri uri, long[] masterKeyIds) {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+ if (masterKeyIds != null && masterKeyIds.length > 0) {
+
+ Cursor cursor = getCursorWithSelectedKeyringMasterKeyIds(context, uri, masterKeyIds);
+
+ if (cursor != null) {
+ int masterIdCol = cursor.getColumnIndex(KeyRings.MASTER_KEY_ID);
+ int dataCol = cursor.getColumnIndex(KeyRings.KEY_RING_DATA);
+ if (cursor.moveToFirst()) {
+ do {
+ Log.d(Constants.TAG, "masterKeyId: " + cursor.getLong(masterIdCol));
+
+ // get actual keyring data blob and write it to ByteArrayOutputStream
+ try {
+ bos.write(cursor.getBlob(dataCol));
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "IOException", e);
+ }
+ } while (cursor.moveToNext());
+ }
+ }
+
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ } else {
+ Log.e(Constants.TAG, "No master keys given!");
+ }
+
+ return bos.toByteArray();
+ }
+
+ private static Cursor getCursorWithSelectedKeyringMasterKeyIds(Context context, Uri baseUri,
+ long[] masterKeyIds) {
+ Cursor cursor = null;
+ if (masterKeyIds != null && masterKeyIds.length > 0) {
+
+ String inMasterKeyList = KeyRings.MASTER_KEY_ID + " IN (";
+ for (int i = 0; i < masterKeyIds.length; ++i) {
+ if (i != 0) {
+ inMasterKeyList += ", ";
+ }
+ inMasterKeyList += DatabaseUtils.sqlEscapeString("" + masterKeyIds[i]);
+ }
+ inMasterKeyList += ")";
+
+ cursor = context.getContentResolver().query(baseUri,
+ new String[] { KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.KEY_RING_DATA },
+ inMasterKeyList, null, null);
+ }
+
+ return cursor;
+ }
+
+ public static ArrayList<String> getRegisteredApiApps(Context context) {
+ Cursor cursor = context.getContentResolver().query(ApiApps.CONTENT_URI, null, null, null,
+ null);
+
+ ArrayList<String> packageNames = new ArrayList<String>();
+ if (cursor != null) {
+ int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME);
+ if (cursor.moveToFirst()) {
+ do {
+ packageNames.add(cursor.getString(packageNameCol));
+ } while (cursor.moveToNext());
+ }
+ }
+
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ return packageNames;
+ }
+
+ private static ContentValues contentValueForApiApps(AppSettings appSettings) {
+ ContentValues values = new ContentValues();
+ values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName());
+ values.put(ApiApps.PACKAGE_SIGNATURE, appSettings.getPackageSignature());
+ values.put(ApiApps.KEY_ID, appSettings.getKeyId());
+ values.put(ApiApps.COMPRESSION, appSettings.getCompression());
+ values.put(ApiApps.ENCRYPTION_ALGORITHM, appSettings.getEncryptionAlgorithm());
+ values.put(ApiApps.HASH_ALORITHM, appSettings.getHashAlgorithm());
+
+ return values;
+ }
+
+ public static void insertApiApp(Context context, AppSettings appSettings) {
+ context.getContentResolver().insert(ApiApps.CONTENT_URI,
+ contentValueForApiApps(appSettings));
+ }
+
+ public static void updateApiApp(Context context, AppSettings appSettings, Uri uri) {
+ if (context.getContentResolver().update(uri, contentValueForApiApps(appSettings), null,
+ null) <= 0) {
+ throw new RuntimeException();
+ }
+ }
+
+ public static AppSettings getApiAppSettings(Context context, Uri uri) {
+ AppSettings settings = null;
+
+ Cursor cur = context.getContentResolver().query(uri, null, null, null, null);
+ if (cur != null && cur.moveToFirst()) {
+ settings = new AppSettings();
+ settings.setPackageName(cur.getString(cur
+ .getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
+ settings.setPackageSignature(cur.getBlob(cur
+ .getColumnIndex(KeychainContract.ApiApps.PACKAGE_SIGNATURE)));
+ settings.setKeyId(cur.getLong(cur.getColumnIndex(KeychainContract.ApiApps.KEY_ID)));
+ settings.setCompression(cur.getInt(cur
+ .getColumnIndexOrThrow(KeychainContract.ApiApps.COMPRESSION)));
+ settings.setHashAlgorithm(cur.getInt(cur
+ .getColumnIndexOrThrow(KeychainContract.ApiApps.HASH_ALORITHM)));
+ settings.setEncryptionAlgorithm(cur.getInt(cur
+ .getColumnIndexOrThrow(KeychainContract.ApiApps.ENCRYPTION_ALGORITHM)));
+ }
+
+ return settings;
+ }
+
+ public static byte[] getApiAppSignature(Context context, String packageName) {
+ Uri queryUri = KeychainContract.ApiApps.buildByPackageNameUri(packageName);
+
+ String[] projection = new String[] { ApiApps.PACKAGE_SIGNATURE };
+
+ ContentResolver cr = context.getContentResolver();
+ Cursor cursor = cr.query(queryUri, projection, null, null, null);
+
+ byte[] signature = null;
+ if (cursor != null && cursor.moveToFirst()) {
+ int signatureCol = 0;
+
+ signature = cursor.getBlob(signatureCol);
+ }
+
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ return signature;
+ }
+}