aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider
diff options
context:
space:
mode:
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider')
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java19
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java52
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java110
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java121
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java115
5 files changed, 342 insertions, 75 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java
index 11d6728e2..ee28b5f36 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java
@@ -51,6 +51,11 @@ public class KeychainContract {
String EXPIRY = "expiry";
}
+ interface UpdatedKeysColumns {
+ String MASTER_KEY_ID = "master_key_id"; // not a database id
+ String LAST_UPDATED = "last_updated"; // time since epoch in seconds
+ }
+
interface UserPacketsColumns {
String MASTER_KEY_ID = "master_key_id"; // foreign key to key_rings._ID
String TYPE = "type"; // not a database id
@@ -90,13 +95,15 @@ public class KeychainContract {
String PACKAGE_NAME = "package_name"; // foreign key to api_apps.package_name
}
- public static final String CONTENT_AUTHORITY = Constants.PACKAGE_NAME + ".provider";
+ public static final String CONTENT_AUTHORITY = Constants.PROVIDER_AUTHORITY;
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_UPDATED_KEYS = "updated_keys";
+
public static final String PATH_UNIFIED = "unified";
public static final String PATH_FIND = "find";
@@ -235,6 +242,16 @@ public class KeychainContract {
}
+ public static class UpdatedKeys implements UpdatedKeysColumns, BaseColumns {
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
+ .appendPath(BASE_UPDATED_KEYS).build();
+
+ public static final String CONTENT_TYPE
+ = "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.updated_keys";
+ public static final String CONTENT_ITEM_TYPE
+ = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.updated_keys";
+ }
+
public static class UserPackets implements UserPacketsColumns, BaseColumns {
public static final String VERIFIED = "verified";
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
index ff661e494..d7fb738fc 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
@@ -34,6 +34,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeysColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns;
import org.sufficientlysecure.keychain.ui.ConsolidateDialogActivity;
import org.sufficientlysecure.keychain.util.Log;
@@ -53,7 +54,7 @@ import java.io.IOException;
*/
public class KeychainDatabase extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "openkeychain.db";
- private static final int DATABASE_VERSION = 9;
+ private static final int DATABASE_VERSION = 12;
static Boolean apgHack = false;
private Context mContext;
@@ -61,6 +62,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
String KEY_RINGS_PUBLIC = "keyrings_public";
String KEY_RINGS_SECRET = "keyrings_secret";
String KEYS = "keys";
+ String UPDATED_KEYS = "updated_keys";
String USER_PACKETS = "user_packets";
String CERTS = "certs";
String API_APPS = "api_apps";
@@ -144,6 +146,14 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ Tables.USER_PACKETS + "(" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.RANK + ") ON DELETE CASCADE"
+ ")";
+ private static final String CREATE_UPDATE_KEYS =
+ "CREATE TABLE IF NOT EXISTS " + Tables.UPDATED_KEYS + " ("
+ + UpdatedKeysColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY, "
+ + UpdatedKeysColumns.LAST_UPDATED + " INTEGER, "
+ + "FOREIGN KEY(" + UpdatedKeysColumns.MASTER_KEY_ID + ") REFERENCES "
+ + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_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, "
@@ -179,7 +189,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ Tables.API_APPS + "(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") ON DELETE CASCADE"
+ ")";
- KeychainDatabase(Context context) {
+ public KeychainDatabase(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mContext = context;
@@ -206,6 +216,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL(CREATE_KEYS);
db.execSQL(CREATE_USER_PACKETS);
db.execSQL(CREATE_CERTS);
+ db.execSQL(CREATE_UPDATE_KEYS);
db.execSQL(CREATE_API_APPS);
db.execSQL(CREATE_API_APPS_ACCOUNTS);
db.execSQL(CREATE_API_APPS_ALLOWED_KEYS);
@@ -272,6 +283,19 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL("DROP TABLE IF EXISTS user_ids");
db.execSQL(CREATE_USER_PACKETS);
db.execSQL(CREATE_CERTS);
+ case 10:
+ // do nothing here, just consolidate
+ case 11:
+ // fix problems in database, see #1402 for details
+ // https://github.com/open-keychain/open-keychain/issues/1402
+ db.execSQL("DELETE FROM api_accounts WHERE key_id BETWEEN 0 AND 3");
+ case 12:
+ db.execSQL(CREATE_UPDATE_KEYS);
+ if (oldVersion == 10) {
+ // no consolidate if we are updating from 10, we're just here for
+ // the api_accounts fix and the new update keys table
+ return;
+ }
}
@@ -295,10 +319,11 @@ public class KeychainDatabase extends SQLiteOpenHelper {
// It's the Java way =(
String[] dbs = context.databaseList();
for (String db : dbs) {
- if (db.equals("apg.db")) {
+ if ("apg.db".equals(db)) {
hasApgDb = true;
- } else if (db.equals("apg_old.db")) {
+ } else if ("apg_old.db".equals(db)) {
Log.d(Constants.TAG, "Found apg_old.db, delete it!");
+ // noinspection ResultOfMethodCallIgnored - if it doesn't happen, it doesn't happen.
context.getDatabasePath("apg_old.db").delete();
}
}
@@ -382,17 +407,22 @@ public class KeychainDatabase extends SQLiteOpenHelper {
}
}
- // delete old database
+ // noinspection ResultOfMethodCallIgnored - not much we can do if this doesn't work
context.getDatabasePath("apg.db").delete();
}
private static void copy(File in, File out) throws IOException {
FileInputStream is = new FileInputStream(in);
FileOutputStream os = new FileOutputStream(out);
- byte[] buf = new byte[512];
- while (is.available() > 0) {
- int count = is.read(buf, 0, 512);
- os.write(buf, 0, count);
+ try {
+ byte[] buf = new byte[512];
+ while (is.available() > 0) {
+ int count = is.read(buf, 0, 512);
+ os.write(buf, 0, count);
+ }
+ } finally {
+ is.close();
+ os.close();
}
}
@@ -409,6 +439,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
} else {
in = context.getDatabasePath(DATABASE_NAME);
out = context.getDatabasePath("debug_backup.db");
+ // noinspection ResultOfMethodCallIgnored - this is a pure debug feature, anyways
out.createNewFile();
}
if (!in.canRead()) {
@@ -423,6 +454,9 @@ public class KeychainDatabase extends SQLiteOpenHelper {
// DANGEROUS, use in test code ONLY!
public void clearDatabase() {
getWritableDatabase().execSQL("delete from " + Tables.KEY_RINGS_PUBLIC);
+ getWritableDatabase().execSQL("delete from " + Tables.API_ACCOUNTS);
+ getWritableDatabase().execSQL("delete from " + Tables.API_ALLOWED_KEYS);
+ getWritableDatabase().execSQL("delete from " + Tables.API_APPS);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
index ecb26b56a..d722fa9e7 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
@@ -39,6 +39,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
@@ -75,6 +76,9 @@ public class KeychainProvider extends ContentProvider {
private static final int KEY_RINGS_FIND_BY_EMAIL = 400;
private static final int KEY_RINGS_FIND_BY_SUBKEY = 401;
+ private static final int UPDATED_KEYS = 500;
+ private static final int UPDATED_KEYS_SPECIFIC = 501;
+
protected UriMatcher mUriMatcher;
/**
@@ -192,6 +196,12 @@ public class KeychainProvider extends ContentProvider {
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*/"
+ KeychainContract.PATH_ALLOWED_KEYS, API_ALLOWED_KEYS);
+ /**
+ * to access table containing last updated dates of keys
+ */
+ matcher.addURI(authority, KeychainContract.BASE_UPDATED_KEYS, UPDATED_KEYS);
+ matcher.addURI(authority, KeychainContract.BASE_UPDATED_KEYS + "/*", UPDATED_KEYS_SPECIFIC);
+
return matcher;
}
@@ -231,6 +241,11 @@ public class KeychainProvider extends ContentProvider {
case KEY_RING_SECRET:
return KeyRings.CONTENT_ITEM_TYPE;
+ case UPDATED_KEYS:
+ return UpdatedKeys.CONTENT_TYPE;
+ case UPDATED_KEYS_SPECIFIC:
+ return UpdatedKeys.CONTENT_ITEM_TYPE;
+
case API_APPS:
return ApiApps.CONTENT_TYPE;
@@ -536,7 +551,6 @@ public class KeychainProvider extends ContentProvider {
}
break;
-
}
case KEY_RINGS_PUBLIC:
@@ -631,23 +645,42 @@ public class KeychainProvider extends ContentProvider {
break;
}
- case API_APPS:
+ case UPDATED_KEYS:
+ case UPDATED_KEYS_SPECIFIC: {
+ HashMap<String, String> projectionMap = new HashMap<>();
+ qb.setTables(Tables.UPDATED_KEYS);
+ projectionMap.put(UpdatedKeys.MASTER_KEY_ID, Tables.UPDATED_KEYS + "."
+ + UpdatedKeys.MASTER_KEY_ID);
+ projectionMap.put(UpdatedKeys.LAST_UPDATED, Tables.UPDATED_KEYS + "."
+ + UpdatedKeys.LAST_UPDATED);
+ qb.setProjectionMap(projectionMap);
+ if (match == UPDATED_KEYS_SPECIFIC) {
+ qb.appendWhere(UpdatedKeys.MASTER_KEY_ID + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(1));
+ }
+ break;
+ }
+
+ case API_APPS: {
qb.setTables(Tables.API_APPS);
break;
- case API_APPS_BY_PACKAGE_NAME:
+ }
+ case API_APPS_BY_PACKAGE_NAME: {
qb.setTables(Tables.API_APPS);
qb.appendWhere(ApiApps.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getLastPathSegment());
break;
- case API_ACCOUNTS:
+ }
+ case API_ACCOUNTS: {
qb.setTables(Tables.API_ACCOUNTS);
qb.appendWhere(Tables.API_ACCOUNTS + "." + ApiAccounts.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(1));
break;
- case API_ACCOUNTS_BY_ACCOUNT_NAME:
+ }
+ case API_ACCOUNTS_BY_ACCOUNT_NAME: {
qb.setTables(Tables.API_ACCOUNTS);
qb.appendWhere(Tables.API_ACCOUNTS + "." + ApiAccounts.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(1));
@@ -656,14 +689,17 @@ public class KeychainProvider extends ContentProvider {
qb.appendWhereEscapeString(uri.getLastPathSegment());
break;
- case API_ALLOWED_KEYS:
+ }
+ case API_ALLOWED_KEYS: {
qb.setTables(Tables.API_ALLOWED_KEYS);
qb.appendWhere(Tables.API_ALLOWED_KEYS + "." + ApiAccounts.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(1));
break;
- default:
+ }
+ default: {
throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")");
+ }
}
@@ -708,47 +744,53 @@ public class KeychainProvider extends ContentProvider {
final int match = mUriMatcher.match(uri);
switch (match) {
- case KEY_RING_PUBLIC:
+ case KEY_RING_PUBLIC: {
db.insertOrThrow(Tables.KEY_RINGS_PUBLIC, null, values);
keyId = values.getAsLong(KeyRings.MASTER_KEY_ID);
break;
-
- case KEY_RING_SECRET:
+ }
+ case KEY_RING_SECRET: {
db.insertOrThrow(Tables.KEY_RINGS_SECRET, null, values);
keyId = values.getAsLong(KeyRings.MASTER_KEY_ID);
break;
-
- case KEY_RING_KEYS:
+ }
+ case KEY_RING_KEYS: {
db.insertOrThrow(Tables.KEYS, null, values);
keyId = values.getAsLong(Keys.MASTER_KEY_ID);
break;
-
- case KEY_RING_USER_IDS:
+ }
+ case KEY_RING_USER_IDS: {
// iff TYPE is null, user_id MUST be null as well
- if ( ! (values.get(UserPacketsColumns.TYPE) == null
+ if (!(values.get(UserPacketsColumns.TYPE) == null
? (values.get(UserPacketsColumns.USER_ID) != null && values.get(UserPacketsColumns.ATTRIBUTE_DATA) == null)
: (values.get(UserPacketsColumns.ATTRIBUTE_DATA) != null && values.get(UserPacketsColumns.USER_ID) == null)
- )) {
+ )) {
throw new AssertionError("Incorrect type for user packet! This is a bug!");
}
- if (values.get(UserPacketsColumns.RANK) == 0 && values.get(UserPacketsColumns.USER_ID) == null) {
+ if (((Number) values.get(UserPacketsColumns.RANK)).intValue() == 0 && values.get(UserPacketsColumns.USER_ID) == null) {
throw new AssertionError("Rank 0 user packet must be a user id!");
}
db.insertOrThrow(Tables.USER_PACKETS, null, values);
keyId = values.getAsLong(UserPackets.MASTER_KEY_ID);
break;
-
- case KEY_RING_CERTS:
+ }
+ case KEY_RING_CERTS: {
// we replace here, keeping only the latest signature
// TODO this would be better handled in savePublicKeyRing directly!
db.replaceOrThrow(Tables.CERTS, null, values);
keyId = values.getAsLong(Certs.MASTER_KEY_ID);
break;
-
- case API_APPS:
+ }
+ case UPDATED_KEYS: {
+ long updatedKeyId = db.replace(Tables.UPDATED_KEYS, null, values);
+ rowUri = UpdatedKeys.CONTENT_URI.buildUpon().appendPath("" + updatedKeyId)
+ .build();
+ break;
+ }
+ case API_APPS: {
db.insertOrThrow(Tables.API_APPS, null, values);
break;
-
+ }
case API_ACCOUNTS: {
// set foreign key automatically based on given uri
// e.g., api_apps/com.example.app/accounts/
@@ -767,8 +809,9 @@ public class KeychainProvider extends ContentProvider {
db.insertOrThrow(Tables.API_ALLOWED_KEYS, null, values);
break;
}
- default:
+ default: {
throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
}
if (keyId != null) {
@@ -826,20 +869,24 @@ public class KeychainProvider extends ContentProvider {
break;
}
- case API_APPS_BY_PACKAGE_NAME:
+ case API_APPS_BY_PACKAGE_NAME: {
count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, additionalSelection),
selectionArgs);
break;
- case API_ACCOUNTS_BY_ACCOUNT_NAME:
+ }
+ case API_ACCOUNTS_BY_ACCOUNT_NAME: {
count = db.delete(Tables.API_ACCOUNTS, buildDefaultApiAccountsSelection(uri, additionalSelection),
selectionArgs);
break;
- case API_ALLOWED_KEYS:
+ }
+ case API_ALLOWED_KEYS: {
count = db.delete(Tables.API_ALLOWED_KEYS, buildDefaultApiAllowedKeysSelection(uri, additionalSelection),
selectionArgs);
break;
- default:
+ }
+ default: {
throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
}
// notify of changes in db
@@ -875,16 +922,19 @@ public class KeychainProvider extends ContentProvider {
count = db.update(Tables.KEYS, values, actualSelection, selectionArgs);
break;
}
- case API_APPS_BY_PACKAGE_NAME:
+ case API_APPS_BY_PACKAGE_NAME: {
count = db.update(Tables.API_APPS, values,
buildDefaultApiAppsSelection(uri, selection), selectionArgs);
break;
- case API_ACCOUNTS_BY_ACCOUNT_NAME:
+ }
+ case API_ACCOUNTS_BY_ACCOUNT_NAME: {
count = db.update(Tables.API_ACCOUNTS, values,
buildDefaultApiAccountsSelection(uri, selection), selectionArgs);
break;
- default:
+ }
+ default: {
throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
}
// notify of changes in db
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
index bf56417e9..d9ef4f3c8 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
@@ -26,6 +26,7 @@ import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
+import android.support.annotation.NonNull;
import android.support.v4.util.LongSparseArray;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
@@ -38,7 +39,7 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
-import org.sufficientlysecure.keychain.operations.ImportExportOperation;
+import org.sufficientlysecure.keychain.operations.ImportOperation;
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
@@ -49,7 +50,7 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.KeyRing;
-import org.sufficientlysecure.keychain.pgp.PgpConstants;
+import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
@@ -61,6 +62,8 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys;
import org.sufficientlysecure.keychain.remote.AccountSettings;
import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.util.IterableIterator;
@@ -81,6 +84,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
/**
* This class contains high level methods for database access. Despite its
@@ -648,10 +652,6 @@ public class ProviderHelper {
UserPacketItem item = uids.get(userIdRank);
operations.add(buildUserIdOperations(masterKeyId, item, userIdRank));
- if (item.selfCert == null) {
- throw new AssertionError("User ids MUST be self-certified at this point!!");
- }
-
if (item.selfRevocation != null) {
operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfRevocation,
Certs.VERIFIED_SELF));
@@ -659,6 +659,10 @@ public class ProviderHelper {
continue;
}
+ if (item.selfCert == null) {
+ throw new AssertionError("User ids MUST be self-certified at this point!!");
+ }
+
operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfCert,
selfCertsAreTrusted ? Certs.VERIFIED_SECRET : Certs.VERIFIED_SELF));
@@ -684,6 +688,36 @@ public class ProviderHelper {
mIndent -= 1;
}
+ // before deleting key, retrieve it's last updated time
+ final int INDEX_MASTER_KEY_ID = 0;
+ final int INDEX_LAST_UPDATED = 1;
+ Cursor lastUpdatedCursor = mContentResolver.query(
+ UpdatedKeys.CONTENT_URI,
+ new String[]{
+ UpdatedKeys.MASTER_KEY_ID,
+ UpdatedKeys.LAST_UPDATED
+ },
+ UpdatedKeys.MASTER_KEY_ID + " = ?",
+ new String[]{"" + masterKeyId},
+ null
+ );
+ if (lastUpdatedCursor.moveToNext()) {
+ // there was an entry to re-insert
+ // this operation must happen after the new key is inserted
+ ContentValues lastUpdatedEntry = new ContentValues(2);
+ lastUpdatedEntry.put(UpdatedKeys.MASTER_KEY_ID,
+ lastUpdatedCursor.getLong(INDEX_MASTER_KEY_ID));
+ lastUpdatedEntry.put(UpdatedKeys.LAST_UPDATED,
+ lastUpdatedCursor.getLong(INDEX_LAST_UPDATED));
+ operations.add(
+ ContentProviderOperation
+ .newInsert(UpdatedKeys.CONTENT_URI)
+ .withValues(lastUpdatedEntry)
+ .build()
+ );
+ }
+ lastUpdatedCursor.close();
+
try {
// delete old version of this keyRing, which also deletes all keys and userIds on cascade
int deleted = mContentResolver.delete(
@@ -725,7 +759,7 @@ public class ProviderHelper {
LongSparseArray<WrappedSignature> trustedCerts = new LongSparseArray<>();
@Override
- public int compareTo(UserPacketItem o) {
+ public int compareTo(@NonNull UserPacketItem o) {
// revoked keys always come last!
//noinspection DoubleNegation
if ((selfRevocation != null) != (o.selfRevocation != null)) {
@@ -782,7 +816,7 @@ public class ProviderHelper {
// first, mark all keys as not available
ContentValues values = new ContentValues();
- values.put(Keys.HAS_SECRET, SecretKeyType.UNAVAILABLE.getNum());
+ values.put(Keys.HAS_SECRET, SecretKeyType.GNU_DUMMY.getNum());
mContentResolver.update(uri, values, null, null);
// then, mark exactly the keys we have available
@@ -831,7 +865,7 @@ public class ProviderHelper {
mIndent -= 1;
// this implicitly leaves all keys which were not in the secret key ring
- // with has_secret = 0
+ // with has_secret = 1
}
log(LogType.MSG_IS_SUCCESS);
@@ -906,7 +940,8 @@ public class ProviderHelper {
// If there is a secret key, merge new data (if any) and save the key for later
CanonicalizedSecretKeyRing canSecretRing;
try {
- UncachedKeyRing secretRing = getCanonicalizedSecretKeyRing(publicRing.getMasterKeyId()).getUncachedKeyRing();
+ UncachedKeyRing secretRing = getCanonicalizedSecretKeyRing(publicRing.getMasterKeyId())
+ .getUncachedKeyRing();
// Merge data from new public ring into secret one
log(LogType.MSG_IP_MERGE_SECRET);
@@ -1031,7 +1066,8 @@ public class ProviderHelper {
publicRing = secretRing.extractPublicKeyRing();
}
- CanonicalizedPublicKeyRing canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent);
+ CanonicalizedPublicKeyRing canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog,
+ mIndent);
if (canPublicRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
}
@@ -1057,6 +1093,7 @@ public class ProviderHelper {
}
+ @NonNull
public ConsolidateResult consolidateDatabaseStep1(Progressable progress) {
OperationLog log = new OperationLog();
@@ -1082,7 +1119,7 @@ public class ProviderHelper {
indent += 1;
final Cursor cursor = mContentResolver.query(KeyRingData.buildSecretKeyRingUri(),
- new String[]{ KeyRingData.KEY_RING_DATA }, null, null, null);
+ new String[]{KeyRingData.KEY_RING_DATA}, null, null, null);
if (cursor == null) {
log.add(LogType.MSG_CON_ERROR_DB, indent);
@@ -1124,6 +1161,7 @@ public class ProviderHelper {
}
});
+ cursor.close();
} catch (IOException e) {
Log.e(Constants.TAG, "error saving secret", e);
@@ -1143,7 +1181,7 @@ public class ProviderHelper {
final Cursor cursor = mContentResolver.query(
KeyRingData.buildPublicKeyRingUri(),
- new String[]{ KeyRingData.KEY_RING_DATA }, null, null, null);
+ new String[]{KeyRingData.KEY_RING_DATA}, null, null, null);
if (cursor == null) {
log.add(LogType.MSG_CON_ERROR_DB, indent);
@@ -1185,6 +1223,7 @@ public class ProviderHelper {
}
});
+ cursor.close();
} catch (IOException e) {
Log.e(Constants.TAG, "error saving public", e);
@@ -1200,12 +1239,14 @@ public class ProviderHelper {
return consolidateDatabaseStep2(log, indent, progress, false);
}
+ @NonNull
public ConsolidateResult consolidateDatabaseStep2(Progressable progress) {
return consolidateDatabaseStep2(new OperationLog(), 0, progress, true);
}
private static boolean mConsolidateCritical = false;
+ @NonNull
private ConsolidateResult consolidateDatabaseStep2(
OperationLog log, int indent, Progressable progress, boolean recovery) {
@@ -1231,6 +1272,28 @@ public class ProviderHelper {
}
// 2. wipe database (IT'S DANGEROUS)
+
+ // first, backup our list of updated key times
+ ArrayList<ContentValues> updatedKeysValues = new ArrayList<>();
+ final int INDEX_MASTER_KEY_ID = 0;
+ final int INDEX_LAST_UPDATED = 1;
+ Cursor lastUpdatedCursor = mContentResolver.query(
+ UpdatedKeys.CONTENT_URI,
+ new String[]{
+ UpdatedKeys.MASTER_KEY_ID,
+ UpdatedKeys.LAST_UPDATED
+ },
+ null, null, null);
+ while (lastUpdatedCursor.moveToNext()) {
+ ContentValues values = new ContentValues();
+ values.put(UpdatedKeys.MASTER_KEY_ID,
+ lastUpdatedCursor.getLong(INDEX_MASTER_KEY_ID));
+ values.put(UpdatedKeys.LAST_UPDATED,
+ lastUpdatedCursor.getLong(INDEX_LAST_UPDATED));
+ updatedKeysValues.add(values);
+ }
+ lastUpdatedCursor.close();
+
log.add(LogType.MSG_CON_DB_CLEAR, indent);
mContentResolver.delete(KeyRings.buildUnifiedKeyRingsUri(), null, null);
@@ -1248,9 +1311,9 @@ public class ProviderHelper {
// 3. Re-Import secret keyrings from cache
if (numSecrets > 0) {
- ImportKeyResult result = new ImportExportOperation(mContext, this,
+ ImportKeyResult result = new ImportOperation(mContext, this,
new ProgressFixedScaler(progress, 10, 25, 100, R.string.progress_con_reimport))
- .importKeyRings(itSecrets, numSecrets, null);
+ .serialKeyRingImport(itSecrets, numSecrets, null, null);
log.add(result, indent);
} else {
log.add(LogType.MSG_CON_REIMPORT_SECRET_SKIP, indent);
@@ -1276,10 +1339,14 @@ public class ProviderHelper {
// 4. Re-Import public keyrings from cache
if (numPublics > 0) {
- ImportKeyResult result = new ImportExportOperation(mContext, this,
+ ImportKeyResult result = new ImportOperation(mContext, this,
new ProgressFixedScaler(progress, 25, 99, 100, R.string.progress_con_reimport))
- .importKeyRings(itPublics, numPublics, null);
+ .serialKeyRingImport(itPublics, numPublics, null, null);
log.add(result, indent);
+ // re-insert our backed up list of updated key times
+ // TODO: can this cause issues in case a public key re-import failed?
+ mContentResolver.bulkInsert(UpdatedKeys.CONTENT_URI,
+ updatedKeysValues.toArray(new ContentValues[updatedKeysValues.size()]));
} else {
log.add(LogType.MSG_CON_REIMPORT_PUBLIC_SKIP, indent);
}
@@ -1389,6 +1456,14 @@ public class ProviderHelper {
return getKeyRingAsArmoredString(data);
}
+ public Uri renewKeyLastUpdatedTime(long masterKeyId, long time, TimeUnit timeUnit) {
+ ContentValues values = new ContentValues();
+ values.put(UpdatedKeys.MASTER_KEY_ID, masterKeyId);
+ values.put(UpdatedKeys.LAST_UPDATED, timeUnit.toSeconds(time));
+
+ return mContentResolver.insert(UpdatedKeys.CONTENT_URI, values);
+ }
+
public ArrayList<String> getRegisteredApiApps() {
Cursor cursor = mContentResolver.query(ApiApps.CONTENT_URI, null, null, null, null);
@@ -1414,7 +1489,7 @@ public class ProviderHelper {
private ContentValues contentValueForApiApps(AppSettings appSettings) {
ContentValues values = new ContentValues();
values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName());
- values.put(ApiApps.PACKAGE_CERTIFICATE, appSettings.getPackageSignature());
+ values.put(ApiApps.PACKAGE_CERTIFICATE, appSettings.getPackageCertificate());
return values;
}
@@ -1426,9 +1501,9 @@ public class ProviderHelper {
// DEPRECATED and thus hardcoded
values.put(KeychainContract.ApiAccounts.COMPRESSION, CompressionAlgorithmTags.ZLIB);
values.put(KeychainContract.ApiAccounts.ENCRYPTION_ALGORITHM,
- PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
+ PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT);
values.put(KeychainContract.ApiAccounts.HASH_ALORITHM,
- PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED);
+ PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT);
return values;
}
@@ -1460,7 +1535,7 @@ public class ProviderHelper {
settings = new AppSettings();
settings.setPackageName(cursor.getString(
cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
- settings.setPackageSignature(cursor.getBlob(
+ settings.setPackageCertificate(cursor.getBlob(
cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_CERTIFICATE)));
}
} finally {
@@ -1514,8 +1589,8 @@ public class ProviderHelper {
return keyIds;
}
- public Set<Long> getAllowedKeyIdsForApp(Uri uri) {
- Set<Long> keyIds = new HashSet<>();
+ public HashSet<Long> getAllowedKeyIdsForApp(Uri uri) {
+ HashSet<Long> keyIds = new HashSet<>();
Cursor cursor = mContentResolver.query(uri, null, null, null, null);
try {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java
index 45f806960..7e9b24989 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java
@@ -18,6 +18,8 @@
package org.sufficientlysecure.keychain.provider;
+
+import android.content.ClipDescription;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
@@ -38,6 +40,25 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.UUID;
+/**
+ * TemporaryStorageProvider stores decrypted files inside the app's cache directory previously to
+ * sharing them with other applications.
+ *
+ * Security:
+ * - It is writable by OpenKeychain only (see Manifest), but exported for reading files
+ * - It uses UUIDs as identifiers which makes predicting files from outside impossible
+ * - Querying a number of files is not allowed, only querying single files
+ * -> You can only open a file if you know the Uri containing the precise UUID, this Uri is only
+ * revealed when the user shares a decrypted file with another app.
+ *
+ * Why is support lib's FileProvider not used?
+ * Because granting Uri permissions temporarily does not work correctly. See
+ * - https://code.google.com/p/android/issues/detail?id=76683
+ * - https://github.com/nmr8acme/FileProvider-permission-bug
+ * - http://stackoverflow.com/q/24467696
+ * - http://stackoverflow.com/q/18249007
+ * - Comments at http://www.blogc.at/2014/03/23/share-private-files-with-other-apps-fileprovider/
+ */
public class TemporaryStorageProvider extends ContentProvider {
private static final String DB_NAME = "tempstorage.db";
@@ -45,17 +66,37 @@ public class TemporaryStorageProvider extends ContentProvider {
private static final String COLUMN_ID = "id";
private static final String COLUMN_NAME = "name";
private static final String COLUMN_TIME = "time";
- private static final Uri BASE_URI = Uri.parse("content://org.sufficientlysecure.keychain.tempstorage/");
- private static final int DB_VERSION = 2;
+ private static final String COLUMN_TYPE = "mimetype";
+ public static final String CONTENT_AUTHORITY = Constants.TEMPSTORAGE_AUTHORITY;
+ private static final Uri BASE_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
+ private static final int DB_VERSION = 3;
private static File cacheDir;
+ public static Uri createFile(Context context, String targetName, String mimeType) {
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(COLUMN_NAME, targetName);
+ contentValues.put(COLUMN_TYPE, mimeType);
+ return context.getContentResolver().insert(BASE_URI, contentValues);
+ }
+
public static Uri createFile(Context context, String targetName) {
ContentValues contentValues = new ContentValues();
contentValues.put(COLUMN_NAME, targetName);
return context.getContentResolver().insert(BASE_URI, contentValues);
}
+ public static Uri createFile(Context context) {
+ ContentValues contentValues = new ContentValues();
+ return context.getContentResolver().insert(BASE_URI, contentValues);
+ }
+
+ public static int setMimeType(Context context, Uri uri, String mimetype) {
+ ContentValues values = new ContentValues();
+ values.put(COLUMN_TYPE, mimetype);
+ return context.getContentResolver().update(uri, values, null, null);
+ }
+
public static int cleanUp(Context context) {
return context.getContentResolver().delete(BASE_URI, COLUMN_TIME + "< ?",
new String[]{Long.toString(System.currentTimeMillis() - Constants.TEMPFILE_TTL)});
@@ -72,6 +113,7 @@ public class TemporaryStorageProvider extends ContentProvider {
db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_FILES + " (" +
COLUMN_ID + " TEXT PRIMARY KEY, " +
COLUMN_NAME + " TEXT, " +
+ COLUMN_TYPE + " TEXT, " +
COLUMN_TIME + " INTEGER" +
");");
}
@@ -88,6 +130,8 @@ public class TemporaryStorageProvider extends ContentProvider {
COLUMN_NAME + " TEXT, " +
COLUMN_TIME + " INTEGER" +
");");
+ case 2:
+ db.execSQL("ALTER TABLE files ADD COLUMN " + COLUMN_TYPE + " TEXT");
}
}
}
@@ -115,6 +159,10 @@ public class TemporaryStorageProvider extends ContentProvider {
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+ if (uri.getLastPathSegment() == null) {
+ throw new SecurityException("Listing temporary files is not allowed, only querying single files.");
+ }
+
File file;
try {
file = getFile(uri);
@@ -125,9 +173,15 @@ public class TemporaryStorageProvider extends ContentProvider {
new String[]{uri.getLastPathSegment()}, null, null, null);
if (fileName != null) {
if (fileName.moveToNext()) {
- MatrixCursor cursor =
- new MatrixCursor(new String[]{OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE, "_data"});
- cursor.newRow().add(fileName.getString(0)).add(file.length()).add(file.getAbsolutePath());
+ MatrixCursor cursor = new MatrixCursor(new String[]{
+ OpenableColumns.DISPLAY_NAME,
+ OpenableColumns.SIZE,
+ "_data"
+ });
+ cursor.newRow()
+ .add(fileName.getString(0))
+ .add(file.length())
+ .add(file.getAbsolutePath());
fileName.close();
return cursor;
}
@@ -138,9 +192,30 @@ public class TemporaryStorageProvider extends ContentProvider {
@Override
public String getType(Uri uri) {
- // Note: If we can find a files mime type, we can decrypt it to temp storage and open it after
- // encryption. The mime type is needed, else UI really sucks and some apps break.
- return "*/*";
+ Cursor cursor = db.getReadableDatabase().query(TABLE_FILES,
+ new String[]{COLUMN_TYPE}, COLUMN_ID + "=?",
+ new String[]{uri.getLastPathSegment()}, null, null, null);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToNext()) {
+ if (!cursor.isNull(0)) {
+ return cursor.getString(0);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ return "application/octet-stream";
+ }
+
+ @Override
+ public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
+ String type = getType(uri);
+ if (ClipDescription.compareMimeTypes(type, mimeTypeFilter)) {
+ return new String[]{type};
+ }
+ return null;
}
@Override
@@ -151,9 +226,14 @@ public class TemporaryStorageProvider extends ContentProvider {
String uuid = UUID.randomUUID().toString();
values.put(COLUMN_ID, uuid);
int insert = (int) db.getWritableDatabase().insert(TABLE_FILES, null, values);
+ if (insert == -1) {
+ Log.e(Constants.TAG, "Insert failed!");
+ return null;
+ }
try {
getFile(uuid).createNewFile();
} catch (IOException e) {
+ Log.e(Constants.TAG, "File creation failed!");
return null;
}
return Uri.withAppendedPath(BASE_URI, uuid);
@@ -161,10 +241,13 @@ public class TemporaryStorageProvider extends ContentProvider {
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
- if (uri.getLastPathSegment() != null) {
- selection = DatabaseUtil.concatenateWhere(selection, COLUMN_ID + "=?");
- selectionArgs = DatabaseUtil.appendSelectionArgs(selectionArgs, new String[]{uri.getLastPathSegment()});
+ if (uri == null || uri.getLastPathSegment() == null) {
+ return 0;
}
+
+ selection = DatabaseUtil.concatenateWhere(selection, COLUMN_ID + "=?");
+ selectionArgs = DatabaseUtil.appendSelectionArgs(selectionArgs, new String[]{uri.getLastPathSegment()});
+
Cursor files = db.getReadableDatabase().query(TABLE_FILES, new String[]{COLUMN_ID}, selection,
selectionArgs, null, null, null);
if (files != null) {
@@ -179,11 +262,19 @@ public class TemporaryStorageProvider extends ContentProvider {
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException("Update not supported");
+ if (values.size() != 1 || !values.containsKey(COLUMN_TYPE)) {
+ throw new UnsupportedOperationException("Update supported only for type field!");
+ }
+ if (selection != null || selectionArgs != null) {
+ throw new UnsupportedOperationException("Update supported only for plain uri!");
+ }
+ return db.getWritableDatabase().update(TABLE_FILES, values,
+ COLUMN_ID + " = ?", new String[]{uri.getLastPathSegment()});
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
return openFileHelper(uri, mode);
}
+
}