diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 6e3169189..0596fc9a4 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -128,9 +128,20 @@ android:label="@string/title_preferences" android:configChanges="keyboardHidden|orientation|keyboard"/> + + - - - \ No newline at end of file + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index d15bc5a21..72c1d1f9c 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -239,5 +239,9 @@ decompressing data... verifying integrity... + + Read key details from APG. + Read of public and secret keys stored in APG, such as key ID and user IDs. The keys themselves can NOT be read. + diff --git a/src/org/thialfihar/android/apg/SelectSecretKeyListActivity.java b/src/org/thialfihar/android/apg/SelectSecretKeyListActivity.java index af50b7e01..cd87a94b6 100644 --- a/src/org/thialfihar/android/apg/SelectSecretKeyListActivity.java +++ b/src/org/thialfihar/android/apg/SelectSecretKeyListActivity.java @@ -44,7 +44,6 @@ public class SelectSecretKeyListActivity extends BaseActivity { public void onItemClick(AdapterView adapterView, View view, int position, long id) { Intent data = new Intent(); data.putExtra(Apg.EXTRA_KEY_ID, id); - data.putExtra(Apg.EXTRA_USER_ID, Apg.getMainUserId(id, Id.database.type_secret)); setResult(RESULT_OK, data); finish(); } diff --git a/src/org/thialfihar/android/apg/provider/DataProvider.java b/src/org/thialfihar/android/apg/provider/DataProvider.java new file mode 100644 index 000000000..d01fc6c92 --- /dev/null +++ b/src/org/thialfihar/android/apg/provider/DataProvider.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * 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.thialfihar.android.apg.provider; + +import java.util.HashMap; + +import org.thialfihar.android.apg.Id; + +import android.content.ContentProvider; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.text.TextUtils; + +public class DataProvider extends ContentProvider { + public static final String AUTHORITY = "org.thialfihar.android.apg.provider"; + + private static final int PUBLIC_KEY_RINGS = 101; + private static final int PUBLIC_KEY_RING_ID = 102; + private static final int PUBLIC_KEY_RING_BY_KEY_ID = 103; + private static final int PUBLIC_KEY_RING_KEYS = 111; + private static final int PUBLIC_KEY_RING_KEY_RANK = 112; + private static final int PUBLIC_KEY_RING_USER_IDS = 121; + private static final int PUBLIC_KEY_RING_USER_ID_RANK = 122; + + private static final int SECRET_KEY_RINGS = 201; + private static final int SECRET_KEY_RING_ID = 202; + private static final int SECRET_KEY_RING_BY_KEY_ID = 203; + private static final int SECRET_KEY_RING_KEYS = 211; + private static final int SECRET_KEY_RING_KEY_RANK = 212; + private static final int SECRET_KEY_RING_USER_IDS = 221; + private static final int SECRET_KEY_RING_USER_ID_RANK = 222; + + private static final String PUBLIC_KEY_RING_CONTENT_DIR_TYPE = + "vnd.android.cursor.dir/vnd.thialfihar.apg.public.key_ring"; + private static final String PUBLIC_KEY_RING_CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/vnd.thialfihar.apg.public.key_ring"; + + private static final String PUBLIC_KEY_CONTENT_DIR_TYPE = + "vnd.android.cursor.dir/vnd.thialfihar.apg.public.key"; + private static final String PUBLIC_KEY_CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/vnd.thialfihar.apg.public.key"; + + private static final String SECRET_KEY_RING_CONTENT_DIR_TYPE = + "vnd.android.cursor.dir/vnd.thialfihar.apg.secret.key_ring"; + private static final String SECRET_KEY_RING_CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/vnd.thialfihar.apg.secret.key_ring"; + + private static final String SECRET_KEY_CONTENT_DIR_TYPE = + "vnd.android.cursor.dir/vnd.thialfihar.apg.secret.key"; + private static final String SECRET_KEY_CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/vnd.thialfihar.apg.secret.key"; + + private static final String USER_ID_CONTENT_DIR_TYPE = + "vnd.android.cursor.dir/vnd.thialfihar.apg.user_id"; + private static final String USER_ID_CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/vnd.thialfihar.apg.user_id"; + + public static final String MASTER_KEY_ID = "master_key_id"; + public static final String KEY_ID = "key_id"; + public static final String USER_ID = "user_id"; + + private static final UriMatcher mUriMatcher; + + private Database mDb; + + static { + mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + mUriMatcher.addURI(AUTHORITY, "key_rings/public/key_id/*", PUBLIC_KEY_RING_BY_KEY_ID); + + mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/keys", PUBLIC_KEY_RING_KEYS); + mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/keys/#", PUBLIC_KEY_RING_KEY_RANK); + + mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/user_ids", PUBLIC_KEY_RING_USER_IDS); + mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/user_ids/#", PUBLIC_KEY_RING_USER_ID_RANK); + + mUriMatcher.addURI(AUTHORITY, "key_rings/public", PUBLIC_KEY_RINGS); + mUriMatcher.addURI(AUTHORITY, "key_rings/public/*", PUBLIC_KEY_RING_ID); + + mUriMatcher.addURI(AUTHORITY, "key_rings/secret/key_id/*", SECRET_KEY_RING_BY_KEY_ID); + + mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/keys", SECRET_KEY_RING_KEYS); + mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/keys/#", SECRET_KEY_RING_KEY_RANK); + + mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/user_ids", SECRET_KEY_RING_USER_IDS); + mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/user_ids/#", SECRET_KEY_RING_USER_ID_RANK); + + mUriMatcher.addURI(AUTHORITY, "key_rings/secret", SECRET_KEY_RINGS); + mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*", SECRET_KEY_RING_ID); + } + + @Override + public boolean onCreate() { + mDb = new Database(getContext()); + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) { + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + HashMap projectionMap = new HashMap(); + int match = mUriMatcher.match(uri); + int type; + switch (match) { + case PUBLIC_KEY_RINGS: + case PUBLIC_KEY_RING_ID: + case PUBLIC_KEY_RING_BY_KEY_ID: + case PUBLIC_KEY_RING_KEYS: + case PUBLIC_KEY_RING_KEY_RANK: + case PUBLIC_KEY_RING_USER_IDS: + case PUBLIC_KEY_RING_USER_ID_RANK: + type = Id.database.type_public; + break; + + case SECRET_KEY_RINGS: + case SECRET_KEY_RING_ID: + case SECRET_KEY_RING_BY_KEY_ID: + case SECRET_KEY_RING_KEYS: + case SECRET_KEY_RING_KEY_RANK: + case SECRET_KEY_RING_USER_IDS: + case SECRET_KEY_RING_USER_ID_RANK: + type = Id.database.type_secret; + break; + + default: { + throw new IllegalArgumentException("Unknown URI " + uri); + } + } + + qb.appendWhere(KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = " + type); + + switch (match) { + case PUBLIC_KEY_RINGS: + case SECRET_KEY_RINGS: { + qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + + "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + + Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " + + Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" + + ") " + + " INNER JOIN " + UserIds.TABLE_NAME + " ON " + + "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " + + UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " + + UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') "); + + projectionMap.put(MASTER_KEY_ID, + KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID); + projectionMap.put(USER_ID, + UserIds.TABLE_NAME + "." + UserIds.USER_ID); + + break; + } + + case PUBLIC_KEY_RING_ID: + case SECRET_KEY_RING_ID: { + qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + + "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + + Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " + + Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" + + ") " + + " INNER JOIN " + UserIds.TABLE_NAME + " ON " + + "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " + + UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " + + UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') "); + + projectionMap.put(MASTER_KEY_ID, + KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID); + projectionMap.put(USER_ID, + UserIds.TABLE_NAME + "." + UserIds.USER_ID); + + qb.appendWhere(" AND " + + KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID + " = "); + qb.appendWhereEscapeString(uri.getPathSegments().get(2)); + break; + } + + case SECRET_KEY_RING_BY_KEY_ID: + case PUBLIC_KEY_RING_BY_KEY_ID: { + qb.setTables(Keys.TABLE_NAME + " AS tmp INNER JOIN " + + KeyRings.TABLE_NAME + " ON (" + + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + + "tmp." + Keys.KEY_RING_ID + ")" + + " INNER JOIN " + Keys.TABLE_NAME + " ON " + + "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + + Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " + + Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" + + ") " + + " INNER JOIN " + UserIds.TABLE_NAME + " ON " + + "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " + + UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " + + UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') "); + + projectionMap.put(MASTER_KEY_ID, + KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID); + projectionMap.put(USER_ID, + UserIds.TABLE_NAME + "." + UserIds.USER_ID); + + qb.appendWhere(" AND tmp." + Keys.KEY_ID + " = "); + qb.appendWhereEscapeString(uri.getPathSegments().get(3)); + + break; + } + + default: { + throw new IllegalArgumentException("Unknown URI " + uri); + } + } + + qb.setProjectionMap(projectionMap); + + // If no sort order is specified use the default + String orderBy; + if (TextUtils.isEmpty(sortOrder)) { + orderBy = null; + } else { + orderBy = sortOrder; + } + + //System.out.println(qb.buildQuery(projection, selection, selectionArgs, null, null, sortOrder, null).replace("WHERE", "WHERE\n")); + Cursor c = qb.query(mDb.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); + return c; + } + + @Override + public String getType(Uri uri) { + switch (mUriMatcher.match(uri)) { + case PUBLIC_KEY_RINGS: + return PUBLIC_KEY_RING_CONTENT_DIR_TYPE; + + case PUBLIC_KEY_RING_ID: + return PUBLIC_KEY_RING_CONTENT_ITEM_TYPE; + + case PUBLIC_KEY_RING_BY_KEY_ID: + return PUBLIC_KEY_RING_CONTENT_ITEM_TYPE; + + case PUBLIC_KEY_RING_KEYS: + return PUBLIC_KEY_CONTENT_DIR_TYPE; + + case PUBLIC_KEY_RING_KEY_RANK: + return PUBLIC_KEY_CONTENT_ITEM_TYPE; + + case PUBLIC_KEY_RING_USER_IDS: + return USER_ID_CONTENT_DIR_TYPE; + + case PUBLIC_KEY_RING_USER_ID_RANK: + return USER_ID_CONTENT_ITEM_TYPE; + + case SECRET_KEY_RINGS: + return SECRET_KEY_RING_CONTENT_DIR_TYPE; + + case SECRET_KEY_RING_ID: + return SECRET_KEY_RING_CONTENT_ITEM_TYPE; + + case SECRET_KEY_RING_BY_KEY_ID: + return SECRET_KEY_RING_CONTENT_ITEM_TYPE; + + case SECRET_KEY_RING_KEYS: + return SECRET_KEY_CONTENT_DIR_TYPE; + + case SECRET_KEY_RING_KEY_RANK: + return SECRET_KEY_CONTENT_ITEM_TYPE; + + case SECRET_KEY_RING_USER_IDS: + return USER_ID_CONTENT_DIR_TYPE; + + case SECRET_KEY_RING_USER_ID_RANK: + return USER_ID_CONTENT_ITEM_TYPE; + + default: + throw new IllegalArgumentException("Unknown URI " + uri); + } + } + + @Override + public Uri insert(Uri uri, ContentValues initialValues) { + // not supported + return null; + } + + @Override + public int delete(Uri uri, String where, String[] whereArgs) { + // not supported + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { + // not supported + return 0; + } +} diff --git a/src/org/thialfihar/android/apg/provider/Database.java b/src/org/thialfihar/android/apg/provider/Database.java index 8c27b130e..810ebebbf 100644 --- a/src/org/thialfihar/android/apg/provider/Database.java +++ b/src/org/thialfihar/android/apg/provider/Database.java @@ -389,9 +389,9 @@ public class Database extends SQLiteOpenHelper { seenIdsStr += id; } mDb.delete(UserIds.TABLE_NAME, - UserIds.KEY_ID + " = ? AND " + - UserIds._ID + " NOT IN (" + seenIdsStr + ")", - new String[] { "" + rowId }); + UserIds.KEY_ID + " = ? AND " + + UserIds._ID + " NOT IN (" + seenIdsStr + ")", + new String[] { "" + rowId }); return (int)rowId; } @@ -414,17 +414,17 @@ public class Database extends SQLiteOpenHelper { private long insertOrUpdateKeyRing(ContentValues values) { Cursor c = mDb.query(KeyRings.TABLE_NAME, new String[] { KeyRings._ID }, - KeyRings.MASTER_KEY_ID + " = ? AND " + KeyRings.TYPE + " = ?", - new String[] { - values.getAsString(KeyRings.MASTER_KEY_ID), - values.getAsString(KeyRings.TYPE), - }, - null, null, null); + KeyRings.MASTER_KEY_ID + " = ? AND " + KeyRings.TYPE + " = ?", + new String[] { + values.getAsString(KeyRings.MASTER_KEY_ID), + values.getAsString(KeyRings.TYPE), + }, + null, null, null); long rowId = -1; if (c != null && c.moveToFirst()) { rowId = c.getLong(0); mDb.update(KeyRings.TABLE_NAME, values, - KeyRings._ID + " = ?", new String[] { "" + rowId }); + KeyRings._ID + " = ?", new String[] { "" + rowId }); mStatus = Id.return_value.updated; } else { rowId = mDb.insert(KeyRings.TABLE_NAME, KeyRings.WHO_ID, values); @@ -440,17 +440,17 @@ public class Database extends SQLiteOpenHelper { private long insertOrUpdateKey(ContentValues values) { Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys._ID }, - Keys.KEY_ID + " = ? AND " + Keys.TYPE + " = ?", - new String[] { - values.getAsString(Keys.KEY_ID), - values.getAsString(Keys.TYPE), - }, - null, null, null); + Keys.KEY_ID + " = ? AND " + Keys.TYPE + " = ?", + new String[] { + values.getAsString(Keys.KEY_ID), + values.getAsString(Keys.TYPE), + }, + null, null, null); long rowId = -1; if (c != null && c.moveToFirst()) { rowId = c.getLong(0); mDb.update(Keys.TABLE_NAME, values, - Keys._ID + " = ?", new String[] { "" + rowId }); + Keys._ID + " = ?", new String[] { "" + rowId }); } else { rowId = mDb.insert(Keys.TABLE_NAME, Keys.KEY_DATA, values); } @@ -464,12 +464,12 @@ public class Database extends SQLiteOpenHelper { private long insertOrUpdateUserId(ContentValues values) { Cursor c = mDb.query(UserIds.TABLE_NAME, new String[] { UserIds._ID }, - UserIds.KEY_ID + " = ? AND " + UserIds.USER_ID + " = ?", - new String[] { - values.getAsString(UserIds.KEY_ID), - values.getAsString(UserIds.USER_ID), - }, - null, null, null); + UserIds.KEY_ID + " = ? AND " + UserIds.USER_ID + " = ?", + new String[] { + values.getAsString(UserIds.KEY_ID), + values.getAsString(UserIds.USER_ID), + }, + null, null, null); long rowId = -1; if (c != null && c.moveToFirst()) { rowId = c.getLong(0); @@ -488,12 +488,12 @@ public class Database extends SQLiteOpenHelper { public Object getKeyRing(int keyRingId) { Cursor c = mDb.query(KeyRings.TABLE_NAME, - new String[] { KeyRings.KEY_RING_DATA, KeyRings.TYPE }, - KeyRings._ID + " = ?", - new String[] { - "" + keyRingId, - }, - null, null, null); + new String[] { KeyRings.KEY_RING_DATA, KeyRings.TYPE }, + KeyRings._ID + " = ?", + new String[] { + "" + keyRingId, + }, + null, null, null); byte[] data = null; Object keyRing = null; if (c != null && c.moveToFirst()) { @@ -522,16 +522,16 @@ public class Database extends SQLiteOpenHelper { public byte[] getKeyRingDataFromKeyId(int type, long keyId) { Cursor c = mDb.query(Keys.TABLE_NAME + " INNER JOIN " + KeyRings.TABLE_NAME + " ON (" + - KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + - Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + ")", - new String[] { KeyRings.TABLE_NAME + "." + KeyRings.KEY_RING_DATA }, - Keys.TABLE_NAME + "." + Keys.KEY_ID + " = ? AND " + - KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", - new String[] { - "" + keyId, - "" + type, - }, - null, null, null); + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + + Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + ")", + new String[] { KeyRings.TABLE_NAME + "." + KeyRings.KEY_RING_DATA }, + Keys.TABLE_NAME + "." + Keys.KEY_ID + " = ? AND " + + KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", + new String[] { + "" + keyId, + "" + type, + }, + null, null, null); byte[] data = null; if (c != null && c.moveToFirst()) { @@ -547,12 +547,12 @@ public class Database extends SQLiteOpenHelper { public byte[] getKeyDataFromKeyId(int type, long keyId) { Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys.KEY_DATA }, - Keys.KEY_ID + " = ? AND " + Keys.TYPE + " = ?", - new String[] { - "" + keyId, - "" + type, - }, - null, null, null); + Keys.KEY_ID + " = ? AND " + Keys.TYPE + " = ?", + new String[] { + "" + keyId, + "" + type, + }, + null, null, null); byte[] data = null; if (c != null && c.moveToFirst()) { data = c.getBlob(0); @@ -568,13 +568,13 @@ public class Database extends SQLiteOpenHelper { public void deleteKeyRing(int keyRingId) { mDb.beginTransaction(); mDb.delete(KeyRings.TABLE_NAME, - KeyRings._ID + " = ?", new String[] { "" + keyRingId }); + KeyRings._ID + " = ?", new String[] { "" + keyRingId }); Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys._ID }, - Keys.KEY_RING_ID + " = ?", - new String[] { - "" + keyRingId, - }, + Keys.KEY_RING_ID + " = ?", + new String[] { + "" + keyRingId, + }, null, null, null); if (c != null && c.moveToFirst()) { do { @@ -593,10 +593,10 @@ public class Database extends SQLiteOpenHelper { private void deleteKey(int keyId) { mDb.delete(Keys.TABLE_NAME, - Keys._ID + " = ?", new String[] { "" + keyId }); + Keys._ID + " = ?", new String[] { "" + keyId }); mDb.delete(UserIds.TABLE_NAME, - UserIds.KEY_ID + " = ?", new String[] { "" + keyId }); + UserIds.KEY_ID + " = ?", new String[] { "" + keyId }); } public SQLiteDatabase db() {